commit 89c68d073875de36f68e8afdbde67e60d4287794 Author: Red Bear OS Date: Sat Jun 27 09:21:43 2026 +0300 Red Bear OS bootloader baseline from 0.1.0 pre-patched archive diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..c3dca1b96f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/build +/target diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000..6ee1dd25c8 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,35 @@ +image: "redoxos/redoxer:latest" + +before_script: + - apt-get install nasm + - rustup component add rust-src + +stages: + - host + +build:i686: + stage: host + script: + - mkdir -p target/i686 + - cd target/i686 + - TARGET=x86-unknown-none make -f ${CI_PROJECT_DIR}/Makefile -C `pwd` `pwd`/bootloader.bin `pwd`/bootloader-live.bin + +build:x86_64: + stage: host + script: + - mkdir -p target/x86_64 + - cd target/x86_64 + - TARGET=x86_64-unknown-uefi make -f ${CI_PROJECT_DIR}/Makefile -C `pwd` `pwd`/bootloader.efi `pwd`/bootloader-live.efi + +build:aarch64: + stage: host + script: + - mkdir -p target/aarch64 + - cd target/aarch64 + - TARGET=aarch64-unknown-uefi make -f ${CI_PROJECT_DIR}/Makefile -C `pwd` `pwd`/bootloader.efi `pwd`/bootloader-live.efi + +fmt: + stage: host + script: + - rustup component add rustfmt-preview + - cargo fmt -- --check diff --git a/.helix/config.toml b/.helix/config.toml new file mode 100644 index 0000000000..a1ec3e0a6a --- /dev/null +++ b/.helix/config.toml @@ -0,0 +1,2 @@ +[editor] +auto-format = false diff --git a/.helix/languages.toml b/.helix/languages.toml new file mode 100644 index 0000000000..a2decf470c --- /dev/null +++ b/.helix/languages.toml @@ -0,0 +1,5 @@ +[[language]] +name = "rust" +# TODO: Add more targets (BIOS, x86_32) +# Uncomment this line and set cargo.target to your target to get accurate completions +# config = { cargo.target = "aarch64-unknown-uefi", check.targets = ["x86_64-unknown-uefi", "aarch64-unknown-uefi"] } diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000000..765a0a7e49 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,360 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "argon2" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db4ce4441f99dbd377ca8a8f57b698c44d0d6e712d8329b5040da5a64aa1ce73" +dependencies = [ + "base64ct", + "blake2", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64ct" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" + +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "dmidecode" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5a070ca68f8ba202b05487d52b9ac56eaebb5b66cdd68d1a17e63174bb11e3b" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "endian-num" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8f59926911ef34d1efb9ea1ee8ca78385df62ce700ccf2bcb149011bd226888" + +[[package]] +name = "fdt" +version = "0.2.0-alpha1" +source = "git+https://github.com/repnop/fdt.git?rev=2fb1409edd1877c714a0aa36b6a7c5351004be54#2fb1409edd1877c714a0aa36b6a7c5351004be54" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "libc" +version = "0.2.176" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" + +[[package]] +name = "linked_list_allocator" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286" +dependencies = [ + "spinning_top", +] + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "lz4_flex" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a" + +[[package]] +name = "raw-cpuid" +version = "10.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox-path" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436d45c2b6a5b159d43da708e62b25be3a4a3d5550d654b72216ade4c4bfd717" + +[[package]] +name = "redox_bootloader" +version = "1.0.0" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "dmidecode", + "fdt", + "linked_list_allocator", + "log", + "redox_syscall", + "redox_uefi", + "redox_uefi_std", + "redoxfs", + "spin", + "x86", +] + +[[package]] +name = "redox_syscall" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +dependencies = [ + "bitflags 2.9.4", +] + +[[package]] +name = "redox_uefi" +version = "0.1.14" +source = "git+https://gitlab.redox-os.org/redox-os/uefi.git#26a499eeaf55d42fb24206830345e26fb8f5f835" + +[[package]] +name = "redox_uefi_alloc" +version = "0.1.14" +source = "git+https://gitlab.redox-os.org/redox-os/uefi.git#26a499eeaf55d42fb24206830345e26fb8f5f835" +dependencies = [ + "redox_uefi", +] + +[[package]] +name = "redox_uefi_std" +version = "0.1.14" +source = "git+https://gitlab.redox-os.org/redox-os/uefi.git#26a499eeaf55d42fb24206830345e26fb8f5f835" +dependencies = [ + "redox_uefi", + "redox_uefi_alloc", +] + +[[package]] +name = "redoxfs" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063eedabd74ddf71810e72aae1c73f3485ffc7b1e757d9466b9099046c05d7be" +dependencies = [ + "aes", + "argon2", + "base64ct", + "bitflags 2.9.4", + "endian-num", + "libc", + "log", + "lz4_flex", + "redox-path", + "redox_syscall", + "seahash", + "uuid", + "xts-mode", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spinning_top" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9eb1a2f4c41445a3a0ff9abc5221c5fcd28e1f13cd7c0397706f9ac938ddb0" +dependencies = [ + "lock_api", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "uuid" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "x86" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2781db97787217ad2a2845c396a5efe286f87467a5810836db6d74926e94a385" +dependencies = [ + "bit_field", + "bitflags 1.3.2", + "raw-cpuid", +] + +[[package]] +name = "xts-mode" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cbddb7545ca0b9ffa7bdc653e8743303e1712687a6918ced25f2cdbed42520" +dependencies = [ + "byteorder", + "cipher", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000000..a23d52f794 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,52 @@ +[package] +name = "redox_bootloader" +version = "1.0.0" +edition = "2024" + +# UEFI uses bin target +[[bin]] +name = "bootloader" +path = "src/main.rs" + +# BIOS uses lib target +[lib] +name = "bootloader" +path = "src/main.rs" +crate-type = ["staticlib"] + +[dependencies] +bitflags = "1.3.2" +linked_list_allocator = "0.10.5" +log = "0.4.17" +redox_syscall = "0.5" +spin = "0.9.5" + +[dependencies.redoxfs] +version = "0.8" +default-features = false +features = ["log"] + +[target.'cfg(target_os = "uefi")'.dependencies] +redox_uefi = { git = "https://gitlab.redox-os.org/redox-os/uefi.git" } +redox_uefi_std = { git = "https://gitlab.redox-os.org/redox-os/uefi.git" } + +#TODO: riscv cannot use target_os = "uefi" at this time +[target.'cfg(target_arch = "riscv64")'.dependencies] +redox_uefi = { git = "https://gitlab.redox-os.org/redox-os/uefi.git" } +redox_uefi_std = { git = "https://gitlab.redox-os.org/redox-os/uefi.git" } + +[target."aarch64-unknown-uefi".dependencies] +dmidecode = "0.8.0" + +[target."x86_64-unknown-uefi".dependencies] +x86 = "0.52.0" + +[target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies] +byteorder = { version = "1", default-features = false } +fdt = { git = "https://github.com/repnop/fdt.git", rev = "2fb1409edd1877c714a0aa36b6a7c5351004be54" } + +[features] +default = [] +live = [] +serial_debug = [] + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..ceeda84edb --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017-2022 Redox OS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..0bfb445248 --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +TARGET?=x86_64-unknown-uefi +SOURCE:=$(dir $(realpath $(lastword $(MAKEFILE_LIST)))) +BUILD:=$(CURDIR) +export RUST_TARGET_PATH?=$(SOURCE)/targets + + +include $(SOURCE)/mk/$(TARGET).mk + +clean: + rm -rf build target + +$(BUILD)/filesystem: + mkdir -p $(BUILD) + rm -f $@.partial + mkdir $@.partial + fallocate -l 1MiB $@.partial/kernel + mv $@.partial $@ + +$(BUILD)/filesystem.bin: $(BUILD)/filesystem + mkdir -p $(BUILD) + rm -f $@.partial + fallocate -l 254MiB $@.partial + redoxfs-ar $@.partial $< + mv $@.partial $@ diff --git a/README.md b/README.md new file mode 100644 index 0000000000..f2f04750b1 --- /dev/null +++ b/README.md @@ -0,0 +1,62 @@ +# Bootloader + +Redox OS Bootloader + +## Requirements + +These software needs to be available on the PATH at build time: + ++ [mtools](https://www.gnu.org/software/mtools/) ++ [nasm](https://nasm.us/) ++ [redoxfs-ar](https://gitlab.redox-os.org/redox-os/redoxfs) + +## Building + +```sh +make TARGET= BUILD=build all +``` + +The `` is one of: + +| ARCH | Boot Mode | Triplets | +|---|---|---| +| `i686` | BIOS | `x86-unknown-none` | +| `x86_64` | BIOS | `x86-unknown-none` | +| `x86_64` | UEFI | `x86_64-unknown-uefi` | +| `aarch64` | UEFI | `aarch64-unknown-uefi` | +| `riscv64gc` | UEFI | `riscv64gc-unknown-uefi` | + +See [mk directory](./mk) for more information of how the build is working. + +## Entry points + +Please read [Boot Process](https://doc.redox-os.org/book/boot-process.html) in the Redox OS Book for an introductory guide. + +In this source code, some interesting files for entry points are: + ++ BIOS boot stages: [asm/x86-unknown-none/bootloader.asm](./asm/x86-unknown-none/bootloader.asm) ++ BIOS boot entry: `fn start` at [src/os/bios/mod.rs](./src/os/bios/mod.rs) ++ UEFI boot entry: `fn main` at [src/os/uefi/mod.rs](src/os/uefi/mod.rs) ++ Common boot process: `fn main` at [src/main.rs](src/main.rs) ++ UEFI kernel entry: `fn kernel_entry` in each arch: + - `x86_64`: [src/os/uefi/arch/x86_64.rs](src/os/uefi/arch/x86_64.rs) + - `aarch64`: [src/os/uefi/arch/aarch64.rs](src/os/uefi/arch/aarch64.rs) + - `riscv64gc`: [src/os/uefi/arch/riscv64/mod.rs](src/os/uefi/arch/riscv64/mod.rs) + +## Debugging + +### QEMU + +```sh +make TARGET= BUILD=build qemu +``` + +## How To Contribute + +To learn how to contribute to this system component you need to read the following document: + +- [CONTRIBUTING.md](https://gitlab.redox-os.org/redox-os/redox/-/blob/master/CONTRIBUTING.md) + +## Development + +To learn how to do development with this system component inside the Redox build system you need to read the [Build System](https://doc.redox-os.org/book/build-system-reference.html) and [Coding and Building](https://doc.redox-os.org/book/coding-and-building.html) pages. diff --git a/asm/aarch64/dummy.S b/asm/aarch64/dummy.S new file mode 100644 index 0000000000..e69de29bb2 diff --git a/asm/arm/start.s b/asm/arm/start.s new file mode 100644 index 0000000000..8495600c4e --- /dev/null +++ b/asm/arm/start.s @@ -0,0 +1,17 @@ +interrupt_vector_table: + b . @ Reset + b . + b . @ SWI instruction + b . + b . + b . + b . + b . + +.comm stack, 0x10000 @ Reserve 64k stack in the BSS +_start: + .globl _start + ldr sp, =stack+0x10000 @ Set up the stack + bl kstart @ Jump to the main function +1: + b 1b @ Halt diff --git a/asm/x86-unknown-none/bootloader.asm b/asm/x86-unknown-none/bootloader.asm new file mode 100644 index 0000000000..3911cd0597 --- /dev/null +++ b/asm/x86-unknown-none/bootloader.asm @@ -0,0 +1,31 @@ +sectalign off + +; stage 1 is sector 0, loaded at 0x7C00 +%include "stage1.asm" + +; GPT area from sector 1 to 33, loaded at 0x7E00 +times (33*512) db 0 + +; stage 2, loaded at 0xC000 +stage2: + %include "stage2.asm" + align 512, db 0 +stage2.end: + +; the maximum size of stage2 is 4 KiB +times (4*1024)-($-stage2) db 0 + +; ISO compatibility, uses up space until 0x12400 +%include "iso.asm" + +times 3072 db 0 ; Pad to 0x13000 + +; stage3, loaded at 0x13000 +stage3: + %defstr STAGE3_STR %[STAGE3] + incbin STAGE3_STR + align 512, db 0 +.end: + +; the maximum size of the boot loader portion is 384 KiB +times (384*1024)-($-$$) db 0 diff --git a/asm/x86-unknown-none/cpuid.asm b/asm/x86-unknown-none/cpuid.asm new file mode 100644 index 0000000000..e4aeb90c0c --- /dev/null +++ b/asm/x86-unknown-none/cpuid.asm @@ -0,0 +1,176 @@ +SECTION .text +USE16 + +cpuid_required_features: + .edx equ cpuid_edx.fpu | cpuid_edx.pse | cpuid_edx.pge | cpuid_edx.fxsr + .ecx equ 0 + +cpuid_check: + ; If bit 21 of EFLAGS can be changed, then CPUID is supported + pushfd ;Save EFLAGS + pushfd ;Store EFLAGS + xor dword [esp],0x00200000 ;Invert the ID bit in stored EFLAGS + popfd ;Load stored EFLAGS (with ID bit inverted) + pushfd ;Store EFLAGS again (ID bit may or may not be inverted) + pop eax ;eax = modified EFLAGS (ID bit may or may not be inverted) + xor eax,[esp] ;eax = whichever bits were changed + popfd ;Restore original EFLAGS + test eax,0x00200000 ;eax = zero if ID bit can't be changed, else non-zero + jz .no_cpuid + + mov eax, 1 + cpuid + + and edx, cpuid_required_features.edx + cmp edx, cpuid_required_features.edx + jne .error + + and ecx, cpuid_required_features.ecx + cmp ecx, cpuid_required_features.ecx + jne .error + + ret + +.no_cpuid: + mov si, .msg_cpuid + call print + + mov si, .msg_line + call print + + jmp .halt + +.error: + push ecx + push edx + + mov si, .msg_features + call print + + mov si, .msg_line + call print + + mov si, .msg_edx + call print + + pop ebx + push ebx + shr ebx, 16 + call print_hex + + pop ebx + call print_hex + + mov si, .msg_must_contain + call print + + mov ebx, cpuid_required_features.edx + shr ebx, 16 + call print_hex + + mov ebx, cpuid_required_features.edx + call print_hex + + mov si, .msg_line + call print + + mov si, .msg_ecx + call print + + pop ebx + push ebx + shr ebx, 16 + call print_hex + + pop ebx + call print_hex + + mov si, .msg_must_contain + call print + + mov ebx, cpuid_required_features.ecx + shr ebx, 16 + call print_hex + + mov ebx, cpuid_required_features.ecx + call print_hex + + mov si, .msg_line + call print + +.halt: + cli + hlt + jmp .halt + +.msg_cpuid: db "CPUID not supported",0 +.msg_features: db "Required CPU features are not present",0 +.msg_line: db 13,10,0 +.msg_edx: db "EDX ",0 +.msg_ecx: db "ECX ",0 +.msg_must_contain: db " must contain ",0 + +cpuid_edx: + .fpu equ 1 << 0 + .vme equ 1 << 1 + .de equ 1 << 2 + .pse equ 1 << 3 + .tsc equ 1 << 4 + .msr equ 1 << 5 + .pae equ 1 << 6 + .mce equ 1 << 7 + .cx8 equ 1 << 8 + .apic equ 1 << 9 + .sep equ 1 << 11 + .mtrr equ 1 << 12 + .pge equ 1 << 13 + .mca equ 1 << 14 + .cmov equ 1 << 15 + .pat equ 1 << 16 + .pse_36 equ 1 << 17 + .psn equ 1 << 18 + .clfsh equ 1 << 19 + .ds equ 1 << 21 + .acpi equ 1 << 22 + .mmx equ 1 << 23 + .fxsr equ 1 << 24 + .sse equ 1 << 25 + .sse2 equ 1 << 26 + .ss equ 1 << 27 + .htt equ 1 << 28 + .tm equ 1 << 29 + .ia64 equ 1 << 30 + .pbe equ 1 << 31 + +cpuid_ecx: + .sse3 equ 1 << 0 + .pclmulqdq equ 1 << 1 + .dtes64 equ 1 << 2 + .monitor equ 1 << 3 + .ds_cpl equ 1 << 4 + .vmx equ 1 << 5 + .smx equ 1 << 6 + .est equ 1 << 7 + .tm2 equ 1 << 8 + .ssse3 equ 1 << 9 + .cnxt_id equ 1 << 10 + .sdbg equ 1 << 11 + .fma equ 1 << 12 + .cmpxchg16b equ 1 << 13 + .xtpr equ 1 << 14 + .pdcm equ 1 << 15 + .pcid equ 1 << 17 + .dca equ 1 << 18 + .sse4_1 equ 1 << 19 + .sse4_2 equ 1 << 20 + .x2apic equ 1 << 21 + .movbe equ 1 << 22 + .popcnt equ 1 << 23 + .tsc_deadline equ 1 << 24 + .aes equ 1 << 25 + .xsave equ 1 << 26 + .osxsave equ 1 << 27 + .avx equ 1 << 28 + .f16c equ 1 << 29 + .rdrand equ 1 << 30 + .hypervisor equ 1 << 31 diff --git a/asm/x86-unknown-none/gdt.asm b/asm/x86-unknown-none/gdt.asm new file mode 100644 index 0000000000..925954ae17 --- /dev/null +++ b/asm/x86-unknown-none/gdt.asm @@ -0,0 +1,128 @@ +SECTION .text ; cannot use .data + +struc GDTEntry + .limitl resw 1 + .basel resw 1 + .basem resb 1 + .attribute resb 1 + .flags__limith resb 1 + .baseh resb 1 +endstruc + +gdt_attr: + .present equ 1 << 7 + .ring1 equ 1 << 5 + .ring2 equ 1 << 6 + .ring3 equ 1 << 5 | 1 << 6 + .user equ 1 << 4 +;user + .code equ 1 << 3 +; code + .conforming equ 1 << 2 + .readable equ 1 << 1 +; data + .expand_down equ 1 << 2 + .writable equ 1 << 1 + .accessed equ 1 << 0 +;system +; legacy + .tssAvailabe16 equ 0x1 + .ldt equ 0x2 + .tssBusy16 equ 0x3 + .call16 equ 0x4 + .task equ 0x5 + .interrupt16 equ 0x6 + .trap16 equ 0x7 + .tssAvailabe32 equ 0x9 + .tssBusy32 equ 0xB + .call32 equ 0xC + .interrupt32 equ 0xE + .trap32 equ 0xF +; long mode + .ldt32 equ 0x2 + .tssAvailabe64 equ 0x9 + .tssBusy64 equ 0xB + .call64 equ 0xC + .interrupt64 equ 0xE + .trap64 equ 0xF + +gdt_flag: + .granularity equ 1 << 7 + .available equ 1 << 4 +;user + .default_operand_size equ 1 << 6 +; code + .long_mode equ 1 << 5 +; data + .reserved equ 1 << 5 + +gdtr: + dw gdt.end + 1 ; size + dq gdt ; offset + +gdt: +.null equ $ - gdt + dq 0 + +.lm64_code equ $ - gdt +istruc GDTEntry + at GDTEntry.limitl, dw 0 + at GDTEntry.basel, dw 0 + at GDTEntry.basem, db 0 + at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.code + at GDTEntry.flags__limith, db gdt_flag.long_mode + at GDTEntry.baseh, db 0 +iend + +.lm64_data equ $ - gdt +istruc GDTEntry + at GDTEntry.limitl, dw 0 + at GDTEntry.basel, dw 0 + at GDTEntry.basem, db 0 +; AMD System Programming Manual states that the writeable bit is ignored in long mode, but ss can not be set to this descriptor without it + at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.writable + at GDTEntry.flags__limith, db 0 + at GDTEntry.baseh, db 0 +iend + +.pm32_code equ $ - gdt + istruc GDTEntry + at GDTEntry.limitl, dw 0xFFFF + at GDTEntry.basel, dw 0 + at GDTEntry.basem, db 0 + at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.code | gdt_attr.readable + at GDTEntry.flags__limith, db 0xF | gdt_flag.granularity | gdt_flag.default_operand_size + at GDTEntry.baseh, db 0 + iend + +.pm32_data equ $ - gdt + istruc GDTEntry + at GDTEntry.limitl, dw 0xFFFF + at GDTEntry.basel, dw 0 + at GDTEntry.basem, db 0 + at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.writable + at GDTEntry.flags__limith, db 0xF | gdt_flag.granularity | gdt_flag.default_operand_size + at GDTEntry.baseh, db 0 + iend + +.pm16_code equ $ - gdt + istruc GDTEntry + at GDTEntry.limitl, dw 0xFFFF + at GDTEntry.basel, dw 0 + at GDTEntry.basem, db 0 + at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.code | gdt_attr.readable + at GDTEntry.flags__limith, db 0xF + at GDTEntry.baseh, db 0 + iend + +.pm16_data equ $ - gdt + istruc GDTEntry + at GDTEntry.limitl, dw 0xFFFF + at GDTEntry.basel, dw 0 + at GDTEntry.basem, db 0 + at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.writable + at GDTEntry.flags__limith, db 0xF + at GDTEntry.baseh, db 0 + iend + +.end equ $ - gdt diff --git a/asm/x86-unknown-none/iso.asm b/asm/x86-unknown-none/iso.asm new file mode 100644 index 0000000000..d8fe2a859a --- /dev/null +++ b/asm/x86-unknown-none/iso.asm @@ -0,0 +1,161 @@ +; Simple ISO emulation with el torito + +; Fill until CD sector 0x10 +times (0x10*2048)-($-$$) db 0 + +; Volume record +;TODO: fill in more fields +iso_volume_record: +db 1 ; Type volume record +db "CD001" ; Identifier +db 1 ; Version +db 0 ; Unused +times 32 db ' ' ; System identifier +.volume_id: ; Volume identifier +db 'Redox OS' +times 32-($-.volume_id) db ' ' +times 8 db 0 ; Unused +db 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15 ; Volume space size (0x15) +times 32 db 0 ; Unused +db 0x01, 0x00, 0x00, 0x01 ; Volume set size +db 0x01, 0x00, 0x00, 0x01 ; Volume sequence number +db 0x00, 0x08, 0x08, 0x00 ; Logical block size in little and big endian + +times 156-($-iso_volume_record) db 0 + +; Root directory entry +.root_directory: +db 0x22 ; Length of entry +db 0x00 ; Length of extended attributes +db 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14 ; Location of extent (0x14) +db 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00 ; Size of extent +db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ; Recording time +db 0x02 ; File flags +db 0x00 ; Interleaved file unit size +db 0x00 ; Interleaved gap size +db 0x01, 0x00, 0x00, 0x01 ; Volume sequence number +db 0x01 ; Length of file identifier +db 0x00 ; File identifier + +times 128 db ' ' ; Volume set identifier +times 128 db ' ' ; Publisher identifier +times 128 db ' ' ; Data preparer identifier +times 128 db ' ' ; Application identifier +times 37 db ' ' ; Copyright file ID +times 37 db ' ' ; Abstract file ID +times 37 db ' ' ; Bibliographic file ID + +times 881-($-iso_volume_record) db 0 + +db 1 ; File structure version + +; Fill until CD sector 0x11 +times (0x11*2048)-($-$$) db 0 + +; Boot record +iso_boot_record: +db 0 ; Type boot record +db "CD001" ; Identifier +db 1 ; Version +db "EL TORITO SPECIFICATION" ; Boot system identifier +times 0x47-($ - iso_boot_record) db 0 ; Padding +dd 0x13 ; Sector of boot catalog + +; Fill until CD sector 0x12 +times (0x12*2048)-($-$$) db 0 + +; Terminator +iso_terminator: +db 0xFF ; Type terminator +db "CD001" ; Identifier +db 1 ; Version + +; Fill until CD sector 0x13 +times (0x13*2048)-($-$$) db 0 + +; Boot catalog +iso_boot_catalog: + +; Validation entry +.validation: +db 1 ; Header ID +db 0 ; Platform ID (x86) +dw 0 ; Reserved +times 24 db 0 ; ID string +dw 0x55aa ; Checksum +dw 0xaa55 ; Key + +; Default entry +.default: +db 0x88 ; Bootable +db 4 ; Hard drive emulation +dw 0 ; Load segment (0 is platform default) +db 0xEE ; Partition type (0xEE is protective MBR) +db 0 ; Unused +dw 1 ; Sector count +dd 0 ; Start address for virtual disk +times 20 db 0 ; Padding + +; EFI section header entry +.efi_section_header: +db 0x91 ; Final header +db 0xEF ; Platform ID (EFI) +dw 1 ; Number of section header entries +times 28 db 0 ; ID string + +; EFI section entry +.efi_section_entry: +db 0x88 ; Bootable +db 0 ; No emulation +dw 0 ; Load segment (0 is platform default) +db 0 ; Partition type (not used) +db 0 ; Unused +dw 512 ; Sector count (1 MiB = 512 CD sectors) +dd 512 ; Start address for virtual disk (1 MiB = 512 CD sectors) +times 20 db 0 ; Padding + +; Fill until CD sector 0x14 +times (0x14*2048)-($-$$) db 0 + +iso_root_directory: +.self: +db 0x22 ; Length of entry +db 0x00 ; Length of extended attributes +db 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14 ; Location of extent (0x14) +db 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00 ; Size of extent +db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ; Recording time +db 0x02 ; File flags +db 0x00 ; Interleaved file unit size +db 0x00 ; Interleaved gap size +db 0x01, 0x00, 0x00, 0x01 ; Volume sequence number +db 0x01 ; Length of file identifier +db 0x00 ; File identifier + +.parent: +db 0x22 ; Length of entry +db 0x00 ; Length of extended attributes +db 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14 ; Location of extent (0x14) +db 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00 ; Size of extent +db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ; Recording time +db 0x02 ; File flags +db 0x00 ; Interleaved file unit size +db 0x00 ; Interleaved gap size +db 0x01, 0x00, 0x00, 0x01 ; Volume sequence number +db 0x01 ; Length of file identifier +db 0x01 ; File identifier + +.boot_cat: +db 0x2C ; Length of entry +db 0x00 ; Length of extended attributes +db 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13 ; Location of extent (0x13) +db 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00 ; Size of extent +db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ; Recording time +db 0x00 ; File flags +db 0x00 ; Interleaved file unit size +db 0x00 ; Interleaved gap size +db 0x01, 0x00, 0x00, 0x01 ; Volume sequence number +db 0x0A ; Length of file identifier +db "BOOT.CAT;1",0 ; File identifier + +; Fill until CD sector 0x15 +times (0x15*2048)-($-$$) db 0 diff --git a/asm/x86-unknown-none/long_mode.asm b/asm/x86-unknown-none/long_mode.asm new file mode 100644 index 0000000000..e0adb04eb1 --- /dev/null +++ b/asm/x86-unknown-none/long_mode.asm @@ -0,0 +1,56 @@ +SECTION .text +USE32 + +long_mode: +.func: dq 0 +.page_table: dd 0 + +.entry: + ; disable interrupts + cli + + ; disable paging + mov eax, cr0 + and eax, 0x7FFFFFFF + mov cr0, eax + + ; enable FXSAVE/FXRSTOR, Page Global, Page Address Extension, and Page Size Extension + mov eax, cr4 + or eax, 1 << 9 | 1 << 7 | 1 << 5 | 1 << 4 + mov cr4, eax + + ; load long mode GDT + lgdt [gdtr] + + ; enable long mode + mov ecx, 0xC0000080 ; Read from the EFER MSR. + rdmsr + or eax, 1 << 11 | 1 << 8 ; Set the Long-Mode-Enable and NXE bit. + wrmsr + + ; set page table + mov eax, [.page_table] + mov cr3, eax + + ; enabling paging and protection simultaneously + mov eax, cr0 + or eax, 1 << 31 | 1 << 16 | 1 ;Bit 31: Paging, Bit 16: write protect kernel, Bit 0: Protected Mode + mov cr0, eax + + ; far jump to enable Long Mode and load CS with 64 bit segment + jmp gdt.lm64_code:.inner + +USE64 + +.inner: + ; load all the other segments with 64 bit data segments + mov rax, gdt.lm64_data + mov ds, rax + mov es, rax + mov fs, rax + mov gs, rax + mov ss, rax + + ; jump to specified function + mov rax, [.func] + jmp rax diff --git a/asm/x86-unknown-none/print.asm b/asm/x86-unknown-none/print.asm new file mode 100644 index 0000000000..5e958a3c34 --- /dev/null +++ b/asm/x86-unknown-none/print.asm @@ -0,0 +1,67 @@ +SECTION .text +USE16 + +; provide function for printing in x86 real mode + +; print a string and a newline +; CLOBBER +; ax +print_line: + mov al, 13 + call print_char + mov al, 10 + jmp print_char + +; print a string +; IN +; si: points at zero-terminated String +; CLOBBER +; si, ax +print: + pushf + cld +.loop: + lodsb + test al, al + jz .done + call print_char + jmp .loop +.done: + popf + ret + +; print a character +; IN +; al: character to print +print_char: + pusha + mov bx, 7 + mov ah, 0x0e + int 0x10 + popa + ret + +; print a number in hex +; IN +; bx: the number +; CLOBBER +; al, cx +print_hex: + mov cx, 4 +.lp: + mov al, bh + shr al, 4 + + cmp al, 0xA + jb .below_0xA + + add al, 'A' - 0xA - '0' +.below_0xA: + add al, '0' + + call print_char + + shl bx, 4 + loop .lp + + ret diff --git a/asm/x86-unknown-none/protected_mode.asm b/asm/x86-unknown-none/protected_mode.asm new file mode 100644 index 0000000000..8edc6219af --- /dev/null +++ b/asm/x86-unknown-none/protected_mode.asm @@ -0,0 +1,36 @@ +SECTION .text +USE16 + +protected_mode: + +.func: dd 0 + +.entry: + ; disable interrupts + cli + + ; load protected mode GDT + lgdt [gdtr] + + ; set protected mode bit of cr0 + mov eax, cr0 + or eax, 1 + mov cr0, eax + + ; far jump to load CS with 32 bit segment + jmp gdt.pm32_code:.inner + +USE32 + +.inner: + ; load all the other segments with 32 bit data segments + mov eax, gdt.pm32_data + mov ds, eax + mov es, eax + mov fs, eax + mov gs, eax + mov ss, eax + + ; jump to specified function + mov eax, [.func] + jmp eax diff --git a/asm/x86-unknown-none/stage1.asm b/asm/x86-unknown-none/stage1.asm new file mode 100644 index 0000000000..d2175e11b2 --- /dev/null +++ b/asm/x86-unknown-none/stage1.asm @@ -0,0 +1,222 @@ +ORG 0x7C00 +SECTION .text +USE16 + +stage1: ; dl comes with disk + ; initialize segment registers + xor ax, ax + mov ds, ax + mov es, ax + mov ss, ax + + ; initialize stack + mov sp, 0x7C00 + + ; initialize CS + push ax + push word .set_cs + retf + +.set_cs: + + ; save disk number + mov [disk], dl + + mov si, stage_msg + call print + mov al, '1' + call print_char + call print_line + + ; read CHS gemotry + ; CL (bits 0-5) = maximum sector number + ; CL (bits 6-7) = high bits of max cylinder number + ; CH = low bits of maximum cylinder number + ; DH = maximum head number + mov ah, 0x08 + mov dl, [disk] + xor di, di + int 0x13 + jc error ; carry flag set on error + mov bl, ch + mov bh, cl + shr bh, 6 + mov [chs.c], bx + shr dx, 8 + inc dx ; returns heads - 1 + mov [chs.h], dx + and cl, 0x3f + mov [chs.s], cl + + mov eax, (stage2 - stage1) / 512 + mov bx, stage2 + mov cx, (stage3.end - stage2) / 512 + mov dx, 0 + call load + + mov si, stage_msg + call print + mov al, '2' + call print_char + call print_line + + jmp stage2.entry + +; load some sectors from disk to a buffer in memory +; buffer has to be below 1MiB +; IN +; ax: start sector +; bx: offset of buffer +; cx: number of sectors (512 Bytes each) +; dx: segment of buffer +; CLOBBER +; ax, bx, cx, dx, si +; TODO rewrite to (eventually) move larger parts at once +; if that is done increase buffer_size_sectors in startup-common to that (max 0x80000 - startup_end) +load: + cmp cx, 127 + jbe .good_size + + pusha + mov cx, 127 + call load + popa + add eax, 127 + add dx, 127 * 512 / 16 + sub cx, 127 + + jmp load +.good_size: + mov [DAPACK.addr], eax + mov [DAPACK.buf], bx + mov [DAPACK.count], cx + mov [DAPACK.seg], dx + + call print_dapack + + cmp byte [chs.s], 0 + jne .chs + ;INT 0x13 extended read does not work on CDROM! + mov dl, [disk] + mov si, DAPACK + mov ah, 0x42 + int 0x13 + jc error ; carry flag set on error + ret + +.chs: + ; calculate CHS + xor edx, edx + mov eax, [DAPACK.addr] + div dword [chs.s] ; divide by sectors + mov ecx, edx ; move sector remainder to ecx + xor edx, edx + div dword [chs.h] ; divide by heads + ; eax has cylinders, edx has heads, ecx has sectors + + ; Sector cannot be greater than 63 + inc ecx ; Sector is base 1 + cmp ecx, 63 + ja error_chs + + ; Head cannot be greater than 255 + cmp edx, 255 + ja error_chs + + ; Cylinder cannot be greater than 1023 + cmp eax, 1023 + ja error_chs + + ; Move CHS values to parameters + mov ch, al + shl ah, 6 + and cl, 0x3f + or cl, ah + shl dx, 8 + + ; read from disk using CHS + mov al, [DAPACK.count] + mov ah, 0x02 ; disk read (CHS) + mov bx, [DAPACK.buf] + mov dl, [disk] + push es ; save ES + mov es, [DAPACK.seg] + int 0x13 + pop es ; restore EC + jc error ; carry flag set on error + ret + +print_dapack: + mov bx, [DAPACK.addr + 2] + call print_hex + + mov bx, [DAPACK.addr] + call print_hex + + mov al, '#' + call print_char + + mov bx, [DAPACK.count] + call print_hex + + mov al, ' ' + call print_char + + mov bx, [DAPACK.seg] + call print_hex + + mov al, ':' + call print_char + + mov bx, [DAPACK.buf] + call print_hex + + call print_line + + ret + +error_chs: + mov ah, 0 + +error: + call print_line + + mov bh, 0 + mov bl, ah + call print_hex + + mov al, ' ' + call print_char + + mov si, error_msg + call print + call print_line +.halt: + cli + hlt + jmp .halt + +%include "print.asm" + +stage_msg: db "Stage ",0 +error_msg: db "ERROR",0 + +disk: db 0 + +chs: +.c: dd 0 +.h: dd 0 +.s: dd 0 + +DAPACK: + db 0x10 + db 0 +.count: dw 0 ; int 13 resets this to # of blocks actually read/written +.buf: dw 0 ; memory buffer destination address (0:7c00) +.seg: dw 0 ; in memory page zero +.addr: dq 0 ; put the lba to read in this spot + +times 446-($-$$) db 0 +partitions: times 4 * 16 db 0 +db 0x55 +db 0xaa diff --git a/asm/x86-unknown-none/stage2.asm b/asm/x86-unknown-none/stage2.asm new file mode 100644 index 0000000000..1ef0344c94 --- /dev/null +++ b/asm/x86-unknown-none/stage2.asm @@ -0,0 +1,134 @@ +SECTION .text +USE16 + +stage2.entry: + ; check for required features + call cpuid_check + + ; enable A20-Line via IO-Port 92, might not work on all motherboards + in al, 0x92 + or al, 2 + out 0x92, al + + mov dword [protected_mode.func], stage3.entry + jmp protected_mode.entry + +%include "cpuid.asm" +%include "gdt.asm" +%include "long_mode.asm" +%include "protected_mode.asm" +%include "thunk.asm" + +USE32 + +stage3.entry: + ; stage3 stack at 448 KiB (512KiB minus 64KiB disk buffer) + mov esp, 0x70000 + + ; push arguments + mov eax, thunk.int16 + push eax + mov eax, thunk.int15 + push eax + mov eax, thunk.int13 + push eax + mov eax, thunk.int10 + push eax + xor eax, eax + mov al, [disk] + push eax + mov eax, kernel.entry + push eax + mov eax, [stage3 + 0x18] + call eax +.halt: + cli + hlt + jmp .halt + +kernel: +.stack: dq 0 +.func: dq 0 +.args: dq 0 + +.entry: + ; page_table: usize + mov eax, [esp + 4] + mov [long_mode.page_table], eax + + ; stack: u64 + mov eax, [esp + 8] + mov [.stack], eax + mov eax, [esp + 12] + mov [.stack + 4], eax + + ; func: u64 + mov eax, [esp + 16] + mov [.func], eax + mov eax, [esp + 20] + mov [.func + 4], eax + + ; args: *const KernelArgs + mov eax, [esp + 24] + mov [.args], eax + + ; long_mode: usize + mov eax, [esp + 28] + test eax, eax + jz .inner32 + + mov eax, .inner64 + mov [long_mode.func], eax + jmp long_mode.entry + +.inner32: + ; disable paging + mov eax, cr0 + and eax, 0x7FFFFFFF + mov cr0, eax + + ;TODO: PAE (1 << 5) + ; enable FXSAVE/FXRSTOR, Page Global, and Page Size Extension + mov eax, cr4 + or eax, 1 << 9 | 1 << 7 | 1 << 4 + mov cr4, eax + + ; set page table + mov eax, [long_mode.page_table] + mov cr3, eax + + ; enabling paging and protection simultaneously + mov eax, cr0 + ; Bit 31: Paging, Bit 16: write protect kernel, Bit 0: Protected Mode + or eax, 1 << 31 | 1 << 16 | 1 + mov cr0, eax + + ; enable FPU + ;TODO: move to Rust + mov eax, cr0 + and al, 11110011b ; Clear task switched (3) and emulation (2) + or al, 00100010b ; Set numeric error (5) monitor co-processor (1) + mov cr0, eax + fninit + + mov esp, [.stack] + mov eax, [.args] + push eax + mov eax, [.func] + call eax +.halt32: + cli + hlt + jmp .halt32 + +USE64 + +.inner64: + mov rsp, [.stack] + mov rax, [.func] + mov rdi, [.args] + call rax +.halt64: + cli + hlt + jmp .halt64 diff --git a/asm/x86-unknown-none/thunk.asm b/asm/x86-unknown-none/thunk.asm new file mode 100644 index 0000000000..4423132ee2 --- /dev/null +++ b/asm/x86-unknown-none/thunk.asm @@ -0,0 +1,149 @@ +SECTION .text +USE32 + +thunk: +.int10: + mov dword [.func], .int10_real + jmp .enter + +.int13: + mov dword [.func], .int13_real + jmp .enter + +.int15: + mov dword [.func], .int15_real + jmp .enter + +.int16: + mov dword [.func], .int16_real + jmp .enter + +.func: dd 0 +.esp: dd 0 +.cr0: dd 0 + +.enter: + ; save flags + pushfd + + ; save registers + pushad + + ; save esp + mov [.esp], esp + + ; load gdt + lgdt [gdtr] + + ; far jump to protected mode 16-bit + jmp gdt.pm16_code:.pm16 + +.exit: + ; set segment selectors to 32-bit protected mode + mov eax, gdt.pm32_data + mov ds, eax + mov es, eax + mov fs, eax + mov gs, eax + mov ss, eax + + ; restore esp + mov esp, [.esp] + + ; restore registers + popad + + ; restore flags + popfd + + ; return + ret + +USE16 + +.int10_real: + int 0x10 + ret + +.int13_real: + int 0x13 + ret + +.int15_real: + int 0x15 + ret + +.int16_real: + int 0x16 + ret + +.pm16: + ; set segment selectors to protected mode 16-bit + mov eax, gdt.pm16_data + mov ds, eax + mov es, eax + mov fs, eax + mov gs, eax + mov ss, eax + + ; save cr0 + mov eax, cr0 + mov [.cr0], eax + + ; disable paging and protected mode + and eax, 0x7FFFFFFE + mov cr0, eax + + ; far jump to real mode + jmp 0:.real + +.real: + ; set segment selectors to real mode + mov eax, 0 + mov ds, eax + mov es, eax + mov fs, eax + mov gs, eax + mov ss, eax + + ; set stack + mov esp, 0x7C00 - 64 + + ; load registers and ES + pop es + pop edi + pop esi + pop ebp + pop ebx + pop edx + pop ecx + pop eax + + ; enable interrupts + sti + + ; call real mode function + call [.func] + + ; disable interrupts + cli + + ; save registers and ES + push eax + push ecx + push edx + push ebx + push ebp + push esi + push edi + push es + + ; load gdt (BIOS sometimes overwrites this) + lgdt [gdtr] + + ; restore cr0, will enable protected mode + mov eax, [.cr0] + mov cr0, eax + + ; far jump to protected mode 32-bit + jmp gdt.pm32_code:.exit diff --git a/linkers/riscv64-unknown-uefi.ld b/linkers/riscv64-unknown-uefi.ld new file mode 100644 index 0000000000..12e23f7494 --- /dev/null +++ b/linkers/riscv64-unknown-uefi.ld @@ -0,0 +1,78 @@ +OUTPUT_FORMAT("elf64-littleriscv", "elf64-littleriscv", "elf64-littleriscv") +OUTPUT_ARCH(riscv) +ENTRY(coff_start) +SECTIONS +{ + PROVIDE(ImageBase = .); + . = SEGMENT_START("text-segment", 0) + SIZEOF_HEADERS; + .note.gnu.build-id : { *(.note.gnu.build-id) } + .hash : { *(.hash) *(.gnu.hash) } + + . = ALIGN(4096); + .text : + { + PROVIDE(_text = .); + *(.text.unlikely .text.*_unlikely .text.unlikely.*) + *(.text.exit .text.exit.*) + *(.text.startup .text.startup.*) + *(.text.hot .text.hot.*) + *(SORT(.text.sorted.*)) + *(.text .stub .text.* .gnu.linkonce.t.*) + /* .gnu.warning sections are handled specially by elf.em. */ + *(.gnu.warning) + } + PROVIDE (__etext = .); + PROVIDE (_etext = .); + PROVIDE (etext = .); + . = ALIGN(4096); + + .rdata : + { + *(.rodata .rodata.* .gnu.linkonce.r.*) + *(.rodata1) + KEEP (*(.eh_frame)) + *(.eh_frame.*) + *(.dynamic) + } + . = ALIGN(4096); + .data : + { + *(.got) *(.igot) + *(.got.plt) *(.igot.plt) + *(.data .data.* .gnu.linkonce.d.*) + *(.data1) + *(.srodata.cst16) *(.srodata.cst8) *(.srodata.cst4) *(.srodata.cst2) *(.srodata .srodata.*) + *(.sdata2 .sdata2.* .gnu.linkonce.s2.*) + *(.sdata .sdata.* .gnu.linkonce.s.*) + PROVIDE (_edata = .); PROVIDE (edata = .); + . = ALIGN(4096); + PROVIDE (__bss_start = .); + *(.sbss2 .sbss2.* .gnu.linkonce.sb2.*) + *(.dynsbss) + *(.sbss .sbss.* .gnu.linkonce.sb.*) + *(.scommon) + *(.dynbss) + *(.bss .bss.* .gnu.linkonce.b.*) + *(COMMON) + . = ALIGN(4096); + } + .reloc : + { + KEEP(*(.reloc*)) + } + .rela : + { + *(.rela.*) + } + .data.rel.ro : + { + *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) + *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*) + } + . = ALIGN(4096); + + .dynsym : { *(.dynsym) } + .dynstr : { *(.dynstr) } + + /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) } +} diff --git a/linkers/x86-unknown-none.ld b/linkers/x86-unknown-none.ld new file mode 100644 index 0000000000..21f5729743 --- /dev/null +++ b/linkers/x86-unknown-none.ld @@ -0,0 +1,57 @@ +ENTRY(start) +OUTPUT_FORMAT(elf32-i386) + +SECTIONS { + /* The start address must match bootloader.asm */ + . = 0x13000; + + . += SIZEOF_HEADERS; + . = ALIGN(4096); + + .text : { + __text_start = .; + *(.text*) + . = ALIGN(4096); + __text_end = .; + } + + .rodata : { + __rodata_start = .; + *(.rodata*) + . = ALIGN(4096); + __rodata_end = .; + } + + .data : { + __data_start = .; + *(.data*) + . = ALIGN(4096); + __data_end = .; + __bss_start = .; + *(.bss*) + . = ALIGN(4096); + __bss_end = .; + } + + .tdata : { + __tdata_start = .; + *(.tdata*) + . = ALIGN(4096); + __tdata_end = .; + __tbss_start = .; + *(.tbss*) + . += 8; + . = ALIGN(4096); + __tbss_end = .; + } + + __end = .; + + /DISCARD/ : { + *(.comment*) + *(.eh_frame*) + *(.gcc_except_table*) + *(.note*) + *(.rel.eh_frame*) + } +} diff --git a/mk/aarch64-unknown-uefi.mk b/mk/aarch64-unknown-uefi.mk new file mode 100644 index 0000000000..49f2e331d3 --- /dev/null +++ b/mk/aarch64-unknown-uefi.mk @@ -0,0 +1,69 @@ +export PARTED?=parted +export QEMU?=qemu-system-aarch64 + +all: $(BUILD)/bootloader.efi + +$(BUILD)/bootloader.efi: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f) + mkdir -p "$(BUILD)" + env RUSTFLAGS="--cfg aes_force_soft" \ + cargo rustc \ + --manifest-path="$<" \ + -Z build-std=core,alloc \ + -Z build-std-features=compiler-builtins-mem \ + --target $(TARGET) \ + --bin bootloader \ + --release \ + -- \ + --emit link="$@" + +$(BUILD)/bootloader-live.efi: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f) + mkdir -p "$(BUILD)" + env RUSTFLAGS="--cfg aes_force_soft" \ + cargo rustc \ + --manifest-path="$<" \ + -Z build-std=core,alloc \ + -Z build-std-features=compiler-builtins-mem \ + --target $(TARGET) \ + --bin bootloader \ + --release \ + --features live \ + -- \ + --emit link="$@" + +$(BUILD)/esp.bin: $(BUILD)/bootloader.efi + rm -f "$@.partial" + fallocate -l 64MiB "$@.partial" + mkfs.vfat -F 32 "$@.partial" + mmd -i "$@.partial" efi + mmd -i "$@.partial" efi/boot + mcopy -i "$@.partial" "$<" ::efi/boot/bootaa64.efi + mv "$@.partial" "$@" + +$(BUILD)/harddrive.bin: $(BUILD)/esp.bin $(BUILD)/filesystem.bin + rm -f "$@.partial" + fallocate -l 320MiB "$@.partial" + $(PARTED) -s -a minimal "$@.partial" mklabel gpt + $(PARTED) -s -a minimal "$@.partial" mkpart ESP FAT32 1MiB 65MiB + $(PARTED) -s -a minimal "$@.partial" mkpart REDOXFS 65MiB 100% + $(PARTED) -s -a minimal "$@.partial" toggle 1 boot + dd if="$(BUILD)/esp.bin" of="$@.partial" bs=1MiB seek=1 conv=notrunc + dd if="$(BUILD)/filesystem.bin" of="$@.partial" bs=1MiB seek=65 conv=notrunc + mv "$@.partial" "$@" + +$(BUILD)/firmware.rom: /usr/share/AAVMF/AAVMF_CODE.fd + cp "$<" "$@" + +qemu: $(BUILD)/harddrive.bin $(BUILD)/firmware.rom + $(QEMU) \ + -d cpu_reset \ + -no-reboot \ + -smp 4 -m 2048 \ + -chardev stdio,id=debug,signal=off,mux=on \ + -serial chardev:debug \ + -mon chardev=debug \ + -device virtio-gpu-pci \ + -machine virt \ + -net none \ + -cpu max \ + -bios "$(BUILD)/firmware.rom" \ + -drive file="$<",format=raw diff --git a/mk/riscv64gc-unknown-uefi.mk b/mk/riscv64gc-unknown-uefi.mk new file mode 100644 index 0000000000..639614a7e5 --- /dev/null +++ b/mk/riscv64gc-unknown-uefi.mk @@ -0,0 +1,108 @@ +LD=riscv64-unknown-redox-ld +OBJCOPY=riscv64-unknown-redox-objcopy +SCRIPT=$(SOURCE)/linkers/riscv64-unknown-uefi.ld +PARTED?=parted +QEMU?=qemu-system-riscv64 + +all: $(BUILD)/bootloader.efi + +$(BUILD)/%.efi: $(BUILD)/%.efi.elf $(BUILD)/%.efi.sym + $(OBJCOPY) -j .text -j .data -j .rdata -j .rela -j .reloc --target pei-riscv64-little \ + --file-alignment 512 --section-alignment 4096 --subsystem 10 "$<" "$@" + +.PRECIOUS: $(BUILD)/%.efi.sym +$(BUILD)/%.efi.sym: $(BUILD)/%.efi.elf + $(OBJCOPY) --only-keep-debug "$<" "$@" + +$(BUILD)/%.efi.elf: $(BUILD)/%.a $(SCRIPT) + $(LD) --gc-sections -z max-page-size=0x1000 --warn-common --no-undefined -z nocombreloc -shared \ + --fatal-warnings -Bsymbolic --entry coff_start -T "$(SCRIPT)" -o "$@" "$<" + +$(BUILD)/bootloader.a: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f) + mkdir -p "$(BUILD)" + env RUSTFLAGS="--cfg aes_force_soft" \ + cargo rustc \ + --manifest-path="$<" \ + -Z build-std=core,alloc \ + -Z build-std-features=compiler-builtins-mem \ + --target $(TARGET) \ + --lib \ + --release \ + -- \ + --emit link=$@ + +$(BUILD)/bootloader-live.a: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f) + mkdir -p "$(BUILD)" + env RUSTFLAGS="--cfg aes_force_soft" \ + cargo rustc \ + --manifest-path="$<" \ + -Z build-std=core,alloc \ + -Z build-std-features=compiler-builtins-mem \ + --target $(TARGET) \ + --lib \ + --release \ + --features live \ + -- \ + --emit link=$@ + + +$(BUILD)/esp.bin: $(BUILD)/bootloader.efi + rm -f $@.partial + fallocate -l 64MiB $@.partial + mkfs.vfat -F 32 $@.partial + mmd -i $@.partial EFI + mmd -i $@.partial EFI/BOOT + mcopy -i $@.partial $< ::EFI/BOOT/BOOTRISCV64.EFI + mv $@.partial $@ + +$(BUILD)/harddrive.bin: $(BUILD)/esp.bin $(BUILD)/filesystem.bin + rm -f $@.partial + fallocate -l 320MiB $@.partial + $(PARTED) -s -a minimal $@.partial mklabel gpt + $(PARTED) -s -a minimal $@.partial mkpart ESP FAT32 1MiB 65MiB + $(PARTED) -s -a minimal $@.partial mkpart REDOXFS 65MiB 100% + $(PARTED) -s -a minimal $@.partial toggle 1 boot + dd if=$(BUILD)/esp.bin of=$@.partial bs=1MiB seek=1 conv=notrunc + dd if=$(BUILD)/filesystem.bin of=$@.partial bs=1MiB seek=65 conv=notrunc + mv $@.partial $@ + +$(BUILD)/fw_vars.img: /usr/share/qemu-efi-riscv64/RISCV_VIRT_VARS.fd + cp "$<" "$@" + +$(BUILD)/firmware.rom: /usr/share/qemu-efi-riscv64/RISCV_VIRT_CODE.fd + cp "$<" "$@" + +qemu-acpi: $(BUILD)/harddrive.bin $(BUILD)/firmware.rom $(BUILD)/fw_vars.img + $(QEMU) \ + -M virt \ + -d cpu_reset \ + -no-reboot \ + -smp 4 -m 2048 \ + -chardev stdio,id=debug,signal=off,mux=on \ + -serial chardev:debug \ + -mon chardev=debug \ + -device virtio-gpu-pci \ + -machine virt \ + -net none \ + -cpu max \ + -drive if=pflash,format=raw,unit=0,file=$(BUILD)/firmware.rom,readonly=on \ + -drive if=pflash,format=raw,unit=1,file=$(BUILD)/fw_vars.img \ + -drive file=$(BUILD)/harddrive.bin,format=raw,if=virtio + + +qemu-dtb: $(BUILD)/harddrive.bin $(BUILD)/firmware.rom $(BUILD)/fw_vars.img + $(QEMU) \ + -M virt,acpi=off \ + -d cpu_reset \ + -no-reboot \ + -smp 4 -m 2048 \ + -chardev stdio,id=debug,signal=off,mux=on \ + -serial chardev:debug \ + -mon chardev=debug \ + -device virtio-gpu-pci \ + -machine virt \ + -net none \ + -cpu max \ + -drive if=pflash,format=raw,unit=0,file=$(BUILD)/firmware.rom,readonly=on \ + -drive if=pflash,format=raw,unit=1,file=$(BUILD)/fw_vars.img \ + -drive file=$(BUILD)/harddrive.bin,format=raw,if=virtio -s diff --git a/mk/x86-unknown-none.mk b/mk/x86-unknown-none.mk new file mode 100644 index 0000000000..16e949de93 --- /dev/null +++ b/mk/x86-unknown-none.mk @@ -0,0 +1,65 @@ +export LD?=ld +export OBJCOPY?=objcopy +export PARTED?=parted +export QEMU?=qemu-system-x86_64 + +all: $(BUILD)/bootloader.bin + +$(BUILD)/libbootloader.a: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f) + mkdir -p "$(BUILD)" + env RUSTFLAGS="--cfg aes_force_soft" \ + cargo rustc \ + --manifest-path="$<" \ + -Z build-std=core,alloc \ + -Z build-std-features=compiler-builtins-mem \ + --target "$(TARGET)" \ + --lib \ + --release \ + -- \ + --emit link="$@" + +$(BUILD)/libbootloader-live.a: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f) + mkdir -p "$(BUILD)" + env RUSTFLAGS="--cfg aes_force_soft" \ + cargo rustc \ + --manifest-path="$<" \ + -Z build-std=core,alloc \ + -Z build-std-features=compiler-builtins-mem \ + --target "$(TARGET)" \ + --lib \ + --release \ + --features live \ + -- \ + --emit link="$@" + +$(BUILD)/%.elf: $(BUILD)/lib%.a $(SOURCE)/linkers/$(TARGET).ld + $(LD) -m elf_i386 --gc-sections -z max-page-size=0x1000 -T "$(SOURCE)/linkers/$(TARGET).ld" -o "$@" "$<" + $(OBJCOPY) --only-keep-debug "$@" "$@.sym" + $(OBJCOPY) --strip-debug "$@" + +$(BUILD)/%.bin: $(BUILD)/%.elf $(shell find $(SOURCE)/asm/$(TARGET) -type f) + nasm -f bin -o "$@" -l "$@.lst" -D STAGE3="$<" -i"$(SOURCE)/asm/$(TARGET)/" "$(SOURCE)/asm/$(TARGET)/bootloader.asm" + +$(BUILD)/harddrive.bin: $(BUILD)/bootloader.bin $(BUILD)/filesystem.bin + rm -f "$@.partial" + fallocate -l 256MiB "$@.partial" + $(PARTED) -s -a minimal "$@.partial" mklabel msdos + $(PARTED) -s -a minimal "$@.partial" mkpart primary 2MiB 100% + dd if="$<" of="$@.partial" bs=1 count=446 conv=notrunc + dd if="$<" of="$@.partial" bs=512 skip=1 seek=1 conv=notrunc + dd if="$(BUILD)/filesystem.bin" of="$@.partial" bs=1MiB seek=2 conv=notrunc + mv "$@.partial" "$@" + +qemu: $(BUILD)/harddrive.bin + $(QEMU) \ + -d cpu_reset \ + -no-reboot \ + -smp 4 -m 2048 \ + -chardev stdio,id=debug,signal=off,mux=on \ + -serial chardev:debug \ + -mon chardev=debug \ + -machine q35 \ + -net none \ + -enable-kvm \ + -cpu host \ + -drive file="$<",format=raw diff --git a/mk/x86_64-unknown-uefi.mk b/mk/x86_64-unknown-uefi.mk new file mode 100644 index 0000000000..3e997a6458 --- /dev/null +++ b/mk/x86_64-unknown-uefi.mk @@ -0,0 +1,70 @@ +export PARTED?=parted +export QEMU?=qemu-system-x86_64 + +all: $(BUILD)/bootloader.efi + +$(BUILD)/bootloader.efi: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f) + mkdir -p "$(BUILD)" + env RUSTFLAGS="--cfg aes_force_soft" \ + cargo rustc \ + --manifest-path="$<" \ + -Z build-std=core,alloc \ + -Z build-std-features=compiler-builtins-mem \ + --target $(TARGET) \ + --bin bootloader \ + --release \ + -- \ + --emit link="$@" + +$(BUILD)/bootloader-live.efi: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f) + mkdir -p $(BUILD) + cd "$(SOURCE)" + env RUSTFLAGS="--cfg aes_force_soft" \ + cargo rustc \ + --manifest-path="$<" \ + -Z build-std=core,alloc \ + -Z build-std-features=compiler-builtins-mem \ + --target $(TARGET) \ + --bin bootloader \ + --release \ + --features live \ + -- \ + --emit link="$@" + +$(BUILD)/esp.bin: $(BUILD)/bootloader.efi + rm -f "$@.partial" + fallocate -l 1MiB $@.partial + mkfs.vfat "$@.partial" + mmd -i "$@.partial" efi + mmd -i "$@.partial" efi/boot + mcopy -i "$@.partial" "$<" ::efi/boot/bootx64.efi + mv "$@.partial" "$@" + +$(BUILD)/harddrive.bin: $(BUILD)/esp.bin $(BUILD)/filesystem.bin + rm -f "$@.partial" + fallocate -l 320MiB "$@.partial" + $(PARTED) -s -a minimal "$@.partial" mklabel gpt + $(PARTED) -s -a minimal "$@.partial" mkpart ESP FAT32 1MiB 2MiB + $(PARTED) -s -a minimal "$@.partial" mkpart REDOXFS 2MiB 100% + $(PARTED) -s -a minimal "$@.partial" toggle 1 boot + dd if="$(BUILD)/esp.bin" of="$@.partial" bs=1MiB seek=1 conv=notrunc + dd if="$(BUILD)/filesystem.bin" of="$@.partial" bs=1MiB seek=2 conv=notrunc + mv "$@.partial" "$@" + +$(BUILD)/firmware.rom: /usr/share/OVMF/OVMF_CODE.fd + cp "$<" "$@" + +qemu: $(BUILD)/harddrive.bin $(BUILD)/firmware.rom + $(QEMU) \ + -d cpu_reset \ + -no-reboot \ + -smp 4 -m 2048 \ + -chardev stdio,id=debug,signal=off,mux=on \ + -serial chardev:debug \ + -mon chardev=debug \ + -machine q35 \ + -net none \ + -enable-kvm \ + -cpu host \ + -bios "$(BUILD)/firmware.rom" \ + -drive file="$<",format=raw diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000000..42f22f6190 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly-2025-10-03" +components = ["rust-src"] diff --git a/src/arch/aarch64.rs b/src/arch/aarch64.rs new file mode 100644 index 0000000000..55c68f5537 --- /dev/null +++ b/src/arch/aarch64.rs @@ -0,0 +1,154 @@ +use crate::area_add; +use crate::os::{Os, OsMemoryEntry, OsMemoryKind, dtb::is_in_dev_mem_region}; +use core::slice; + +pub(crate) const PF_PRESENT: u64 = 1 << 0; +pub(crate) const PF_TABLE: u64 = 1 << 1; +pub(crate) const PF_OUTER_SHAREABLE: u64 = 0b01 << 8; +pub(crate) const PF_INNER_SHAREABLE: u64 = 0b11 << 8; +pub(crate) const PF_ACCESS: u64 = 1 << 10; + +pub(crate) const PF_DEV: u64 = PF_OUTER_SHAREABLE | 2 << 2; +pub(crate) const PF_RAM: u64 = PF_INNER_SHAREABLE; + +pub(crate) const ENTRY_ADDRESS_MASK: u64 = 0x000F_FFFF_FFFF_F000; +pub(crate) const PAGE_ENTRIES: usize = 512; +const PAGE_SIZE: usize = 4096; +pub(crate) const PHYS_OFFSET: u64 = 0xFFFF_8000_0000_0000; + +unsafe fn paging_allocate(os: &impl Os) -> Option<&'static mut [u64]> { + unsafe { + let ptr = os.alloc_zeroed_page_aligned(PAGE_SIZE); + if !ptr.is_null() { + area_add(OsMemoryEntry { + base: ptr as u64, + size: PAGE_SIZE as u64, + kind: OsMemoryKind::Reclaim, + }); + Some(slice::from_raw_parts_mut(ptr as *mut u64, PAGE_ENTRIES)) + } else { + None + } + } +} + +pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -> Option { + unsafe { + // Create L0 + let l0 = paging_allocate(os)?; + + { + // Create L1 for identity mapping + let l1 = paging_allocate(os)?; + + // Link first user and first kernel L0 entry to L1 + l0[0] = l1.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT; + l0[256] = l1.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT; + + // Identity map 8 GiB using 1 GiB pages + for l1_i in 0..8 { + let addr = l1_i as u64 * 0x4000_0000; + //TODO: is PF_RAM okay? + l1[l1_i] = addr | PF_ACCESS | PF_DEV | PF_PRESENT; + } + } + + { + // Create L1 for kernel mapping + let l1 = paging_allocate(os)?; + + // Link second to last L0 entry to L1 + l0[510] = l1.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT; + + // Map kernel_size at kernel offset + let mut kernel_mapped = 0; + let mut l1_i = 0; + while kernel_mapped < kernel_size && l1_i < l1.len() { + let l2 = paging_allocate(os)?; + l1[l1_i] = l2.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT; + l1_i += 1; + + let mut l2_i = 0; + while kernel_mapped < kernel_size && l2_i < l2.len() { + let l3 = paging_allocate(os)?; + l2[l2_i] = l3.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT; + l2_i += 1; + + let mut l3_i = 0; + while kernel_mapped < kernel_size && l3_i < l3.len() { + let addr = kernel_phys + kernel_mapped; + l3[l3_i] = addr | PF_ACCESS | PF_RAM | PF_TABLE | PF_PRESENT; + l3_i += 1; + kernel_mapped += PAGE_SIZE as u64; + } + } + } + assert!(kernel_mapped >= kernel_size); + } + + Some(l0.as_ptr() as usize) + } +} + +pub unsafe fn paging_framebuffer( + os: &impl Os, + page_phys: usize, + framebuffer_phys: u64, + framebuffer_size: u64, +) -> Option { + unsafe { + //TODO: smarter test for framebuffer already mapped + if framebuffer_phys + framebuffer_size <= 0x2_0000_0000 { + return Some(framebuffer_phys + PHYS_OFFSET); + } + + let l0_i = ((framebuffer_phys / 0x80_0000_0000) + 256) as usize; + let mut l1_i = ((framebuffer_phys % 0x80_0000_0000) / 0x4000_0000) as usize; + let mut l2_i = ((framebuffer_phys % 0x4000_0000) / 0x20_0000) as usize; + let mut l3_i = ((framebuffer_phys % 0x20_0000) / (PAGE_SIZE as u64)) as usize; + assert_eq!(framebuffer_phys % (PAGE_SIZE as u64), 0); + + let l0 = slice::from_raw_parts_mut(page_phys as *mut u64, PAGE_ENTRIES); + + // Create l1 for framebuffer mapping + let l1 = if l0[l0_i] == 0 { + let l1 = paging_allocate(os)?; + l0[l0_i] = l1.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT; + l1 + } else { + slice::from_raw_parts_mut((l0[l0_i] & ENTRY_ADDRESS_MASK) as *mut u64, PAGE_ENTRIES) + }; + + // Map framebuffer_size at framebuffer offset + let mut framebuffer_mapped = 0; + while framebuffer_mapped < framebuffer_size && l1_i < l1.len() { + let l2 = paging_allocate(os)?; + assert_eq!(l1[l1_i], 0); + l1[l1_i] = l2.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT; + + while framebuffer_mapped < framebuffer_size && l2_i < l2.len() { + let l3 = paging_allocate(os)?; + assert_eq!(l2[l2_i], 0); + l2[l2_i] = l3.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT; + + while framebuffer_mapped < framebuffer_size && l3_i < l3.len() { + let addr = framebuffer_phys + framebuffer_mapped; + assert_eq!(l3[l3_i], 0); + //TODO: is PF_RAM okay? + l3[l3_i] = addr | PF_ACCESS | PF_RAM | PF_TABLE | PF_PRESENT; + framebuffer_mapped += PAGE_SIZE as u64; + l3_i += 1; + } + + l2_i += 1; + l3_i = 0; + } + + l1_i += 1; + l2_i = 0; + } + assert!(framebuffer_mapped >= framebuffer_size); + + Some(framebuffer_phys + PHYS_OFFSET) + } +} diff --git a/src/arch/mod.rs b/src/arch/mod.rs new file mode 100644 index 0000000000..16a1c8b83b --- /dev/null +++ b/src/arch/mod.rs @@ -0,0 +1,17 @@ +#[cfg(target_arch = "aarch64")] +pub use self::aarch64::*; + +#[cfg(target_arch = "aarch64")] +mod aarch64; + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +pub use self::x86::*; + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +mod x86; + +#[cfg(target_arch = "riscv64")] +pub use self::riscv64::*; + +#[cfg(target_arch = "riscv64")] +mod riscv64; diff --git a/src/arch/riscv64/mod.rs b/src/arch/riscv64/mod.rs new file mode 100644 index 0000000000..a757e3c7b4 --- /dev/null +++ b/src/arch/riscv64/mod.rs @@ -0,0 +1,59 @@ +use core::slice; + +use crate::area_add; +use crate::os::{Os, OsMemoryEntry, OsMemoryKind}; + +pub(crate) mod sv39; +pub(crate) mod sv48; +pub(crate) mod sv57; + +// Common constants +const PAGE_SHIFT: usize = 12; +const TABLE_SHIFT: usize = 9; +const TABLE_MASK: usize = (1 << TABLE_SHIFT) - 1; +const PAGE_ENTRIES: usize = 512; +const PAGE_SIZE: usize = 4096; +const PHYS_MASK: usize = (1usize << 44) - 1; + +const VALID: u64 = 1; +const RWX: u64 = 7 << 1; +const ACCESSED: u64 = 1 << 6; +const DIRTY: u64 = 1 << 7; + +extern crate alloc; + +pub(crate) use sv39::PHYS_OFFSET; +pub(crate) use sv39::SATP_BITS; +pub(crate) use sv39::paging_create; +pub(crate) use sv39::paging_physmem as paging_framebuffer; + +unsafe fn paging_allocate(os: &impl Os) -> Option<&'static mut [u64]> { + unsafe { + let ptr = os.alloc_zeroed_page_aligned(PAGE_SIZE); + if !ptr.is_null() { + area_add(OsMemoryEntry { + base: ptr as u64, + size: PAGE_SIZE as u64, + kind: OsMemoryKind::Reclaim, + }); + Some(slice::from_raw_parts_mut(ptr as *mut u64, PAGE_ENTRIES)) + } else { + None + } + } +} + +unsafe fn get_table(os: &impl Os, parent: &mut [u64], index: usize) -> Option<&'static mut [u64]> { + unsafe { + if parent[index] == 0 { + let table = paging_allocate(os)?; + parent[index] = table.as_ptr() as u64 >> 2 | VALID; + Some(table) + } else { + Some(slice::from_raw_parts_mut( + (((parent[index] >> 10) & PHYS_MASK as u64) << 12) as *mut u64, + PAGE_ENTRIES, + )) + } + } +} diff --git a/src/arch/riscv64/sv39.rs b/src/arch/riscv64/sv39.rs new file mode 100644 index 0000000000..746fe5b097 --- /dev/null +++ b/src/arch/riscv64/sv39.rs @@ -0,0 +1,90 @@ +use core::slice; + +use super::*; +use crate::os::Os; + +// Sv39 scheme + +pub(crate) const PHYS_OFFSET: u64 = 0xFFFF_FFC0_0000_0000; +pub(crate) const SATP_BITS: usize = 8; + +pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -> Option { + unsafe { + // Create L2 + let l2 = paging_allocate(os)?; + + { + // Create L1 for identity mapping + for l2_i in 0..8 { + let addr = l2_i as u64 * 0x4000_0000; + // Identity map 8 GiB using 1GB pages + l2[l2_i] = addr >> 2 | RWX | VALID | ACCESSED | DIRTY; + // map phys into kernel VAS + l2[(PAGE_ENTRIES / 2) + l2_i] = addr >> 2 | RWX | VALID | ACCESSED | DIRTY; + } + } + + { + // Create L1 for kernel mapping + let l1 = paging_allocate(os)?; + + // Link second to last L0 entry to L1 + l2[510] = l1.as_ptr() as u64 >> 2 | VALID; + + // Map kernel_size at kernel offset + let mut kernel_mapped = 0; + let mut l1_i = 0; + while kernel_mapped < kernel_size && l1_i < l1.len() { + let l0 = paging_allocate(os)?; + l1[l1_i] = l0.as_ptr() as u64 >> 2 | VALID; + l1_i += 1; + + let mut l0_i = 0; + while kernel_mapped < kernel_size && l0_i < l2.len() { + let addr = kernel_phys + kernel_mapped; + l0[l0_i] = addr >> 2 | RWX | VALID | ACCESSED | DIRTY; + l0_i += 1; + kernel_mapped += PAGE_SIZE as u64; + } + } + assert!(kernel_mapped >= kernel_size); + } + + Some(l2.as_ptr() as usize) + } +} + +pub unsafe fn paging_physmem(os: &impl Os, page_phys: usize, phys: u64, size: u64) -> Option { + unsafe { + if phys + size <= 0x2_0000_0000 { + return Some(phys + PHYS_OFFSET); + } + + let mut l1_i = (phys as usize >> (PAGE_SHIFT + 2 * TABLE_SHIFT)) + PAGE_ENTRIES / 2; + let mut l0_i = (phys as usize >> (PAGE_SHIFT + TABLE_SHIFT)) & TABLE_MASK; + assert_eq!(phys & ((1 << (PAGE_SHIFT + TABLE_SHIFT)) - 1), 0); + + let l1 = slice::from_raw_parts_mut(page_phys as *mut u64, PAGE_ENTRIES); + + // Map framebuffer_size at framebuffer offset + let mut mapped = 0; + while mapped < size && l1_i < l1.len() { + let l0 = get_table(os, l1, l1_i)?; + + while mapped < size && l0_i < l0.len() { + let addr = phys + mapped; + assert_eq!(l0[l0_i], 0); + l0[l0_i] = (addr >> 2) | RWX | VALID | ACCESSED | DIRTY; + mapped += 1 << (PAGE_SHIFT + TABLE_SHIFT); // Map with 2mb mega-pages + l0_i += 1; + } + + l1_i += 1; + l0_i = 0; + } + + assert!(mapped >= size); + + Some(phys + PHYS_OFFSET) + } +} diff --git a/src/arch/riscv64/sv48.rs b/src/arch/riscv64/sv48.rs new file mode 100644 index 0000000000..d6cff97d3e --- /dev/null +++ b/src/arch/riscv64/sv48.rs @@ -0,0 +1,108 @@ +use core::slice; + +use super::*; +use crate::os::Os; + +// Sv48 scheme + +pub(crate) const PHYS_OFFSET: u64 = 0xFFFF_8000_0000_0000; +pub(crate) const SATP_BITS: usize = 9; + +pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -> Option { + unsafe { + // Create L3 + let l3 = paging_allocate(os)?; + + { + // Create L2 for identity mapping + let l2 = paging_allocate(os)?; + + // Map L2 into beginning of userspace and kernelspace + l3[0] = (l2.as_ptr() as u64 >> 2) | VALID; + l3[PAGE_ENTRIES / 2] = (l2.as_ptr() as u64 >> 2) | VALID; + + // Identity map 8 GiB using 1GB pages + for l2_i in 0..8 { + let addr = l2_i as u64 * 0x4000_0000; + l2[l2_i] = addr >> 2 | RWX | VALID; + } + } + + { + // Create L2 for kernel mapping + let l2 = paging_allocate(os)?; + + // Link last L3 entry to L2 + l3[511] = l2.as_ptr() as u64 >> 2 | VALID; + + // Create L1 for kernel mapping + let l1 = paging_allocate(os)?; + + // Link last L1 entry to L2 + l2[510] = l1.as_ptr() as u64 >> 2 | VALID; + + // Map kernel_size at kernel offset + let mut kernel_mapped = 0; + let mut l1_i = 0; + while kernel_mapped < kernel_size && l1_i < l1.len() { + let l0 = paging_allocate(os)?; + l1[l1_i] = l0.as_ptr() as u64 >> 2 | VALID; + l1_i += 1; + + let mut l0_i = 0; + while kernel_mapped < kernel_size && l0_i < l2.len() { + let addr = kernel_phys + kernel_mapped; + l0[l0_i] = addr >> 2 | RWX | VALID | ACCESSED | DIRTY; + l0_i += 1; + kernel_mapped += PAGE_SIZE as u64; + } + } + assert!(kernel_mapped >= kernel_size); + } + + Some(l3.as_ptr() as usize) + } +} + +pub unsafe fn paging_physmem(os: &impl Os, page_phys: usize, phys: u64, size: u64) -> Option { + unsafe { + if phys + size <= 0x2_0000_0000 { + return Some(phys + PHYS_OFFSET); + } + + let mut l2_i = (phys as usize >> (PAGE_SHIFT + 3 * TABLE_SHIFT)) + PAGE_ENTRIES / 2; + let mut l1_i = (phys as usize >> (PAGE_SHIFT + 2 * TABLE_SHIFT)) & TABLE_MASK; + let mut l0_i = (phys as usize >> (PAGE_SHIFT + TABLE_SHIFT)) & TABLE_MASK; + assert_eq!(phys & ((1 << (PAGE_SHIFT + TABLE_SHIFT)) - 1), 0); + + let l2 = slice::from_raw_parts_mut(page_phys as *mut u64, PAGE_ENTRIES); + + // Map framebuffer_size at framebuffer offset + let mut mapped = 0; + while mapped < size && l2_i < l2.len() { + let l1 = get_table(os, l2, l2_i)?; + + while mapped < size && l1_i < l1.len() { + let l0 = get_table(os, l1, l1_i)?; + + while mapped < size && l0_i < l0.len() { + let addr = phys + mapped; + assert_eq!(l0[l0_i], 0); + l0[l0_i] = (addr >> 2) | RWX | VALID; + mapped += 1 << (PAGE_SHIFT + TABLE_SHIFT); + l0_i += 1; + } + + l1_i += 1; + l0_i = 0; + } + + l2_i += 1; + l1_i = 0; + } + + assert!(mapped >= size); + + Some(phys + PHYS_OFFSET) + } +} diff --git a/src/arch/riscv64/sv57.rs b/src/arch/riscv64/sv57.rs new file mode 100644 index 0000000000..e85f511ecd --- /dev/null +++ b/src/arch/riscv64/sv57.rs @@ -0,0 +1,124 @@ +use core::slice; + +use super::*; +use crate::os::Os; + +// Sv57 scheme + +pub(crate) const PHYS_OFFSET: u64 = 0xFF00_0000_0000_0000; +pub(crate) const SATP_BIT: usize = 10; + +pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -> Option { + unsafe { + // Create L4 + let l4 = paging_allocate(os)?; + + { + // Create L3 + let l3 = paging_allocate(os)?; + + // Map L3 into beginning of userspace and kernelspace + l4[0] = (l3.as_ptr() as u64 >> 2) | VALID; + l4[PAGE_ENTRIES / 2] = (l3.as_ptr() as u64 >> 2) | VALID; + + // Create L2 for identity mapping + let l2 = paging_allocate(os)?; + + // Identity map 8 GiB using 1GB pages + for l2_i in 0..8 { + let addr = l2_i as u64 * 0x4000_0000; + l2[l2_i] = addr >> 2 | RWX | VALID; + } + } + + { + // Create L3 + let l3 = paging_allocate(os)?; + + // Link last L4 entry to L3 + l4[511] = l3.as_ptr() as u64 >> 2 | VALID; + + // Create L2 for kernel mapping + let l2 = paging_allocate(os)?; + + // Link last L3 entry to L2 + l3[511] = l2.as_ptr() as u64 >> 2 | VALID; + + // Create L1 for kernel mapping + let l1 = paging_allocate(os)?; + + // Link last L1 entry to L2 + l2[510] = l1.as_ptr() as u64 >> 2 | VALID; + + // Map kernel_size at kernel offset + let mut kernel_mapped = 0; + let mut l1_i = 0; + while kernel_mapped < kernel_size && l1_i < l1.len() { + let l0 = paging_allocate(os)?; + l1[l1_i] = l0.as_ptr() as u64 >> 2 | VALID; + l1_i += 1; + + let mut l0_i = 0; + while kernel_mapped < kernel_size && l0_i < l2.len() { + let addr = kernel_phys + kernel_mapped; + l0[l0_i] = addr >> 2 | RWX | VALID | ACCESSED | DIRTY; + l0_i += 1; + kernel_mapped += PAGE_SIZE as u64; + } + } + assert!(kernel_mapped >= kernel_size); + } + + Some(l4.as_ptr() as usize) + } +} + +pub unsafe fn paging_physmem(os: &impl Os, page_phys: usize, phys: u64, size: u64) -> Option { + unsafe { + if phys + size <= 0x2_0000_0000 { + return Some(phys + PHYS_OFFSET); + } + let mut l3_i = (phys as usize >> (PAGE_SHIFT + 4 * TABLE_SHIFT)) + PAGE_ENTRIES / 2; + let mut l2_i = (phys as usize >> (PAGE_SHIFT + 3 * TABLE_SHIFT)) & TABLE_MASK; + let mut l1_i = (phys as usize >> (PAGE_SHIFT + 2 * TABLE_SHIFT)) & TABLE_MASK; + let mut l0_i = (phys as usize >> (PAGE_SHIFT + TABLE_SHIFT)) & TABLE_MASK; + assert_eq!(phys & ((1 << (PAGE_SHIFT + TABLE_SHIFT)) - 1), 0); + + let l3 = slice::from_raw_parts_mut(page_phys as *mut u64, PAGE_ENTRIES); + + // Map framebuffer_size at framebuffer offset + let mut mapped = 0; + + while mapped < size && l3_i < l3.len() { + let l2 = get_table(os, l3, l3_i)?; + + while mapped < size && l2_i < l2.len() { + let l1 = get_table(os, l2, l2_i)?; + + while mapped < size && l1_i < l1.len() { + let l0 = get_table(os, l1, l1_i)?; + + while mapped < size && l0_i < l0.len() { + let addr = phys + mapped; + assert_eq!(l0[l0_i], 0); + l0[l0_i] = (addr >> 2) | RWX | VALID; + mapped += 1 << (PAGE_SHIFT + TABLE_SHIFT); + l0_i += 1; + } + + l1_i += 1; + l0_i = 0; + } + + l2_i += 1; + l1_i = 0; + } + l3_i += 1; + l2_i += 0; + } + + assert!(mapped >= size); + + Some(phys + PHYS_OFFSET) + } +} diff --git a/src/arch/x86/mod.rs b/src/arch/x86/mod.rs new file mode 100644 index 0000000000..bda3f5d79c --- /dev/null +++ b/src/arch/x86/mod.rs @@ -0,0 +1,29 @@ +use crate::os::Os; + +pub(crate) mod x32; +pub(crate) mod x64; + +pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -> Option { + unsafe { + if crate::KERNEL_64BIT { + x64::paging_create(os, kernel_phys, kernel_size) + } else { + x32::paging_create(os, kernel_phys, kernel_size) + } + } +} + +pub unsafe fn paging_framebuffer( + os: &impl Os, + page_phys: usize, + framebuffer_phys: u64, + framebuffer_size: u64, +) -> Option { + unsafe { + if crate::KERNEL_64BIT { + x64::paging_framebuffer(os, page_phys, framebuffer_phys, framebuffer_size) + } else { + x32::paging_framebuffer(os, page_phys, framebuffer_phys, framebuffer_size) + } + } +} diff --git a/src/arch/x86/x32.rs b/src/arch/x86/x32.rs new file mode 100644 index 0000000000..7774ff4c83 --- /dev/null +++ b/src/arch/x86/x32.rs @@ -0,0 +1,88 @@ +use crate::area_add; +use crate::os::{Os, OsMemoryEntry, OsMemoryKind}; +use core::slice; + +const PAGE_ENTRIES: usize = 1024; +const PAGE_SIZE: usize = 4096; +pub(crate) const PHYS_OFFSET: u32 = 0x8000_0000; + +unsafe fn paging_allocate(os: &impl Os) -> Option<&'static mut [u32]> { + unsafe { + let ptr = os.alloc_zeroed_page_aligned(PAGE_SIZE); + if !ptr.is_null() { + area_add(OsMemoryEntry { + base: ptr as u64, + size: PAGE_SIZE as u64, + kind: OsMemoryKind::Reclaim, + }); + Some(slice::from_raw_parts_mut(ptr as *mut u32, PAGE_ENTRIES)) + } else { + None + } + } +} + +pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -> Option { + unsafe { + let pd = paging_allocate(os)?; + //Identity map 1 GiB using 4 MiB pages, also map at PHYS_OFFSET + for pd_i in 0..256 { + let addr = pd_i as u32 * 0x40_0000; + pd[pd_i] = addr | 1 << 7 | 1 << 1 | 1; + pd[pd_i + 512] = addr | 1 << 7 | 1 << 1 | 1; + } + + // Map kernel_size at kernel offset + let mut kernel_mapped = 0; + let mut pd_i = 0xC000_0000 / 0x40_0000; + while kernel_mapped < kernel_size && pd_i < pd.len() { + let pt = paging_allocate(os)?; + pd[pd_i] = pt.as_ptr() as u32 | 1 << 1 | 1; + pd_i += 1; + + let mut pt_i = 0; + while kernel_mapped < kernel_size && pt_i < pt.len() { + let addr = kernel_phys + kernel_mapped; + pt[pt_i] = addr as u32 | 1 << 1 | 1; + pt_i += 1; + kernel_mapped += PAGE_SIZE as u64; + } + } + assert!(kernel_mapped >= kernel_size); + + Some(pd.as_ptr() as usize) + } +} + +pub unsafe fn paging_framebuffer( + os: &impl Os, + page_phys: usize, + framebuffer_phys: u64, + framebuffer_size: u64, +) -> Option { + unsafe { + let framebuffer_virt = 0xD000_0000; // 256 MiB after kernel mapping, but before heap mapping + + let pd = slice::from_raw_parts_mut(page_phys as *mut u32, PAGE_ENTRIES); + + // Map framebuffer_size at framebuffer offset + let mut framebuffer_mapped = 0; + let mut pd_i = framebuffer_virt / 0x40_0000; + while framebuffer_mapped < framebuffer_size && pd_i < pd.len() { + let pt = paging_allocate(os)?; + pd[pd_i] = pt.as_ptr() as u32 | 1 << 1 | 1; + pd_i += 1; + + let mut pt_i = 0; + while framebuffer_mapped < framebuffer_size && pt_i < pt.len() { + let addr = framebuffer_phys + framebuffer_mapped; + pt[pt_i] = addr as u32 | 1 << 1 | 1; + pt_i += 1; + framebuffer_mapped += PAGE_SIZE as u64; + } + } + assert!(framebuffer_mapped >= framebuffer_size); + + Some(framebuffer_virt as u64) + } +} diff --git a/src/arch/x86/x64.rs b/src/arch/x86/x64.rs new file mode 100644 index 0000000000..a0a275ab77 --- /dev/null +++ b/src/arch/x86/x64.rs @@ -0,0 +1,148 @@ +use core::slice; + +use crate::area_add; +use crate::os::{Os, OsMemoryEntry, OsMemoryKind}; + +const ENTRY_ADDRESS_MASK: u64 = 0x000F_FFFF_FFFF_F000; +const PAGE_ENTRIES: usize = 512; +const PAGE_SIZE: usize = 4096; +pub(crate) const PHYS_OFFSET: u64 = 0xFFFF_8000_0000_0000; + +unsafe fn paging_allocate(os: &impl Os) -> Option<&'static mut [u64]> { + unsafe { + let ptr = os.alloc_zeroed_page_aligned(PAGE_SIZE); + if !ptr.is_null() { + area_add(OsMemoryEntry { + base: ptr as u64, + size: PAGE_SIZE as u64, + kind: OsMemoryKind::Reclaim, + }); + + Some(slice::from_raw_parts_mut(ptr as *mut u64, PAGE_ENTRIES)) + } else { + None + } + } +} + +const PRESENT: u64 = 1; +const WRITABLE: u64 = 1 << 1; +const LARGE: u64 = 1 << 7; + +pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -> Option { + unsafe { + // Create PML4 + let pml4 = paging_allocate(os)?; + + { + // Create PDP for identity mapping + let pdp = paging_allocate(os)?; + + // Link first user and first kernel PML4 entry to PDP + pml4[0] = pdp.as_ptr() as u64 | WRITABLE | PRESENT; + pml4[256] = pdp.as_ptr() as u64 | WRITABLE | PRESENT; + + // Identity map 8 GiB using 2 MiB pages + for pdp_i in 0..8 { + let pd = paging_allocate(os)?; + pdp[pdp_i] = pd.as_ptr() as u64 | WRITABLE | PRESENT; + for pd_i in 0..pd.len() { + let addr = pdp_i as u64 * 0x4000_0000 + pd_i as u64 * 0x20_0000; + pd[pd_i] = addr | LARGE | WRITABLE | PRESENT; + } + } + } + + { + // Create PDP (spanning 512 GiB) for kernel mapping + let pdp = paging_allocate(os)?; + + // Link last PML4 entry to PDP + pml4[511] = pdp.as_ptr() as u64 | WRITABLE | PRESENT; + + // Create PD (spanning 1 GiB) for kernel mapping. + let pd = paging_allocate(os)?; + + // The kernel is mapped at -2^31, i.e. 0xFFFF_FFFF_8000_0000. Since a PD is 1 GiB, link + // the second last PDP entry to PD. + pdp[510] = pd.as_ptr() as u64 | WRITABLE | PRESENT; + + // Map kernel_size bytes to kernel offset, i.e. to the start of the PD. + + let mut kernel_mapped = 0; + + let mut pd_idx = 0; + while kernel_mapped < kernel_size && pd_idx < pd.len() { + let pt = paging_allocate(os)?; + pd[pd_idx] = pt.as_ptr() as u64 | WRITABLE | PRESENT; + pd_idx += 1; + + let mut pt_idx = 0; + while kernel_mapped < kernel_size && pt_idx < pt.len() { + let addr = kernel_phys + kernel_mapped; + pt[pt_idx] = addr | WRITABLE | PRESENT; + pt_idx += 1; + kernel_mapped += PAGE_SIZE as u64; + } + } + assert!(kernel_mapped >= kernel_size); + } + + Some(pml4.as_ptr() as usize) + } +} + +pub unsafe fn paging_framebuffer( + os: &impl Os, + page_phys: usize, + framebuffer_phys: u64, + framebuffer_size: u64, +) -> Option { + unsafe { + //TODO: smarter test for framebuffer already mapped + if framebuffer_phys + framebuffer_size <= 0x2_0000_0000 { + return Some(framebuffer_phys + PHYS_OFFSET); + } + + let pml4_i = ((framebuffer_phys / 0x80_0000_0000) + 256) as usize; + let mut pdp_i = ((framebuffer_phys % 0x80_0000_0000) / 0x4000_0000) as usize; + let mut pd_i = ((framebuffer_phys % 0x4000_0000) / 0x20_0000) as usize; + assert_eq!(framebuffer_phys % 0x20_0000, 0); + + let pml4 = slice::from_raw_parts_mut(page_phys as *mut u64, PAGE_ENTRIES); + + // Create PDP for framebuffer mapping + let pdp = if pml4[pml4_i] == 0 { + let pdp = paging_allocate(os)?; + pml4[pml4_i] = pdp.as_ptr() as u64 | 1 << 1 | 1; + pdp + } else { + slice::from_raw_parts_mut( + (pml4[pml4_i] & ENTRY_ADDRESS_MASK) as *mut u64, + PAGE_ENTRIES, + ) + }; + + // Map framebuffer_size at framebuffer offset + let mut framebuffer_mapped = 0; + while framebuffer_mapped < framebuffer_size && pdp_i < pdp.len() { + let pd = paging_allocate(os)?; + assert_eq!(pdp[pdp_i], 0); + pdp[pdp_i] = pd.as_ptr() as u64 | 1 << 1 | 1; + + while framebuffer_mapped < framebuffer_size && pd_i < pd.len() { + let addr = framebuffer_phys + framebuffer_mapped; + assert_eq!(pd[pd_i], 0); + pd[pd_i] = addr | 1 << 7 | 1 << 1 | 1; + framebuffer_mapped += 0x20_0000; + pd_i += 1; + } + + pdp_i += 1; + pd_i = 0; + } + assert!(framebuffer_mapped >= framebuffer_size); + + Some(framebuffer_phys + PHYS_OFFSET) + } +} diff --git a/src/editor.rs b/src/editor.rs new file mode 100644 index 0000000000..a4ab291007 --- /dev/null +++ b/src/editor.rs @@ -0,0 +1,121 @@ +use crate::os::{Os, OsKey}; + +fn edit_banner(os: &impl Os) { + os.clear_text(); + println!("--- Redox Bootloader Environment Editor ---"); + println!("ENTER twice to boot. UP/DOWN to edit lines."); + println!("-------------------------------------------"); +} + +pub fn edit_env(os: &impl Os, env_ptr: *mut u8, env_size: &mut usize, max_size: usize) { + edit_banner(os); + + let env_slice = unsafe { core::slice::from_raw_parts_mut(env_ptr, max_size) }; + // counting at line index + let mut cursor = 0xFFF; + // position at current line, not including LF + let mut cursor_start = 0; + let mut cursor_end = 0; + let original_size = *env_size; + + loop { + os.set_text_position(0, 4); + + let mut iline = 0; + for i in 0..*env_size { + let c = env_slice[i] as char; + if c == '\n' { + os.set_text_highlight(iline == cursor); + print!(" "); + os.set_text_highlight(false); + print!("\n"); + iline += 1; + if iline == cursor { + cursor_start = i + 1; + } + } else { + print!("{}", c); + } + if iline == cursor { + cursor_end = i + 1; + } + } + if cursor > iline { + cursor = iline; + // update cursors, should never hang + continue; + } + os.set_text_highlight(iline == cursor); + print!(" "); + os.set_text_highlight(false); + + match os.get_key() { + OsKey::Enter => { + if cursor_start == cursor_end { + // blank line to boot + break; + } + + if *env_size < max_size - 1 { + if *env_size == max_size { + continue; + } + for i in (cursor_end..*env_size).rev() { + env_slice[i + 1] = env_slice[i]; + } + env_slice[cursor_end] = b'\n'; + *env_size += 1; + cursor += 1; + edit_banner(os); + } + } + OsKey::Backspace => { + if cursor_end == 0 || *env_size == 0 { + continue; + } + if cursor_start == cursor_end && iline > 0 { + iline -= 1; + } + for i in cursor_end..*env_size { + env_slice[i - 1] = env_slice[i]; + } + *env_size -= 1; + edit_banner(os); + } + OsKey::Up => { + if cursor > 0 { + cursor -= 1; + } + } + OsKey::Down => { + cursor += 1; + } + OsKey::Char(c) => { + if *env_size == max_size { + continue; + } + for i in (cursor_end..*env_size).rev() { + env_slice[i + 1] = env_slice[i]; + } + env_slice[cursor_end] = c as u8; + *env_size += 1; + } + _ => (), + } + } + + if *env_size == 0 || env_slice[*env_size - 1] != b'\n' { + if *env_size < max_size { + env_slice[*env_size] = b'\n'; + *env_size += 1; + } + } + + if *env_size < original_size { + for i in (*env_size..original_size).rev() { + env_slice[i] = 0; + } + } + + println!("\nBooting..."); +} diff --git a/src/logger.rs b/src/logger.rs new file mode 100644 index 0000000000..afd1b717fd --- /dev/null +++ b/src/logger.rs @@ -0,0 +1,26 @@ +use log::{LevelFilter, Log, Metadata, Record}; + +pub static LOGGER: Logger = Logger; + +pub struct Logger; + +impl Logger { + pub fn init(&'static self) { + log::set_logger(self).unwrap(); + log::set_max_level(LevelFilter::Info); + } +} + +impl Log for Logger { + fn enabled(&self, _metadata: &Metadata) -> bool { + true + } + + fn log(&self, record: &Record) { + if self.enabled(record.metadata()) { + println!("{} - {}", record.level(), record.args()); + } + } + + fn flush(&self) {} +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000000..542b059812 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,675 @@ +#![no_std] +#![cfg_attr(any(target_arch = "riscv64", target_os = "uefi"), no_main)] + +extern crate alloc; + +#[cfg(any(target_arch = "riscv64", target_os = "uefi"))] +#[macro_use] +extern crate uefi_std as std; + +use alloc::{format, string::String, vec::Vec}; +use core::{ + cmp, + fmt::{self, Write}, + mem, ptr, slice, str, +}; +use redoxfs::{Disk, Node, TreeData}; + +use self::arch::{paging_create, paging_framebuffer}; +use self::os::{Os, OsHwDesc, OsKey, OsMemoryEntry, OsMemoryKind, OsVideoMode}; + +#[macro_use] +mod os; + +mod arch; +mod editor; +mod logger; +mod serial_16550; + +const KIBI: usize = 1024; +const MIBI: usize = KIBI * KIBI; + +//TODO: allocate this in a more reasonable manner +static mut AREAS: [OsMemoryEntry; 1024] = [OsMemoryEntry { + base: 0, + size: 0, + kind: OsMemoryKind::Null, +}; 1024]; +static mut AREAS_LEN: usize = 0; + +pub fn area_add(area: OsMemoryEntry) { + #[allow(static_mut_refs)] + unsafe { + for existing_area in &mut AREAS[0..AREAS_LEN] { + if existing_area.kind == area.kind { + if existing_area.base.unchecked_add(existing_area.size) == area.base { + existing_area.size += area.size; + return; + } + if area.base.unchecked_add(area.size) == existing_area.base { + existing_area.size += area.size; + existing_area.base = area.base; + return; + } + } + } + *AREAS.get_mut(AREAS_LEN).expect("AREAS overflowed!") = area; + AREAS_LEN += 1; + } +} + +pub static mut KERNEL_64BIT: bool = false; + +pub static mut LIVE_OPT: Option<(u64, &'static [u8])> = None; + +struct SliceWriter<'a> { + slice: &'a mut [u8], + i: usize, +} + +impl<'a> Write for SliceWriter<'a> { + fn write_str(&mut self, s: &str) -> fmt::Result { + for b in s.bytes() { + if let Some(slice_b) = self.slice.get_mut(self.i) { + *slice_b = b; + self.i += 1; + } else { + return Err(fmt::Error); + } + } + Ok(()) + } +} + +#[allow(dead_code)] +#[derive(Debug)] +#[repr(C, packed(8))] +pub struct KernelArgs { + kernel_base: u64, + kernel_size: u64, + stack_base: u64, + stack_size: u64, + env_base: u64, + env_size: u64, + + /// The base pointer to the saved RSDP. + /// + /// This field can be NULL, and if so, the system has not booted with UEFI or in some other way + /// retrieved the RSDPs. The kernel or a userspace driver will thus try searching the BIOS + /// memory instead. On UEFI systems, searching is not guaranteed to actually work though. + acpi_rsdp_base: u64, + /// The size of the RSDP region. + acpi_rsdp_size: u64, + + areas_base: u64, + areas_size: u64, + + bootstrap_base: u64, + bootstrap_size: u64, +} + +fn select_mode( + os: &impl Os, + output_i: usize, + live: &mut bool, + edit_env: &mut bool, +) -> Option { + let mut modes = Vec::new(); + for mode in os.video_modes(output_i) { + let mut aspect_w = mode.width; + let mut aspect_h = mode.height; + for i in 2..cmp::min(aspect_w / 2, aspect_h / 2) { + while aspect_w % i == 0 && aspect_h % i == 0 { + aspect_w /= i; + aspect_h /= i; + } + } + + modes.push(( + mode, + format!( + "{:>4}x{:<4} {:>3}:{:<3}", + mode.width, mode.height, aspect_w, aspect_h + ), + )); + } + + if modes.is_empty() { + return None; + } + + // Sort modes by pixel area, reversed + modes.sort_by(|a, b| (b.0.width * b.0.height).cmp(&(a.0.width * a.0.height))); + + // Set selected based on best resolution + print!("Output {}", output_i); + let mut selected = modes.first().map_or(0, |x| x.0.id); + if let Some((best_width, best_height)) = os.best_resolution(output_i) { + print!(", best resolution: {}x{}", best_width, best_height); + for (mode, _text) in modes.iter() { + if mode.width == best_width && mode.height == best_height { + selected = mode.id; + break; + } + } + } + println!(); + + println!("Arrow keys and enter select mode"); + let live_mode = os.get_text_position(); + if *live { + println!("Press l to disable live mode"); + } else { + println!("Press l to enable live mode"); + } + println!("Press e to edit boot environment"); + println!(); + print!(" "); + + let (off_x, off_y) = os.get_text_position(); + let rows = 12; + let mut mode_opt = None; + while !modes.is_empty() { + let mut row = 0; + let mut col = 0; + for (mode, text) in modes.iter() { + if row >= rows { + col += 1; + row = 0; + } + + os.set_text_position(off_x + col * 20, off_y + row); + os.set_text_highlight(mode.id == selected); + + print!("{}", text); + + row += 1; + } + + // Read keypress + match os.get_key() { + OsKey::Left => { + if let Some(mut mode_i) = modes.iter().position(|x| x.0.id == selected) { + if mode_i < rows { + while mode_i < modes.len() { + mode_i += rows; + } + } + mode_i -= rows; + if let Some(new) = modes.get(mode_i) { + selected = new.0.id; + } + } + } + OsKey::Right => { + if let Some(mut mode_i) = modes.iter().position(|x| x.0.id == selected) { + mode_i += rows; + if mode_i >= modes.len() { + mode_i %= rows; + } + if let Some(new) = modes.get(mode_i) { + selected = new.0.id; + } + } + } + OsKey::Up => { + if let Some(mut mode_i) = modes.iter().position(|x| x.0.id == selected) { + if mode_i % rows == 0 { + mode_i += rows; + if mode_i > modes.len() { + mode_i = modes.len(); + } + } + mode_i -= 1; + if let Some(new) = modes.get(mode_i) { + selected = new.0.id; + } + } + } + OsKey::Down => { + if let Some(mut mode_i) = modes.iter().position(|x| x.0.id == selected) { + mode_i += 1; + if mode_i % rows == 0 { + mode_i -= rows; + } + if mode_i >= modes.len() { + mode_i = mode_i - mode_i % rows; + } + if let Some(new) = modes.get(mode_i) { + selected = new.0.id; + } + } + } + OsKey::Enter => { + if let Some(mode_i) = modes.iter().position(|x| x.0.id == selected) { + if let Some((mode, _text)) = modes.get(mode_i) { + mode_opt = Some(*mode); + } + } + break; + } + OsKey::Char('l') => { + *live = !*live; + os.set_text_position(live_mode.0, live_mode.1); + if *live { + println!("Press l to disable live mode"); + } else { + println!("Press l to enable live mode"); + } + } + OsKey::Char('e') => { + if let Some(mode_i) = modes.iter().position(|x| x.0.id == selected) { + if let Some((mode, _text)) = modes.get(mode_i) { + *edit_env = true; + mode_opt = Some(*mode); + } + } + break; + } + _ => (), + } + } + + os.set_text_position(0, off_y + rows); + os.set_text_highlight(false); + println!(); + + mode_opt +} + +fn redoxfs(os: &O) -> (redoxfs::FileSystem, Option<&'static [u8]>) { + let attempts = 10; + for attempt in 0..=attempts { + let mut password_opt = None; + if attempt > 0 { + print!("\rRedoxFS password ({}/{}): ", attempt, attempts); + + let mut password = String::new(); + + loop { + match os.get_key() { + OsKey::Backspace | OsKey::Delete => { + if !password.is_empty() { + print!("\x08 \x08"); + password.pop(); + } + } + OsKey::Char(c) => { + print!("*"); + password.push(c) + } + OsKey::Enter => break, + _ => (), + } + } + + // Erase password information + while os.get_text_position().0 > 0 { + print!("\x08 \x08"); + } + + if !password.is_empty() { + password_opt = Some(password); + } + } + match os.filesystem(password_opt.as_ref().map(|x| x.as_bytes())) { + Ok(fs) => { + return ( + fs, + password_opt.map(|password| { + // Copy password to page aligned memory + let password_size = password.len(); + let password_base = os.alloc_zeroed_page_aligned(password_size); + + area_add(OsMemoryEntry { + base: password_base as u64, + size: password_size as u64, + kind: OsMemoryKind::Reserved, + }); + + unsafe { + ptr::copy(password.as_ptr(), password_base, password_size); + slice::from_raw_parts(password_base, password_size) + } + }), + ); + } + Err(err) => match err.errno { + // Incorrect password, try again + syscall::ENOKEY => (), + _ => { + panic!("Failed to open RedoxFS: {}", err); + } + }, + } + } + panic!("RedoxFS out of unlock attempts"); +} + +#[derive(PartialEq)] +enum Filetype { + Elf, + Initfs, +} +fn load_to_memory( + os: &O, + fs: &mut redoxfs::FileSystem, + path: &str, + filetype: Filetype, +) -> &'static mut [u8] { + fs.tx(|tx| { + let mut node = None; + for component in path.split('/') { + node = Some( + tx.find_node( + node.map_or(redoxfs::TreePtr::root(), |node: TreeData| node.ptr()), + component, + ) + .unwrap_or_else(|err| panic!("Failed to find {component}: {err}")), + ); + } + let node = node.unwrap(); + + let size = node.data().size(); + + print!("{}: 0/{} MiB", path, size / MIBI as u64); + + let ptr = os.alloc_zeroed_page_aligned(size as usize); + if ptr.is_null() { + panic!("Failed to allocate memory for {}", path); + } + + let slice = unsafe { slice::from_raw_parts_mut(ptr, size as usize) }; + + let mut i = 0; + for chunk in slice.chunks_mut(MIBI) { + print!("\r{}: {}/{} MiB", path, i / MIBI as u64, size / MIBI as u64); + i += tx + .read_node_inner(&node, i, chunk) + .unwrap_or_else(|err| panic!("Failed to read `{}` file: {}", path, err)) + as u64; + } + println!("\r{}: {}/{} MiB", path, i / MIBI as u64, size / MIBI as u64); + + if filetype == Filetype::Elf { + let magic = &slice[..4]; + if magic != b"\x7FELF" { + panic!("{} has invalid magic number {:#X?}", path, magic); + } + } else if filetype == Filetype::Initfs { + let magic = &slice[..8]; + if magic != b"RedoxFtw" { + panic!("{} has invalid magic number {:#X?}", path, magic); + } + } + + Ok(slice) + }) + .unwrap_or_else(|err| { + panic!( + "RedoxFS transaction failed while loading `{}`: {}", + path, err + ) + }) +} + +fn elf_entry(data: &[u8]) -> (u64, bool) { + match (data[4], data[5]) { + // 32-bit, little endian + (1, 1) => ( + u32::from_le_bytes( + <[u8; 4]>::try_from(&data[0x18..0x18 + 4]).expect("conversion cannot fail"), + ) as u64, + false, + ), + // 32-bit, big endian + (1, 2) => ( + u32::from_be_bytes( + <[u8; 4]>::try_from(&data[0x18..0x18 + 4]).expect("conversion cannot fail"), + ) as u64, + false, + ), + // 64-bit, little endian + (2, 1) => ( + u64::from_le_bytes( + <[u8; 8]>::try_from(&data[0x18..0x18 + 8]).expect("conversion cannot fail"), + ), + true, + ), + // 64-bit, big endian + (2, 2) => ( + u64::from_be_bytes( + <[u8; 8]>::try_from(&data[0x18..0x18 + 8]).expect("conversion cannot fail"), + ), + true, + ), + (ei_class, ei_data) => { + panic!("Unsupported ELF EI_CLASS {} EI_DATA {}", ei_class, ei_data); + } + } +} + +fn main(os: &impl Os) -> (usize, u64, KernelArgs) { + println!( + "Redox OS Bootloader {} on {}", + env!("CARGO_PKG_VERSION"), + os.name() + ); + + let hwdesc = os.hwdesc(); + println!("Hardware descriptor: {:x?}", hwdesc); + let (acpi_rsdp_base, acpi_rsdp_size) = match hwdesc { + OsHwDesc::Acpi(base, size) => (base, size), + OsHwDesc::DeviceTree(base, size) => (base, size), + OsHwDesc::NotFound => (0, 0), + }; + + let (mut fs, password_opt) = redoxfs(os); + + print!("RedoxFS "); + for i in 0..fs.header.uuid().len() { + if i == 4 || i == 6 || i == 8 || i == 10 { + print!("-"); + } + + print!("{:>02x}", fs.header.uuid()[i]); + } + println!(": {} MiB", fs.header.size() / MIBI as u64); + println!(); + + let mut mode_opts = Vec::new(); + let mut live = cfg!(feature = "live"); + let mut edit_env = false; + for output_i in 0..os.video_outputs() { + if output_i > 0 { + os.clear_text(); + } + mode_opts.push(select_mode(os, output_i, &mut live, &mut edit_env)); + } + + let stack_size = 128 * KIBI; + let stack_base = os.alloc_zeroed_page_aligned(stack_size); + if stack_base.is_null() { + panic!("Failed to allocate memory for stack"); + } + + let live_opt = if live { + let size = fs.header.size(); + + print!("live: 0/{} MiB", size / MIBI as u64); + + let ptr = os.alloc_zeroed_page_aligned(size as usize); + if ptr.is_null() { + panic!("Failed to allocate memory for live"); + } + + let live = unsafe { slice::from_raw_parts_mut(ptr, size as usize) }; + + let mut i = 0; + for chunk in live.chunks_mut(MIBI) { + print!("\rlive: {}/{} MiB", i / MIBI as u64, size / MIBI as u64); + i += unsafe { + fs.disk + .read_at(fs.block + i / redoxfs::BLOCK_SIZE, chunk) + .expect("Failed to read live disk") as u64 + }; + } + println!("\rlive: {}/{} MiB", i / MIBI as u64, size / MIBI as u64); + + println!("Switching to live disk"); + unsafe { + LIVE_OPT = Some((fs.block, slice::from_raw_parts_mut(ptr, size as usize))); + } + + area_add(OsMemoryEntry { + base: live.as_ptr() as u64, + size: live.len() as u64, + kind: OsMemoryKind::Reserved, + }); + + Some(live) + } else { + None + }; + + let (kernel, kernel_entry) = { + let kernel = load_to_memory(os, &mut fs, "usr/lib/boot/kernel", Filetype::Elf); + let (kernel_entry, kernel_64bit) = elf_entry(kernel); + unsafe { + KERNEL_64BIT = kernel_64bit; + } + (kernel, kernel_entry) + }; + + let (bootstrap_size, bootstrap_base) = { + let initfs_slice = load_to_memory(os, &mut fs, "usr/lib/boot/initfs", Filetype::Initfs); + + let memory = unsafe { + let total_size = initfs_slice.len().next_multiple_of(4096); + let ptr = os.alloc_zeroed_page_aligned(total_size); + assert!(!ptr.is_null(), "failed to allocate bootstrap+initfs memory"); + core::slice::from_raw_parts_mut(ptr, total_size) + }; + memory[..initfs_slice.len()].copy_from_slice(initfs_slice); + + (memory.len() as u64, memory.as_mut_ptr() as u64) + }; + + let page_phys = unsafe { paging_create(os, kernel.as_ptr() as u64, kernel.len() as u64) } + .expect("Failed to set up paging"); + + let max_env_size = 64 * KIBI; + let mut env_size = max_env_size; + let env_base = os.alloc_zeroed_page_aligned(env_size); + if env_base.is_null() { + panic!("Failed to allocate memory for stack"); + } + + { + let mut w = SliceWriter { + slice: unsafe { slice::from_raw_parts_mut(env_base, max_env_size) }, + i: 0, + }; + + match hwdesc { + OsHwDesc::Acpi(addr, size) => { + writeln!(w, "RSDP_ADDR={addr:016x}").unwrap(); + writeln!(w, "RSDP_SIZE={size:016x}").unwrap(); + } + OsHwDesc::DeviceTree(addr, size) => { + writeln!(w, "DTB_ADDR={addr:016x}").unwrap(); + writeln!(w, "DTB_SIZE={size:016x}").unwrap(); + } + OsHwDesc::NotFound => {} + } + + if let Some(live) = live_opt { + writeln!(w, "DISK_LIVE_ADDR={:016x}", live.as_ptr() as usize).unwrap(); + writeln!(w, "DISK_LIVE_SIZE={:016x}", live.len()).unwrap(); + writeln!(w, "REDOXFS_BLOCK={:016x}", 0).unwrap(); + } else { + writeln!(w, "REDOXFS_BLOCK={:016x}", fs.block).unwrap(); + } + write!(w, "REDOXFS_UUID=").unwrap(); + for i in 0..fs.header.uuid().len() { + if i == 4 || i == 6 || i == 8 || i == 10 { + write!(w, "-").unwrap(); + } + + write!(w, "{:>02x}", fs.header.uuid()[i]).unwrap(); + } + writeln!(w).unwrap(); + if let Some(password) = password_opt { + writeln!( + w, + "REDOXFS_PASSWORD_ADDR={:016x}", + password.as_ptr() as usize + ) + .unwrap(); + writeln!(w, "REDOXFS_PASSWORD_SIZE={:016x}", password.len()).unwrap(); + } + + #[cfg(target_arch = "riscv64")] + { + let boot_hartid = os::efi_get_boot_hartid() + .expect("Could not retrieve boot hart id from EFI implementation!"); + writeln!(w, "BOOT_HART_ID={:016x}", boot_hartid).unwrap(); + } + if edit_env { + editor::edit_env(os, env_base, &mut w.i, max_env_size); + } + + for output_i in 0..os.video_outputs() { + if let Some(mut mode) = mode_opts[output_i] { + // Set mode to get updated values + os.set_video_mode(output_i, &mut mode); + + if output_i == 0 { + let virt = unsafe { + paging_framebuffer( + os, + page_phys, + mode.base, + (mode.stride * mode.height * 4) as u64, + ) + } + .expect("Failed to map framebuffer"); + + writeln!(w, "FRAMEBUFFER_ADDR={:016x}", mode.base).unwrap(); + writeln!(w, "FRAMEBUFFER_VIRT={virt:016x}").unwrap(); + writeln!(w, "FRAMEBUFFER_WIDTH={:016x}", mode.width).unwrap(); + writeln!(w, "FRAMEBUFFER_HEIGHT={:016x}", mode.height).unwrap(); + writeln!(w, "FRAMEBUFFER_STRIDE={:016x}", mode.stride).unwrap(); + } else { + writeln!( + w, + "FRAMEBUFFER{}={:#x},{},{},{}", + output_i, mode.base, mode.width, mode.height, mode.stride, + ) + .unwrap(); + } + } + } + env_size = w.i; + } + + #[allow(static_mut_refs)] + ( + page_phys, + kernel_entry, + KernelArgs { + kernel_base: kernel.as_ptr() as u64, + kernel_size: kernel.len() as u64, + stack_base: stack_base as u64, + stack_size: stack_size as u64, + env_base: env_base as u64, + env_size: env_size as u64, + acpi_rsdp_base, + acpi_rsdp_size, + areas_base: unsafe { AREAS.as_ptr() as u64 }, + areas_size: unsafe { (AREAS.len() * mem::size_of::()) as u64 }, + bootstrap_base, + bootstrap_size, + }, + ) +} diff --git a/src/os/bios/disk.rs b/src/os/bios/disk.rs new file mode 100644 index 0000000000..4570292339 --- /dev/null +++ b/src/os/bios/disk.rs @@ -0,0 +1,177 @@ +use core::{mem, ptr}; +use redoxfs::{BLOCK_SIZE, Disk}; +use syscall::error::{EIO, Error, Result}; + +use super::{DISK_ADDRESS_PACKET_ADDR, DISK_BIOS_ADDR, ThunkData}; + +const SECTOR_SIZE: u64 = 512; +const BLOCKS_PER_SECTOR: u64 = BLOCK_SIZE / SECTOR_SIZE; +// 128 sectors is the amount allocated for DISK_BIOS_ADDR +// 127 sectors is the maximum for many BIOSes +const MAX_SECTORS: u64 = 127; +const MAX_BLOCKS: u64 = MAX_SECTORS * SECTOR_SIZE / BLOCK_SIZE; + +#[allow(dead_code)] +#[derive(Clone, Copy)] +#[repr(C, packed)] +pub struct DiskAddressPacket { + size: u8, + reserved: u8, + sectors: u16, + buffer: u16, + segment: u16, + address: u64, +} + +impl DiskAddressPacket { + pub fn from_block(block: u64, count: u64) -> DiskAddressPacket { + let address = block * BLOCKS_PER_SECTOR; + let sectors = count * BLOCKS_PER_SECTOR; + assert!(sectors <= MAX_SECTORS); + DiskAddressPacket { + size: mem::size_of::() as u8, + reserved: 0, + sectors: sectors as u16, + buffer: (DISK_BIOS_ADDR & 0xF) as u16, + segment: (DISK_BIOS_ADDR >> 4) as u16, + address, + } + } +} + +pub struct DiskBios { + boot_disk: u8, + thunk13: extern "C" fn(), + chs_opt: Option<(u32, u32, u32)>, +} + +impl DiskBios { + pub fn new(boot_disk: u8, thunk13: extern "C" fn()) -> Self { + let chs_opt = unsafe { + let mut data = ThunkData::new(); + data.eax = 0x4100; + data.ebx = 0x55AA; + data.edx = boot_disk as u32; + + data.with(thunk13); + + if (data.ebx & 0xFFFF) == 0xAA55 { + // Extensions are installed, do not use CHS + None + } else { + // Extensions are not installed, get CHS geometry + data = ThunkData::new(); + data.eax = 0x0800; + data.edx = boot_disk as u32; + data.edi = 0; + + data.with(thunk13); + + //TODO: return result on error + let ah = ({ data.eax } >> 8) & 0xFF; + assert_eq!(ah, 0); + + let c = (data.ecx >> 8) & 0xFF | ((data.ecx >> 6) & 0x3) << 8; + let h = ((data.edx >> 8) & 0xFF) + 1; + let s = data.ecx & 0x3F; + + Some((c, h, s)) + } + }; + + Self { + boot_disk, + thunk13, + chs_opt, + } + } +} + +impl Disk for DiskBios { + unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result { + unsafe { + // Optimization for live disks + if let Some(live) = crate::LIVE_OPT { + if block >= live.0 { + let start = ((block - live.0) * BLOCK_SIZE) as usize; + let end = start + buffer.len(); + if end <= live.1.len() { + buffer.copy_from_slice(&live.1[start..end]); + return Ok(buffer.len()); + } + } + } + + for (i, chunk) in buffer + .chunks_mut((MAX_BLOCKS * BLOCK_SIZE) as usize) + .enumerate() + { + let dap = DiskAddressPacket::from_block( + block + i as u64 * MAX_BLOCKS, + chunk.len() as u64 / BLOCK_SIZE, + ); + + if let Some((_, h_max, s_max)) = self.chs_opt { + let s = (dap.address % s_max as u64) + 1; + assert!(s <= 63, "invalid sector {s}"); + + let tmp = dap.address / s_max as u64; + let h = tmp % h_max as u64; + assert!(h <= 255, "invalid head {h}"); + + let c = tmp / h_max as u64; + assert!(c <= 1023, "invalid cylinder {c}"); + + let mut data = ThunkData::new(); + data.eax = 0x0200 | (dap.sectors as u32); + data.ebx = dap.buffer as u32; + data.ecx = + (s as u32) | (((c as u32) & 0xFF) << 8) | ((((c as u32) >> 8) & 0x3) << 6); + data.edx = (self.boot_disk as u32) | ((h as u32) << 8); + data.es = dap.segment; + + data.with(self.thunk13); + + //TODO: return result on error + let ah = ({ data.eax } >> 8) & 0xFF; + assert_eq!(ah, 0); + } else { + ptr::write(DISK_ADDRESS_PACKET_ADDR as *mut DiskAddressPacket, dap); + + let mut data = ThunkData::new(); + data.eax = 0x4200; + data.edx = self.boot_disk as u32; + data.esi = DISK_ADDRESS_PACKET_ADDR as u32; + + data.with(self.thunk13); + + //TODO: return result on error + let ah = ({ data.eax } >> 8) & 0xFF; + assert_eq!(ah, 0); + + //TODO: check blocks transferred + // dap = ptr::read(DISK_ADDRESS_PACKET_ADDR as *mut DiskAddressPacket); + } + + ptr::copy(DISK_BIOS_ADDR as *const u8, chunk.as_mut_ptr(), chunk.len()); + } + + Ok(buffer.len()) + } + } + + unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result { + log::error!( + "DiskBios::write_at(0x{:X}, 0x{:X}:0x{:X}) not allowed", + block, + buffer.as_ptr() as usize, + buffer.len() + ); + Err(Error::new(EIO)) + } + + fn size(&mut self) -> Result { + log::error!("DiskBios::size not implemented"); + Err(Error::new(EIO)) + } +} diff --git a/src/os/bios/macros.rs b/src/os/bios/macros.rs new file mode 100644 index 0000000000..1dc11eb8ef --- /dev/null +++ b/src/os/bios/macros.rs @@ -0,0 +1,20 @@ +/// Print to console +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => ({ + use core::fmt::Write; + #[cfg(feature = "serial_debug")] + { + let _ = write!($crate::os::serial::COM1.lock(), $($arg)*); + } + let _ = write!($crate::os::VGA.lock(), $($arg)*); + }); +} + +/// Print with new line to console +#[macro_export] +macro_rules! println { + () => (print!("\n")); + ($fmt:expr_2021) => (print!(concat!($fmt, "\n"))); + ($fmt:expr_2021, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*)); +} diff --git a/src/os/bios/memory_map.rs b/src/os/bios/memory_map.rs new file mode 100644 index 0000000000..d47f6b2844 --- /dev/null +++ b/src/os/bios/memory_map.rs @@ -0,0 +1,84 @@ +use core::{cmp, mem, ptr}; + +use crate::area_add; +use crate::os::{OsMemoryEntry, OsMemoryKind}; + +use super::{MEMORY_MAP_ADDR, thunk::ThunkData}; + +#[repr(C, packed)] +struct MemoryMapEntry { + pub base: u64, + pub size: u64, + pub kind: u32, +} + +pub struct MemoryMapIter { + thunk15: extern "C" fn(), + data: ThunkData, + first: bool, +} + +impl MemoryMapIter { + pub fn new(thunk15: extern "C" fn()) -> Self { + Self { + thunk15, + data: ThunkData::new(), + first: true, + } + } +} + +impl Iterator for MemoryMapIter { + type Item = OsMemoryEntry; + fn next(&mut self) -> Option { + if self.first { + self.first = false; + } else if self.data.ebx == 0 { + return None; + } + + self.data.eax = 0xE820; + self.data.ecx = mem::size_of::() as u32; + self.data.edx = 0x534D4150; + self.data.edi = MEMORY_MAP_ADDR as u32; + + unsafe { + self.data.with(self.thunk15); + } + + //TODO: return error? + assert_eq!({ self.data.eax }, 0x534D4150); + assert_eq!({ self.data.ecx }, mem::size_of::() as u32); + + let entry = unsafe { ptr::read(MEMORY_MAP_ADDR as *const MemoryMapEntry) }; + Some(Self::Item { + base: entry.base, + size: entry.size, + kind: match entry.kind { + 0 => OsMemoryKind::Null, + 1 => OsMemoryKind::Free, + 3 => OsMemoryKind::Reclaim, + _ => OsMemoryKind::Reserved, + }, + }) + } +} + +pub unsafe fn memory_map(thunk15: extern "C" fn()) -> Option<(usize, usize)> { + let mut heap_limits = None; + for entry in MemoryMapIter::new(thunk15) { + let heap_start = 1024 * 1024; + if { entry.kind } == OsMemoryKind::Free + && entry.base <= heap_start as u64 + && (entry.base + entry.size) >= heap_start as u64 + { + let heap_end = cmp::min(entry.base + entry.size, usize::MAX as u64) as usize; + if heap_end >= heap_start { + heap_limits = Some((heap_start, heap_end - heap_start)); + } + } + + area_add(entry); + } + heap_limits +} diff --git a/src/os/bios/mod.rs b/src/os/bios/mod.rs new file mode 100644 index 0000000000..a9873d769d --- /dev/null +++ b/src/os/bios/mod.rs @@ -0,0 +1,318 @@ +use alloc::alloc::{Layout, alloc_zeroed}; +use core::{convert::TryFrom, mem, ptr, slice}; +use linked_list_allocator::LockedHeap; +use spin::Mutex; + +use crate::KernelArgs; +use crate::logger::LOGGER; +use crate::os::{Os, OsHwDesc, OsKey, OsVideoMode}; + +use self::disk::DiskBios; +use self::memory_map::memory_map; +use self::thunk::ThunkData; +use self::vbe::VideoModeIter; +use self::vga::{Vga, VgaTextColor}; + +#[macro_use] +mod macros; + +mod disk; +mod memory_map; +mod panic; +pub(crate) mod serial; +mod thunk; +mod vbe; +mod vga; + +// Real mode memory allocation, for use with thunk +// 0x500 to 0x7BFF is free +const DISK_BIOS_ADDR: usize = 0x70000; // 64 KiB at 448 KiB, ends at 512 KiB +const VBE_CARD_INFO_ADDR: usize = 0x1000; // 512 bytes, ends at 0x11FF +const VBE_MODE_INFO_ADDR: usize = 0x1200; // 256 bytes, ends at 0x12FF +const VBE_EDID_ADDR: usize = 0x1300; // 128 bytes, ends at 0x137F +const MEMORY_MAP_ADDR: usize = 0x1380; // 24 bytes, ends at 0x1397 +const DISK_ADDRESS_PACKET_ADDR: usize = 0x1398; // 16 bytes, ends at 0x13A7 +const THUNK_STACK_ADDR: usize = 0x7C00; // Grows downwards +const VGA_ADDR: usize = 0xB8000; + +#[global_allocator] +static ALLOCATOR: LockedHeap = LockedHeap::empty(); + +pub(crate) static VGA: Mutex = Mutex::new(unsafe { Vga::new(VGA_ADDR, 80, 25) }); + +pub struct OsBios { + boot_disk: usize, + thunk10: extern "C" fn(), + thunk13: extern "C" fn(), + thunk15: extern "C" fn(), + thunk16: extern "C" fn(), +} + +#[allow(dead_code)] +#[derive(Copy, Clone, Debug)] +#[repr(C, packed)] +pub struct Rsdp { + signature: [u8; 8], + checksum: u8, + oemid: [u8; 6], + revision: u8, + rsdt_address: u32, +} + +#[allow(dead_code)] +#[derive(Copy, Clone, Debug)] +#[repr(C, packed)] +pub struct Xsdp { + rsdp: Rsdp, + + length: u32, + xsdt_address: u64, + extended_checksum: u8, + reserved: [u8; 3], +} + +unsafe fn search_rsdp(start: usize, end: usize) -> Option<(u64, u64)> { + unsafe { + // Align start up to 16 bytes + let mut addr = start.div_ceil(16) * 16; + // Search until reading the end of the Rsdp would be past the end of the memory area + while addr + mem::size_of::() <= end { + let rsdp = ptr::read(addr as *const Rsdp); + if &rsdp.signature == b"RSD PTR " { + //TODO: check checksum? + if rsdp.revision == 0 { + return Some((addr as u64, mem::size_of::() as u64)); + } else if rsdp.revision == 2 { + let xsdp = ptr::read(addr as *const Xsdp); + //TODO: check extended checksum? + return Some((addr as u64, xsdp.length as u64)); + } + } + + // Rsdp is always aligned to 16 bytes + addr += 16; + } + None + } +} + +impl Os for OsBios { + type D = DiskBios; + type V = VideoModeIter; + + fn name(&self) -> &str { + "x86/BIOS" + } + + fn alloc_zeroed_page_aligned(&self, size: usize) -> *mut u8 { + assert!(size != 0); + + let page_size = self.page_size(); + let pages = size.div_ceil(page_size); + + let ptr = + unsafe { alloc_zeroed(Layout::from_size_align(pages * page_size, page_size).unwrap()) }; + + assert!(!ptr.is_null()); + ptr + } + + fn page_size(&self) -> usize { + 4096 + } + + fn filesystem( + &self, + password_opt: Option<&[u8]>, + ) -> syscall::Result> { + let disk = DiskBios::new(u8::try_from(self.boot_disk).unwrap(), self.thunk13); + + //TODO: get block from partition table + let block = 2 * crate::MIBI as u64 / redoxfs::BLOCK_SIZE; + redoxfs::FileSystem::open(disk, password_opt, Some(block), false) + } + + fn hwdesc(&self) -> OsHwDesc { + // See ACPI specification - Finding the RSDP on IA-PC Systems + unsafe { + let ebda_segment = ptr::read(0x40E as *const u16); + let ebda_addr = (ebda_segment as usize) << 4; + if let Some((addr, size)) = + search_rsdp(ebda_addr, ebda_addr + 1024).or(search_rsdp(0xE0000, 0xFFFFF)) + { + // Copy to a page + let page_aligned = self.alloc_zeroed_page_aligned(size as usize); + ptr::copy(addr as *const u8, page_aligned, size as usize); + return OsHwDesc::Acpi(page_aligned as u64, size); + } + } + OsHwDesc::NotFound + } + + fn video_outputs(&self) -> usize { + //TODO: return 1 only if vbe supported? + 1 + } + + fn video_modes(&self, _output_i: usize) -> VideoModeIter { + VideoModeIter::new(self.thunk10) + } + + fn set_video_mode(&self, _output_i: usize, mode: &mut OsVideoMode) { + // Set video mode + let mut data = ThunkData::new(); + data.eax = 0x4F02; + data.ebx = mode.id; + unsafe { + data.with(self.thunk10); + } + //TODO: check result + } + + fn best_resolution(&self, _output_i: usize) -> Option<(u32, u32)> { + let mut data = ThunkData::new(); + data.eax = 0x4F15; + data.ebx = 0x01; + data.ecx = 0; + data.edx = 0; + data.edi = VBE_EDID_ADDR as u32; + unsafe { + data.with(self.thunk10); + } + + if data.eax == 0x4F { + let edid = unsafe { slice::from_raw_parts(VBE_EDID_ADDR as *const u8, 128) }; + + Some(( + (edid[0x38] as u32) | (((edid[0x3A] as u32) & 0xF0) << 4), + (edid[0x3B] as u32) | (((edid[0x3D] as u32) & 0xF0) << 4), + )) + } else { + log::warn!("Failed to get VBE EDID: 0x{:X}", { data.eax }); + None + } + } + + fn get_key(&self) -> OsKey { + // Read keypress + let mut data = ThunkData::new(); + unsafe { + data.with(self.thunk16); + } + match (data.eax >> 8) as u8 { + 0x4B => OsKey::Left, + 0x4D => OsKey::Right, + 0x48 => OsKey::Up, + 0x50 => OsKey::Down, + 0x0E => OsKey::Backspace, + 0x53 => OsKey::Delete, + 0x1C => OsKey::Enter, + _ => match data.eax as u8 { + 0 => OsKey::Other, + b => OsKey::Char(b as char), + }, + } + } + + fn clear_text(&self) { + let mut vga = VGA.lock(); + vga.clear(); + } + + fn get_text_position(&self) -> (usize, usize) { + let vga = VGA.lock(); + (vga.x, vga.y) + } + + fn set_text_position(&self, x: usize, y: usize) { + //TODO: ensure this is inside bounds! + let mut vga = VGA.lock(); + vga.x = x; + vga.y = y; + } + + fn set_text_highlight(&self, highlight: bool) { + let mut vga = VGA.lock(); + if highlight { + vga.bg = VgaTextColor::Gray; + vga.fg = VgaTextColor::Black; + } else { + vga.bg = VgaTextColor::Black; + vga.fg = VgaTextColor::Gray; + } + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn start( + kernel_entry: extern "C" fn( + page_table: usize, + stack: u64, + func: u64, + args: *const KernelArgs, + long_mode: usize, + ) -> !, + boot_disk: usize, + thunk10: extern "C" fn(), + thunk13: extern "C" fn(), + thunk15: extern "C" fn(), + thunk16: extern "C" fn(), +) -> ! { + unsafe { + #[cfg(feature = "serial_debug")] + { + let mut com1 = serial::COM1.lock(); + com1.init(); + com1.write(b"SERIAL\n"); + } + + { + // Make sure we are in mode 3 (80x25 text mode) + let mut data = ThunkData::new(); + data.eax = 0x03; + data.with(thunk10); + } + + { + // Disable cursor + let mut data = ThunkData::new(); + data.eax = 0x0100; + data.ecx = 0x3F00; + data.with(thunk10); + } + + // Clear screen + VGA.lock().clear(); + + // Set logger + LOGGER.init(); + + let mut os = OsBios { + boot_disk, + thunk10, + thunk13, + thunk15, + thunk16, + }; + + let (heap_start, heap_size) = memory_map(os.thunk15).expect("No memory for heap"); + + ALLOCATOR.lock().init(heap_start as *mut u8, heap_size); + + let (page_phys, func, args) = crate::main(&mut os); + + kernel_entry( + page_phys, + args.stack_base + + args.stack_size + + if crate::KERNEL_64BIT { + crate::arch::x64::PHYS_OFFSET + } else { + crate::arch::x32::PHYS_OFFSET as u64 + }, + func, + &args, + if crate::KERNEL_64BIT { 1 } else { 0 }, + ); + } +} diff --git a/src/os/bios/panic.rs b/src/os/bios/panic.rs new file mode 100644 index 0000000000..7271971f25 --- /dev/null +++ b/src/os/bios/panic.rs @@ -0,0 +1,16 @@ +//! Intrinsics for panic handling + +use core::alloc::Layout; +use core::arch::asm; +use core::panic::PanicInfo; + +/// Required to handle panics +#[panic_handler] +pub fn rust_begin_unwind(info: &PanicInfo) -> ! { + unsafe { + println!("BOOTLOADER PANIC:\n{}", info); + loop { + asm!("hlt"); + } + } +} diff --git a/src/os/bios/serial.rs b/src/os/bios/serial.rs new file mode 100644 index 0000000000..a673c1f652 --- /dev/null +++ b/src/os/bios/serial.rs @@ -0,0 +1,9 @@ +use spin::Mutex; +use syscall::Pio; + +use crate::serial_16550::SerialPort; + +pub static COM1: Mutex>> = Mutex::new(SerialPort::>::new(0x3F8)); +pub static COM2: Mutex>> = Mutex::new(SerialPort::>::new(0x2F8)); +pub static COM3: Mutex>> = Mutex::new(SerialPort::>::new(0x3E8)); +pub static COM4: Mutex>> = Mutex::new(SerialPort::>::new(0x2E8)); diff --git a/src/os/bios/thunk.rs b/src/os/bios/thunk.rs new file mode 100644 index 0000000000..99ec43d9cc --- /dev/null +++ b/src/os/bios/thunk.rs @@ -0,0 +1,52 @@ +use core::ptr; + +use super::THUNK_STACK_ADDR; + +#[allow(dead_code)] +#[derive(Clone, Copy, Debug)] +#[repr(C, packed)] +pub struct ThunkData { + pub es: u16, + pub edi: u32, + pub esi: u32, + pub ebp: u32, + pub ebx: u32, + pub edx: u32, + pub ecx: u32, + pub eax: u32, +} + +impl ThunkData { + pub fn new() -> Self { + Self { + es: 0, + edi: 0, + esi: 0, + ebp: 0, + ebx: 0, + edx: 0, + ecx: 0, + eax: 0, + } + } + + pub unsafe fn save(&self) { + unsafe { + ptr::write((THUNK_STACK_ADDR - 64) as *mut ThunkData, *self); + } + } + + pub unsafe fn load(&mut self) { + unsafe { + *self = ptr::read((THUNK_STACK_ADDR - 64) as *const ThunkData); + } + } + + pub unsafe fn with(&mut self, f: extern "C" fn()) { + unsafe { + self.save(); + f(); + self.load(); + } + } +} diff --git a/src/os/bios/vbe.rs b/src/os/bios/vbe.rs new file mode 100644 index 0000000000..9498b8dcf3 --- /dev/null +++ b/src/os/bios/vbe.rs @@ -0,0 +1,151 @@ +use core::ptr; +use log::error; + +use crate::os::OsVideoMode; + +use super::{ThunkData, VBE_CARD_INFO_ADDR, VBE_MODE_INFO_ADDR}; + +#[derive(Clone, Copy, Debug)] +#[repr(C, packed)] +pub struct VbeFarPtr { + pub offset: u16, + pub segment: u16, +} + +impl VbeFarPtr { + pub unsafe fn as_ptr(&self) -> *const T { + (((self.segment as usize) << 4) + (self.offset as usize)) as *const T + } +} + +#[derive(Clone, Copy, Debug)] +#[repr(C, packed)] +pub struct VbeCardInfo { + pub signature: [u8; 4], + pub version: u16, + pub oemstring: VbeFarPtr, + pub capabilities: [u8; 4], + pub videomodeptr: VbeFarPtr, + pub totalmemory: u16, + pub oemsoftwarerev: u16, + pub oemvendornameptr: VbeFarPtr, + pub oemproductnameptr: VbeFarPtr, + pub oemproductrevptr: VbeFarPtr, + pub reserved: [u8; 222], + pub oemdata: [u8; 256], +} + +#[derive(Clone, Copy, Debug)] +#[repr(C, packed)] +pub struct VbeModeInfo { + pub attributes: u16, + pub win_a: u8, + pub win_b: u8, + pub granularity: u16, + pub winsize: u16, + pub segment_a: u16, + pub segment_b: u16, + pub winfuncptr: u32, + pub bytesperscanline: u16, + pub xresolution: u16, + pub yresolution: u16, + pub xcharsize: u8, + pub ycharsize: u8, + pub numberofplanes: u8, + pub bitsperpixel: u8, + pub numberofbanks: u8, + pub memorymodel: u8, + pub banksize: u8, + pub numberofimagepages: u8, + pub unused: u8, + pub redmasksize: u8, + pub redfieldposition: u8, + pub greenmasksize: u8, + pub greenfieldposition: u8, + pub bluemasksize: u8, + pub bluefieldposition: u8, + pub rsvdmasksize: u8, + pub rsvdfieldposition: u8, + pub directcolormodeinfo: u8, + pub physbaseptr: u32, + pub offscreenmemoryoffset: u32, + pub offscreenmemsize: u16, + pub reserved: [u8; 206], +} + +pub struct VideoModeIter { + thunk10: extern "C" fn(), + mode_ptr: *const u16, +} + +impl VideoModeIter { + pub fn new(thunk10: extern "C" fn()) -> Self { + // Get card info + let mut data = ThunkData::new(); + data.eax = 0x4F00; + data.edi = VBE_CARD_INFO_ADDR as u32; + unsafe { + data.with(thunk10); + } + let mode_ptr = if data.eax == 0x004F { + let card_info = unsafe { ptr::read(VBE_CARD_INFO_ADDR as *const VbeCardInfo) }; + unsafe { card_info.videomodeptr.as_ptr::() } + } else { + error!("Failed to read VBE card info: 0x{:04X}", { data.eax }); + ptr::null() + }; + Self { thunk10, mode_ptr } + } +} + +impl Iterator for VideoModeIter { + type Item = OsVideoMode; + fn next(&mut self) -> Option { + if self.mode_ptr.is_null() { + return None; + } + + loop { + // Set bit 14 to get linear frame buffer + let mode = unsafe { *self.mode_ptr } | (1 << 14); + if mode == 0xFFFF { + return None; + } + self.mode_ptr = unsafe { self.mode_ptr.add(1) }; + + // Get mode info + let mut data = ThunkData::new(); + data.eax = 0x4F01; + data.ecx = mode as u32; + data.edi = VBE_MODE_INFO_ADDR as u32; + unsafe { + data.with(self.thunk10); + } + if data.eax == 0x004F { + let mode_info = unsafe { ptr::read(VBE_MODE_INFO_ADDR as *const VbeModeInfo) }; + + // We only support 32-bits per pixel modes + if mode_info.bitsperpixel != 32 { + continue; + } + + let width = mode_info.xresolution as u32; + let height = mode_info.yresolution as u32; + //TODO: support stride that is not a multiple of 4 + let stride = mode_info.bytesperscanline as u32 / 4; + + return Some(OsVideoMode { + id: mode as u32, + width, + height, + stride, + base: mode_info.physbaseptr as u64, + }); + } else { + error!("Failed to read VBE mode 0x{:04X} info: 0x{:04X}", mode, { + data.eax + }); + } + } + } +} diff --git a/src/os/bios/vga.rs b/src/os/bios/vga.rs new file mode 100644 index 0000000000..25e64188b9 --- /dev/null +++ b/src/os/bios/vga.rs @@ -0,0 +1,121 @@ +use core::{fmt, slice}; + +#[derive(Clone, Copy)] +#[repr(C, packed)] +pub struct VgaTextBlock { + pub char: u8, + pub color: u8, +} + +#[allow(dead_code)] +#[derive(Clone, Copy)] +#[repr(u8)] +pub enum VgaTextColor { + Black = 0, + Blue = 1, + Green = 2, + Cyan = 3, + Red = 4, + Purple = 5, + Brown = 6, + Gray = 7, + DarkGray = 8, + LightBlue = 9, + LightGreen = 10, + LightCyan = 11, + LightRed = 12, + LightPurple = 13, + Yellow = 14, + White = 15, +} + +pub struct Vga { + pub base: usize, + pub width: usize, + pub height: usize, + pub x: usize, + pub y: usize, + pub bg: VgaTextColor, + pub fg: VgaTextColor, +} + +impl Vga { + pub const unsafe fn new(base: usize, width: usize, height: usize) -> Self { + Self { + base, + width, + height, + x: 0, + y: 0, + bg: VgaTextColor::Black, + fg: VgaTextColor::Gray, + } + } + + pub unsafe fn blocks(&mut self) -> &'static mut [VgaTextBlock] { + unsafe { + slice::from_raw_parts_mut(self.base as *mut VgaTextBlock, self.width * self.height) + } + } + + pub fn clear(&mut self) { + self.x = 0; + self.y = 0; + let blocks = unsafe { self.blocks() }; + for i in 0..blocks.len() { + blocks[i] = VgaTextBlock { + char: 0, + color: ((self.bg as u8) << 4) | (self.fg as u8), + }; + } + } +} + +impl fmt::Write for Vga { + fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { + let blocks = unsafe { self.blocks() }; + for c in s.chars() { + if self.x >= self.width { + self.x = 0; + self.y += 1; + } + while self.y >= self.height { + for y in 1..self.height { + for x in 0..self.width { + let i = y * self.width + x; + let j = i - self.width; + blocks[j] = blocks[i]; + if y + 1 == self.height { + blocks[i].char = 0; + } + } + } + self.y -= 1; + } + match c { + '\x08' => { + if self.x > 0 { + self.x -= 1; + } + } + '\r' => { + self.x = 0; + } + '\n' => { + self.x = 0; + self.y += 1; + } + _ => { + let i = self.y * self.width + self.x; + if let Some(block) = blocks.get_mut(i) { + block.char = c as u8; + block.color = ((self.bg as u8) << 4) | (self.fg as u8); + } + self.x += 1; + } + } + } + + Ok(()) + } +} diff --git a/src/os/mod.rs b/src/os/mod.rs new file mode 100644 index 0000000000..92c00c90cc --- /dev/null +++ b/src/os/mod.rs @@ -0,0 +1,95 @@ +use redoxfs::Disk; + +#[cfg(all(target_arch = "x86", target_os = "none"))] +pub use self::bios::*; + +#[cfg(all(target_arch = "x86", target_os = "none"))] +#[macro_use] +mod bios; + +#[cfg(any(target_arch = "riscv64", target_os = "uefi"))] +#[allow(unused_imports)] +pub use self::uefi::*; + +#[cfg(any(target_arch = "riscv64", target_os = "uefi"))] +#[macro_use] +mod uefi; + +#[derive(Clone, Copy, Debug)] +pub enum OsHwDesc { + Acpi(u64, u64), + DeviceTree(u64, u64), + NotFound, +} + +#[derive(Clone, Copy, Debug)] +pub enum OsKey { + Left, + Right, + Up, + Down, + Backspace, + Delete, + Enter, + Char(char), + Other, +} + +// Keep synced with BootloaderMemoryKind in kernel +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(u64)] +pub enum OsMemoryKind { + Null = 0, + Free = 1, + Reclaim = 2, + Reserved = 3, +} + +// Keep synced with BootloaderMemoryEntry in kernel +#[derive(Clone, Copy, Debug)] +#[repr(C, packed(8))] +pub struct OsMemoryEntry { + pub base: u64, + pub size: u64, + pub kind: OsMemoryKind, +} + +#[derive(Clone, Copy, Debug)] +pub struct OsVideoMode { + pub id: u32, + pub width: u32, + pub height: u32, + pub stride: u32, + pub base: u64, +} + +pub trait Os { + type D: Disk; + type V: Iterator; + + fn name(&self) -> &str; + + fn alloc_zeroed_page_aligned(&self, size: usize) -> *mut u8; + + #[allow(dead_code)] + fn page_size(&self) -> usize; + + fn filesystem( + &self, + password_opt: Option<&[u8]>, + ) -> syscall::Result>; + + fn hwdesc(&self) -> OsHwDesc; + + fn video_outputs(&self) -> usize; + fn video_modes(&self, output_i: usize) -> Self::V; + fn set_video_mode(&self, output_i: usize, mode: &mut OsVideoMode); + fn best_resolution(&self, output_i: usize) -> Option<(u32, u32)>; + + fn get_key(&self) -> OsKey; + + fn clear_text(&self); + fn get_text_position(&self) -> (usize, usize); + fn set_text_position(&self, x: usize, y: usize); + fn set_text_highlight(&self, highlight: bool); +} diff --git a/src/os/uefi/acpi.rs b/src/os/uefi/acpi.rs new file mode 100644 index 0000000000..02259d4c58 --- /dev/null +++ b/src/os/uefi/acpi.rs @@ -0,0 +1,110 @@ +use core::slice; +use uefi::guid::{ACPI_20_TABLE_GUID, ACPI_TABLE_GUID}; + +use crate::Os; + +struct Invalid; + +fn validate_rsdp(address: usize, _v2: bool) -> core::result::Result { + #[repr(C, packed)] + #[derive(Clone, Copy, Debug)] + struct Rsdp { + signature: [u8; 8], // b"RSD PTR " + chksum: u8, + oem_id: [u8; 6], + revision: u8, + rsdt_addr: u32, + // the following fields are only available for ACPI 2.0, and are reserved otherwise + length: u32, + xsdt_addr: u64, + extended_chksum: u8, + _rsvd: [u8; 3], + } + // paging is not enabled at this stage; we can just read the physical address here. + let rsdp_bytes = + unsafe { core::slice::from_raw_parts(address as *const u8, core::mem::size_of::()) }; + let rsdp = unsafe { + (rsdp_bytes.as_ptr() as *const Rsdp) + .as_ref::<'static>() + .unwrap() + }; + + log::debug!("RSDP: {:?}", rsdp); + + if rsdp.signature != *b"RSD PTR " { + return Err(Invalid); + } + let mut base_sum = 0u8; + for base_byte in &rsdp_bytes[..20] { + base_sum = base_sum.wrapping_add(*base_byte); + } + if base_sum != 0 { + return Err(Invalid); + } + + if rsdp.revision == 2 { + let mut extended_sum = 0u8; + for byte in rsdp_bytes { + extended_sum = extended_sum.wrapping_add(*byte); + } + + if extended_sum != 0 { + return Err(Invalid); + } + } + + let length = if rsdp.revision == 2 { + rsdp.length as usize + } else { + core::mem::size_of::() + }; + + Ok(length) +} + +pub(crate) fn find_acpi_table_pointers(os: &impl Os) -> Option<(u64, u64)> { + let cfg_tables = std::system_table().config_tables(); + let mut acpi = None; + let mut acpi2 = None; + for cfg_table in cfg_tables.iter() { + if cfg_table.VendorGuid == ACPI_TABLE_GUID { + match validate_rsdp(cfg_table.VendorTable, false) { + Ok(length) => { + acpi = Some(unsafe { + core::slice::from_raw_parts(cfg_table.VendorTable as *const u8, length) + }); + } + Err(_) => log::warn!( + "Found RSDP that was not valid at {:p}", + cfg_table.VendorTable as *const u8 + ), + } + } else if cfg_table.VendorGuid == ACPI_20_TABLE_GUID { + match validate_rsdp(cfg_table.VendorTable, true) { + Ok(length) => { + acpi2 = Some(unsafe { + core::slice::from_raw_parts(cfg_table.VendorTable as *const u8, length) + }); + } + Err(_) => log::warn!( + "Found RSDP that was not valid at {:p}", + cfg_table.VendorTable as *const u8 + ), + } + } + } + + let rsdp_area = acpi2.or(acpi).unwrap_or(&[]); + + if !rsdp_area.is_empty() { + unsafe { + // Copy to page aligned area + let size = rsdp_area.len(); + let base = os.alloc_zeroed_page_aligned(size); + slice::from_raw_parts_mut(base, size).copy_from_slice(rsdp_area); + Some((base as u64, size as u64)) + } + } else { + None + } +} diff --git a/src/os/uefi/arch/aarch64.rs b/src/os/uefi/arch/aarch64.rs new file mode 100644 index 0000000000..3e36bebcb5 --- /dev/null +++ b/src/os/uefi/arch/aarch64.rs @@ -0,0 +1,236 @@ +use core::{arch::asm, fmt::Write, mem, slice}; +use uefi::status::Result; + +use crate::{ + KernelArgs, + arch::{ENTRY_ADDRESS_MASK, PAGE_ENTRIES, PF_PRESENT, PF_TABLE, PHYS_OFFSET}, + logger::LOGGER, +}; + +use super::super::{OsEfi, memory_map::memory_map}; + +unsafe fn dump_page_tables(table_phys: u64, table_virt: u64, table_level: u64) { + unsafe { + let entries = slice::from_raw_parts(table_phys as *const u64, PAGE_ENTRIES); + for (i, entry) in entries.iter().enumerate() { + let phys = entry & ENTRY_ADDRESS_MASK; + let flags = entry & !ENTRY_ADDRESS_MASK; + if flags & PF_PRESENT == 0 { + continue; + } + let mut shift = 39u64; + for _ in 0..table_level { + shift -= 9; + print!("\t"); + } + let virt = table_virt + (i as u64) << shift; + println!( + "index {} virt {:#x}: phys {:#x} flags {:#x}", + i, virt, phys, flags + ); + if table_level < 3 && flags & PF_TABLE == PF_TABLE { + dump_page_tables(phys, virt, table_level + 1); + } + } + } +} + +unsafe extern "C" fn kernel_entry( + page_phys: usize, + stack: u64, + func: u64, + args: *const KernelArgs, +) -> ! { + unsafe { + // Read memory map and exit boot services + memory_map().exit_boot_services(); + + let currentel: u64; + asm!( + "mrs {0}, currentel", // Read current exception level + out(reg) currentel, + ); + if currentel == (2 << 2) { + // Need to drop from EL2 to EL1 + + // Allow access to timers + asm!( + "mrs {0}, cnthctl_el2", + "orr {0}, {0}, #0x3", + "msr cnthctl_el2, {0}", + "msr cntvoff_el2, xzr", + out(reg) _ + ); + + // Initialize ID registers + asm!( + "mrs {0}, midr_el1", + "msr vpidr_el2, {0}", + "mrs {0}, mpidr_el1", + "msr vmpidr_el2, {0}", + out(reg) _ + ); + + // Disable traps + asm!( + "msr cptr_el2, {0}", + "msr hstr_el2, xzr", + in(reg) 0x33FF as u64 + ); + + // Enable floating point + asm!( + "msr cpacr_el1, {0}", + in(reg) (3 << 20) as u64 + ); + + // Set EL1 system control register + asm!( + "msr sctlr_el1, {0}", + in(reg) 0x30d00800 as u64 + ); + + // Set EL1 stack and VBAR + asm!( + "mov {0}, sp", + "msr sp_el1, {0}", + "mrs {0}, vbar_el2", + "msr vbar_el1, {0}", + out(reg) _ + ); + + // Configure execution state of EL1 as aarch64 and disable hypervisor call. + asm!( + "msr hcr_el2, {0}", + in(reg) ((1u64 << 31) | (1u64 << 29)), + ); + + // Set saved program status register + asm!( + "msr spsr_el2, {0}", + in(reg) 0x3C5 as u64 + ); + + // Switch to EL1 + asm!( + "adr {0}, 1f", + "msr elr_el2, {0}", + "eret", + "1:", + out(reg) _ + ); + } else if currentel == (1 << 2) { + // Already in EL1 + } else { + //TODO: what to do if not EL2 or already EL1? + loop { + asm!("wfi"); + } + } + + // Disable MMU + asm!( + "mrs {0}, sctlr_el1", // Read system control register + "bic {0}, {0}, 1", // Clear MMU enable bit + "msr sctlr_el1, {0}", // Write system control register + "isb", // Instruction sync barrier + out(reg) _, + ); + + // Set MAIR + // You can think about MAIRs as of an array with 8 elements each of 8 bits long. + // You can store inside MAIRs up to 8 attributes sets and reffer them by the index 0..7 stored in INDX (AttrIndx) field of the table descriptor. + // https://lowenware.com/blog/aarch64-mmu-programming/ + // https://developer.arm.com/documentation/102376/0200/Describing-memory-in-AArch64 + // https://developer.arm.com/documentation/ddi0595/2021-06/AArch64-Registers/MAIR-EL1--Memory-Attribute-Indirection-Register--EL1- + // Attribute 0 (0xFF) - normal memory, caches are enabled + // Attribute 1 (0x44) - normal memory, caches are disabled. Atomics wouldn't work here if memory doesn't support exclusive access (most real hardware don't) + // Attribute 2 (0x00) - nGnRnE device memory, caches are disabled, gathering, re-ordering, and early write acknowledgement aren't allowed. + asm!( + "msr mair_el1, {0}", + in(reg) 0x00000000000044FF as u64, // MAIR: Arrange for Device, Normal Non-Cache, Normal Write-Back access types + ); + + // Set TCR + asm!( + "mrs {1}, id_aa64mmfr0_el1", // Read memory model feature register + "bfi {0}, {1}, #32, #3", + "msr tcr_el1, {0}", // Write translation control register + "isb", // Instruction sync barrier + in(reg) 0x1085100510u64, // TCR: (TxSZ, ASID_16, TG1_4K, Cache Attrs, SMP Attrs) + out(reg) _, + ); + + // Set page tables + asm!( + "dsb sy", // Data sync barrier + "msr ttbr1_el1, {0}", // Set higher half page table + "msr ttbr0_el1, {0}", // Set lower half page table + "isb", // Instruction sync barrier + "dsb ishst", // Data sync barrier, only for stores, and only for inner shareable domain + "tlbi vmalle1is", // Invalidate TLB + "dsb ish", // Dta sync bariar, only for inner shareable domain + "isb", // Instruction sync barrier + in(reg) page_phys, + ); + + // Enable MMU + asm!( + "mrs {2}, sctlr_el1", // Read system control register + "bic {2}, {2}, {0}", // Clear bits + "orr {2}, {2}, {1}", // Set bits + "msr sctlr_el1, {2}", // Write system control register + "isb", // Instruction sync barrier + in(reg) 0x32802c2u64, // Clear SCTLR bits: (EE, EOE, IESB, WXN, UMA, ITD, THEE, A) + in(reg) 0x3485d13du64, // Set SCTLR bits: (LSMAOE, nTLSMD, UCI, SPAN, nTWW, nTWI, UCT, DZE, I, SED, SA0, SA, C, M, CP15BEN) + out(reg) _, + ); + + // Set stack + asm!("mov sp, {}", in(reg) stack); + + // Call kernel entry + let entry_fn: extern "C" fn(*const KernelArgs) -> ! = mem::transmute(func); + entry_fn(args); + } +} + +pub fn main() -> Result<()> { + LOGGER.init(); + + let mut os = OsEfi::new(); + + // Disable cursor + let _ = (os.st.ConsoleOut.EnableCursor)(os.st.ConsoleOut, false); + + let currentel: u64; + unsafe { + asm!( + "mrs {0}, currentel", // Read current exception level + out(reg) currentel, + ); + } + log::info!("Currently in EL{}", (currentel >> 2) & 3); + + let (page_phys, func, args) = crate::main(&mut os); + + unsafe { + let stack = args.stack_base + args.stack_size + PHYS_OFFSET; + + // dump_page_tables(page_phys as _, 0, 0); + + println!( + "kernel_entry({:#x}, {:#x}, {:#x}, {:p})", + page_phys, stack, func, &args + ); + println!("{:#x?}", args); + + kernel_entry(page_phys, stack, func, &args); + } +} + +pub fn disable_interrupts() { + unsafe { + asm!("msr daifset, #2"); + } +} diff --git a/src/os/uefi/arch/mod.rs b/src/os/uefi/arch/mod.rs new file mode 100644 index 0000000000..7c8d61d4e7 --- /dev/null +++ b/src/os/uefi/arch/mod.rs @@ -0,0 +1,14 @@ +#[cfg(target_arch = "aarch64")] +mod aarch64; +#[cfg(target_arch = "aarch64")] +pub use self::aarch64::*; + +#[cfg(target_arch = "x86_64")] +mod x86_64; +#[cfg(target_arch = "x86_64")] +pub use self::x86_64::*; + +#[cfg(target_arch = "riscv64")] +mod riscv64; +#[cfg(target_arch = "riscv64")] +pub use self::riscv64::*; diff --git a/src/os/uefi/arch/riscv64/boot_protocol.rs b/src/os/uefi/arch/riscv64/boot_protocol.rs new file mode 100644 index 0000000000..f92fc43c8b --- /dev/null +++ b/src/os/uefi/arch/riscv64/boot_protocol.rs @@ -0,0 +1,45 @@ +use std::proto::Protocol; +use uefi::guid::Guid; +use uefi::status::{Result, Status}; + +#[derive(Debug)] +#[repr(C)] +struct RiscVEfiBootProtocol { + pub revision: u64, + pub efi_get_boot_hartid: + unsafe extern "efiapi" fn(this: *mut Self, phartid: *mut usize) -> Status, +} + +impl RiscVEfiBootProtocol { + pub const GUID: Guid = Guid::parse_str("ccd15fec-6f73-4eec-8395-3e69e4b940bf"); + // pub const REVISION: u64 = 0x00010000; +} + +struct RiscVEfiBoot(pub &'static mut RiscVEfiBootProtocol); + +impl Protocol for RiscVEfiBoot { + fn guid() -> Guid { + RiscVEfiBootProtocol::GUID + } + + fn new(inner: &'static mut RiscVEfiBootProtocol) -> Self { + Self(inner) + } +} + +impl RiscVEfiBoot { + pub fn efi_get_boot_hartid(&mut self) -> Result { + let mut boot_hartid: usize = 0; + match unsafe { (self.0.efi_get_boot_hartid)(self.0, &mut boot_hartid) } { + ok if ok.is_success() => Ok(boot_hartid), + err => Err(err), + } + } +} + +pub fn efi_get_boot_hartid() -> Result { + let handles = RiscVEfiBoot::locate_handle()?; + let handle = handles.first().ok_or(Status::NOT_FOUND)?; + let mut proto = RiscVEfiBoot::handle_protocol(*handle)?; + proto.efi_get_boot_hartid() +} diff --git a/src/os/uefi/arch/riscv64/coff_helper.rs b/src/os/uefi/arch/riscv64/coff_helper.rs new file mode 100644 index 0000000000..92f07de224 --- /dev/null +++ b/src/os/uefi/arch/riscv64/coff_helper.rs @@ -0,0 +1,113 @@ +use core::arch::{global_asm, naked_asm}; + +/// Unfortunately this can't be written in Rust because it might use some not-yet +/// relocated data such as jump tables +#[unsafe(naked)] +#[unsafe(no_mangle)] +extern "C" fn coff_relocate(dynentry: *const u8, base: usize) -> usize { + unsafe { + naked_asm!( + " + mv t4, zero // RELA + li t5, -1 // RELASZ + li t6, -1 // RELAENT + +5: + ld t0, 0(a0) + beqz t0, 6f + addi a0, a0, 16 + addi t0, t0, -4 + bltz t0, 3f // fail on DT_NEEDED=1, DT_PLTRELSZ=2, DT_PLTGOT=3 + addi t0, t0, -3 + bltz t0, 5b // skip DT_HASH=4, DT_STRTAB=5, DT_SYMTAB=6 + bnez t0, 2f + ld t4, -8(a0) // DT_RELA=7 + j 5b +2: addi t0, t0, -1 // DT_RELASZ=8 + bnez t0, 2f + ld t5, -8(a0) + j 5b +2: addi t0, t0, -1 // DT_RELAENT=9 + bnez t0, 2f + ld t6, -8(a0) + j 5b +2: addi t0, t0, -3 + bltz t0, 5b // skip DT_STRSZ=10, DT_SYMENT=11 + addi t0, t0, -2 + bltz t0, 3f // fail on DT_INIT=12, DT_FINI=13 + beqz t0, 5b // skip DT_SONAME=14 +2: addi t0, t0, -2 + bltz t0, 3f // fail on DT_RPATH + beqz t0, 5b // skip SYMBOLIC=16 + li t1, 0x6ffffef5-16 + sub t0, t0, t1 + beqz t0, 5b // skip DT_GNU_HASH=0x6ffffef5 + nop +3: // error + mv a0, zero + ret + +6: + bnez t4, 2f +4: // success + li a0, 1 + ret +2: bltz t5, 3b + blez t6, 3b + + add t4, t4, a1 + add t5, t5, t4 +7: + bge t4, t5, 4b + ld t0, 0(t4) // r_offset + add t0, t0, a1 + lwu t1, 8(t4) // r_type + ld t2, 16(t4) // r_addend + add t4, t4, t6 + addi t1, t1, -3 // R_RISCV_RELATIVE=3 + bnez t1, 3b + add t2, t2, a1 // RELATIVE: *value = base + addend + sd t2, 0(t0) + j 7b + " + ) + } +} + +global_asm!( + r#" + .global coff_start +coff_start: + .option norelax + addi sp, sp, -24 + sd a0, 0(sp) + sd a1, 8(sp) + sd ra, 16(sp) + lla a0, _DYNAMIC + lla a1, ImageBase // actual loaded image base to relocate to + jal coff_relocate + .option relax + mv t0, a0 + ld a0, 0(sp) + ld a1, 8(sp) + ld ra, 16(sp) + addi sp, sp, 24 + beqz t0, 2f + j efi_main +2: ret +"# +); + +// GNU-EFI .reloc trick to make objcopy say we are relocatable +global_asm!( + r#" + .section .data + DUMMY_RELOCATION: .4byte 0 + .section .reloc, "a" + +2: + .4byte DUMMY_RELOCATION - ImageBase + .4byte 12 + .4byte 0 +"# +); diff --git a/src/os/uefi/arch/riscv64/mod.rs b/src/os/uefi/arch/riscv64/mod.rs new file mode 100644 index 0000000000..ab9321b625 --- /dev/null +++ b/src/os/uefi/arch/riscv64/mod.rs @@ -0,0 +1,70 @@ +use crate::KernelArgs; +use crate::arch::PHYS_OFFSET; +use crate::arch::SATP_BITS; +use crate::logger::LOGGER; +use crate::os::OsEfi; +use crate::os::uefi::memory_map::memory_map; +use core::arch::asm; +use core::mem; +use uefi::status::Result; + +mod boot_protocol; +mod coff_helper; + +pub use boot_protocol::*; + +unsafe extern "C" fn kernel_entry( + page_phys: usize, + stack: u64, + func: u64, + args: *const KernelArgs, +) -> ! { + unsafe { + // Set page tables + asm!( + "csrw satp, {0}", + "sfence.vma", + in(reg) (page_phys >> 12 | SATP_BITS << 60) + ); + + let entry_fn: extern "C" fn(*const KernelArgs) -> ! = mem::transmute(func); + + // Set stack and go to kernel + asm!("mv sp, {0}", + "mv a0, {1}", + "jalr {2}", + in(reg) stack, + in(reg) args, + in(reg) entry_fn + ); + loop {} + } +} + +pub fn main() -> Result<()> { + LOGGER.init(); + + let mut os = OsEfi::new(); + + // Disable cursor + let _ = (os.st.ConsoleOut.EnableCursor)(os.st.ConsoleOut, false); + + let (page_phys, func, args) = crate::main(&mut os); + + unsafe { + memory_map().exit_boot_services(); + + kernel_entry( + page_phys, + args.stack_base + args.stack_size + PHYS_OFFSET, + func, + &args, + ); + } +} + +pub fn disable_interrupts() { + unsafe { + asm!("csrci sstatus, 2"); + } +} diff --git a/src/os/uefi/arch/x86_64.rs b/src/os/uefi/arch/x86_64.rs new file mode 100644 index 0000000000..e1ecaa5599 --- /dev/null +++ b/src/os/uefi/arch/x86_64.rs @@ -0,0 +1,82 @@ +use core::{arch::asm, mem}; +use uefi::status::Result; +use x86::{ + controlregs::{self, Cr0, Cr4}, + msr, +}; + +use crate::{KernelArgs, logger::LOGGER}; + +use super::super::{OsEfi, memory_map::memory_map}; + +unsafe extern "C" fn kernel_entry( + page_phys: usize, + stack: u64, + func: u64, + args: *const KernelArgs, +) -> ! { + unsafe { + // Read memory map and exit boot services + memory_map().exit_boot_services(); + + // Enable FXSAVE/FXRSTOR, Page Global, Page Address Extension, and Page Size Extension + let mut cr4 = controlregs::cr4(); + cr4 |= Cr4::CR4_ENABLE_SSE + | Cr4::CR4_ENABLE_GLOBAL_PAGES + | Cr4::CR4_ENABLE_PAE + | Cr4::CR4_ENABLE_PSE; + controlregs::cr4_write(cr4); + + // Enable Long mode and NX bit + let mut efer = msr::rdmsr(msr::IA32_EFER); + efer |= 1 << 11 | 1 << 8; + msr::wrmsr(msr::IA32_EFER, efer); + + // Set new page map + controlregs::cr3_write(page_phys as u64); + + // Enable paging, write protect kernel, protected mode + let mut cr0 = controlregs::cr0(); + cr0 |= Cr0::CR0_ENABLE_PAGING | Cr0::CR0_WRITE_PROTECT | Cr0::CR0_PROTECTED_MODE; + controlregs::cr0_write(cr0); + + // Set stack + asm!("mov rsp, {}", in(reg) stack); + + // Call kernel entry + let entry_fn: extern "sysv64" fn(*const KernelArgs) -> ! = mem::transmute(func); + entry_fn(args); + } +} + +pub fn main() -> Result<()> { + LOGGER.init(); + + let mut os = OsEfi::new(); + + // Disable cursor + let _ = (os.st.ConsoleOut.EnableCursor)(os.st.ConsoleOut, false); + + let (page_phys, func, args) = crate::main(&mut os); + + unsafe { + kernel_entry( + page_phys, + args.stack_base + + args.stack_size + + if crate::KERNEL_64BIT { + crate::arch::x64::PHYS_OFFSET + } else { + crate::arch::x32::PHYS_OFFSET as u64 + }, + func, + &args, + ); + } +} + +pub fn disable_interrupts() { + unsafe { + asm!("cli"); + } +} diff --git a/src/os/uefi/device.rs b/src/os/uefi/device.rs new file mode 100644 index 0000000000..36b48dc2d8 --- /dev/null +++ b/src/os/uefi/device.rs @@ -0,0 +1,509 @@ +use alloc::{string::String, vec, vec::Vec}; +use core::{fmt::Write, mem, ptr, slice}; +use uefi::{ + Handle, + device::{ + DevicePath, DevicePathAcpiType, DevicePathBbsType, DevicePathEndType, + DevicePathHardwareType, DevicePathMediaType, DevicePathMessagingType, DevicePathType, + }, + guid::Guid, + status::Status, +}; +use uefi_std::{fs::FileSystem, loaded_image::LoadedImage, proto::Protocol}; + +use super::disk::{DiskEfi, DiskOrFileEfi}; + +#[derive(Debug)] +enum DevicePathRelation { + This, + Parent(usize), + Child(usize), + None, +} + +fn device_path_relation(a_path: &DevicePath, b_path: &DevicePath) -> DevicePathRelation { + let mut a_iter = DevicePathIter::new(a_path); + let mut b_iter = DevicePathIter::new(b_path); + loop { + match (a_iter.next(), b_iter.next()) { + (None, None) => return DevicePathRelation::This, + (None, Some(_)) => return DevicePathRelation::Parent(b_iter.count()), + (Some(_), None) => return DevicePathRelation::Child(a_iter.count()), + (Some((a_node, a_data)), Some((b_node, b_data))) => { + if a_node.Type != b_node.Type { + return DevicePathRelation::None; + } + + if a_node.SubType != b_node.SubType { + return DevicePathRelation::None; + } + + if a_data != b_data { + return DevicePathRelation::None; + } + } + } + } +} + +fn esp_live_image(esp_handle: Handle, esp_device_path: &DevicePath) -> Option> { + let mut esp_fs = match FileSystem::handle_protocol(esp_handle) { + Ok(esp_fs) => esp_fs, + Err(err) => { + log::warn!("Failed to find SimpleFileSystem protocol: {:?}", err); + return None; + } + }; + + let mut root = match esp_fs.root() { + Ok(root) => root, + Err(err) => { + log::warn!("Failed to open ESP filesystem: {:?}", err); + return None; + } + }; + + const fn as_utf16_str(s: [u8; N]) -> [u16; N] { + let mut ret = [0; N]; + let mut i = 0; + while i < N { + ret[i] = s[i] as u16; + i += 1; + } + ret + } + + let filename = const { &as_utf16_str(*b"redox-live.iso\0") }; + let mut live_image = match root.open(filename) { + Ok(live_image) => live_image, + Err(Status::NOT_FOUND) => return None, + Err(err) => { + log::warn!( + "Failed to open {}\\redox-live.iso: {:?}", + device_path_to_string(esp_device_path), + err + ); + return None; + } + }; + + let mut buffer = Vec::new(); + + live_image.read_to_end(&mut buffer).unwrap(); + + Some(buffer) +} + +pub struct DiskDevice { + pub handle: Handle, + pub disk: DiskOrFileEfi, + pub partition_offset: u64, + pub device_path: DevicePathProtocol, + pub file_path: Option<&'static str>, +} + +pub fn disk_device_priority() -> Vec { + // Get the handle of the partition this program was loaded from, which should be the ESP + let esp_handle = match LoadedImage::handle_protocol(std::handle()) { + Ok(loaded_image) => loaded_image.0.DeviceHandle, + Err(err) => { + log::warn!("Failed to find LoadedImage protocol: {:?}", err); + return Vec::new(); + } + }; + + // Get the device path of the ESP + let esp_device_path = match DevicePathProtocol::handle_protocol(esp_handle) { + Ok(ok) => ok, + Err(err) => { + log::warn!( + "Failed to find device path protocol on {:?}: {:?}", + esp_handle, + err + ); + return Vec::new(); + } + }; + + if cfg!(feature = "live") { + // First try to get a live image from redox-live.iso. This is required to support netbooting. + if let Some(buffer) = esp_live_image(esp_handle, esp_device_path.0) { + return vec![DiskDevice { + handle: esp_handle, + // Support both a copy of livedisk.iso and a standalone redoxfs partition + partition_offset: if &buffer[512..520] == b"EFI PART" { + //TODO: get block from partition table + 2 * crate::MIBI as u64 + } else { + 0 + }, + disk: DiskOrFileEfi::File(buffer), + device_path: esp_device_path, + file_path: Some("redox-live.iso"), + }]; + } + } + + // Get all block I/O handles along with their block I/O implementations and device paths + let handles = match DiskEfi::locate_handle() { + Ok(ok) => ok, + Err(err) => { + log::warn!("Failed to find block I/O handles: {:?}", err); + Vec::new() + } + }; + let mut devices = Vec::with_capacity(handles.len()); + for handle in handles { + let disk = match DiskEfi::handle_protocol(handle) { + Ok(ok) => ok, + Err(err) => { + log::warn!( + "Failed to find block I/O protocol on {:?}: {:?}", + handle, + err + ); + continue; + } + }; + + if !disk.0.Media.MediaPresent { + continue; + } + + let device_path = match DevicePathProtocol::handle_protocol(handle) { + Ok(ok) => ok, + Err(err) => { + log::warn!( + "Failed to find device path protocol on {:?}: {:?}", + handle, + err + ); + continue; + } + }; + + devices.push(DiskDevice { + handle, + partition_offset: if disk.0.Media.LogicalPartition { + 0 + } else { + //TODO: get block from partition table + 2 * crate::MIBI as u64 + }, + disk: DiskOrFileEfi::Disk(disk), + device_path, + file_path: None, + }); + } + + // Find possible boot disks + let mut boot_disks = Vec::with_capacity(1); + { + let mut i = 0; + while i < devices.len() { + if let DevicePathRelation::Parent(0) = + device_path_relation(devices[i].device_path.0, esp_device_path.0) + { + boot_disks.push(devices.remove(i)); + continue; + } + + i += 1; + } + } + + // Find all children of possible boot devices + let mut priority = Vec::with_capacity(devices.capacity()); + for boot_disk in boot_disks { + let mut i = 0; + while i < devices.len() { + // Only prioritize non-ESP devices + if devices[i].handle != esp_handle { + if let DevicePathRelation::Child(0) = + device_path_relation(devices[i].device_path.0, boot_disk.device_path.0) + { + priority.push(devices.remove(i)); + continue; + } + } + + i += 1; + } + + priority.push(boot_disk); + } + + // Add any remaining devices + priority.extend(devices); + + priority +} + +#[repr(C, packed)] +#[allow(dead_code)] +struct DevicePathHarddrive { + partition_number: u32, + partition_start: u64, + partition_size: u64, + partition_signature: [u8; 16], + partition_format: u8, + signature_type: u8, +} + +pub fn device_path_to_string(device_path: &DevicePath) -> String { + let mut s = String::new(); + for (node, node_data) in DevicePathIter::new(device_path) { + let read_u16 = |i: usize| -> u16 { (node_data[i] as u16) | (node_data[i + 1] as u16) << 8 }; + + let read_u32 = |i: usize| -> u32 { + (node_data[i] as u32) + | (node_data[i + 1] as u32) << 8 + | (node_data[i + 2] as u32) << 16 + | (node_data[i + 3] as u32) << 24 + }; + + if !s.is_empty() { + s.push('/'); + } + + let _ = match DevicePathType::try_from(node.Type) { + Ok(path_type) => match path_type { + DevicePathType::Hardware => match DevicePathHardwareType::try_from(node.SubType) { + Ok(sub_type) => match sub_type { + DevicePathHardwareType::Pci if node_data.len() == 2 => { + let func = node_data[0]; + let dev = node_data[1]; + write!(s, "Pci(0x{dev:X},0x{func:X})") + } + _ => write!(s, "{path_type:?} {sub_type:?} {node_data:X?}"), + }, + Err(()) => write!(s, "{:?} 0x{:02X} {:X?}", path_type, node.SubType, node_data), + }, + DevicePathType::Acpi => match DevicePathAcpiType::try_from(node.SubType) { + Ok(sub_type) => match sub_type { + DevicePathAcpiType::Acpi if node_data.len() == 8 => { + let hid = read_u32(0); + let uid = read_u32(4); + if hid & 0xFFFF == 0x41D0 { + write!(s, "Acpi(PNP{:04X},0x{:X})", hid >> 16, uid) + } else { + write!(s, "Acpi(0x{hid:08X},0x{uid:X})") + } + } + _ => write!(s, "{path_type:?} {sub_type:?} {node_data:X?}"), + }, + Err(()) => write!(s, "{:?} 0x{:02X} {:X?}", path_type, node.SubType, node_data), + }, + DevicePathType::Messaging => { + match DevicePathMessagingType::try_from(node.SubType) { + Ok(sub_type) => match sub_type { + DevicePathMessagingType::Sata if node_data.len() == 6 => { + let hba_port = read_u16(0); + let multiplier_port = read_u16(2); + let logical_unit = read_u16(4); + if multiplier_port & (1 << 15) != 0 { + write!(s, "Sata(0x{hba_port:X},0x{logical_unit:X})") + } else { + write!( + s, + "Sata(0x{hba_port:X},0x{multiplier_port:X},0x{logical_unit:X})" + ) + } + } + DevicePathMessagingType::Usb if node_data.len() == 2 => { + let port = node_data[0]; + let iface = node_data[1]; + write!(s, "Usb(0x{port:X},0x{iface:X})") + } + DevicePathMessagingType::Nvme if node_data.len() == 12 => { + let nsid = read_u32(0); + let eui = &node_data[4..]; + if eui == [0, 0, 0, 0, 0, 0, 0, 0] { + write!(s, "NVMe(0x{nsid:X})") + } else { + write!( + s, + "NVMe(0x{:X},{:02X}-{:02X}-{:02X}-{:02X}-{:02X}-{:02X}-{:02X}-{:02X})", + nsid, + eui[0], + eui[1], + eui[2], + eui[3], + eui[4], + eui[5], + eui[6], + eui[7], + ) + } + } + DevicePathMessagingType::Mac + if node_data.len() == 33 && node_data[32] == 0 + || node_data[32] == 1 => + { + write!( + s, + "Mac({:02x}{:02x}{:02x}{:02x}{:02x}{:02x},{:#02x})", + node_data[0], + node_data[1], + node_data[2], + node_data[3], + node_data[4], + node_data[5], + node_data[32], + ) + } + _ => write!(s, "{path_type:?} {sub_type:?} {node_data:X?}"), + }, + Err(()) => { + write!(s, "{:?} 0x{:02X} {:X?}", path_type, node.SubType, node_data) + } + } + } + DevicePathType::Media => match DevicePathMediaType::try_from(node.SubType) { + Ok(sub_type) => { + match sub_type { + DevicePathMediaType::Harddrive + if node_data.len() == mem::size_of::() => + { + let harddrive = unsafe { + ptr::read(node_data.as_ptr() as *const DevicePathHarddrive) + }; + let partition_number = unsafe { + ptr::read_unaligned(ptr::addr_of!(harddrive.partition_number)) + }; + match harddrive.signature_type { + 1 => { + let id = unsafe { + ptr::read(harddrive.partition_signature.as_ptr() + as *const u32) + }; + write!(s, "HD(0x{partition_number:X},MBR,0x{id:X})") + } + 2 => { + let guid = unsafe { + ptr::read(harddrive.partition_signature.as_ptr() + as *const Guid) + }; + write!( + s, + "HD(0x{:X},GPT,{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X})", + partition_number, + guid.0, + guid.1, + guid.2, + guid.3[0], + guid.3[1], + guid.3[2], + guid.3[3], + guid.3[4], + guid.3[5], + guid.3[6], + guid.3[7], + ) + } + _ => { + write!( + s, + "HD(0x{:X},0x{:X},{:X?})", + partition_number, + harddrive.signature_type, + harddrive.partition_signature + ) + } + } + } + DevicePathMediaType::Filepath => { + for chunk in node_data.chunks_exact(2) { + let data = (chunk[0] as u16) | (chunk[1] as u16) << 8; + match unsafe { char::from_u32_unchecked(data as u32) } { + '\\' => s.push('/'), + c => s.push(c), + } + } + Ok(()) + } + _ => write!(s, "{path_type:?} {sub_type:?} {node_data:X?}"), + } + } + Err(()) => write!(s, "{:?} 0x{:02X} {:X?}", path_type, node.SubType, node_data), + }, + DevicePathType::Bbs => match DevicePathBbsType::try_from(node.SubType) { + Ok(sub_type) => write!(s, "{path_type:?} {sub_type:?} {node_data:X?}"), + Err(()) => write!(s, "{:?} 0x{:02X} {:X?}", path_type, node.SubType, node_data), + }, + DevicePathType::End => match DevicePathEndType::try_from(node.SubType) { + Ok(sub_type) => write!(s, "{path_type:?} {sub_type:?} {node_data:X?}"), + Err(()) => write!(s, "{:?} 0x{:02X} {:X?}", path_type, node.SubType, node_data), + }, + }, + Err(()) => { + write!( + s, + "0x{:02X} 0x{:02X} {:X?}", + node.Type, node.SubType, node_data + ) + } + }; + } + s +} + +pub struct DevicePathProtocol(pub &'static mut DevicePath); + +impl Protocol for DevicePathProtocol { + fn guid() -> Guid { + uefi::guid::DEVICE_PATH_GUID + } + + fn new(inner: &'static mut DevicePath) -> Self { + Self(inner) + } +} + +pub struct LoadedImageDevicePathProtocol(pub &'static mut DevicePath); + +impl Protocol for LoadedImageDevicePathProtocol { + fn guid() -> Guid { + uefi::guid::LOADED_IMAGE_DEVICE_PATH_GUID + } + + fn new(inner: &'static mut DevicePath) -> Self { + Self(inner) + } +} + +pub struct DevicePathIter<'a> { + device_path: &'a DevicePath, + node_ptr: *const DevicePath, +} + +impl<'a> DevicePathIter<'a> { + pub fn new(device_path: &'a DevicePath) -> Self { + Self { + device_path, + node_ptr: device_path as *const DevicePath, + } + } +} + +impl<'a> Iterator for DevicePathIter<'a> { + type Item = (&'a DevicePath, &'a [u8]); + fn next(&mut self) -> Option { + let node = unsafe { &*self.node_ptr }; + + if node.Type == DevicePathType::End as u8 { + return None; + } + + let node_data = unsafe { + slice::from_raw_parts( + self.node_ptr.add(1) as *mut u8, + node.Length.saturating_sub(4) as usize, + ) + }; + + self.node_ptr = (self.node_ptr as usize + node.Length as usize) as *const DevicePath; + + Some((node, node_data)) + } +} diff --git a/src/os/uefi/disk.rs b/src/os/uefi/disk.rs new file mode 100644 index 0000000000..31e02db813 --- /dev/null +++ b/src/os/uefi/disk.rs @@ -0,0 +1,119 @@ +use alloc::vec::Vec; +use core::slice; +use redoxfs::{BLOCK_SIZE, Disk, RECORD_SIZE}; +use std::proto::Protocol; +use syscall::{EINVAL, EIO, Error, Result}; +use uefi::block_io::BlockIo as UefiBlockIo; +use uefi::guid::{BLOCK_IO_GUID, Guid}; + +pub enum DiskOrFileEfi { + Disk(DiskEfi), + File(Vec), +} + +impl redoxfs::Disk for DiskOrFileEfi { + unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> syscall::Result { + unsafe { + match self { + DiskOrFileEfi::Disk(disk_efi) => disk_efi.read_at(block, buffer), + DiskOrFileEfi::File(data) => { + buffer.copy_from_slice( + &data[(block * redoxfs::BLOCK_SIZE) as usize + ..(block * redoxfs::BLOCK_SIZE) as usize + buffer.len()], + ); + Ok(buffer.len()) + } + } + } + } + + unsafe fn write_at(&mut self, _block: u64, _buffer: &[u8]) -> syscall::Result { + unreachable!() + } + + fn size(&mut self) -> syscall::Result { + unreachable!() + } +} + +pub struct DiskEfi(pub &'static mut UefiBlockIo, &'static mut [u8]); + +impl Protocol for DiskEfi { + fn guid() -> Guid { + BLOCK_IO_GUID + } + + fn new(inner: &'static mut UefiBlockIo) -> Self { + // Hack to get aligned buffer + let block = unsafe { + let ptr = super::alloc_zeroed_page_aligned(RECORD_SIZE as usize); + slice::from_raw_parts_mut(ptr, RECORD_SIZE as usize) + }; + + Self(inner, block) + } +} + +impl Disk for DiskEfi { + unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result { + unsafe { + // Optimization for live disks + if let Some(live) = crate::LIVE_OPT { + if block >= live.0 { + let start = ((block - live.0) * BLOCK_SIZE) as usize; + let end = start + buffer.len(); + if end <= live.1.len() { + buffer.copy_from_slice(&live.1[start..end]); + return Ok(buffer.len()); + } + } + } + + // Use aligned buffer if necessary + let mut ptr = buffer.as_mut_ptr(); + if self.0.Media.IoAlign != 0 { + if (ptr as usize) % (self.0.Media.IoAlign as usize) != 0 { + if buffer.len() <= self.1.len() { + ptr = self.1.as_mut_ptr(); + } else { + println!( + "DiskEfi::read_at 0x{:X} requires alignment, ptr = 0x{:p}, len = 0x{:x}", + block, + ptr, + buffer.len() + ); + return Err(Error::new(EINVAL)); + } + } + } + + let block_size = self.0.Media.BlockSize as u64; + let lba = block * BLOCK_SIZE / block_size; + + match (self.0.ReadBlocks)(self.0, self.0.Media.MediaId, lba, buffer.len(), ptr) { + status if status.is_success() => { + // Copy to original buffer if using aligned buffer + if ptr != buffer.as_mut_ptr() { + let (left, _) = self.1.split_at(buffer.len()); + buffer.copy_from_slice(left); + } + Ok(buffer.len()) + } + err => { + println!("DiskEfi::read_at 0x{:X} failed: {:?}", block, err); + Err(Error::new(EIO)) + } + } + } + } + + unsafe fn write_at(&mut self, block: u64, _buffer: &[u8]) -> Result { + println!("DiskEfi::write_at 0x{:X} not implemented", block); + Err(Error::new(EIO)) + } + + fn size(&mut self) -> Result { + println!("DiskEfi::size not implemented"); + Err(Error::new(EIO)) + } +} diff --git a/src/os/uefi/display.rs b/src/os/uefi/display.rs new file mode 100644 index 0000000000..95b7c2fd5e --- /dev/null +++ b/src/os/uefi/display.rs @@ -0,0 +1,41 @@ +use std::proto::Protocol; +use uefi::graphics::GraphicsOutput; +use uefi::guid::{GRAPHICS_OUTPUT_PROTOCOL_GUID, Guid}; + +pub struct Output(pub &'static mut GraphicsOutput); + +impl Protocol for Output { + fn guid() -> Guid { + GRAPHICS_OUTPUT_PROTOCOL_GUID + } + + fn new(inner: &'static mut GraphicsOutput) -> Self { + Output(inner) + } +} + +const EDID_ACTIVE_PROTOCOL_GUID: Guid = Guid( + 0xbd8c1056, + 0x9f36, + 0x44ec, + [0x92, 0xa8, 0xa6, 0x33, 0x7f, 0x81, 0x79, 0x86], +); + +#[allow(non_snake_case)] +#[repr(C)] +pub struct EdidActiveProtocol { + pub SizeOfEdid: u32, + pub Edid: *const u8, +} + +pub struct EdidActive(pub &'static mut EdidActiveProtocol); + +impl Protocol for EdidActive { + fn guid() -> Guid { + EDID_ACTIVE_PROTOCOL_GUID + } + + fn new(inner: &'static mut EdidActiveProtocol) -> Self { + EdidActive(inner) + } +} diff --git a/src/os/uefi/dtb.rs b/src/os/uefi/dtb.rs new file mode 100644 index 0000000000..812c75249d --- /dev/null +++ b/src/os/uefi/dtb.rs @@ -0,0 +1,125 @@ +use crate::Os; +use alloc::vec::Vec; +use byteorder::BE; +use byteorder::ByteOrder; +use core::slice; +use fdt::Fdt; +use uefi::guid::DEVICE_TREE_GUID; +#[cfg(target_arch = "aarch64")] +use uefi::{ + guid::SMBIOS3_TABLE_GUID, + status::{Result, Status}, +}; + +pub static mut DEV_MEM_AREA: Vec<(usize, usize)> = Vec::new(); + +pub unsafe fn is_in_dev_mem_region(addr: usize) -> bool { + #[allow(static_mut_refs)] + unsafe { + if DEV_MEM_AREA.is_empty() { + return false; + } + for item in DEV_MEM_AREA.iter() { + if (addr >= item.0) && (addr < item.0 + item.1) { + return true; + } + } + return false; + } +} + +unsafe fn get_dev_mem_region(fdt: &Fdt) { + unsafe { + let Some(soc) = fdt.find_node("/soc") else { + return; + }; + let Some(ranges) = soc.ranges() else { + return; + }; + let cell_sizes = soc.cell_sizes(); + for chunk in ranges { + let child_bus_addr = chunk.child_bus_address; + let parent_bus_addr = chunk.parent_bus_address; + let addr_size = chunk.size; + println!( + "dev mem 0x{:08x} 0x{:08x} 0x{:08x}", + child_bus_addr, parent_bus_addr, addr_size + ); + #[allow(static_mut_refs)] + DEV_MEM_AREA.push((parent_bus_addr as usize, addr_size as usize)); + } + } +} + +fn parse_dtb(os: &impl Os, address: *const u8) -> Option<(u64, u64)> { + unsafe { + if let Ok(fdt) = fdt::Fdt::from_ptr(address) { + let mut rsdps_area = Vec::new(); + //println!("DTB model = {}", fdt.root().model()); + get_dev_mem_region(&fdt); + let length = fdt.total_size(); + let align = 8; + rsdps_area.extend(core::slice::from_raw_parts(address, length)); + rsdps_area.resize(((rsdps_area.len() + (align - 1)) / align) * align, 0u8); + let size = rsdps_area.len(); + let base = os.alloc_zeroed_page_aligned(size); + slice::from_raw_parts_mut(base, size).copy_from_slice(&rsdps_area); + Some((base as u64, size as u64)) + } else { + println!("Failed to parse DTB"); + None + } + } +} + +#[cfg(target_arch = "aarch64")] +fn find_smbios3_system(address: *const u8) -> Result> { + unsafe { + let smb = core::slice::from_raw_parts(address, 24); + if let Ok(smbios) = dmidecode::EntryPoint::search(smb) { + let smb_structure_data = core::slice::from_raw_parts( + smbios.smbios_address() as *const u8, + smbios.smbios_len() as usize, + ); + for structure in smbios.structures(smb_structure_data) { + if let Ok(sval) = structure { + //println!("SMBIOS: {:#?}", sval); + if let dmidecode::Structure::System(buf) = sval { + return Ok(buf); + } + } + } + } + } + Err(Status::NOT_FOUND) +} + +pub(crate) fn find_dtb(os: &impl Os) -> Option<(u64, u64)> { + let cfg_tables = std::system_table().config_tables(); + for cfg_table in cfg_tables.iter() { + if cfg_table.VendorGuid == DEVICE_TREE_GUID { + let addr = cfg_table.VendorTable; + return parse_dtb(os, addr as *const u8); + } + } + + /* This hack is no longer needed, but can be re-enabled for testing + #[cfg(target_arch = "aarch64")] + for cfg_table in cfg_tables.iter() { + if cfg_table.VendorGuid == SMBIOS3_TABLE_GUID { + let addr = cfg_table.VendorTable; + if let Ok(sys) = find_smbios3_system(addr as *const u8) { + let get_dtb_addr = match (sys.manufacturer, sys.version) { + ("QEMU", version) if version.starts_with("virt") => Some(0x4000_0000 as usize), + _ => None, + }; + if let Some(dtb_addr) = get_dtb_addr { + return parse_dtb(os, dtb_addr as *const u8); + } + } + } + } + */ + + None +} diff --git a/src/os/uefi/memory_map.rs b/src/os/uefi/memory_map.rs new file mode 100644 index 0000000000..11474477b1 --- /dev/null +++ b/src/os/uefi/memory_map.rs @@ -0,0 +1,141 @@ +use alloc::vec; +use alloc::vec::Vec; +use core::{mem, ptr}; +use uefi::memory::{MemoryDescriptor, MemoryType}; + +use crate::area_add; +use crate::os::{OsMemoryEntry, OsMemoryKind}; + +use super::status_to_result; + +pub struct MemoryMapIter { + map: Vec, + map_key: usize, + descriptor_size: usize, + descriptor_version: u32, + i: usize, +} + +impl MemoryMapIter { + pub fn new() -> Self { + let uefi = std::system_table(); + + let mut map = vec![0; 65536]; + let mut map_size = map.len(); + let mut map_key = 0; + let mut descriptor_size = 0; + let mut descriptor_version = 0; + status_to_result((uefi.BootServices.GetMemoryMap)( + &mut map_size, + map.as_mut_ptr() as *mut MemoryDescriptor, + &mut map_key, + &mut descriptor_size, + &mut descriptor_version, + )) + .expect("Failed to get UEFI memory map"); + + // Ensure descriptor size is usable + assert!(descriptor_size >= mem::size_of::()); + + // Ensure descriptor version is supported + assert_eq!(descriptor_version, 1); + + // Reduce map size to returned value + map.truncate(map_size); + + Self { + map, + map_key, + descriptor_size, + descriptor_version, + i: 0, + } + } + + pub fn exit_boot_services(mut self) { + let handle = std::handle(); + let uefi = std::system_table(); + + // We are writing to the memory map that will be passed to + // SetVirtualAddressMap before ExitBootServices as on some firmware + // EfiLoaderData memory regions like this one are marked as read-only + // after ExitBootServices + for i in 0..self.map.len() / self.descriptor_size { + let descriptor_ptr = unsafe { self.map.as_mut_ptr().add(i * self.descriptor_size) }; + let descriptor = unsafe { &mut *(descriptor_ptr as *mut MemoryDescriptor) }; + + // Map all memory regions even when not marked as EFI_MEMORY_RUNTIME + // as some firmware uses memory regions not marked as + // EFI_MEMORY_RUNTIME in runtime services. Linux has a list of + // exactly which memory regions need to be mapped, but for simplicity + // we are mapping all regions here. + + // Identity map all memory regions as some firmware fails to update + // all pointers in SetVirtualAddressMap. + + descriptor.VirtualStart.0 = descriptor.PhysicalStart.0; + } + + status_to_result((uefi.BootServices.ExitBootServices)(handle, self.map_key)) + .expect("Failed to exit UEFI boot services"); + + // Runtime services must be called with interrupts disabled + super::arch::disable_interrupts(); + + status_to_result((uefi.RuntimeServices.SetVirtualAddressMap)( + self.map.len(), + self.descriptor_size, + self.descriptor_version, + self.map.as_ptr() as *const MemoryDescriptor, + )) + .expect("Failed to set UEFI runtime services virtual address map"); + + // After ExitBootServices, GlobalAlloc::dealloc() is not allowed anymore + // as it uses boot services. + mem::forget(self); + } +} + +impl Iterator for MemoryMapIter { + type Item = OsMemoryEntry; + fn next(&mut self) -> Option { + if self.i < self.map.len() / self.descriptor_size { + let descriptor_ptr = unsafe { self.map.as_ptr().add(self.i * self.descriptor_size) }; + self.i += 1; + + let descriptor = unsafe { ptr::read(descriptor_ptr as *const MemoryDescriptor) }; + let descriptor_type: MemoryType = unsafe { mem::transmute(descriptor.Type) }; + + Some(OsMemoryEntry { + base: descriptor.PhysicalStart.0, + //TODO: do not hard code page size + size: descriptor.NumberOfPages * 4096, + kind: match descriptor_type { + MemoryType::EfiLoaderCode + | MemoryType::EfiLoaderData + | MemoryType::EfiBootServicesCode + | MemoryType::EfiBootServicesData + | MemoryType::EfiConventionalMemory => OsMemoryKind::Free, + //TODO: mark ACPI memory as reclaim + _ => OsMemoryKind::Reserved, + }, + }) + } else { + None + } + } +} + +pub unsafe fn memory_map() -> MemoryMapIter { + let mut iter = MemoryMapIter::new(); + + // Using next to avoid consuming iterator + while let Some(entry) = iter.next() { + area_add(entry); + } + + // Rewind iterator + iter.i = 0; + + iter +} diff --git a/src/os/uefi/mod.rs b/src/os/uefi/mod.rs new file mode 100644 index 0000000000..bbd034b136 --- /dev/null +++ b/src/os/uefi/mod.rs @@ -0,0 +1,396 @@ +use alloc::vec::Vec; +use core::{cell::RefCell, mem, ptr, slice}; +use std::proto::Protocol; +use uefi::{ + Handle, + boot::LocateSearchType, + memory::MemoryType, + reset::ResetType, + status::{Result, Status}, + system::SystemTable, + text::TextInputKey, +}; + +use crate::os::{Os, OsHwDesc, OsKey, OsVideoMode}; + +use self::{ + device::{device_path_to_string, disk_device_priority}, + disk::DiskOrFileEfi, + display::{EdidActive, Output}, + video_mode::VideoModeIter, +}; + +mod acpi; +mod arch; +mod device; +mod disk; +mod display; +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] +pub mod dtb; +mod memory_map; +mod video_mode; + +#[cfg(target_arch = "riscv64")] +pub use arch::efi_get_boot_hartid; + +pub(crate) fn page_size() -> usize { + // EDK2 always uses 4096 as the page size + 4096 +} + +pub(crate) fn alloc_zeroed_page_aligned(size: usize) -> *mut u8 { + assert!(size != 0); + + let page_size = page_size(); + let pages = size.div_ceil(page_size); + + let ptr = { + // Max address mapped by src/arch paging code (8 GiB) + let mut ptr = 0x2_0000_0000; + status_to_result((std::system_table().BootServices.AllocatePages)( + 1, // AllocateMaxAddress + MemoryType::EfiRuntimeServicesData, // Keeps this memory out of free space list + pages, + &mut ptr, + )) + .unwrap(); + ptr as *mut u8 + }; + + assert!(!ptr.is_null()); + unsafe { ptr::write_bytes(ptr, 0, pages * page_size) }; + ptr +} + +pub struct OsEfi { + st: &'static SystemTable, + outputs: RefCell)>>, +} + +impl OsEfi { + pub fn new() -> Self { + let st = std::system_table(); + let mut outputs = Vec::<(Output, Option)>::new(); + { + let guid = Output::guid(); + let mut handles = Vec::with_capacity(256); + let mut len = handles.capacity() * mem::size_of::(); + match status_to_result((st.BootServices.LocateHandle)( + LocateSearchType::ByProtocol, + &guid, + ptr::null(), + &mut len, + handles.as_mut_ptr(), + )) { + Ok(_) => { + unsafe { + handles.set_len(len / mem::size_of::()); + } + 'handles: for handle in handles { + //TODO: do we have to query all modes to get good edid? + match Output::handle_protocol(handle) { + Ok(output) => { + log::debug!( + "Output {:?} at {:x}", + handle, + output.0.Mode.FrameBufferBase + ); + + if output.0.Mode.FrameBufferBase == 0 { + log::debug!("Skipping output with frame buffer base of 0"); + continue 'handles; + } + + for other_output in outputs.iter() { + if output.0.Mode.FrameBufferBase + == other_output.0.0.Mode.FrameBufferBase + { + log::debug!( + "Skipping output with frame buffer base matching another output" + ); + continue 'handles; + } + } + + outputs.push(( + output, + match EdidActive::handle_protocol(handle) { + Ok(efi_edid) => Some(efi_edid), + Err(err) => { + log::warn!( + "Failed to get EFI EDID from handle {:?}: {:?}", + handle, + err + ); + None + } + }, + )); + } + Err(err) => { + log::warn!( + "Failed to get Output from handle {:?}: {:?}", + handle, + err + ); + } + } + } + } + Err(err) => { + log::warn!("Failed to locate Outputs: {:?}", err); + } + } + } + Self { + st, + outputs: RefCell::new(outputs), + } + } +} + +impl Os for OsEfi { + type D = DiskOrFileEfi; + type V = VideoModeIter; + + #[cfg(target_arch = "aarch64")] + fn name(&self) -> &str { + "aarch64/UEFI" + } + + #[cfg(target_arch = "x86_64")] + fn name(&self) -> &str { + "x86_64/UEFI" + } + + #[cfg(target_arch = "riscv64")] + fn name(&self) -> &str { + "riscv64/UEFI" + } + + fn alloc_zeroed_page_aligned(&self, size: usize) -> *mut u8 { + alloc_zeroed_page_aligned(size) + } + + fn page_size(&self) -> usize { + page_size() + } + + fn filesystem( + &self, + password_opt: Option<&[u8]>, + ) -> syscall::Result> { + // Search for RedoxFS on disks in prioritized order + println!("Looking for RedoxFS:"); + for device in disk_device_priority() { + if let Some(file_path) = device.file_path { + log::debug!( + " - {}\\{}", + device_path_to_string(device.device_path.0), + file_path + ); + } else { + log::debug!(" - {}", device_path_to_string(device.device_path.0)); + } + + let block = device.partition_offset / redoxfs::BLOCK_SIZE; + + match redoxfs::FileSystem::open(device.disk, password_opt, Some(block), false) { + Ok(ok) => return Ok(ok), + Err(err) => match err.errno { + // Ignore header not found error + syscall::ENOENT => (), + // Print any other errors + _ => { + log::warn!("BlockIo error: {:?}", err); + } + }, + } + } + + log::warn!("No RedoxFS partitions found"); + Err(syscall::Error::new(syscall::ENOENT)) + } + + fn hwdesc(&self) -> OsHwDesc { + //TODO: if both DTB and ACPI are found, we should probably let the OS choose what to use? + + // For now we will prefer DTB on platforms that have it + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + if let Some((addr, size)) = dtb::find_dtb(self) { + return OsHwDesc::DeviceTree(addr, size); + } + + if let Some((addr, size)) = acpi::find_acpi_table_pointers(self) { + return OsHwDesc::Acpi(addr, size); + } + + OsHwDesc::NotFound + } + + fn video_outputs(&self) -> usize { + self.outputs.borrow().len() + } + + fn video_modes(&self, output_i: usize) -> VideoModeIter { + let output_opt = match self.outputs.borrow_mut().get_mut(output_i) { + Some(output) => unsafe { + // Hack to enable clone + let ptr = output.0.0 as *mut _; + Some(Output::new(&mut *ptr)) + }, + None => None, + }; + VideoModeIter::new(output_opt) + } + + fn set_video_mode(&self, output_i: usize, mode: &mut OsVideoMode) { + //TODO: return error? + let mut outputs = self.outputs.borrow_mut(); + let (output, _efi_edid_opt) = &mut outputs[output_i]; + status_to_result((output.0.SetMode)(output.0, mode.id)).unwrap(); + + // Update with actual mode information + mode.width = output.0.Mode.Info.HorizontalResolution; + mode.height = output.0.Mode.Info.VerticalResolution; + mode.base = output.0.Mode.FrameBufferBase as u64; + } + + fn best_resolution(&self, output_i: usize) -> Option<(u32, u32)> { + let mut outputs = self.outputs.borrow_mut(); + let (output, efi_edid_opt) = outputs.get_mut(output_i)?; + + if let Some(efi_edid) = efi_edid_opt { + let edid = + unsafe { slice::from_raw_parts(efi_edid.0.Edid, efi_edid.0.SizeOfEdid as usize) }; + + if edid.len() > 0x3D { + return Some(( + (edid[0x38] as u32) | (((edid[0x3A] as u32) & 0xF0) << 4), + (edid[0x3B] as u32) | (((edid[0x3D] as u32) & 0xF0) << 4), + )); + } else { + log::warn!("EFI EDID too small: {}", edid.len()); + } + } + + // Fallback to the current output resolution + Some(( + output.0.Mode.Info.HorizontalResolution, + output.0.Mode.Info.VerticalResolution, + )) + } + + fn get_key(&self) -> OsKey { + //TODO: do not unwrap + + let mut index = 0; + status_to_result((self.st.BootServices.WaitForEvent)( + 1, + &self.st.ConsoleIn.WaitForKey, + &mut index, + )) + .unwrap(); + + let mut key = TextInputKey { + ScanCode: 0, + UnicodeChar: 0, + }; + status_to_result((self.st.ConsoleIn.ReadKeyStroke)( + self.st.ConsoleIn, + &mut key, + )) + .unwrap(); + + match key.ScanCode { + 0 => match key.UnicodeChar { + 8 => OsKey::Backspace, + 13 => OsKey::Enter, + w => match char::from_u32(w as u32) { + Some(c) => OsKey::Char(c), + None => OsKey::Other, + }, + }, + 1 => OsKey::Up, + 2 => OsKey::Down, + 3 => OsKey::Right, + 4 => OsKey::Left, + 8 => OsKey::Delete, + _ => OsKey::Other, + } + } + + fn clear_text(&self) { + //TODO: why does this sometimes return InvalidParameter, but otherwise appear to work? + let _ = status_to_result((self.st.ConsoleOut.ClearScreen)(self.st.ConsoleOut)); + } + + fn get_text_position(&self) -> (usize, usize) { + ( + self.st.ConsoleOut.Mode.CursorColumn as usize, + self.st.ConsoleOut.Mode.CursorRow as usize, + ) + } + + fn set_text_position(&self, x: usize, y: usize) { + // Ignore error because Tow-Boot appears to not implement this + let _ = status_to_result((self.st.ConsoleOut.SetCursorPosition)( + self.st.ConsoleOut, + x, + y, + )); + } + + fn set_text_highlight(&self, highlight: bool) { + let attr = if highlight { 0x70 } else { 0x07 }; + status_to_result((self.st.ConsoleOut.SetAttribute)(self.st.ConsoleOut, attr)).unwrap(); + } +} + +fn status_to_result(status: Status) -> Result { + match status { + Status(ok) if status.is_success() => Ok(ok), + err => Err(err), + } +} + +fn set_max_mode(output: &uefi::text::TextOutput) -> Result<()> { + let mut max_i = None; + let mut max_w = 0; + let mut max_h = 0; + + for i in 0..output.Mode.MaxMode as usize { + let mut w = 0; + let mut h = 0; + if (output.QueryMode)(output, i, &mut w, &mut h).is_success() { + if w >= max_w && h >= max_h { + max_i = Some(i); + max_w = w; + max_h = h; + } + } + } + + if let Some(i) = max_i { + status_to_result((output.SetMode)(output, i))?; + } + + Ok(()) +} + +#[unsafe(no_mangle)] +pub extern "C" fn main() -> Status { + let uefi = std::system_table(); + + let _ = (uefi.BootServices.SetWatchdogTimer)(0, 0, 0, ptr::null()); + + if let Err(err) = set_max_mode(uefi.ConsoleOut) { + println!("Failed to set max mode: {:?}", err); + } + + if let Err(err) = arch::main() { + panic!("App error: {:?}", err); + } + + (uefi.RuntimeServices.ResetSystem)(ResetType::Cold, Status(0), 0, ptr::null()); +} diff --git a/src/os/uefi/video_mode.rs b/src/os/uefi/video_mode.rs new file mode 100644 index 0000000000..b6e020a69e --- /dev/null +++ b/src/os/uefi/video_mode.rs @@ -0,0 +1,57 @@ +use core::ptr; +use log::error; +use uefi::status::Status; + +use crate::os::OsVideoMode; +use crate::os::uefi::display::Output; + +pub struct VideoModeIter { + output_opt: Option, + i: u32, +} + +impl VideoModeIter { + pub fn new(output_opt: Option) -> Self { + Self { output_opt, i: 0 } + } +} + +impl Iterator for VideoModeIter { + type Item = OsVideoMode; + fn next(&mut self) -> Option { + if let Some(ref mut output) = self.output_opt { + while self.i < output.0.Mode.MaxMode { + let id = self.i; + self.i += 1; + + let mut mode_ptr = ::core::ptr::null_mut(); + let mut mode_size = 0; + match (output.0.QueryMode)(output.0, id, &mut mode_size, &mut mode_ptr) { + Status::SUCCESS => (), + err => { + error!("Failed to read mode {}: {:?}", id, err); + continue; + } + } + + //TODO: ensure mode_size is set correctly + let mode = unsafe { ptr::read(mode_ptr) }; + + let width = mode.HorizontalResolution; + let height = mode.VerticalResolution; + let stride = mode.PixelsPerScanLine; + + return Some(OsVideoMode { + id, + width, + height, + stride, + // Base is retrieved later by setting the mode + base: 0, + }); + } + } + + None + } +} diff --git a/src/serial_16550.rs b/src/serial_16550.rs new file mode 100644 index 0000000000..adaa8747ce --- /dev/null +++ b/src/serial_16550.rs @@ -0,0 +1,142 @@ +use bitflags::bitflags; +use core::convert::TryInto; +use core::fmt; +use core::ptr::{addr_of, addr_of_mut}; +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +use syscall::io::Pio; +use syscall::io::{Io, Mmio, ReadOnly}; + +bitflags! { + /// Interrupt enable flags + struct IntEnFlags: u8 { + const RECEIVED = 1; + const SENT = 1 << 1; + const ERRORED = 1 << 2; + const STATUS_CHANGE = 1 << 3; + // 4 to 7 are unused + } +} + +bitflags! { + /// Line status flags + struct LineStsFlags: u8 { + const INPUT_FULL = 1; + // 1 to 4 unknown + const OUTPUT_EMPTY = 1 << 5; + // 6 and 7 unknown + } +} + +#[allow(dead_code)] +#[repr(C, packed)] +pub struct SerialPort { + /// Data register, read to receive, write to send + data: T, + /// Interrupt enable + int_en: T, + /// FIFO control + fifo_ctrl: T, + /// Line control + line_ctrl: T, + /// Modem control + modem_ctrl: T, + /// Line status + line_sts: ReadOnly, + /// Modem status + modem_sts: ReadOnly, +} + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +impl SerialPort> { + pub const fn new(base: u16) -> SerialPort> { + SerialPort { + data: Pio::new(base), + int_en: Pio::new(base + 1), + fifo_ctrl: Pio::new(base + 2), + line_ctrl: Pio::new(base + 3), + modem_ctrl: Pio::new(base + 4), + line_sts: ReadOnly::new(Pio::new(base + 5)), + modem_sts: ReadOnly::new(Pio::new(base + 6)), + } + } +} + +impl SerialPort> { + pub unsafe fn new(base: usize) -> &'static mut SerialPort> { + unsafe { &mut *(base as *mut Self) } + } +} + +impl SerialPort +where + T::Value: From + TryInto, +{ + pub fn init(&mut self) { + unsafe { + //TODO: Cleanup + // FIXME: Fix UB if unaligned + (*addr_of_mut!(self.int_en)).write(0x00.into()); + (*addr_of_mut!(self.line_ctrl)).write(0x80.into()); + (*addr_of_mut!(self.data)).write(0x01.into()); + (*addr_of_mut!(self.int_en)).write(0x00.into()); + (*addr_of_mut!(self.line_ctrl)).write(0x03.into()); + (*addr_of_mut!(self.fifo_ctrl)).write(0xC7.into()); + (*addr_of_mut!(self.modem_ctrl)).write(0x0B.into()); + (*addr_of_mut!(self.int_en)).write(0x01.into()); + } + } + + fn line_sts(&self) -> LineStsFlags { + LineStsFlags::from_bits_truncate( + (unsafe { &*addr_of!(self.line_sts) }.read() & 0xFF.into()) + .try_into() + .unwrap_or(0), + ) + } + + pub fn receive(&mut self) -> Option { + if self.line_sts().contains(LineStsFlags::INPUT_FULL) { + Some( + (unsafe { &*addr_of!(self.data) }.read() & 0xFF.into()) + .try_into() + .unwrap_or(0), + ) + } else { + None + } + } + + pub fn send(&mut self, data: u8) { + while !self.line_sts().contains(LineStsFlags::OUTPUT_EMPTY) {} + unsafe { &mut *addr_of_mut!(self.data) }.write(data.into()) + } + + pub fn write(&mut self, buf: &[u8]) { + for &b in buf { + match b { + 8 | 0x7F => { + self.send(8); + self.send(b' '); + self.send(8); + } + b'\n' => { + self.send(b'\r'); + self.send(b'\n'); + } + _ => { + self.send(b); + } + } + } + } +} + +impl fmt::Write for SerialPort +where + T::Value: From + TryInto, +{ + fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { + self.write(s.as_bytes()); + Ok(()) + } +} diff --git a/targets/aarch64-unknown-uefi.json b/targets/aarch64-unknown-uefi.json new file mode 100644 index 0000000000..c22cfef835 --- /dev/null +++ b/targets/aarch64-unknown-uefi.json @@ -0,0 +1,25 @@ +{ + "arch": "aarch64", + "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", + "default-hidden-visibility": true, + "emit-debug-gdb-scripts": false, + "exe-suffix": ".efi", + "executables": true, + "is-like-windows": true, + "linker": "rust-lld", + "linker-flavor": "lld-link", + "llvm-target": "aarch64-pc-windows-msvc", + "os": "uefi", + "panic-strategy": "abort", + "pre-link-args": { + "lld-link": [ + "/subsystem:EFI_Application", + "/entry:efi_main", + "/machine:arm64" + ] + }, + "stack_probes": true, + "target-c-int-width": 32, + "target-endian": "little", + "target-pointer-width": 64 +} diff --git a/targets/riscv64gc-unknown-uefi.json b/targets/riscv64gc-unknown-uefi.json new file mode 100644 index 0000000000..abe75da89f --- /dev/null +++ b/targets/riscv64gc-unknown-uefi.json @@ -0,0 +1,25 @@ +{ + "arch": "riscv64", + "code-model": "medium", + "cpu": "generic-rv64", + "data-layout": "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128", + "emit-debug-gdb-scripts": false, + "exe-suffix": ".elf", + "executables": true, + "linker-flavor": "gnu-cc", + "linker": "riscv64-unknown-redox-gcc", + "llvm-abiname": "lp64d", + "features": "+m,+a,+f,+d,+c", + "llvm-target": "riscv64-unknown-none-elf", + "os": "none", + "metadata": { + "description": null, + "host_tools": null, + "std": null, + "tier": null + }, + "panic-strategy": "abort", + "target-c-int-width": 32, + "target-endian": "little", + "target-pointer-width": 64 +} diff --git a/targets/x86-unknown-none.json b/targets/x86-unknown-none.json new file mode 100644 index 0000000000..bbf32053a3 --- /dev/null +++ b/targets/x86-unknown-none.json @@ -0,0 +1,29 @@ +{ + "llvm-target": "i686-unknown-none", + "target-endian": "little", + "target-pointer-width": 32, + "target-c-int-width": 32, + "data-layout": "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:32-n8:16:32-S128", + "arch": "x86", + "os": "none", + "env": "", + "vendor": "unknown", + "linker-flavor": "gcc", + "panic-strategy": "abort", + "pre-link-args": { + "gcc": ["-m32", "-nostdlib", "-static"] + }, + "features": "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,+soft-float", + "rustc-abi": "x86-softfloat", + "dynamic-linking": false, + "executables": false, + "relocation-model": "static", + "code-model": "large", + "disable-redzone": true, + "frame-pointer": "always", + "exe-suffix": "", + "has-rpath": false, + "no-default-libraries": true, + "position-independent-executables": false, + "tls-model": "global-dynamic" +}