├── .gitignore ├── .gitlab-ci.yml ├── .helix ├── config.toml └── languages.toml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── Makefile ├── asm ├── aarch64 │ └── dummy.S ├── arm │ └── start.s └── x86-unknown-none │ ├── bootloader.asm │ ├── cpuid.asm │ ├── gdt.asm │ ├── iso.asm │ ├── long_mode.asm │ ├── print.asm │ ├── protected_mode.asm │ ├── stage1.asm │ ├── stage2.asm │ └── thunk.asm ├── linkers ├── riscv64-unknown-uefi.ld └── x86-unknown-none.ld ├── mk ├── aarch64-unknown-uefi.mk ├── riscv64gc-unknown-uefi.mk ├── x86-unknown-none.mk └── x86_64-unknown-uefi.mk ├── rust-toolchain.toml ├── src ├── arch │ ├── aarch64.rs │ ├── mod.rs │ ├── riscv64.rs │ └── x86 │ │ ├── mod.rs │ │ ├── x32.rs │ │ └── x64.rs ├── logger.rs ├── main.rs ├── os │ ├── bios │ │ ├── disk.rs │ │ ├── macros.rs │ │ ├── memory_map.rs │ │ ├── mod.rs │ │ ├── panic.rs │ │ ├── serial.rs │ │ ├── thunk.rs │ │ ├── vbe.rs │ │ └── vga.rs │ ├── mod.rs │ └── uefi │ │ ├── acpi.rs │ │ ├── arch │ │ ├── aarch64.rs │ │ ├── mod.rs │ │ ├── riscv64 │ │ │ ├── boot_protocol.rs │ │ │ ├── coff_helper.rs │ │ │ └── mod.rs │ │ └── x86_64.rs │ │ ├── device.rs │ │ ├── disk.rs │ │ ├── display.rs │ │ ├── dtb.rs │ │ ├── memory_map.rs │ │ ├── mod.rs │ │ └── video_mode.rs └── serial_16550.rs └── targets ├── aarch64-unknown-uefi.json ├── riscv64gc-unknown-uefi.json └── x86-unknown-none.json /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /target 3 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: "redoxos/redoxer:latest" 2 | 3 | before_script: 4 | - apt-get install nasm 5 | - rustup component add rust-src 6 | 7 | stages: 8 | - host 9 | 10 | build:i686: 11 | stage: host 12 | script: 13 | - mkdir -p target/i686 14 | - cd target/i686 15 | - TARGET=x86-unknown-none make -f ${CI_PROJECT_DIR}/Makefile -C `pwd` `pwd`/bootloader.bin `pwd`/bootloader-live.bin 16 | 17 | build:x86_64: 18 | stage: host 19 | script: 20 | - mkdir -p target/x86_64 21 | - cd target/x86_64 22 | - TARGET=x86_64-unknown-uefi make -f ${CI_PROJECT_DIR}/Makefile -C `pwd` `pwd`/bootloader.efi `pwd`/bootloader-live.efi 23 | 24 | build:aarch64: 25 | stage: host 26 | script: 27 | - mkdir -p target/aarch64 28 | - cd target/aarch64 29 | - TARGET=aarch64-unknown-uefi make -f ${CI_PROJECT_DIR}/Makefile -C `pwd` `pwd`/bootloader.efi `pwd`/bootloader-live.efi 30 | 31 | fmt: 32 | stage: host 33 | script: 34 | - rustup component add rustfmt-preview 35 | - cargo fmt -- --check 36 | -------------------------------------------------------------------------------- /.helix/config.toml: -------------------------------------------------------------------------------- 1 | [editor] 2 | auto-format = false 3 | -------------------------------------------------------------------------------- /.helix/languages.toml: -------------------------------------------------------------------------------- 1 | [[language]] 2 | name = "rust" 3 | # TODO: Add more targets (BIOS, x86_32) 4 | # Uncomment this line and set cargo.target to your target to get accurate completions 5 | # config = { cargo.target = "aarch64-unknown-uefi", check.targets = ["x86_64-unknown-uefi", "aarch64-unknown-uefi"] } 6 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aes" 7 | version = "0.7.5" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" 10 | dependencies = [ 11 | "cfg-if", 12 | "cipher", 13 | "cpufeatures", 14 | "opaque-debug", 15 | ] 16 | 17 | [[package]] 18 | name = "argon2" 19 | version = "0.4.1" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "db4ce4441f99dbd377ca8a8f57b698c44d0d6e712d8329b5040da5a64aa1ce73" 22 | dependencies = [ 23 | "base64ct", 24 | "blake2", 25 | ] 26 | 27 | [[package]] 28 | name = "autocfg" 29 | version = "1.4.0" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 32 | 33 | [[package]] 34 | name = "base64ct" 35 | version = "1.6.0" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" 38 | 39 | [[package]] 40 | name = "bit_field" 41 | version = "0.10.2" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" 44 | 45 | [[package]] 46 | name = "bitflags" 47 | version = "1.3.2" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 50 | 51 | [[package]] 52 | name = "bitflags" 53 | version = "2.6.0" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 56 | 57 | [[package]] 58 | name = "blake2" 59 | version = "0.10.6" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" 62 | dependencies = [ 63 | "digest", 64 | ] 65 | 66 | [[package]] 67 | name = "block-buffer" 68 | version = "0.10.4" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 71 | dependencies = [ 72 | "generic-array", 73 | ] 74 | 75 | [[package]] 76 | name = "byteorder" 77 | version = "1.5.0" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 80 | 81 | [[package]] 82 | name = "cfg-if" 83 | version = "1.0.0" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 86 | 87 | [[package]] 88 | name = "cipher" 89 | version = "0.3.0" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" 92 | dependencies = [ 93 | "generic-array", 94 | ] 95 | 96 | [[package]] 97 | name = "cpufeatures" 98 | version = "0.2.14" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" 101 | dependencies = [ 102 | "libc", 103 | ] 104 | 105 | [[package]] 106 | name = "crypto-common" 107 | version = "0.1.6" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 110 | dependencies = [ 111 | "generic-array", 112 | "typenum", 113 | ] 114 | 115 | [[package]] 116 | name = "digest" 117 | version = "0.10.7" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 120 | dependencies = [ 121 | "block-buffer", 122 | "crypto-common", 123 | "subtle", 124 | ] 125 | 126 | [[package]] 127 | name = "dmidecode" 128 | version = "0.8.4" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "48d911dab24c970a29e258304e2e1792df4ab02b546ffc41fa503e874a70cd9d" 131 | dependencies = [ 132 | "bitflags 1.3.2", 133 | ] 134 | 135 | [[package]] 136 | name = "endian-num" 137 | version = "0.1.2" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "f8f59926911ef34d1efb9ea1ee8ca78385df62ce700ccf2bcb149011bd226888" 140 | 141 | [[package]] 142 | name = "fdt" 143 | version = "0.1.5" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "784a4df722dc6267a04af36895398f59d21d07dce47232adf31ec0ff2fa45e67" 146 | 147 | [[package]] 148 | name = "generic-array" 149 | version = "0.14.7" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 152 | dependencies = [ 153 | "typenum", 154 | "version_check", 155 | ] 156 | 157 | [[package]] 158 | name = "libc" 159 | version = "0.2.159" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" 162 | 163 | [[package]] 164 | name = "linked_list_allocator" 165 | version = "0.10.5" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286" 168 | dependencies = [ 169 | "spinning_top", 170 | ] 171 | 172 | [[package]] 173 | name = "lock_api" 174 | version = "0.4.12" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 177 | dependencies = [ 178 | "autocfg", 179 | "scopeguard", 180 | ] 181 | 182 | [[package]] 183 | name = "log" 184 | version = "0.4.22" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 187 | 188 | [[package]] 189 | name = "opaque-debug" 190 | version = "0.3.1" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" 193 | 194 | [[package]] 195 | name = "raw-cpuid" 196 | version = "10.7.0" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" 199 | dependencies = [ 200 | "bitflags 1.3.2", 201 | ] 202 | 203 | [[package]] 204 | name = "redox-path" 205 | version = "0.3.1" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "436d45c2b6a5b159d43da708e62b25be3a4a3d5550d654b72216ade4c4bfd717" 208 | 209 | [[package]] 210 | name = "redox_bootloader" 211 | version = "1.0.0" 212 | dependencies = [ 213 | "bitflags 1.3.2", 214 | "byteorder", 215 | "dmidecode", 216 | "fdt", 217 | "linked_list_allocator", 218 | "log", 219 | "redox_syscall", 220 | "redox_uefi", 221 | "redox_uefi_std", 222 | "redoxfs", 223 | "spin", 224 | "x86", 225 | ] 226 | 227 | [[package]] 228 | name = "redox_syscall" 229 | version = "0.5.7" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" 232 | dependencies = [ 233 | "bitflags 2.6.0", 234 | ] 235 | 236 | [[package]] 237 | name = "redox_uefi" 238 | version = "0.1.13" 239 | source = "git+https://gitlab.redox-os.org/redox-os/uefi.git#c1f3dd9be1c8a107ed252e773aa4e05ab9487a4e" 240 | 241 | [[package]] 242 | name = "redox_uefi_alloc" 243 | version = "0.1.13" 244 | source = "git+https://gitlab.redox-os.org/redox-os/uefi.git#c1f3dd9be1c8a107ed252e773aa4e05ab9487a4e" 245 | dependencies = [ 246 | "redox_uefi", 247 | ] 248 | 249 | [[package]] 250 | name = "redox_uefi_std" 251 | version = "0.1.13" 252 | source = "git+https://gitlab.redox-os.org/redox-os/uefi.git#c1f3dd9be1c8a107ed252e773aa4e05ab9487a4e" 253 | dependencies = [ 254 | "redox_uefi", 255 | "redox_uefi_alloc", 256 | ] 257 | 258 | [[package]] 259 | name = "redoxfs" 260 | version = "0.6.7" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "d7a579fab9d51965b0660af059f9eae4da343c04cc3d7c20cd32bccd24af2d91" 263 | dependencies = [ 264 | "aes", 265 | "argon2", 266 | "base64ct", 267 | "endian-num", 268 | "libc", 269 | "log", 270 | "redox-path", 271 | "redox_syscall", 272 | "seahash", 273 | "uuid", 274 | ] 275 | 276 | [[package]] 277 | name = "scopeguard" 278 | version = "1.2.0" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 281 | 282 | [[package]] 283 | name = "seahash" 284 | version = "4.1.0" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" 287 | 288 | [[package]] 289 | name = "spin" 290 | version = "0.9.8" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 293 | dependencies = [ 294 | "lock_api", 295 | ] 296 | 297 | [[package]] 298 | name = "spinning_top" 299 | version = "0.2.5" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "5b9eb1a2f4c41445a3a0ff9abc5221c5fcd28e1f13cd7c0397706f9ac938ddb0" 302 | dependencies = [ 303 | "lock_api", 304 | ] 305 | 306 | [[package]] 307 | name = "subtle" 308 | version = "2.6.1" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 311 | 312 | [[package]] 313 | name = "typenum" 314 | version = "1.17.0" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 317 | 318 | [[package]] 319 | name = "uuid" 320 | version = "1.11.0" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" 323 | 324 | [[package]] 325 | name = "version_check" 326 | version = "0.9.5" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 329 | 330 | [[package]] 331 | name = "x86" 332 | version = "0.52.0" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "2781db97787217ad2a2845c396a5efe286f87467a5810836db6d74926e94a385" 335 | dependencies = [ 336 | "bit_field", 337 | "bitflags 1.3.2", 338 | "raw-cpuid", 339 | ] 340 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "redox_bootloader" 3 | version = "1.0.0" 4 | edition = "2021" 5 | 6 | # UEFI uses bin target 7 | [[bin]] 8 | name = "bootloader" 9 | path = "src/main.rs" 10 | 11 | # BIOS uses lib target 12 | [lib] 13 | name = "bootloader" 14 | path = "src/main.rs" 15 | crate-type = ["staticlib"] 16 | 17 | [dependencies] 18 | bitflags = "1.3.2" 19 | linked_list_allocator = "0.10.5" 20 | log = "0.4.17" 21 | redox_syscall = "0.5" 22 | spin = "0.9.5" 23 | 24 | [dependencies.redoxfs] 25 | version = "0.6.0" 26 | default-features = false 27 | features = ["force-soft", "log"] 28 | 29 | [target.'cfg(target_os = "uefi")'.dependencies] 30 | redox_uefi = { git = "https://gitlab.redox-os.org/redox-os/uefi.git" } 31 | redox_uefi_std = { git = "https://gitlab.redox-os.org/redox-os/uefi.git" } 32 | 33 | #TODO: riscv cannot use target_os = "uefi" at this time 34 | [target.'cfg(target_arch = "riscv64")'.dependencies] 35 | redox_uefi = { git = "https://gitlab.redox-os.org/redox-os/uefi.git" } 36 | redox_uefi_std = { git = "https://gitlab.redox-os.org/redox-os/uefi.git" } 37 | 38 | [target."aarch64-unknown-uefi".dependencies] 39 | dmidecode = "0.8.0" 40 | 41 | [target."x86_64-unknown-uefi".dependencies] 42 | x86 = "0.52.0" 43 | 44 | [target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies] 45 | byteorder = { version = "1", default-features = false } 46 | fdt = "0.1.5" 47 | 48 | [features] 49 | default = [] 50 | live = [] 51 | serial_debug = [] 52 | 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2022 Redox OS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET?=x86_64-unknown-uefi 2 | SOURCE:=$(dir $(realpath $(lastword $(MAKEFILE_LIST)))) 3 | BUILD:=$(CURDIR) 4 | export RUST_TARGET_PATH?=$(SOURCE)/targets 5 | 6 | 7 | include $(SOURCE)/mk/$(TARGET).mk 8 | 9 | clean: 10 | rm -rf build target 11 | 12 | $(BUILD)/filesystem: 13 | mkdir -p $(BUILD) 14 | rm -f $@.partial 15 | mkdir $@.partial 16 | fallocate -l 1MiB $@.partial/kernel 17 | mv $@.partial $@ 18 | 19 | $(BUILD)/filesystem.bin: $(BUILD)/filesystem 20 | mkdir -p $(BUILD) 21 | rm -f $@.partial 22 | fallocate -l 254MiB $@.partial 23 | redoxfs-ar $@.partial $< 24 | mv $@.partial $@ 25 | -------------------------------------------------------------------------------- /asm/aarch64/dummy.S: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redox-os/bootloader/b40d768c28e4328025b7e8eb9416488c3af69aa9/asm/aarch64/dummy.S -------------------------------------------------------------------------------- /asm/arm/start.s: -------------------------------------------------------------------------------- 1 | interrupt_vector_table: 2 | b . @ Reset 3 | b . 4 | b . @ SWI instruction 5 | b . 6 | b . 7 | b . 8 | b . 9 | b . 10 | 11 | .comm stack, 0x10000 @ Reserve 64k stack in the BSS 12 | _start: 13 | .globl _start 14 | ldr sp, =stack+0x10000 @ Set up the stack 15 | bl kstart @ Jump to the main function 16 | 1: 17 | b 1b @ Halt 18 | -------------------------------------------------------------------------------- /asm/x86-unknown-none/bootloader.asm: -------------------------------------------------------------------------------- 1 | sectalign off 2 | 3 | ; stage 1 is sector 0, loaded at 0x7C00 4 | %include "stage1.asm" 5 | 6 | ; GPT area from sector 1 to 33, loaded at 0x7E00 7 | times (33*512) db 0 8 | 9 | ; stage 2, loaded at 0xC000 10 | stage2: 11 | %include "stage2.asm" 12 | align 512, db 0 13 | stage2.end: 14 | 15 | ; the maximum size of stage2 is 4 KiB 16 | times (4*1024)-($-stage2) db 0 17 | 18 | ; ISO compatibility, uses up space until 0x12400 19 | %include "iso.asm" 20 | 21 | times 3072 db 0 ; Pad to 0x13000 22 | 23 | ; stage3, loaded at 0x13000 24 | stage3: 25 | %defstr STAGE3_STR %[STAGE3] 26 | incbin STAGE3_STR 27 | align 512, db 0 28 | .end: 29 | 30 | ; the maximum size of the boot loader portion is 384 KiB 31 | times (384*1024)-($-$$) db 0 32 | -------------------------------------------------------------------------------- /asm/x86-unknown-none/cpuid.asm: -------------------------------------------------------------------------------- 1 | SECTION .text 2 | USE16 3 | 4 | cpuid_required_features: 5 | .edx equ cpuid_edx.fpu | cpuid_edx.pse | cpuid_edx.pge | cpuid_edx.fxsr 6 | .ecx equ 0 7 | 8 | cpuid_check: 9 | ; If bit 21 of EFLAGS can be changed, then CPUID is supported 10 | pushfd ;Save EFLAGS 11 | pushfd ;Store EFLAGS 12 | xor dword [esp],0x00200000 ;Invert the ID bit in stored EFLAGS 13 | popfd ;Load stored EFLAGS (with ID bit inverted) 14 | pushfd ;Store EFLAGS again (ID bit may or may not be inverted) 15 | pop eax ;eax = modified EFLAGS (ID bit may or may not be inverted) 16 | xor eax,[esp] ;eax = whichever bits were changed 17 | popfd ;Restore original EFLAGS 18 | test eax,0x00200000 ;eax = zero if ID bit can't be changed, else non-zero 19 | jz .no_cpuid 20 | 21 | mov eax, 1 22 | cpuid 23 | 24 | and edx, cpuid_required_features.edx 25 | cmp edx, cpuid_required_features.edx 26 | jne .error 27 | 28 | and ecx, cpuid_required_features.ecx 29 | cmp ecx, cpuid_required_features.ecx 30 | jne .error 31 | 32 | ret 33 | 34 | .no_cpuid: 35 | mov si, .msg_cpuid 36 | call print 37 | 38 | mov si, .msg_line 39 | call print 40 | 41 | jmp .halt 42 | 43 | .error: 44 | push ecx 45 | push edx 46 | 47 | mov si, .msg_features 48 | call print 49 | 50 | mov si, .msg_line 51 | call print 52 | 53 | mov si, .msg_edx 54 | call print 55 | 56 | pop ebx 57 | push ebx 58 | shr ebx, 16 59 | call print_hex 60 | 61 | pop ebx 62 | call print_hex 63 | 64 | mov si, .msg_must_contain 65 | call print 66 | 67 | mov ebx, cpuid_required_features.edx 68 | shr ebx, 16 69 | call print_hex 70 | 71 | mov ebx, cpuid_required_features.edx 72 | call print_hex 73 | 74 | mov si, .msg_line 75 | call print 76 | 77 | mov si, .msg_ecx 78 | call print 79 | 80 | pop ebx 81 | push ebx 82 | shr ebx, 16 83 | call print_hex 84 | 85 | pop ebx 86 | call print_hex 87 | 88 | mov si, .msg_must_contain 89 | call print 90 | 91 | mov ebx, cpuid_required_features.ecx 92 | shr ebx, 16 93 | call print_hex 94 | 95 | mov ebx, cpuid_required_features.ecx 96 | call print_hex 97 | 98 | mov si, .msg_line 99 | call print 100 | 101 | .halt: 102 | cli 103 | hlt 104 | jmp .halt 105 | 106 | .msg_cpuid: db "CPUID not supported",0 107 | .msg_features: db "Required CPU features are not present",0 108 | .msg_line: db 13,10,0 109 | .msg_edx: db "EDX ",0 110 | .msg_ecx: db "ECX ",0 111 | .msg_must_contain: db " must contain ",0 112 | 113 | cpuid_edx: 114 | .fpu equ 1 << 0 115 | .vme equ 1 << 1 116 | .de equ 1 << 2 117 | .pse equ 1 << 3 118 | .tsc equ 1 << 4 119 | .msr equ 1 << 5 120 | .pae equ 1 << 6 121 | .mce equ 1 << 7 122 | .cx8 equ 1 << 8 123 | .apic equ 1 << 9 124 | .sep equ 1 << 11 125 | .mtrr equ 1 << 12 126 | .pge equ 1 << 13 127 | .mca equ 1 << 14 128 | .cmov equ 1 << 15 129 | .pat equ 1 << 16 130 | .pse_36 equ 1 << 17 131 | .psn equ 1 << 18 132 | .clfsh equ 1 << 19 133 | .ds equ 1 << 21 134 | .acpi equ 1 << 22 135 | .mmx equ 1 << 23 136 | .fxsr equ 1 << 24 137 | .sse equ 1 << 25 138 | .sse2 equ 1 << 26 139 | .ss equ 1 << 27 140 | .htt equ 1 << 28 141 | .tm equ 1 << 29 142 | .ia64 equ 1 << 30 143 | .pbe equ 1 << 31 144 | 145 | cpuid_ecx: 146 | .sse3 equ 1 << 0 147 | .pclmulqdq equ 1 << 1 148 | .dtes64 equ 1 << 2 149 | .monitor equ 1 << 3 150 | .ds_cpl equ 1 << 4 151 | .vmx equ 1 << 5 152 | .smx equ 1 << 6 153 | .est equ 1 << 7 154 | .tm2 equ 1 << 8 155 | .ssse3 equ 1 << 9 156 | .cnxt_id equ 1 << 10 157 | .sdbg equ 1 << 11 158 | .fma equ 1 << 12 159 | .cmpxchg16b equ 1 << 13 160 | .xtpr equ 1 << 14 161 | .pdcm equ 1 << 15 162 | .pcid equ 1 << 17 163 | .dca equ 1 << 18 164 | .sse4_1 equ 1 << 19 165 | .sse4_2 equ 1 << 20 166 | .x2apic equ 1 << 21 167 | .movbe equ 1 << 22 168 | .popcnt equ 1 << 23 169 | .tsc_deadline equ 1 << 24 170 | .aes equ 1 << 25 171 | .xsave equ 1 << 26 172 | .osxsave equ 1 << 27 173 | .avx equ 1 << 28 174 | .f16c equ 1 << 29 175 | .rdrand equ 1 << 30 176 | .hypervisor equ 1 << 31 177 | -------------------------------------------------------------------------------- /asm/x86-unknown-none/gdt.asm: -------------------------------------------------------------------------------- 1 | SECTION .text ; cannot use .data 2 | 3 | struc GDTEntry 4 | .limitl resw 1 5 | .basel resw 1 6 | .basem resb 1 7 | .attribute resb 1 8 | .flags__limith resb 1 9 | .baseh resb 1 10 | endstruc 11 | 12 | gdt_attr: 13 | .present equ 1 << 7 14 | .ring1 equ 1 << 5 15 | .ring2 equ 1 << 6 16 | .ring3 equ 1 << 5 | 1 << 6 17 | .user equ 1 << 4 18 | ;user 19 | .code equ 1 << 3 20 | ; code 21 | .conforming equ 1 << 2 22 | .readable equ 1 << 1 23 | ; data 24 | .expand_down equ 1 << 2 25 | .writable equ 1 << 1 26 | .accessed equ 1 << 0 27 | ;system 28 | ; legacy 29 | .tssAvailabe16 equ 0x1 30 | .ldt equ 0x2 31 | .tssBusy16 equ 0x3 32 | .call16 equ 0x4 33 | .task equ 0x5 34 | .interrupt16 equ 0x6 35 | .trap16 equ 0x7 36 | .tssAvailabe32 equ 0x9 37 | .tssBusy32 equ 0xB 38 | .call32 equ 0xC 39 | .interrupt32 equ 0xE 40 | .trap32 equ 0xF 41 | ; long mode 42 | .ldt32 equ 0x2 43 | .tssAvailabe64 equ 0x9 44 | .tssBusy64 equ 0xB 45 | .call64 equ 0xC 46 | .interrupt64 equ 0xE 47 | .trap64 equ 0xF 48 | 49 | gdt_flag: 50 | .granularity equ 1 << 7 51 | .available equ 1 << 4 52 | ;user 53 | .default_operand_size equ 1 << 6 54 | ; code 55 | .long_mode equ 1 << 5 56 | ; data 57 | .reserved equ 1 << 5 58 | 59 | gdtr: 60 | dw gdt.end + 1 ; size 61 | dq gdt ; offset 62 | 63 | gdt: 64 | .null equ $ - gdt 65 | dq 0 66 | 67 | .lm64_code equ $ - gdt 68 | istruc GDTEntry 69 | at GDTEntry.limitl, dw 0 70 | at GDTEntry.basel, dw 0 71 | at GDTEntry.basem, db 0 72 | at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.code 73 | at GDTEntry.flags__limith, db gdt_flag.long_mode 74 | at GDTEntry.baseh, db 0 75 | iend 76 | 77 | .lm64_data equ $ - gdt 78 | istruc GDTEntry 79 | at GDTEntry.limitl, dw 0 80 | at GDTEntry.basel, dw 0 81 | at GDTEntry.basem, db 0 82 | ; 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 83 | at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.writable 84 | at GDTEntry.flags__limith, db 0 85 | at GDTEntry.baseh, db 0 86 | iend 87 | 88 | .pm32_code equ $ - gdt 89 | istruc GDTEntry 90 | at GDTEntry.limitl, dw 0xFFFF 91 | at GDTEntry.basel, dw 0 92 | at GDTEntry.basem, db 0 93 | at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.code | gdt_attr.readable 94 | at GDTEntry.flags__limith, db 0xF | gdt_flag.granularity | gdt_flag.default_operand_size 95 | at GDTEntry.baseh, db 0 96 | iend 97 | 98 | .pm32_data equ $ - gdt 99 | istruc GDTEntry 100 | at GDTEntry.limitl, dw 0xFFFF 101 | at GDTEntry.basel, dw 0 102 | at GDTEntry.basem, db 0 103 | at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.writable 104 | at GDTEntry.flags__limith, db 0xF | gdt_flag.granularity | gdt_flag.default_operand_size 105 | at GDTEntry.baseh, db 0 106 | iend 107 | 108 | .pm16_code equ $ - gdt 109 | istruc GDTEntry 110 | at GDTEntry.limitl, dw 0xFFFF 111 | at GDTEntry.basel, dw 0 112 | at GDTEntry.basem, db 0 113 | at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.code | gdt_attr.readable 114 | at GDTEntry.flags__limith, db 0xF 115 | at GDTEntry.baseh, db 0 116 | iend 117 | 118 | .pm16_data equ $ - gdt 119 | istruc GDTEntry 120 | at GDTEntry.limitl, dw 0xFFFF 121 | at GDTEntry.basel, dw 0 122 | at GDTEntry.basem, db 0 123 | at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.writable 124 | at GDTEntry.flags__limith, db 0xF 125 | at GDTEntry.baseh, db 0 126 | iend 127 | 128 | .end equ $ - gdt 129 | -------------------------------------------------------------------------------- /asm/x86-unknown-none/iso.asm: -------------------------------------------------------------------------------- 1 | ; Simple ISO emulation with el torito 2 | 3 | ; Fill until CD sector 0x10 4 | times (0x10*2048)-($-$$) db 0 5 | 6 | ; Volume record 7 | ;TODO: fill in more fields 8 | iso_volume_record: 9 | db 1 ; Type volume record 10 | db "CD001" ; Identifier 11 | db 1 ; Version 12 | db 0 ; Unused 13 | times 32 db ' ' ; System identifier 14 | .volume_id: ; Volume identifier 15 | db 'Redox OS' 16 | times 32-($-.volume_id) db ' ' 17 | times 8 db 0 ; Unused 18 | db 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15 ; Volume space size (0x15) 19 | times 32 db 0 ; Unused 20 | db 0x01, 0x00, 0x00, 0x01 ; Volume set size 21 | db 0x01, 0x00, 0x00, 0x01 ; Volume sequence number 22 | db 0x00, 0x08, 0x08, 0x00 ; Logical block size in little and big endian 23 | 24 | times 156-($-iso_volume_record) db 0 25 | 26 | ; Root directory entry 27 | .root_directory: 28 | db 0x22 ; Length of entry 29 | db 0x00 ; Length of extended attributes 30 | db 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14 ; Location of extent (0x14) 31 | db 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00 ; Size of extent 32 | db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ; Recording time 33 | db 0x02 ; File flags 34 | db 0x00 ; Interleaved file unit size 35 | db 0x00 ; Interleaved gap size 36 | db 0x01, 0x00, 0x00, 0x01 ; Volume sequence number 37 | db 0x01 ; Length of file identifier 38 | db 0x00 ; File identifier 39 | 40 | times 128 db ' ' ; Volume set identifier 41 | times 128 db ' ' ; Publisher identifier 42 | times 128 db ' ' ; Data preparer identifier 43 | times 128 db ' ' ; Application identifier 44 | times 37 db ' ' ; Copyright file ID 45 | times 37 db ' ' ; Abstract file ID 46 | times 37 db ' ' ; Bibliographic file ID 47 | 48 | times 881-($-iso_volume_record) db 0 49 | 50 | db 1 ; File structure version 51 | 52 | ; Fill until CD sector 0x11 53 | times (0x11*2048)-($-$$) db 0 54 | 55 | ; Boot record 56 | iso_boot_record: 57 | db 0 ; Type boot record 58 | db "CD001" ; Identifier 59 | db 1 ; Version 60 | db "EL TORITO SPECIFICATION" ; Boot system identifier 61 | times 0x47-($ - iso_boot_record) db 0 ; Padding 62 | dd 0x13 ; Sector of boot catalog 63 | 64 | ; Fill until CD sector 0x12 65 | times (0x12*2048)-($-$$) db 0 66 | 67 | ; Terminator 68 | iso_terminator: 69 | db 0xFF ; Type terminator 70 | db "CD001" ; Identifier 71 | db 1 ; Version 72 | 73 | ; Fill until CD sector 0x13 74 | times (0x13*2048)-($-$$) db 0 75 | 76 | ; Boot catalog 77 | iso_boot_catalog: 78 | 79 | ; Validation entry 80 | .validation: 81 | db 1 ; Header ID 82 | db 0 ; Platform ID (x86) 83 | dw 0 ; Reserved 84 | times 24 db 0 ; ID string 85 | dw 0x55aa ; Checksum 86 | dw 0xaa55 ; Key 87 | 88 | ; Default entry 89 | .default: 90 | db 0x88 ; Bootable 91 | db 4 ; Hard drive emulation 92 | dw 0 ; Load segment (0 is platform default) 93 | db 0xEE ; Partition type (0xEE is protective MBR) 94 | db 0 ; Unused 95 | dw 1 ; Sector count 96 | dd 0 ; Start address for virtual disk 97 | times 20 db 0 ; Padding 98 | 99 | ; EFI section header entry 100 | .efi_section_header: 101 | db 0x91 ; Final header 102 | db 0xEF ; Platform ID (EFI) 103 | dw 1 ; Number of section header entries 104 | times 28 db 0 ; ID string 105 | 106 | ; EFI section entry 107 | .efi_section_entry: 108 | db 0x88 ; Bootable 109 | db 0 ; No emulation 110 | dw 0 ; Load segment (0 is platform default) 111 | db 0 ; Partition type (not used) 112 | db 0 ; Unused 113 | dw 512 ; Sector count (1 MiB = 512 CD sectors) 114 | dd 512 ; Start address for virtual disk (1 MiB = 512 CD sectors) 115 | times 20 db 0 ; Padding 116 | 117 | ; Fill until CD sector 0x14 118 | times (0x14*2048)-($-$$) db 0 119 | 120 | iso_root_directory: 121 | .self: 122 | db 0x22 ; Length of entry 123 | db 0x00 ; Length of extended attributes 124 | db 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14 ; Location of extent (0x14) 125 | db 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00 ; Size of extent 126 | db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ; Recording time 127 | db 0x02 ; File flags 128 | db 0x00 ; Interleaved file unit size 129 | db 0x00 ; Interleaved gap size 130 | db 0x01, 0x00, 0x00, 0x01 ; Volume sequence number 131 | db 0x01 ; Length of file identifier 132 | db 0x00 ; File identifier 133 | 134 | .parent: 135 | db 0x22 ; Length of entry 136 | db 0x00 ; Length of extended attributes 137 | db 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14 ; Location of extent (0x14) 138 | db 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00 ; Size of extent 139 | db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ; Recording time 140 | db 0x02 ; File flags 141 | db 0x00 ; Interleaved file unit size 142 | db 0x00 ; Interleaved gap size 143 | db 0x01, 0x00, 0x00, 0x01 ; Volume sequence number 144 | db 0x01 ; Length of file identifier 145 | db 0x01 ; File identifier 146 | 147 | .boot_cat: 148 | db 0x2C ; Length of entry 149 | db 0x00 ; Length of extended attributes 150 | db 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13 ; Location of extent (0x13) 151 | db 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00 ; Size of extent 152 | db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ; Recording time 153 | db 0x00 ; File flags 154 | db 0x00 ; Interleaved file unit size 155 | db 0x00 ; Interleaved gap size 156 | db 0x01, 0x00, 0x00, 0x01 ; Volume sequence number 157 | db 0x0A ; Length of file identifier 158 | db "BOOT.CAT;1",0 ; File identifier 159 | 160 | ; Fill until CD sector 0x15 161 | times (0x15*2048)-($-$$) db 0 162 | -------------------------------------------------------------------------------- /asm/x86-unknown-none/long_mode.asm: -------------------------------------------------------------------------------- 1 | SECTION .text 2 | USE32 3 | 4 | long_mode: 5 | .func: dq 0 6 | .page_table: dd 0 7 | 8 | .entry: 9 | ; disable interrupts 10 | cli 11 | 12 | ; disable paging 13 | mov eax, cr0 14 | and eax, 0x7FFFFFFF 15 | mov cr0, eax 16 | 17 | ; enable FXSAVE/FXRSTOR, Page Global, Page Address Extension, and Page Size Extension 18 | mov eax, cr4 19 | or eax, 1 << 9 | 1 << 7 | 1 << 5 | 1 << 4 20 | mov cr4, eax 21 | 22 | ; load long mode GDT 23 | lgdt [gdtr] 24 | 25 | ; enable long mode 26 | mov ecx, 0xC0000080 ; Read from the EFER MSR. 27 | rdmsr 28 | or eax, 1 << 11 | 1 << 8 ; Set the Long-Mode-Enable and NXE bit. 29 | wrmsr 30 | 31 | ; set page table 32 | mov eax, [.page_table] 33 | mov cr3, eax 34 | 35 | ; enabling paging and protection simultaneously 36 | mov eax, cr0 37 | or eax, 1 << 31 | 1 << 16 | 1 ;Bit 31: Paging, Bit 16: write protect kernel, Bit 0: Protected Mode 38 | mov cr0, eax 39 | 40 | ; far jump to enable Long Mode and load CS with 64 bit segment 41 | jmp gdt.lm64_code:.inner 42 | 43 | USE64 44 | 45 | .inner: 46 | ; load all the other segments with 64 bit data segments 47 | mov rax, gdt.lm64_data 48 | mov ds, rax 49 | mov es, rax 50 | mov fs, rax 51 | mov gs, rax 52 | mov ss, rax 53 | 54 | ; jump to specified function 55 | mov rax, [.func] 56 | jmp rax 57 | -------------------------------------------------------------------------------- /asm/x86-unknown-none/print.asm: -------------------------------------------------------------------------------- 1 | SECTION .text 2 | USE16 3 | 4 | ; provide function for printing in x86 real mode 5 | 6 | ; print a string and a newline 7 | ; CLOBBER 8 | ; ax 9 | print_line: 10 | mov al, 13 11 | call print_char 12 | mov al, 10 13 | jmp print_char 14 | 15 | ; print a string 16 | ; IN 17 | ; si: points at zero-terminated String 18 | ; CLOBBER 19 | ; si, ax 20 | print: 21 | pushf 22 | cld 23 | .loop: 24 | lodsb 25 | test al, al 26 | jz .done 27 | call print_char 28 | jmp .loop 29 | .done: 30 | popf 31 | ret 32 | 33 | ; print a character 34 | ; IN 35 | ; al: character to print 36 | print_char: 37 | pusha 38 | mov bx, 7 39 | mov ah, 0x0e 40 | int 0x10 41 | popa 42 | ret 43 | 44 | ; print a number in hex 45 | ; IN 46 | ; bx: the number 47 | ; CLOBBER 48 | ; al, cx 49 | print_hex: 50 | mov cx, 4 51 | .lp: 52 | mov al, bh 53 | shr al, 4 54 | 55 | cmp al, 0xA 56 | jb .below_0xA 57 | 58 | add al, 'A' - 0xA - '0' 59 | .below_0xA: 60 | add al, '0' 61 | 62 | call print_char 63 | 64 | shl bx, 4 65 | loop .lp 66 | 67 | ret 68 | -------------------------------------------------------------------------------- /asm/x86-unknown-none/protected_mode.asm: -------------------------------------------------------------------------------- 1 | SECTION .text 2 | USE16 3 | 4 | protected_mode: 5 | 6 | .func: dd 0 7 | 8 | .entry: 9 | ; disable interrupts 10 | cli 11 | 12 | ; load protected mode GDT 13 | lgdt [gdtr] 14 | 15 | ; set protected mode bit of cr0 16 | mov eax, cr0 17 | or eax, 1 18 | mov cr0, eax 19 | 20 | ; far jump to load CS with 32 bit segment 21 | jmp gdt.pm32_code:.inner 22 | 23 | USE32 24 | 25 | .inner: 26 | ; load all the other segments with 32 bit data segments 27 | mov eax, gdt.pm32_data 28 | mov ds, eax 29 | mov es, eax 30 | mov fs, eax 31 | mov gs, eax 32 | mov ss, eax 33 | 34 | ; jump to specified function 35 | mov eax, [.func] 36 | jmp eax 37 | -------------------------------------------------------------------------------- /asm/x86-unknown-none/stage1.asm: -------------------------------------------------------------------------------- 1 | ORG 0x7C00 2 | SECTION .text 3 | USE16 4 | 5 | stage1: ; dl comes with disk 6 | ; initialize segment registers 7 | xor ax, ax 8 | mov ds, ax 9 | mov es, ax 10 | mov ss, ax 11 | 12 | ; initialize stack 13 | mov sp, 0x7C00 14 | 15 | ; initialize CS 16 | push ax 17 | push word .set_cs 18 | retf 19 | 20 | .set_cs: 21 | 22 | ; save disk number 23 | mov [disk], dl 24 | 25 | mov si, stage_msg 26 | call print 27 | mov al, '1' 28 | call print_char 29 | call print_line 30 | 31 | ; read CHS gemotry 32 | ; CL (bits 0-5) = maximum sector number 33 | ; CL (bits 6-7) = high bits of max cylinder number 34 | ; CH = low bits of maximum cylinder number 35 | ; DH = maximum head number 36 | mov ah, 0x08 37 | mov dl, [disk] 38 | xor di, di 39 | int 0x13 40 | jc error ; carry flag set on error 41 | mov bl, ch 42 | mov bh, cl 43 | shr bh, 6 44 | mov [chs.c], bx 45 | shr dx, 8 46 | inc dx ; returns heads - 1 47 | mov [chs.h], dx 48 | and cl, 0x3f 49 | mov [chs.s], cl 50 | 51 | mov eax, (stage2 - stage1) / 512 52 | mov bx, stage2 53 | mov cx, (stage3.end - stage2) / 512 54 | mov dx, 0 55 | call load 56 | 57 | mov si, stage_msg 58 | call print 59 | mov al, '2' 60 | call print_char 61 | call print_line 62 | 63 | jmp stage2.entry 64 | 65 | ; load some sectors from disk to a buffer in memory 66 | ; buffer has to be below 1MiB 67 | ; IN 68 | ; ax: start sector 69 | ; bx: offset of buffer 70 | ; cx: number of sectors (512 Bytes each) 71 | ; dx: segment of buffer 72 | ; CLOBBER 73 | ; ax, bx, cx, dx, si 74 | ; TODO rewrite to (eventually) move larger parts at once 75 | ; if that is done increase buffer_size_sectors in startup-common to that (max 0x80000 - startup_end) 76 | load: 77 | cmp cx, 127 78 | jbe .good_size 79 | 80 | pusha 81 | mov cx, 127 82 | call load 83 | popa 84 | add eax, 127 85 | add dx, 127 * 512 / 16 86 | sub cx, 127 87 | 88 | jmp load 89 | .good_size: 90 | mov [DAPACK.addr], eax 91 | mov [DAPACK.buf], bx 92 | mov [DAPACK.count], cx 93 | mov [DAPACK.seg], dx 94 | 95 | call print_dapack 96 | 97 | cmp byte [chs.s], 0 98 | jne .chs 99 | ;INT 0x13 extended read does not work on CDROM! 100 | mov dl, [disk] 101 | mov si, DAPACK 102 | mov ah, 0x42 103 | int 0x13 104 | jc error ; carry flag set on error 105 | ret 106 | 107 | .chs: 108 | ; calculate CHS 109 | xor edx, edx 110 | mov eax, [DAPACK.addr] 111 | div dword [chs.s] ; divide by sectors 112 | mov ecx, edx ; move sector remainder to ecx 113 | xor edx, edx 114 | div dword [chs.h] ; divide by heads 115 | ; eax has cylinders, edx has heads, ecx has sectors 116 | 117 | ; Sector cannot be greater than 63 118 | inc ecx ; Sector is base 1 119 | cmp ecx, 63 120 | ja error_chs 121 | 122 | ; Head cannot be greater than 255 123 | cmp edx, 255 124 | ja error_chs 125 | 126 | ; Cylinder cannot be greater than 1023 127 | cmp eax, 1023 128 | ja error_chs 129 | 130 | ; Move CHS values to parameters 131 | mov ch, al 132 | shl ah, 6 133 | and cl, 0x3f 134 | or cl, ah 135 | shl dx, 8 136 | 137 | ; read from disk using CHS 138 | mov al, [DAPACK.count] 139 | mov ah, 0x02 ; disk read (CHS) 140 | mov bx, [DAPACK.buf] 141 | mov dl, [disk] 142 | push es ; save ES 143 | mov es, [DAPACK.seg] 144 | int 0x13 145 | pop es ; restore EC 146 | jc error ; carry flag set on error 147 | ret 148 | 149 | print_dapack: 150 | mov bx, [DAPACK.addr + 2] 151 | call print_hex 152 | 153 | mov bx, [DAPACK.addr] 154 | call print_hex 155 | 156 | mov al, '#' 157 | call print_char 158 | 159 | mov bx, [DAPACK.count] 160 | call print_hex 161 | 162 | mov al, ' ' 163 | call print_char 164 | 165 | mov bx, [DAPACK.seg] 166 | call print_hex 167 | 168 | mov al, ':' 169 | call print_char 170 | 171 | mov bx, [DAPACK.buf] 172 | call print_hex 173 | 174 | call print_line 175 | 176 | ret 177 | 178 | error_chs: 179 | mov ah, 0 180 | 181 | error: 182 | call print_line 183 | 184 | mov bh, 0 185 | mov bl, ah 186 | call print_hex 187 | 188 | mov al, ' ' 189 | call print_char 190 | 191 | mov si, error_msg 192 | call print 193 | call print_line 194 | .halt: 195 | cli 196 | hlt 197 | jmp .halt 198 | 199 | %include "print.asm" 200 | 201 | stage_msg: db "Stage ",0 202 | error_msg: db "ERROR",0 203 | 204 | disk: db 0 205 | 206 | chs: 207 | .c: dd 0 208 | .h: dd 0 209 | .s: dd 0 210 | 211 | DAPACK: 212 | db 0x10 213 | db 0 214 | .count: dw 0 ; int 13 resets this to # of blocks actually read/written 215 | .buf: dw 0 ; memory buffer destination address (0:7c00) 216 | .seg: dw 0 ; in memory page zero 217 | .addr: dq 0 ; put the lba to read in this spot 218 | 219 | times 446-($-$$) db 0 220 | partitions: times 4 * 16 db 0 221 | db 0x55 222 | db 0xaa 223 | -------------------------------------------------------------------------------- /asm/x86-unknown-none/stage2.asm: -------------------------------------------------------------------------------- 1 | SECTION .text 2 | USE16 3 | 4 | stage2.entry: 5 | ; check for required features 6 | call cpuid_check 7 | 8 | ; enable A20-Line via IO-Port 92, might not work on all motherboards 9 | in al, 0x92 10 | or al, 2 11 | out 0x92, al 12 | 13 | mov dword [protected_mode.func], stage3.entry 14 | jmp protected_mode.entry 15 | 16 | %include "cpuid.asm" 17 | %include "gdt.asm" 18 | %include "long_mode.asm" 19 | %include "protected_mode.asm" 20 | %include "thunk.asm" 21 | 22 | USE32 23 | 24 | stage3.entry: 25 | ; stage3 stack at 448 KiB (512KiB minus 64KiB disk buffer) 26 | mov esp, 0x70000 27 | 28 | ; push arguments 29 | mov eax, thunk.int16 30 | push eax 31 | mov eax, thunk.int15 32 | push eax 33 | mov eax, thunk.int13 34 | push eax 35 | mov eax, thunk.int10 36 | push eax 37 | xor eax, eax 38 | mov al, [disk] 39 | push eax 40 | mov eax, kernel.entry 41 | push eax 42 | mov eax, [stage3 + 0x18] 43 | call eax 44 | .halt: 45 | cli 46 | hlt 47 | jmp .halt 48 | 49 | kernel: 50 | .stack: dq 0 51 | .func: dq 0 52 | .args: dq 0 53 | 54 | .entry: 55 | ; page_table: usize 56 | mov eax, [esp + 4] 57 | mov [long_mode.page_table], eax 58 | 59 | ; stack: u64 60 | mov eax, [esp + 8] 61 | mov [.stack], eax 62 | mov eax, [esp + 12] 63 | mov [.stack + 4], eax 64 | 65 | ; func: u64 66 | mov eax, [esp + 16] 67 | mov [.func], eax 68 | mov eax, [esp + 20] 69 | mov [.func + 4], eax 70 | 71 | ; args: *const KernelArgs 72 | mov eax, [esp + 24] 73 | mov [.args], eax 74 | 75 | ; long_mode: usize 76 | mov eax, [esp + 28] 77 | test eax, eax 78 | jz .inner32 79 | 80 | mov eax, .inner64 81 | mov [long_mode.func], eax 82 | jmp long_mode.entry 83 | 84 | .inner32: 85 | ; disable paging 86 | mov eax, cr0 87 | and eax, 0x7FFFFFFF 88 | mov cr0, eax 89 | 90 | ;TODO: PAE (1 << 5) 91 | ; enable FXSAVE/FXRSTOR, Page Global, and Page Size Extension 92 | mov eax, cr4 93 | or eax, 1 << 9 | 1 << 7 | 1 << 4 94 | mov cr4, eax 95 | 96 | ; set page table 97 | mov eax, [long_mode.page_table] 98 | mov cr3, eax 99 | 100 | ; enabling paging and protection simultaneously 101 | mov eax, cr0 102 | ; Bit 31: Paging, Bit 16: write protect kernel, Bit 0: Protected Mode 103 | or eax, 1 << 31 | 1 << 16 | 1 104 | mov cr0, eax 105 | 106 | ; enable FPU 107 | ;TODO: move to Rust 108 | mov eax, cr0 109 | and al, 11110011b ; Clear task switched (3) and emulation (2) 110 | or al, 00100010b ; Set numeric error (5) monitor co-processor (1) 111 | mov cr0, eax 112 | fninit 113 | 114 | mov esp, [.stack] 115 | mov eax, [.args] 116 | push eax 117 | mov eax, [.func] 118 | call eax 119 | .halt32: 120 | cli 121 | hlt 122 | jmp .halt32 123 | 124 | USE64 125 | 126 | .inner64: 127 | mov rsp, [.stack] 128 | mov rax, [.func] 129 | mov rdi, [.args] 130 | call rax 131 | .halt64: 132 | cli 133 | hlt 134 | jmp .halt64 135 | -------------------------------------------------------------------------------- /asm/x86-unknown-none/thunk.asm: -------------------------------------------------------------------------------- 1 | SECTION .text 2 | USE32 3 | 4 | thunk: 5 | .int10: 6 | mov dword [.func], .int10_real 7 | jmp .enter 8 | 9 | .int13: 10 | mov dword [.func], .int13_real 11 | jmp .enter 12 | 13 | .int15: 14 | mov dword [.func], .int15_real 15 | jmp .enter 16 | 17 | .int16: 18 | mov dword [.func], .int16_real 19 | jmp .enter 20 | 21 | .func: dd 0 22 | .esp: dd 0 23 | .cr0: dd 0 24 | 25 | .enter: 26 | ; save flags 27 | pushfd 28 | 29 | ; save registers 30 | pushad 31 | 32 | ; save esp 33 | mov [.esp], esp 34 | 35 | ; load gdt 36 | lgdt [gdtr] 37 | 38 | ; far jump to protected mode 16-bit 39 | jmp gdt.pm16_code:.pm16 40 | 41 | .exit: 42 | ; set segment selectors to 32-bit protected mode 43 | mov eax, gdt.pm32_data 44 | mov ds, eax 45 | mov es, eax 46 | mov fs, eax 47 | mov gs, eax 48 | mov ss, eax 49 | 50 | ; restore esp 51 | mov esp, [.esp] 52 | 53 | ; restore registers 54 | popad 55 | 56 | ; restore flags 57 | popfd 58 | 59 | ; return 60 | ret 61 | 62 | USE16 63 | 64 | .int10_real: 65 | int 0x10 66 | ret 67 | 68 | .int13_real: 69 | int 0x13 70 | ret 71 | 72 | .int15_real: 73 | int 0x15 74 | ret 75 | 76 | .int16_real: 77 | int 0x16 78 | ret 79 | 80 | .pm16: 81 | ; set segment selectors to protected mode 16-bit 82 | mov eax, gdt.pm16_data 83 | mov ds, eax 84 | mov es, eax 85 | mov fs, eax 86 | mov gs, eax 87 | mov ss, eax 88 | 89 | ; save cr0 90 | mov eax, cr0 91 | mov [.cr0], eax 92 | 93 | ; disable paging and protected mode 94 | and eax, 0x7FFFFFFE 95 | mov cr0, eax 96 | 97 | ; far jump to real mode 98 | jmp 0:.real 99 | 100 | .real: 101 | ; set segment selectors to real mode 102 | mov eax, 0 103 | mov ds, eax 104 | mov es, eax 105 | mov fs, eax 106 | mov gs, eax 107 | mov ss, eax 108 | 109 | ; set stack 110 | mov esp, 0x7C00 - 64 111 | 112 | ; load registers and ES 113 | pop es 114 | pop edi 115 | pop esi 116 | pop ebp 117 | pop ebx 118 | pop edx 119 | pop ecx 120 | pop eax 121 | 122 | ; enable interrupts 123 | sti 124 | 125 | ; call real mode function 126 | call [.func] 127 | 128 | ; disable interrupts 129 | cli 130 | 131 | ; save registers and ES 132 | push eax 133 | push ecx 134 | push edx 135 | push ebx 136 | push ebp 137 | push esi 138 | push edi 139 | push es 140 | 141 | ; load gdt (BIOS sometimes overwrites this) 142 | lgdt [gdtr] 143 | 144 | ; restore cr0, will enable protected mode 145 | mov eax, [.cr0] 146 | mov cr0, eax 147 | 148 | ; far jump to protected mode 32-bit 149 | jmp gdt.pm32_code:.exit 150 | -------------------------------------------------------------------------------- /linkers/riscv64-unknown-uefi.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT("elf64-littleriscv", "elf64-littleriscv", "elf64-littleriscv") 2 | OUTPUT_ARCH(riscv) 3 | ENTRY(coff_start) 4 | SECTIONS 5 | { 6 | PROVIDE(ImageBase = .); 7 | . = SEGMENT_START("text-segment", 0) + SIZEOF_HEADERS; 8 | .note.gnu.build-id : { *(.note.gnu.build-id) } 9 | .hash : { *(.hash) *(.gnu.hash) } 10 | 11 | . = ALIGN(4096); 12 | .text : 13 | { 14 | PROVIDE(_text = .); 15 | *(.text.unlikely .text.*_unlikely .text.unlikely.*) 16 | *(.text.exit .text.exit.*) 17 | *(.text.startup .text.startup.*) 18 | *(.text.hot .text.hot.*) 19 | *(SORT(.text.sorted.*)) 20 | *(.text .stub .text.* .gnu.linkonce.t.*) 21 | /* .gnu.warning sections are handled specially by elf.em. */ 22 | *(.gnu.warning) 23 | } 24 | PROVIDE (__etext = .); 25 | PROVIDE (_etext = .); 26 | PROVIDE (etext = .); 27 | . = ALIGN(4096); 28 | 29 | .rdata : 30 | { 31 | *(.rodata .rodata.* .gnu.linkonce.r.*) 32 | *(.rodata1) 33 | KEEP (*(.eh_frame)) 34 | *(.eh_frame.*) 35 | *(.dynamic) 36 | } 37 | . = ALIGN(4096); 38 | .data : 39 | { 40 | *(.got) *(.igot) 41 | *(.got.plt) *(.igot.plt) 42 | *(.data .data.* .gnu.linkonce.d.*) 43 | *(.data1) 44 | *(.srodata.cst16) *(.srodata.cst8) *(.srodata.cst4) *(.srodata.cst2) *(.srodata .srodata.*) 45 | *(.sdata2 .sdata2.* .gnu.linkonce.s2.*) 46 | *(.sdata .sdata.* .gnu.linkonce.s.*) 47 | PROVIDE (_edata = .); PROVIDE (edata = .); 48 | . = ALIGN(4096); 49 | PROVIDE (__bss_start = .); 50 | *(.sbss2 .sbss2.* .gnu.linkonce.sb2.*) 51 | *(.dynsbss) 52 | *(.sbss .sbss.* .gnu.linkonce.sb.*) 53 | *(.scommon) 54 | *(.dynbss) 55 | *(.bss .bss.* .gnu.linkonce.b.*) 56 | *(COMMON) 57 | . = ALIGN(4096); 58 | } 59 | .reloc : 60 | { 61 | KEEP(*(.reloc*)) 62 | } 63 | .rela : 64 | { 65 | *(.rela.*) 66 | } 67 | .data.rel.ro : 68 | { 69 | *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) 70 | *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*) 71 | } 72 | . = ALIGN(4096); 73 | 74 | .dynsym : { *(.dynsym) } 75 | .dynstr : { *(.dynstr) } 76 | 77 | /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) } 78 | } 79 | -------------------------------------------------------------------------------- /linkers/x86-unknown-none.ld: -------------------------------------------------------------------------------- 1 | ENTRY(start) 2 | OUTPUT_FORMAT(elf32-i386) 3 | 4 | SECTIONS { 5 | /* The start address must match bootloader.asm */ 6 | . = 0x13000; 7 | 8 | . += SIZEOF_HEADERS; 9 | . = ALIGN(4096); 10 | 11 | .text : { 12 | __text_start = .; 13 | *(.text*) 14 | . = ALIGN(4096); 15 | __text_end = .; 16 | } 17 | 18 | .rodata : { 19 | __rodata_start = .; 20 | *(.rodata*) 21 | . = ALIGN(4096); 22 | __rodata_end = .; 23 | } 24 | 25 | .data : { 26 | __data_start = .; 27 | *(.data*) 28 | . = ALIGN(4096); 29 | __data_end = .; 30 | __bss_start = .; 31 | *(.bss*) 32 | . = ALIGN(4096); 33 | __bss_end = .; 34 | } 35 | 36 | .tdata : { 37 | __tdata_start = .; 38 | *(.tdata*) 39 | . = ALIGN(4096); 40 | __tdata_end = .; 41 | __tbss_start = .; 42 | *(.tbss*) 43 | . += 8; 44 | . = ALIGN(4096); 45 | __tbss_end = .; 46 | } 47 | 48 | __end = .; 49 | 50 | /DISCARD/ : { 51 | *(.comment*) 52 | *(.eh_frame*) 53 | *(.gcc_except_table*) 54 | *(.note*) 55 | *(.rel.eh_frame*) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /mk/aarch64-unknown-uefi.mk: -------------------------------------------------------------------------------- 1 | export PARTED?=parted 2 | export QEMU?=qemu-system-aarch64 3 | 4 | all: $(BUILD)/bootloader.efi 5 | 6 | $(BUILD)/bootloader.efi: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f) 7 | mkdir -p "$(BUILD)" 8 | env RUSTFLAGS="-C soft-float" \ 9 | cargo rustc \ 10 | --manifest-path="$<" \ 11 | -Z build-std=core,alloc \ 12 | -Z build-std-features=compiler-builtins-mem \ 13 | --target $(TARGET) \ 14 | --bin bootloader \ 15 | --release \ 16 | -- \ 17 | --emit link="$@" 18 | 19 | $(BUILD)/bootloader-live.efi: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f) 20 | mkdir -p "$(BUILD)" 21 | env RUSTFLAGS="-C soft-float" \ 22 | cargo rustc \ 23 | --manifest-path="$<" \ 24 | -Z build-std=core,alloc \ 25 | -Z build-std-features=compiler-builtins-mem \ 26 | --target $(TARGET) \ 27 | --bin bootloader \ 28 | --release \ 29 | --features live \ 30 | -- \ 31 | --emit link="$@" 32 | 33 | $(BUILD)/esp.bin: $(BUILD)/bootloader.efi 34 | rm -f "$@.partial" 35 | fallocate -l 64MiB "$@.partial" 36 | mkfs.vfat -F 32 "$@.partial" 37 | mmd -i "$@.partial" efi 38 | mmd -i "$@.partial" efi/boot 39 | mcopy -i "$@.partial" "$<" ::efi/boot/bootaa64.efi 40 | mv "$@.partial" "$@" 41 | 42 | $(BUILD)/harddrive.bin: $(BUILD)/esp.bin $(BUILD)/filesystem.bin 43 | rm -f "$@.partial" 44 | fallocate -l 320MiB "$@.partial" 45 | $(PARTED) -s -a minimal "$@.partial" mklabel gpt 46 | $(PARTED) -s -a minimal "$@.partial" mkpart ESP FAT32 1MiB 65MiB 47 | $(PARTED) -s -a minimal "$@.partial" mkpart REDOXFS 65MiB 100% 48 | $(PARTED) -s -a minimal "$@.partial" toggle 1 boot 49 | dd if="$(BUILD)/esp.bin" of="$@.partial" bs=1MiB seek=1 conv=notrunc 50 | dd if="$(BUILD)/filesystem.bin" of="$@.partial" bs=1MiB seek=65 conv=notrunc 51 | mv "$@.partial" "$@" 52 | 53 | $(BUILD)/firmware.rom: /usr/share/AAVMF/AAVMF_CODE.fd 54 | cp "$<" "$@" 55 | 56 | qemu: $(BUILD)/harddrive.bin $(BUILD)/firmware.rom 57 | $(QEMU) \ 58 | -d cpu_reset \ 59 | -no-reboot \ 60 | -smp 4 -m 2048 \ 61 | -chardev stdio,id=debug,signal=off,mux=on \ 62 | -serial chardev:debug \ 63 | -mon chardev=debug \ 64 | -device virtio-gpu-pci \ 65 | -machine virt \ 66 | -net none \ 67 | -cpu max \ 68 | -bios "$(BUILD)/firmware.rom" \ 69 | -drive file="$<",format=raw 70 | -------------------------------------------------------------------------------- /mk/riscv64gc-unknown-uefi.mk: -------------------------------------------------------------------------------- 1 | LD=riscv64-unknown-redox-ld 2 | OBJCOPY=riscv64-unknown-redox-objcopy 3 | SCRIPT=$(SOURCE)/linkers/riscv64-unknown-uefi.ld 4 | PARTED?=parted 5 | QEMU?=qemu-system-riscv64 6 | 7 | all: $(BUILD)/bootloader.efi 8 | 9 | $(BUILD)/%.efi: $(BUILD)/%.efi.elf $(BUILD)/%.efi.sym 10 | $(OBJCOPY) -j .text -j .data -j .rdata -j .rela -j .reloc --target pei-riscv64-little \ 11 | --file-alignment 512 --section-alignment 4096 --subsystem 10 "$<" "$@" 12 | 13 | .PRECIOUS: $(BUILD)/%.efi.sym 14 | $(BUILD)/%.efi.sym: $(BUILD)/%.efi.elf 15 | $(OBJCOPY) --only-keep-debug "$<" "$@" 16 | 17 | $(BUILD)/%.efi.elf: $(BUILD)/%.a $(SCRIPT) 18 | $(LD) --gc-sections -z max-page-size=0x1000 --warn-common --no-undefined -z nocombreloc -shared \ 19 | --fatal-warnings -Bsymbolic --entry coff_start -T "$(SCRIPT)" -o "$@" "$<" 20 | 21 | $(BUILD)/bootloader.a: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f) 22 | mkdir -p "$(BUILD)" 23 | env RUSTFLAGS="-C soft-float" \ 24 | cargo rustc \ 25 | --manifest-path="$<" \ 26 | -Z build-std=core,alloc \ 27 | -Z build-std-features=compiler-builtins-mem \ 28 | --target $(TARGET) \ 29 | --lib \ 30 | --release \ 31 | -- \ 32 | --emit link=$@ 33 | 34 | $(BUILD)/bootloader-live.a: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f) 35 | mkdir -p "$(BUILD)" 36 | env RUSTFLAGS="-C soft-float" \ 37 | cargo rustc \ 38 | --manifest-path="$<" \ 39 | -Z build-std=core,alloc \ 40 | -Z build-std-features=compiler-builtins-mem \ 41 | --target $(TARGET) \ 42 | --lib \ 43 | --release \ 44 | --features live \ 45 | -- \ 46 | --emit link=$@ 47 | 48 | 49 | $(BUILD)/esp.bin: $(BUILD)/bootloader.efi 50 | rm -f $@.partial 51 | fallocate -l 64MiB $@.partial 52 | mkfs.vfat -F 32 $@.partial 53 | mmd -i $@.partial EFI 54 | mmd -i $@.partial EFI/BOOT 55 | mcopy -i $@.partial $< ::EFI/BOOT/BOOTRISCV64.EFI 56 | mv $@.partial $@ 57 | 58 | $(BUILD)/harddrive.bin: $(BUILD)/esp.bin $(BUILD)/filesystem.bin 59 | rm -f $@.partial 60 | fallocate -l 320MiB $@.partial 61 | $(PARTED) -s -a minimal $@.partial mklabel gpt 62 | $(PARTED) -s -a minimal $@.partial mkpart ESP FAT32 1MiB 65MiB 63 | $(PARTED) -s -a minimal $@.partial mkpart REDOXFS 65MiB 100% 64 | $(PARTED) -s -a minimal $@.partial toggle 1 boot 65 | dd if=$(BUILD)/esp.bin of=$@.partial bs=1MiB seek=1 conv=notrunc 66 | dd if=$(BUILD)/filesystem.bin of=$@.partial bs=1MiB seek=65 conv=notrunc 67 | mv $@.partial $@ 68 | 69 | $(BUILD)/fw_vars.img: /usr/share/qemu-efi-riscv64/RISCV_VIRT_VARS.fd 70 | cp "$<" "$@" 71 | 72 | $(BUILD)/firmware.rom: /usr/share/qemu-efi-riscv64/RISCV_VIRT_CODE.fd 73 | cp "$<" "$@" 74 | 75 | qemu-acpi: $(BUILD)/harddrive.bin $(BUILD)/firmware.rom $(BUILD)/fw_vars.img 76 | $(QEMU) \ 77 | -M virt \ 78 | -d cpu_reset \ 79 | -no-reboot \ 80 | -smp 4 -m 2048 \ 81 | -chardev stdio,id=debug,signal=off,mux=on \ 82 | -serial chardev:debug \ 83 | -mon chardev=debug \ 84 | -device virtio-gpu-pci \ 85 | -machine virt \ 86 | -net none \ 87 | -cpu max \ 88 | -drive if=pflash,format=raw,unit=0,file=$(BUILD)/firmware.rom,readonly=on \ 89 | -drive if=pflash,format=raw,unit=1,file=$(BUILD)/fw_vars.img \ 90 | -drive file=$(BUILD)/harddrive.bin,format=raw,if=virtio 91 | 92 | 93 | qemu-dtb: $(BUILD)/harddrive.bin $(BUILD)/firmware.rom $(BUILD)/fw_vars.img 94 | $(QEMU) \ 95 | -M virt,acpi=off \ 96 | -d cpu_reset \ 97 | -no-reboot \ 98 | -smp 4 -m 2048 \ 99 | -chardev stdio,id=debug,signal=off,mux=on \ 100 | -serial chardev:debug \ 101 | -mon chardev=debug \ 102 | -device virtio-gpu-pci \ 103 | -machine virt \ 104 | -net none \ 105 | -cpu max \ 106 | -drive if=pflash,format=raw,unit=0,file=$(BUILD)/firmware.rom,readonly=on \ 107 | -drive if=pflash,format=raw,unit=1,file=$(BUILD)/fw_vars.img \ 108 | -drive file=$(BUILD)/harddrive.bin,format=raw,if=virtio -s 109 | -------------------------------------------------------------------------------- /mk/x86-unknown-none.mk: -------------------------------------------------------------------------------- 1 | export LD?=ld 2 | export OBJCOPY?=objcopy 3 | export PARTED?=parted 4 | export QEMU?=qemu-system-x86_64 5 | 6 | all: $(BUILD)/bootloader.bin 7 | 8 | $(BUILD)/libbootloader.a: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f) 9 | mkdir -p "$(BUILD)" 10 | env RUSTFLAGS="-C soft-float" \ 11 | cargo rustc \ 12 | --manifest-path="$<" \ 13 | -Z build-std=core,alloc \ 14 | -Z build-std-features=compiler-builtins-mem \ 15 | --target "$(TARGET)" \ 16 | --lib \ 17 | --release \ 18 | -- \ 19 | --emit link="$@" 20 | 21 | $(BUILD)/libbootloader-live.a: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f) 22 | mkdir -p "$(BUILD)" 23 | env RUSTFLAGS="-C soft-float" \ 24 | cargo rustc \ 25 | --manifest-path="$<" \ 26 | -Z build-std=core,alloc \ 27 | -Z build-std-features=compiler-builtins-mem \ 28 | --target "$(TARGET)" \ 29 | --lib \ 30 | --release \ 31 | --features live \ 32 | -- \ 33 | --emit link="$@" 34 | 35 | $(BUILD)/%.elf: $(BUILD)/lib%.a $(SOURCE)/linkers/$(TARGET).ld 36 | $(LD) -m elf_i386 --gc-sections -z max-page-size=0x1000 -T "$(SOURCE)/linkers/$(TARGET).ld" -o "$@" "$<" 37 | $(OBJCOPY) --only-keep-debug "$@" "$@.sym" 38 | $(OBJCOPY) --strip-debug "$@" 39 | 40 | $(BUILD)/%.bin: $(BUILD)/%.elf $(shell find $(SOURCE)/asm/$(TARGET) -type f) 41 | nasm -f bin -o "$@" -l "$@.lst" -D STAGE3="$<" -i"$(SOURCE)/asm/$(TARGET)/" "$(SOURCE)/asm/$(TARGET)/bootloader.asm" 42 | 43 | $(BUILD)/harddrive.bin: $(BUILD)/bootloader.bin $(BUILD)/filesystem.bin 44 | rm -f "$@.partial" 45 | fallocate -l 256MiB "$@.partial" 46 | $(PARTED) -s -a minimal "$@.partial" mklabel msdos 47 | $(PARTED) -s -a minimal "$@.partial" mkpart primary 2MiB 100% 48 | dd if="$<" of="$@.partial" bs=1 count=446 conv=notrunc 49 | dd if="$<" of="$@.partial" bs=512 skip=1 seek=1 conv=notrunc 50 | dd if="$(BUILD)/filesystem.bin" of="$@.partial" bs=1MiB seek=2 conv=notrunc 51 | mv "$@.partial" "$@" 52 | 53 | qemu: $(BUILD)/harddrive.bin 54 | $(QEMU) \ 55 | -d cpu_reset \ 56 | -no-reboot \ 57 | -smp 4 -m 2048 \ 58 | -chardev stdio,id=debug,signal=off,mux=on \ 59 | -serial chardev:debug \ 60 | -mon chardev=debug \ 61 | -machine q35 \ 62 | -net none \ 63 | -enable-kvm \ 64 | -cpu host \ 65 | -drive file="$<",format=raw 66 | -------------------------------------------------------------------------------- /mk/x86_64-unknown-uefi.mk: -------------------------------------------------------------------------------- 1 | export PARTED?=parted 2 | export QEMU?=qemu-system-x86_64 3 | 4 | all: $(BUILD)/bootloader.efi 5 | 6 | $(BUILD)/bootloader.efi: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f) 7 | mkdir -p "$(BUILD)" 8 | env RUSTFLAGS="-C soft-float" \ 9 | cargo rustc \ 10 | --manifest-path="$<" \ 11 | -Z build-std=core,alloc \ 12 | -Z build-std-features=compiler-builtins-mem \ 13 | --target $(TARGET) \ 14 | --bin bootloader \ 15 | --release \ 16 | -- \ 17 | --emit link="$@" 18 | 19 | $(BUILD)/bootloader-live.efi: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f) 20 | mkdir -p $(BUILD) 21 | cd "$(SOURCE)" 22 | env RUSTFLAGS="-C soft-float" \ 23 | cargo rustc \ 24 | --manifest-path="$<" \ 25 | -Z build-std=core,alloc \ 26 | -Z build-std-features=compiler-builtins-mem \ 27 | --target $(TARGET) \ 28 | --bin bootloader \ 29 | --release \ 30 | --features live \ 31 | -- \ 32 | --emit link="$@" 33 | 34 | $(BUILD)/esp.bin: $(BUILD)/bootloader.efi 35 | rm -f "$@.partial" 36 | fallocate -l 1MiB $@.partial 37 | mkfs.vfat "$@.partial" 38 | mmd -i "$@.partial" efi 39 | mmd -i "$@.partial" efi/boot 40 | mcopy -i "$@.partial" "$<" ::efi/boot/bootx64.efi 41 | mv "$@.partial" "$@" 42 | 43 | $(BUILD)/harddrive.bin: $(BUILD)/esp.bin $(BUILD)/filesystem.bin 44 | rm -f "$@.partial" 45 | fallocate -l 320MiB "$@.partial" 46 | $(PARTED) -s -a minimal "$@.partial" mklabel gpt 47 | $(PARTED) -s -a minimal "$@.partial" mkpart ESP FAT32 1MiB 2MiB 48 | $(PARTED) -s -a minimal "$@.partial" mkpart REDOXFS 2MiB 100% 49 | $(PARTED) -s -a minimal "$@.partial" toggle 1 boot 50 | dd if="$(BUILD)/esp.bin" of="$@.partial" bs=1MiB seek=1 conv=notrunc 51 | dd if="$(BUILD)/filesystem.bin" of="$@.partial" bs=1MiB seek=2 conv=notrunc 52 | mv "$@.partial" "$@" 53 | 54 | $(BUILD)/firmware.rom: /usr/share/OVMF/OVMF_CODE.fd 55 | cp "$<" "$@" 56 | 57 | qemu: $(BUILD)/harddrive.bin $(BUILD)/firmware.rom 58 | $(QEMU) \ 59 | -d cpu_reset \ 60 | -no-reboot \ 61 | -smp 4 -m 2048 \ 62 | -chardev stdio,id=debug,signal=off,mux=on \ 63 | -serial chardev:debug \ 64 | -mon chardev=debug \ 65 | -machine q35 \ 66 | -net none \ 67 | -enable-kvm \ 68 | -cpu host \ 69 | -bios "$(BUILD)/firmware.rom" \ 70 | -drive file="$<",format=raw 71 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly-2024-05-11" 3 | components = ["rust-src"] 4 | -------------------------------------------------------------------------------- /src/arch/aarch64.rs: -------------------------------------------------------------------------------- 1 | use crate::area_add; 2 | use crate::os::{dtb::is_in_dev_mem_region, Os, OsMemoryEntry, OsMemoryKind}; 3 | use core::slice; 4 | 5 | pub(crate) const PF_PRESENT: u64 = 1 << 0; 6 | pub(crate) const PF_TABLE: u64 = 1 << 1; 7 | pub(crate) const PF_OUTER_SHAREABLE: u64 = 0b01 << 8; 8 | pub(crate) const PF_INNER_SHAREABLE: u64 = 0b11 << 8; 9 | pub(crate) const PF_ACCESS: u64 = 1 << 10; 10 | 11 | pub(crate) const PF_DEV: u64 = PF_OUTER_SHAREABLE | 2 << 2; 12 | pub(crate) const PF_RAM: u64 = PF_INNER_SHAREABLE; 13 | 14 | pub(crate) const ENTRY_ADDRESS_MASK: u64 = 0x000F_FFFF_FFFF_F000; 15 | pub(crate) const PAGE_ENTRIES: usize = 512; 16 | const PAGE_SIZE: usize = 4096; 17 | pub(crate) const PHYS_OFFSET: u64 = 0xFFFF_8000_0000_0000; 18 | 19 | unsafe fn paging_allocate(os: &impl Os) -> Option<&'static mut [u64]> { 20 | let ptr = os.alloc_zeroed_page_aligned(PAGE_SIZE); 21 | if !ptr.is_null() { 22 | area_add(OsMemoryEntry { 23 | base: ptr as u64, 24 | size: PAGE_SIZE as u64, 25 | kind: OsMemoryKind::Reclaim, 26 | }); 27 | Some(slice::from_raw_parts_mut(ptr as *mut u64, PAGE_ENTRIES)) 28 | } else { 29 | None 30 | } 31 | } 32 | 33 | pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -> Option { 34 | // Create L0 35 | let l0 = paging_allocate(os)?; 36 | 37 | { 38 | // Create L1 for identity mapping 39 | let l1 = paging_allocate(os)?; 40 | 41 | // Link first user and first kernel L0 entry to L1 42 | l0[0] = l1.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT; 43 | l0[256] = l1.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT; 44 | 45 | // Identity map 8 GiB using 1 GiB pages 46 | for l1_i in 0..8 { 47 | let addr = l1_i as u64 * 0x4000_0000; 48 | //TODO: is PF_RAM okay? 49 | l1[l1_i] = addr | PF_ACCESS | PF_DEV | PF_PRESENT; 50 | } 51 | } 52 | 53 | { 54 | // Create L1 for kernel mapping 55 | let l1 = paging_allocate(os)?; 56 | 57 | // Link second to last L0 entry to L1 58 | l0[510] = l1.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT; 59 | 60 | // Map kernel_size at kernel offset 61 | let mut kernel_mapped = 0; 62 | let mut l1_i = 0; 63 | while kernel_mapped < kernel_size && l1_i < l1.len() { 64 | let l2 = paging_allocate(os)?; 65 | l1[l1_i] = l2.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT; 66 | l1_i += 1; 67 | 68 | let mut l2_i = 0; 69 | while kernel_mapped < kernel_size && l2_i < l2.len() { 70 | let l3 = paging_allocate(os)?; 71 | l2[l2_i] = l3.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT; 72 | l2_i += 1; 73 | 74 | let mut l3_i = 0; 75 | while kernel_mapped < kernel_size && l3_i < l3.len() { 76 | let addr = kernel_phys + kernel_mapped; 77 | l3[l3_i] = addr | PF_ACCESS | PF_RAM | PF_TABLE | PF_PRESENT; 78 | l3_i += 1; 79 | kernel_mapped += PAGE_SIZE as u64; 80 | } 81 | } 82 | } 83 | assert!(kernel_mapped >= kernel_size); 84 | } 85 | 86 | Some(l0.as_ptr() as usize) 87 | } 88 | 89 | pub unsafe fn paging_framebuffer( 90 | os: &impl Os, 91 | page_phys: usize, 92 | framebuffer_phys: u64, 93 | framebuffer_size: u64, 94 | ) -> Option { 95 | //TODO: smarter test for framebuffer already mapped 96 | if framebuffer_phys + framebuffer_size <= 0x2_0000_0000 { 97 | return Some(framebuffer_phys + PHYS_OFFSET); 98 | } 99 | 100 | let l0_i = ((framebuffer_phys / 0x80_0000_0000) + 256) as usize; 101 | let mut l1_i = ((framebuffer_phys % 0x80_0000_0000) / 0x4000_0000) as usize; 102 | let mut l2_i = ((framebuffer_phys % 0x4000_0000) / 0x20_0000) as usize; 103 | assert_eq!(framebuffer_phys % 0x20_0000, 0); 104 | 105 | let l0 = slice::from_raw_parts_mut(page_phys as *mut u64, PAGE_ENTRIES); 106 | 107 | // Create l1 for framebuffer mapping 108 | let l1 = if l0[l0_i] == 0 { 109 | let l1 = paging_allocate(os)?; 110 | l0[l0_i] = l1.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT; 111 | l1 112 | } else { 113 | slice::from_raw_parts_mut((l0[l0_i] & ENTRY_ADDRESS_MASK) as *mut u64, PAGE_ENTRIES) 114 | }; 115 | 116 | // Map framebuffer_size at framebuffer offset 117 | let mut framebuffer_mapped = 0; 118 | while framebuffer_mapped < framebuffer_size && l1_i < l1.len() { 119 | let l2 = paging_allocate(os)?; 120 | assert_eq!(l1[l1_i], 0); 121 | l1[l1_i] = l2.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT; 122 | 123 | while framebuffer_mapped < framebuffer_size && l2_i < l2.len() { 124 | let addr = framebuffer_phys + framebuffer_mapped; 125 | assert_eq!(l2[l2_i], 0); 126 | //TODO: is PF_RAM okay? 127 | l2[l2_i] = addr | PF_ACCESS | PF_RAM | PF_PRESENT; 128 | framebuffer_mapped += 0x20_0000; 129 | l2_i += 1; 130 | } 131 | 132 | l1_i += 1; 133 | l2_i = 0; 134 | } 135 | assert!(framebuffer_mapped >= framebuffer_size); 136 | 137 | Some(framebuffer_phys + PHYS_OFFSET) 138 | } 139 | -------------------------------------------------------------------------------- /src/arch/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_arch = "aarch64")] 2 | pub use self::aarch64::*; 3 | 4 | #[cfg(target_arch = "aarch64")] 5 | mod aarch64; 6 | 7 | #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 8 | pub use self::x86::*; 9 | 10 | #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 11 | mod x86; 12 | 13 | #[cfg(target_arch = "riscv64")] 14 | pub use self::riscv64::*; 15 | 16 | #[cfg(target_arch = "riscv64")] 17 | mod riscv64; 18 | -------------------------------------------------------------------------------- /src/arch/riscv64.rs: -------------------------------------------------------------------------------- 1 | use core::slice; 2 | 3 | use crate::os::{Os, OsMemoryEntry, OsMemoryKind}; 4 | 5 | // Sv48 scheme 6 | 7 | const PAGE_SHIFT: usize = 12; 8 | const TABLE_SHIFT: usize = 9; 9 | const TABLE_MASK: usize = (1 << TABLE_SHIFT) - 1; 10 | const PAGE_ENTRIES: usize = 512; 11 | const PAGE_SIZE: usize = 4096; 12 | const PHYS_MASK: usize = (1usize << 44) - 1; 13 | pub(crate) const PHYS_OFFSET: u64 = 0xFFFF_8000_0000_0000; 14 | 15 | unsafe fn paging_allocate(os: &impl Os) -> Option<&'static mut [u64]> { 16 | let ptr = os.alloc_zeroed_page_aligned(PAGE_SIZE); 17 | if !ptr.is_null() { 18 | area_add(OsMemoryEntry { 19 | base: ptr as u64, 20 | size: PAGE_SIZE as u64, 21 | kind: OsMemoryKind::Reclaim, 22 | }); 23 | Some(slice::from_raw_parts_mut(ptr as *mut u64, PAGE_ENTRIES)) 24 | } else { 25 | None 26 | } 27 | } 28 | 29 | const VALID: u64 = 1; 30 | const RWX: u64 = 7 << 1; 31 | 32 | unsafe fn get_table(os: &impl Os, parent: &mut [u64], index: usize) -> Option<&'static mut [u64]> { 33 | if parent[index] == 0 { 34 | let table = paging_allocate(os)?; 35 | parent[index] = table.as_ptr() as u64 >> 2 | VALID; 36 | Some(table) 37 | } else { 38 | Some(slice::from_raw_parts_mut( 39 | (((parent[index] >> 10) & PHYS_MASK as u64) << 12) as *mut u64, 40 | PAGE_ENTRIES, 41 | )) 42 | } 43 | } 44 | 45 | pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -> Option { 46 | // Create L0 47 | let l0 = paging_allocate(os)?; 48 | 49 | { 50 | // Create L1 for identity mapping 51 | let l1 = paging_allocate(os)?; 52 | 53 | // Map L1 into beginning of userspace and kernelspace 54 | l0[0] = (l1.as_ptr() as u64 >> 2) | VALID; 55 | l0[PAGE_ENTRIES / 2] = (l1.as_ptr() as u64 >> 2) | VALID; 56 | 57 | // Identity map 8 GiB using 1GB pages 58 | for l1_i in 0..8 { 59 | let addr = l1_i as u64 * 0x4000_0000; 60 | l1[l1_i] = addr >> 2 | RWX | VALID; 61 | } 62 | } 63 | 64 | { 65 | // Create L1 for kernel mapping 66 | let l1 = paging_allocate(os)?; 67 | 68 | // Link second to last L0 entry to L1 69 | l0[510] = l1.as_ptr() as u64 >> 2 | VALID; 70 | 71 | // Map kernel_size at kernel offset 72 | let mut kernel_mapped = 0; 73 | let mut l1_i = 0; 74 | while kernel_mapped < kernel_size && l1_i < l1.len() { 75 | let l2 = paging_allocate(os)?; 76 | l1[l1_i] = l2.as_ptr() as u64 >> 2 | VALID; 77 | l1_i += 1; 78 | 79 | let mut l2_i = 0; 80 | while kernel_mapped < kernel_size && l2_i < l2.len() { 81 | let l3 = paging_allocate(os)?; 82 | l2[l2_i] = l3.as_ptr() as u64 >> 2 | VALID; 83 | l2_i += 1; 84 | 85 | let mut l3_i = 0; 86 | while kernel_mapped < kernel_size && l3_i < l3.len() { 87 | let addr = kernel_phys + kernel_mapped; 88 | l3[l3_i] = addr >> 2 | RWX | VALID; 89 | l3_i += 1; 90 | kernel_mapped += PAGE_SIZE as u64; 91 | } 92 | } 93 | } 94 | assert!(kernel_mapped >= kernel_size); 95 | } 96 | 97 | Some(l0.as_ptr() as usize) 98 | } 99 | 100 | pub unsafe fn paging_physmem(os: &impl Os, page_phys: usize, phys: u64, size: u64) -> Option { 101 | if phys + size <= 0x2_0000_0000 { 102 | return Some(phys + PHYS_OFFSET); 103 | } 104 | 105 | let mut l0_i = (phys as usize >> (PAGE_SHIFT + 3 * TABLE_SHIFT)) + PAGE_ENTRIES / 2; 106 | let mut l1_i = (phys as usize >> (PAGE_SHIFT + 2 * TABLE_SHIFT)) & TABLE_MASK; 107 | let mut l2_i = (phys as usize >> (PAGE_SHIFT + TABLE_SHIFT)) & TABLE_MASK; 108 | assert_eq!(phys & (1 << (PAGE_SHIFT + TABLE_SHIFT) - 1), 0); 109 | 110 | let l0 = slice::from_raw_parts_mut(page_phys as *mut u64, PAGE_ENTRIES); 111 | 112 | // Map framebuffer_size at framebuffer offset 113 | let mut mapped = 0; 114 | while mapped < size && l0_i < l0.len() { 115 | let l1 = get_table(os, l0, l0_i)?; 116 | 117 | while mapped < size && l1_i < l1.len() { 118 | let l2 = get_table(os, l1, l1_i)?; 119 | 120 | while mapped < size && l2_i < l2.len() { 121 | let addr = phys + mapped; 122 | assert_eq!(l2[l2_i], 0); 123 | l2[l2_i] = ((addr >> 2) | RWX | VALID) as u64; 124 | mapped += 1 << (PAGE_SHIFT + TABLE_SHIFT); 125 | l2_i += 1; 126 | } 127 | 128 | l1_i += 1; 129 | l2_i = 0; 130 | } 131 | 132 | l0_i += 1; 133 | l1_i = 0; 134 | } 135 | 136 | assert!(mapped >= size); 137 | 138 | Some(phys + PHYS_OFFSET) 139 | } 140 | 141 | use crate::area_add; 142 | pub use paging_physmem as paging_framebuffer; 143 | -------------------------------------------------------------------------------- /src/arch/x86/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::os::Os; 2 | 3 | pub(crate) mod x32; 4 | pub(crate) mod x64; 5 | 6 | pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -> Option { 7 | if crate::KERNEL_64BIT { 8 | x64::paging_create(os, kernel_phys, kernel_size) 9 | } else { 10 | x32::paging_create(os, kernel_phys, kernel_size) 11 | } 12 | } 13 | 14 | pub unsafe fn paging_framebuffer( 15 | os: &impl Os, 16 | page_phys: usize, 17 | framebuffer_phys: u64, 18 | framebuffer_size: u64, 19 | ) -> Option { 20 | if crate::KERNEL_64BIT { 21 | x64::paging_framebuffer(os, page_phys, framebuffer_phys, framebuffer_size) 22 | } else { 23 | x32::paging_framebuffer(os, page_phys, framebuffer_phys, framebuffer_size) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/arch/x86/x32.rs: -------------------------------------------------------------------------------- 1 | use crate::area_add; 2 | use crate::os::{Os, OsMemoryEntry, OsMemoryKind}; 3 | use core::slice; 4 | 5 | const PAGE_ENTRIES: usize = 1024; 6 | const PAGE_SIZE: usize = 4096; 7 | pub(crate) const PHYS_OFFSET: u32 = 0x8000_0000; 8 | 9 | unsafe fn paging_allocate(os: &impl Os) -> Option<&'static mut [u32]> { 10 | let ptr = os.alloc_zeroed_page_aligned(PAGE_SIZE); 11 | if !ptr.is_null() { 12 | area_add(OsMemoryEntry { 13 | base: ptr as u64, 14 | size: PAGE_SIZE as u64, 15 | kind: OsMemoryKind::Reclaim, 16 | }); 17 | Some(slice::from_raw_parts_mut(ptr as *mut u32, PAGE_ENTRIES)) 18 | } else { 19 | None 20 | } 21 | } 22 | 23 | pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -> Option { 24 | let pd = paging_allocate(os)?; 25 | //Identity map 1 GiB using 4 MiB pages, also map at PHYS_OFFSET 26 | for pd_i in 0..256 { 27 | let addr = pd_i as u32 * 0x40_0000; 28 | pd[pd_i] = addr | 1 << 7 | 1 << 1 | 1; 29 | pd[pd_i + 512] = addr | 1 << 7 | 1 << 1 | 1; 30 | } 31 | 32 | // Map kernel_size at kernel offset 33 | let mut kernel_mapped = 0; 34 | let mut pd_i = 0xC000_0000 / 0x40_0000; 35 | while kernel_mapped < kernel_size && pd_i < pd.len() { 36 | let pt = paging_allocate(os)?; 37 | pd[pd_i] = pt.as_ptr() as u32 | 1 << 1 | 1; 38 | pd_i += 1; 39 | 40 | let mut pt_i = 0; 41 | while kernel_mapped < kernel_size && pt_i < pt.len() { 42 | let addr = kernel_phys + kernel_mapped; 43 | pt[pt_i] = addr as u32 | 1 << 1 | 1; 44 | pt_i += 1; 45 | kernel_mapped += PAGE_SIZE as u64; 46 | } 47 | } 48 | assert!(kernel_mapped >= kernel_size); 49 | 50 | Some(pd.as_ptr() as usize) 51 | } 52 | 53 | pub unsafe fn paging_framebuffer( 54 | os: &impl Os, 55 | page_phys: usize, 56 | framebuffer_phys: u64, 57 | framebuffer_size: u64, 58 | ) -> Option { 59 | let framebuffer_virt = 0xD000_0000; // 256 MiB after kernel mapping, but before heap mapping 60 | 61 | let pd = slice::from_raw_parts_mut(page_phys as *mut u32, PAGE_ENTRIES); 62 | 63 | // Map framebuffer_size at framebuffer offset 64 | let mut framebuffer_mapped = 0; 65 | let mut pd_i = framebuffer_virt / 0x40_0000; 66 | while framebuffer_mapped < framebuffer_size && pd_i < pd.len() { 67 | let pt = paging_allocate(os)?; 68 | pd[pd_i] = pt.as_ptr() as u32 | 1 << 1 | 1; 69 | pd_i += 1; 70 | 71 | let mut pt_i = 0; 72 | while framebuffer_mapped < framebuffer_size && pt_i < pt.len() { 73 | let addr = framebuffer_phys + framebuffer_mapped; 74 | pt[pt_i] = addr as u32 | 1 << 1 | 1; 75 | pt_i += 1; 76 | framebuffer_mapped += PAGE_SIZE as u64; 77 | } 78 | } 79 | assert!(framebuffer_mapped >= framebuffer_size); 80 | 81 | Some(framebuffer_virt as u64) 82 | } 83 | -------------------------------------------------------------------------------- /src/arch/x86/x64.rs: -------------------------------------------------------------------------------- 1 | use core::slice; 2 | 3 | use crate::area_add; 4 | use crate::os::{Os, OsMemoryEntry, OsMemoryKind}; 5 | 6 | const ENTRY_ADDRESS_MASK: u64 = 0x000F_FFFF_FFFF_F000; 7 | const PAGE_ENTRIES: usize = 512; 8 | const PAGE_SIZE: usize = 4096; 9 | pub(crate) const PHYS_OFFSET: u64 = 0xFFFF_8000_0000_0000; 10 | 11 | unsafe fn paging_allocate(os: &impl Os) -> Option<&'static mut [u64]> { 12 | let ptr = os.alloc_zeroed_page_aligned(PAGE_SIZE); 13 | if !ptr.is_null() { 14 | area_add(OsMemoryEntry { 15 | base: ptr as u64, 16 | size: PAGE_SIZE as u64, 17 | kind: OsMemoryKind::Reclaim, 18 | }); 19 | 20 | Some(slice::from_raw_parts_mut(ptr as *mut u64, PAGE_ENTRIES)) 21 | } else { 22 | None 23 | } 24 | } 25 | 26 | const PRESENT: u64 = 1; 27 | const WRITABLE: u64 = 1 << 1; 28 | const LARGE: u64 = 1 << 7; 29 | 30 | pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -> Option { 31 | // Create PML4 32 | let pml4 = paging_allocate(os)?; 33 | 34 | { 35 | // Create PDP for identity mapping 36 | let pdp = paging_allocate(os)?; 37 | 38 | // Link first user and first kernel PML4 entry to PDP 39 | pml4[0] = pdp.as_ptr() as u64 | WRITABLE | PRESENT; 40 | pml4[256] = pdp.as_ptr() as u64 | WRITABLE | PRESENT; 41 | 42 | // Identity map 8 GiB using 2 MiB pages 43 | for pdp_i in 0..8 { 44 | let pd = paging_allocate(os)?; 45 | pdp[pdp_i] = pd.as_ptr() as u64 | WRITABLE | PRESENT; 46 | for pd_i in 0..pd.len() { 47 | let addr = pdp_i as u64 * 0x4000_0000 + pd_i as u64 * 0x20_0000; 48 | pd[pd_i] = addr | LARGE | WRITABLE | PRESENT; 49 | } 50 | } 51 | } 52 | 53 | { 54 | // Create PDP (spanning 512 GiB) for kernel mapping 55 | let pdp = paging_allocate(os)?; 56 | 57 | // Link last PML4 entry to PDP 58 | pml4[511] = pdp.as_ptr() as u64 | WRITABLE | PRESENT; 59 | 60 | // Create PD (spanning 1 GiB) for kernel mapping. 61 | let pd = paging_allocate(os)?; 62 | 63 | // The kernel is mapped at -2^31, i.e. 0xFFFF_FFFF_8000_0000. Since a PD is 1 GiB, link 64 | // the second last PDP entry to PD. 65 | pdp[510] = pd.as_ptr() as u64 | WRITABLE | PRESENT; 66 | 67 | // Map kernel_size bytes to kernel offset, i.e. to the start of the PD. 68 | 69 | let mut kernel_mapped = 0; 70 | 71 | let mut pd_idx = 0; 72 | while kernel_mapped < kernel_size && pd_idx < pd.len() { 73 | let pt = paging_allocate(os)?; 74 | pd[pd_idx] = pt.as_ptr() as u64 | WRITABLE | PRESENT; 75 | pd_idx += 1; 76 | 77 | let mut pt_idx = 0; 78 | while kernel_mapped < kernel_size && pt_idx < pt.len() { 79 | let addr = kernel_phys + kernel_mapped; 80 | pt[pt_idx] = addr | WRITABLE | PRESENT; 81 | pt_idx += 1; 82 | kernel_mapped += PAGE_SIZE as u64; 83 | } 84 | } 85 | assert!(kernel_mapped >= kernel_size); 86 | } 87 | 88 | Some(pml4.as_ptr() as usize) 89 | } 90 | 91 | pub unsafe fn paging_framebuffer( 92 | os: &impl Os, 93 | page_phys: usize, 94 | framebuffer_phys: u64, 95 | framebuffer_size: u64, 96 | ) -> Option { 97 | //TODO: smarter test for framebuffer already mapped 98 | if framebuffer_phys + framebuffer_size <= 0x2_0000_0000 { 99 | return Some(framebuffer_phys + PHYS_OFFSET); 100 | } 101 | 102 | let pml4_i = ((framebuffer_phys / 0x80_0000_0000) + 256) as usize; 103 | let mut pdp_i = ((framebuffer_phys % 0x80_0000_0000) / 0x4000_0000) as usize; 104 | let mut pd_i = ((framebuffer_phys % 0x4000_0000) / 0x20_0000) as usize; 105 | assert_eq!(framebuffer_phys % 0x20_0000, 0); 106 | 107 | let pml4 = slice::from_raw_parts_mut(page_phys as *mut u64, PAGE_ENTRIES); 108 | 109 | // Create PDP for framebuffer mapping 110 | let pdp = if pml4[pml4_i] == 0 { 111 | let pdp = paging_allocate(os)?; 112 | pml4[pml4_i] = pdp.as_ptr() as u64 | 1 << 1 | 1; 113 | pdp 114 | } else { 115 | slice::from_raw_parts_mut( 116 | (pml4[pml4_i] & ENTRY_ADDRESS_MASK) as *mut u64, 117 | PAGE_ENTRIES, 118 | ) 119 | }; 120 | 121 | // Map framebuffer_size at framebuffer offset 122 | let mut framebuffer_mapped = 0; 123 | while framebuffer_mapped < framebuffer_size && pdp_i < pdp.len() { 124 | let pd = paging_allocate(os)?; 125 | assert_eq!(pdp[pdp_i], 0); 126 | pdp[pdp_i] = pd.as_ptr() as u64 | 1 << 1 | 1; 127 | 128 | while framebuffer_mapped < framebuffer_size && pd_i < pd.len() { 129 | let addr = framebuffer_phys + framebuffer_mapped; 130 | assert_eq!(pd[pd_i], 0); 131 | pd[pd_i] = addr | 1 << 7 | 1 << 1 | 1; 132 | framebuffer_mapped += 0x20_0000; 133 | pd_i += 1; 134 | } 135 | 136 | pdp_i += 1; 137 | pd_i = 0; 138 | } 139 | assert!(framebuffer_mapped >= framebuffer_size); 140 | 141 | Some(framebuffer_phys + PHYS_OFFSET) 142 | } 143 | -------------------------------------------------------------------------------- /src/logger.rs: -------------------------------------------------------------------------------- 1 | use log::{LevelFilter, Log, Metadata, Record}; 2 | 3 | pub static LOGGER: Logger = Logger; 4 | 5 | pub struct Logger; 6 | 7 | impl Logger { 8 | pub fn init(&'static self) { 9 | log::set_logger(self).unwrap(); 10 | log::set_max_level(LevelFilter::Info); 11 | } 12 | } 13 | 14 | impl Log for Logger { 15 | fn enabled(&self, _metadata: &Metadata) -> bool { 16 | true 17 | } 18 | 19 | fn log(&self, record: &Record) { 20 | if self.enabled(record.metadata()) { 21 | println!("{} - {}", record.level(), record.args()); 22 | } 23 | } 24 | 25 | fn flush(&self) {} 26 | } 27 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![feature(alloc_error_handler)] 3 | #![feature(int_roundings)] 4 | #![feature(lang_items)] 5 | #![allow(internal_features)] 6 | #![feature(let_chains)] 7 | #![cfg_attr( 8 | any(target_arch = "riscv64", target_os = "uefi"), 9 | no_main, 10 | feature(try_trait_v2) 11 | )] 12 | #![cfg_attr(target_arch = "riscv64", feature(naked_functions))] 13 | 14 | extern crate alloc; 15 | 16 | #[cfg(any(target_arch = "riscv64", target_os = "uefi"))] 17 | #[macro_use] 18 | extern crate uefi_std as std; 19 | 20 | use alloc::{format, string::String, vec::Vec}; 21 | use core::{ 22 | cmp, 23 | fmt::{self, Write}, 24 | mem, ptr, slice, str, 25 | }; 26 | use redoxfs::Disk; 27 | 28 | use self::arch::{paging_create, paging_framebuffer}; 29 | use self::os::{Os, OsHwDesc, OsKey, OsMemoryEntry, OsMemoryKind, OsVideoMode}; 30 | 31 | #[macro_use] 32 | mod os; 33 | 34 | mod arch; 35 | mod logger; 36 | mod serial_16550; 37 | 38 | const KIBI: usize = 1024; 39 | const MIBI: usize = KIBI * KIBI; 40 | 41 | //TODO: allocate this in a more reasonable manner 42 | static mut AREAS: [OsMemoryEntry; 1024] = [OsMemoryEntry { 43 | base: 0, 44 | size: 0, 45 | kind: OsMemoryKind::Null, 46 | }; 1024]; 47 | static mut AREAS_LEN: usize = 0; 48 | 49 | pub fn area_add(area: OsMemoryEntry) { 50 | unsafe { 51 | for existing_area in &mut AREAS[0..AREAS_LEN] { 52 | if existing_area.kind == area.kind { 53 | if existing_area.base.unchecked_add(existing_area.size) == area.base { 54 | existing_area.size += area.size; 55 | return; 56 | } 57 | if area.base.unchecked_add(area.size) == existing_area.base { 58 | existing_area.base = area.base; 59 | return; 60 | } 61 | } 62 | } 63 | *AREAS.get_mut(AREAS_LEN).expect("AREAS overflowed!") = area; 64 | AREAS_LEN += 1; 65 | } 66 | } 67 | 68 | pub static mut KERNEL_64BIT: bool = false; 69 | 70 | pub static mut LIVE_OPT: Option<(u64, &'static [u8])> = None; 71 | 72 | struct SliceWriter<'a> { 73 | slice: &'a mut [u8], 74 | i: usize, 75 | } 76 | 77 | impl<'a> Write for SliceWriter<'a> { 78 | fn write_str(&mut self, s: &str) -> fmt::Result { 79 | for b in s.bytes() { 80 | if let Some(slice_b) = self.slice.get_mut(self.i) { 81 | *slice_b = b; 82 | self.i += 1; 83 | } else { 84 | return Err(fmt::Error); 85 | } 86 | } 87 | Ok(()) 88 | } 89 | } 90 | 91 | #[allow(dead_code)] 92 | #[derive(Debug)] 93 | #[repr(C, packed(8))] 94 | pub struct KernelArgs { 95 | kernel_base: u64, 96 | kernel_size: u64, 97 | stack_base: u64, 98 | stack_size: u64, 99 | env_base: u64, 100 | env_size: u64, 101 | 102 | /// The base pointer to the saved RSDP. 103 | /// 104 | /// This field can be NULL, and if so, the system has not booted with UEFI or in some other way 105 | /// retrieved the RSDPs. The kernel or a userspace driver will thus try searching the BIOS 106 | /// memory instead. On UEFI systems, searching is not guaranteed to actually work though. 107 | acpi_rsdp_base: u64, 108 | /// The size of the RSDP region. 109 | acpi_rsdp_size: u64, 110 | 111 | areas_base: u64, 112 | areas_size: u64, 113 | 114 | bootstrap_base: u64, 115 | bootstrap_size: u64, 116 | } 117 | 118 | fn select_mode(os: &impl Os, output_i: usize) -> Option { 119 | let mut modes = Vec::new(); 120 | for mode in os.video_modes(output_i) { 121 | let mut aspect_w = mode.width; 122 | let mut aspect_h = mode.height; 123 | for i in 2..cmp::min(aspect_w / 2, aspect_h / 2) { 124 | while aspect_w % i == 0 && aspect_h % i == 0 { 125 | aspect_w /= i; 126 | aspect_h /= i; 127 | } 128 | } 129 | 130 | modes.push(( 131 | mode, 132 | format!( 133 | "{:>4}x{:<4} {:>3}:{:<3}", 134 | mode.width, mode.height, aspect_w, aspect_h 135 | ), 136 | )); 137 | } 138 | 139 | if modes.is_empty() { 140 | return None; 141 | } 142 | 143 | // Sort modes by pixel area, reversed 144 | modes.sort_by(|a, b| (b.0.width * b.0.height).cmp(&(a.0.width * a.0.height))); 145 | 146 | // Set selected based on best resolution 147 | print!("Output {}", output_i); 148 | let mut selected = modes.first().map_or(0, |x| x.0.id); 149 | if let Some((best_width, best_height)) = os.best_resolution(output_i) { 150 | print!(", best resolution: {}x{}", best_width, best_height); 151 | for (mode, _text) in modes.iter() { 152 | if mode.width == best_width && mode.height == best_height { 153 | selected = mode.id; 154 | break; 155 | } 156 | } 157 | } 158 | println!(); 159 | 160 | println!("Arrow keys and enter select mode"); 161 | println!(); 162 | print!(" "); 163 | 164 | let (off_x, off_y) = os.get_text_position(); 165 | let rows = 12; 166 | let mut mode_opt = None; 167 | while !modes.is_empty() { 168 | let mut row = 0; 169 | let mut col = 0; 170 | for (mode, text) in modes.iter() { 171 | if row >= rows { 172 | col += 1; 173 | row = 0; 174 | } 175 | 176 | os.set_text_position(off_x + col * 20, off_y + row); 177 | os.set_text_highlight(mode.id == selected); 178 | 179 | print!("{}", text); 180 | 181 | row += 1; 182 | } 183 | 184 | // Read keypress 185 | match os.get_key() { 186 | OsKey::Left => { 187 | if let Some(mut mode_i) = modes.iter().position(|x| x.0.id == selected) { 188 | if mode_i < rows { 189 | while mode_i < modes.len() { 190 | mode_i += rows; 191 | } 192 | } 193 | mode_i -= rows; 194 | if let Some(new) = modes.get(mode_i) { 195 | selected = new.0.id; 196 | } 197 | } 198 | } 199 | OsKey::Right => { 200 | if let Some(mut mode_i) = modes.iter().position(|x| x.0.id == selected) { 201 | mode_i += rows; 202 | if mode_i >= modes.len() { 203 | mode_i %= rows; 204 | } 205 | if let Some(new) = modes.get(mode_i) { 206 | selected = new.0.id; 207 | } 208 | } 209 | } 210 | OsKey::Up => { 211 | if let Some(mut mode_i) = modes.iter().position(|x| x.0.id == selected) { 212 | if mode_i % rows == 0 { 213 | mode_i += rows; 214 | if mode_i > modes.len() { 215 | mode_i = modes.len(); 216 | } 217 | } 218 | mode_i -= 1; 219 | if let Some(new) = modes.get(mode_i) { 220 | selected = new.0.id; 221 | } 222 | } 223 | } 224 | OsKey::Down => { 225 | if let Some(mut mode_i) = modes.iter().position(|x| x.0.id == selected) { 226 | mode_i += 1; 227 | if mode_i % rows == 0 { 228 | mode_i -= rows; 229 | } 230 | if mode_i >= modes.len() { 231 | mode_i = mode_i - mode_i % rows; 232 | } 233 | if let Some(new) = modes.get(mode_i) { 234 | selected = new.0.id; 235 | } 236 | } 237 | } 238 | OsKey::Enter => { 239 | if let Some(mode_i) = modes.iter().position(|x| x.0.id == selected) { 240 | if let Some((mode, _text)) = modes.get(mode_i) { 241 | mode_opt = Some(*mode); 242 | } 243 | } 244 | break; 245 | } 246 | _ => (), 247 | } 248 | } 249 | 250 | os.set_text_position(0, off_y + rows); 251 | os.set_text_highlight(false); 252 | println!(); 253 | 254 | mode_opt 255 | } 256 | 257 | fn redoxfs(os: &O) -> (redoxfs::FileSystem, Option<&'static [u8]>) { 258 | let attempts = 10; 259 | for attempt in 0..=attempts { 260 | let mut password_opt = None; 261 | if attempt > 0 { 262 | print!("\rRedoxFS password ({}/{}): ", attempt, attempts); 263 | 264 | let mut password = String::new(); 265 | 266 | loop { 267 | match os.get_key() { 268 | OsKey::Backspace | OsKey::Delete => { 269 | if !password.is_empty() { 270 | print!("\x08 \x08"); 271 | password.pop(); 272 | } 273 | } 274 | OsKey::Char(c) => { 275 | print!("*"); 276 | password.push(c) 277 | } 278 | OsKey::Enter => break, 279 | _ => (), 280 | } 281 | } 282 | 283 | // Erase password information 284 | while os.get_text_position().0 > 0 { 285 | print!("\x08 \x08"); 286 | } 287 | 288 | if !password.is_empty() { 289 | password_opt = Some(password); 290 | } 291 | } 292 | match os.filesystem(password_opt.as_ref().map(|x| x.as_bytes())) { 293 | Ok(fs) => { 294 | return ( 295 | fs, 296 | password_opt.map(|password| { 297 | // Copy password to page aligned memory 298 | let password_size = password.len(); 299 | let password_base = os.alloc_zeroed_page_aligned(password_size); 300 | unsafe { 301 | ptr::copy(password.as_ptr(), password_base, password_size); 302 | slice::from_raw_parts(password_base, password_size) 303 | } 304 | }), 305 | ); 306 | } 307 | Err(err) => match err.errno { 308 | // Incorrect password, try again 309 | syscall::ENOKEY => (), 310 | _ => { 311 | panic!("Failed to open RedoxFS: {}", err); 312 | } 313 | }, 314 | } 315 | } 316 | panic!("RedoxFS out of unlock attempts"); 317 | } 318 | 319 | #[derive(PartialEq)] 320 | enum Filetype { 321 | Elf, 322 | Initfs, 323 | } 324 | fn load_to_memory( 325 | os: &O, 326 | fs: &mut redoxfs::FileSystem, 327 | dirname: &str, 328 | filename: &str, 329 | filetype: Filetype, 330 | ) -> &'static mut [u8] { 331 | fs.tx(|tx| { 332 | let dir_node = tx 333 | .find_node(redoxfs::TreePtr::root(), dirname) 334 | .unwrap_or_else(|err| panic!("Failed to find {} directory: {}", dirname, err)); 335 | 336 | let node = tx 337 | .find_node(dir_node.ptr(), filename) 338 | .unwrap_or_else(|err| panic!("Failed to find {} file: {}", filename, err)); 339 | 340 | let size = node.data().size(); 341 | 342 | print!("{}: 0/{} MiB", filename, size / MIBI as u64); 343 | 344 | let ptr = os.alloc_zeroed_page_aligned(size as usize); 345 | if ptr.is_null() { 346 | panic!("Failed to allocate memory for {}", filename); 347 | } 348 | 349 | let slice = unsafe { slice::from_raw_parts_mut(ptr, size as usize) }; 350 | 351 | let mut i = 0; 352 | for chunk in slice.chunks_mut(MIBI) { 353 | print!( 354 | "\r{}: {}/{} MiB", 355 | filename, 356 | i / MIBI as u64, 357 | size / MIBI as u64 358 | ); 359 | i += tx 360 | .read_node_inner(&node, i, chunk) 361 | .unwrap_or_else(|err| panic!("Failed to read `{}` file: {}", filename, err)) 362 | as u64; 363 | } 364 | println!( 365 | "\r{}: {}/{} MiB", 366 | filename, 367 | i / MIBI as u64, 368 | size / MIBI as u64 369 | ); 370 | 371 | if filetype == Filetype::Elf { 372 | let magic = &slice[..4]; 373 | if magic != b"\x7FELF" { 374 | panic!("{} has invalid magic number {:#X?}", filename, magic); 375 | } 376 | } else if filetype == Filetype::Initfs { 377 | let magic = &slice[..8]; 378 | if magic != b"RedoxFtw" { 379 | panic!("{} has invalid magic number {:#X?}", filename, magic); 380 | } 381 | } 382 | 383 | Ok(slice) 384 | }) 385 | .unwrap_or_else(|err| { 386 | panic!( 387 | "RedoxFS transaction failed while loading `{}`: {}", 388 | filename, err 389 | ) 390 | }) 391 | } 392 | 393 | fn elf_entry(data: &[u8]) -> (u64, bool) { 394 | match (data[4], data[5]) { 395 | // 32-bit, little endian 396 | (1, 1) => ( 397 | u32::from_le_bytes( 398 | <[u8; 4]>::try_from(&data[0x18..0x18 + 4]).expect("conversion cannot fail"), 399 | ) as u64, 400 | false, 401 | ), 402 | // 32-bit, big endian 403 | (1, 2) => ( 404 | u32::from_be_bytes( 405 | <[u8; 4]>::try_from(&data[0x18..0x18 + 4]).expect("conversion cannot fail"), 406 | ) as u64, 407 | false, 408 | ), 409 | // 64-bit, little endian 410 | (2, 1) => ( 411 | u64::from_le_bytes( 412 | <[u8; 8]>::try_from(&data[0x18..0x18 + 8]).expect("conversion cannot fail"), 413 | ), 414 | true, 415 | ), 416 | // 64-bit, big endian 417 | (2, 2) => ( 418 | u64::from_be_bytes( 419 | <[u8; 8]>::try_from(&data[0x18..0x18 + 8]).expect("conversion cannot fail"), 420 | ), 421 | true, 422 | ), 423 | (ei_class, ei_data) => { 424 | panic!("Unsupported ELF EI_CLASS {} EI_DATA {}", ei_class, ei_data); 425 | } 426 | } 427 | } 428 | 429 | fn main(os: &impl Os) -> (usize, u64, KernelArgs) { 430 | println!( 431 | "Redox OS Bootloader {} on {}", 432 | env!("CARGO_PKG_VERSION"), 433 | os.name() 434 | ); 435 | 436 | let hwdesc = os.hwdesc(); 437 | println!("Hardware descriptor: {:x?}", hwdesc); 438 | let (acpi_rsdp_base, acpi_rsdp_size) = match hwdesc { 439 | OsHwDesc::Acpi(base, size) => (base, size), 440 | OsHwDesc::DeviceTree(base, size) => (base, size), 441 | OsHwDesc::NotFound => (0, 0), 442 | }; 443 | 444 | let (mut fs, password_opt) = redoxfs(os); 445 | 446 | print!("RedoxFS "); 447 | for i in 0..fs.header.uuid().len() { 448 | if i == 4 || i == 6 || i == 8 || i == 10 { 449 | print!("-"); 450 | } 451 | 452 | print!("{:>02x}", fs.header.uuid()[i]); 453 | } 454 | println!(": {} MiB", fs.header.size() / MIBI as u64); 455 | println!(); 456 | 457 | let mut mode_opts = Vec::new(); 458 | for output_i in 0..os.video_outputs() { 459 | if output_i > 0 { 460 | os.clear_text(); 461 | } 462 | mode_opts.push(select_mode(os, output_i)); 463 | } 464 | 465 | let stack_size = 128 * KIBI; 466 | let stack_base = os.alloc_zeroed_page_aligned(stack_size); 467 | if stack_base.is_null() { 468 | panic!("Failed to allocate memory for stack"); 469 | } 470 | 471 | let live_opt = if cfg!(feature = "live") { 472 | let size = fs.header.size(); 473 | 474 | print!("live: 0/{} MiB", size / MIBI as u64); 475 | 476 | let ptr = os.alloc_zeroed_page_aligned(size as usize); 477 | if ptr.is_null() { 478 | panic!("Failed to allocate memory for live"); 479 | } 480 | 481 | let live = unsafe { slice::from_raw_parts_mut(ptr, size as usize) }; 482 | 483 | let mut i = 0; 484 | for chunk in live.chunks_mut(MIBI) { 485 | print!("\rlive: {}/{} MiB", i / MIBI as u64, size / MIBI as u64); 486 | i += unsafe { 487 | fs.disk 488 | .read_at(fs.block + i / redoxfs::BLOCK_SIZE, chunk) 489 | .expect("Failed to read live disk") as u64 490 | }; 491 | } 492 | println!("\rlive: {}/{} MiB", i / MIBI as u64, size / MIBI as u64); 493 | 494 | println!("Switching to live disk"); 495 | unsafe { 496 | LIVE_OPT = Some((fs.block, slice::from_raw_parts_mut(ptr, size as usize))); 497 | } 498 | 499 | area_add(OsMemoryEntry { 500 | base: live.as_ptr() as u64, 501 | size: live.len() as u64, 502 | kind: OsMemoryKind::Reserved, 503 | }); 504 | 505 | Some(live) 506 | } else { 507 | None 508 | }; 509 | 510 | let (kernel, kernel_entry) = { 511 | let kernel = load_to_memory(os, &mut fs, "boot", "kernel", Filetype::Elf); 512 | let (kernel_entry, kernel_64bit) = elf_entry(kernel); 513 | unsafe { 514 | KERNEL_64BIT = kernel_64bit; 515 | } 516 | (kernel, kernel_entry) 517 | }; 518 | 519 | let (bootstrap_size, bootstrap_base) = { 520 | let initfs_slice = load_to_memory(os, &mut fs, "boot", "initfs", Filetype::Initfs); 521 | 522 | let memory = unsafe { 523 | let total_size = initfs_slice.len().next_multiple_of(4096); 524 | let ptr = os.alloc_zeroed_page_aligned(total_size); 525 | assert!(!ptr.is_null(), "failed to allocate bootstrap+initfs memory"); 526 | core::slice::from_raw_parts_mut(ptr, total_size) 527 | }; 528 | memory[..initfs_slice.len()].copy_from_slice(initfs_slice); 529 | 530 | (memory.len() as u64, memory.as_mut_ptr() as u64) 531 | }; 532 | 533 | let page_phys = unsafe { paging_create(os, kernel.as_ptr() as u64, kernel.len() as u64) } 534 | .expect("Failed to set up paging"); 535 | 536 | let mut env_size = 64 * KIBI; 537 | let env_base = os.alloc_zeroed_page_aligned(env_size); 538 | if env_base.is_null() { 539 | panic!("Failed to allocate memory for stack"); 540 | } 541 | 542 | { 543 | let mut w = SliceWriter { 544 | slice: unsafe { slice::from_raw_parts_mut(env_base, env_size) }, 545 | i: 0, 546 | }; 547 | 548 | match hwdesc { 549 | OsHwDesc::Acpi(addr, size) => { 550 | writeln!(w, "RSDP_ADDR={addr:016x}").unwrap(); 551 | writeln!(w, "RSDP_SIZE={size:016x}").unwrap(); 552 | } 553 | OsHwDesc::DeviceTree(addr, size) => { 554 | writeln!(w, "DTB_ADDR={addr:016x}").unwrap(); 555 | writeln!(w, "DTB_SIZE={size:016x}").unwrap(); 556 | } 557 | OsHwDesc::NotFound => {} 558 | } 559 | 560 | if let Some(live) = live_opt { 561 | writeln!(w, "DISK_LIVE_ADDR={:016x}", live.as_ptr() as usize).unwrap(); 562 | writeln!(w, "DISK_LIVE_SIZE={:016x}", live.len()).unwrap(); 563 | writeln!(w, "REDOXFS_BLOCK={:016x}", 0).unwrap(); 564 | } else { 565 | writeln!(w, "REDOXFS_BLOCK={:016x}", fs.block).unwrap(); 566 | } 567 | write!(w, "REDOXFS_UUID=").unwrap(); 568 | for i in 0..fs.header.uuid().len() { 569 | if i == 4 || i == 6 || i == 8 || i == 10 { 570 | write!(w, "-").unwrap(); 571 | } 572 | 573 | write!(w, "{:>02x}", fs.header.uuid()[i]).unwrap(); 574 | } 575 | writeln!(w).unwrap(); 576 | if let Some(password) = password_opt { 577 | writeln!( 578 | w, 579 | "REDOXFS_PASSWORD_ADDR={:016x}", 580 | password.as_ptr() as usize 581 | ) 582 | .unwrap(); 583 | writeln!(w, "REDOXFS_PASSWORD_SIZE={:016x}", password.len()).unwrap(); 584 | } 585 | 586 | #[cfg(target_arch = "riscv64")] 587 | { 588 | let boot_hartid = os::efi_get_boot_hartid() 589 | .expect("Could not retrieve boot hart id from EFI implementation!"); 590 | writeln!(w, "BOOT_HART_ID={:016x}", boot_hartid).unwrap(); 591 | } 592 | 593 | for output_i in 0..os.video_outputs() { 594 | if let Some(mut mode) = mode_opts[output_i] { 595 | // Set mode to get updated values 596 | os.set_video_mode(output_i, &mut mode); 597 | 598 | if output_i == 0 { 599 | let virt = unsafe { 600 | paging_framebuffer( 601 | os, 602 | page_phys, 603 | mode.base, 604 | (mode.stride * mode.height * 4) as u64, 605 | ) 606 | } 607 | .expect("Failed to map framebuffer"); 608 | 609 | writeln!(w, "FRAMEBUFFER_ADDR={:016x}", mode.base).unwrap(); 610 | writeln!(w, "FRAMEBUFFER_VIRT={virt:016x}").unwrap(); 611 | writeln!(w, "FRAMEBUFFER_WIDTH={:016x}", mode.width).unwrap(); 612 | writeln!(w, "FRAMEBUFFER_HEIGHT={:016x}", mode.height).unwrap(); 613 | writeln!(w, "FRAMEBUFFER_STRIDE={:016x}", mode.stride).unwrap(); 614 | } else { 615 | writeln!( 616 | w, 617 | "FRAMEBUFFER{}={:#x},{},{},{}", 618 | output_i, mode.base, mode.width, mode.height, mode.stride, 619 | ) 620 | .unwrap(); 621 | } 622 | } 623 | } 624 | 625 | env_size = w.i; 626 | } 627 | 628 | ( 629 | page_phys, 630 | kernel_entry, 631 | KernelArgs { 632 | kernel_base: kernel.as_ptr() as u64, 633 | kernel_size: kernel.len() as u64, 634 | stack_base: stack_base as u64, 635 | stack_size: stack_size as u64, 636 | env_base: env_base as u64, 637 | env_size: env_size as u64, 638 | acpi_rsdp_base, 639 | acpi_rsdp_size, 640 | areas_base: unsafe { AREAS.as_ptr() as u64 }, 641 | areas_size: unsafe { (AREAS.len() * mem::size_of::()) as u64 }, 642 | bootstrap_base, 643 | bootstrap_size, 644 | }, 645 | ) 646 | } 647 | -------------------------------------------------------------------------------- /src/os/bios/disk.rs: -------------------------------------------------------------------------------- 1 | use core::{mem, ptr}; 2 | use redoxfs::{Disk, BLOCK_SIZE}; 3 | use syscall::error::{Error, Result, EIO}; 4 | 5 | use super::{ThunkData, DISK_ADDRESS_PACKET_ADDR, DISK_BIOS_ADDR}; 6 | 7 | const SECTOR_SIZE: u64 = 512; 8 | const BLOCKS_PER_SECTOR: u64 = BLOCK_SIZE / SECTOR_SIZE; 9 | // 128 sectors is the amount allocated for DISK_BIOS_ADDR 10 | // 127 sectors is the maximum for many BIOSes 11 | const MAX_SECTORS: u64 = 127; 12 | const MAX_BLOCKS: u64 = MAX_SECTORS * SECTOR_SIZE / BLOCK_SIZE; 13 | 14 | #[allow(dead_code)] 15 | #[derive(Clone, Copy)] 16 | #[repr(C, packed)] 17 | pub struct DiskAddressPacket { 18 | size: u8, 19 | reserved: u8, 20 | sectors: u16, 21 | buffer: u16, 22 | segment: u16, 23 | address: u64, 24 | } 25 | 26 | impl DiskAddressPacket { 27 | pub fn from_block(block: u64, count: u64) -> DiskAddressPacket { 28 | let address = block * BLOCKS_PER_SECTOR; 29 | let sectors = count * BLOCKS_PER_SECTOR; 30 | assert!(sectors <= MAX_SECTORS); 31 | DiskAddressPacket { 32 | size: mem::size_of::() as u8, 33 | reserved: 0, 34 | sectors: sectors as u16, 35 | buffer: (DISK_BIOS_ADDR & 0xF) as u16, 36 | segment: (DISK_BIOS_ADDR >> 4) as u16, 37 | address, 38 | } 39 | } 40 | } 41 | 42 | pub struct DiskBios { 43 | boot_disk: u8, 44 | thunk13: extern "C" fn(), 45 | chs_opt: Option<(u32, u32, u32)>, 46 | } 47 | 48 | impl DiskBios { 49 | pub fn new(boot_disk: u8, thunk13: extern "C" fn()) -> Self { 50 | let chs_opt = unsafe { 51 | let mut data = ThunkData::new(); 52 | data.eax = 0x4100; 53 | data.ebx = 0x55AA; 54 | data.edx = boot_disk as u32; 55 | 56 | data.with(thunk13); 57 | 58 | if (data.ebx & 0xFFFF) == 0xAA55 { 59 | // Extensions are installed, do not use CHS 60 | None 61 | } else { 62 | // Extensions are not installed, get CHS geometry 63 | data = ThunkData::new(); 64 | data.eax = 0x0800; 65 | data.edx = boot_disk as u32; 66 | data.edi = 0; 67 | 68 | data.with(thunk13); 69 | 70 | //TODO: return result on error 71 | let ah = ({ data.eax } >> 8) & 0xFF; 72 | assert_eq!(ah, 0); 73 | 74 | let c = (data.ecx >> 8) & 0xFF | ((data.ecx >> 6) & 0x3) << 8; 75 | let h = ((data.edx >> 8) & 0xFF) + 1; 76 | let s = data.ecx & 0x3F; 77 | 78 | Some((c, h, s)) 79 | } 80 | }; 81 | 82 | Self { 83 | boot_disk, 84 | thunk13, 85 | chs_opt, 86 | } 87 | } 88 | } 89 | 90 | impl Disk for DiskBios { 91 | unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result { 92 | // Optimization for live disks 93 | if let Some(live) = crate::LIVE_OPT { 94 | if block >= live.0 { 95 | let start = ((block - live.0) * BLOCK_SIZE) as usize; 96 | let end = start + buffer.len(); 97 | if end <= live.1.len() { 98 | buffer.copy_from_slice(&live.1[start..end]); 99 | return Ok(buffer.len()); 100 | } 101 | } 102 | } 103 | 104 | for (i, chunk) in buffer 105 | .chunks_mut((MAX_BLOCKS * BLOCK_SIZE) as usize) 106 | .enumerate() 107 | { 108 | let dap = DiskAddressPacket::from_block( 109 | block + i as u64 * MAX_BLOCKS, 110 | chunk.len() as u64 / BLOCK_SIZE, 111 | ); 112 | 113 | if let Some((_, h_max, s_max)) = self.chs_opt { 114 | let s = (dap.address % s_max as u64) + 1; 115 | assert!(s <= 63, "invalid sector {s}"); 116 | 117 | let tmp = dap.address / s_max as u64; 118 | let h = tmp % h_max as u64; 119 | assert!(h <= 255, "invalid head {h}"); 120 | 121 | let c = tmp / h_max as u64; 122 | assert!(c <= 1023, "invalid cylinder {c}"); 123 | 124 | let mut data = ThunkData::new(); 125 | data.eax = 0x0200 | (dap.sectors as u32); 126 | data.ebx = dap.buffer as u32; 127 | data.ecx = 128 | (s as u32) | (((c as u32) & 0xFF) << 8) | ((((c as u32) >> 8) & 0x3) << 6); 129 | data.edx = (self.boot_disk as u32) | ((h as u32) << 8); 130 | data.es = dap.segment; 131 | 132 | data.with(self.thunk13); 133 | 134 | //TODO: return result on error 135 | let ah = ({ data.eax } >> 8) & 0xFF; 136 | assert_eq!(ah, 0); 137 | } else { 138 | ptr::write(DISK_ADDRESS_PACKET_ADDR as *mut DiskAddressPacket, dap); 139 | 140 | let mut data = ThunkData::new(); 141 | data.eax = 0x4200; 142 | data.edx = self.boot_disk as u32; 143 | data.esi = DISK_ADDRESS_PACKET_ADDR as u32; 144 | 145 | data.with(self.thunk13); 146 | 147 | //TODO: return result on error 148 | let ah = ({ data.eax } >> 8) & 0xFF; 149 | assert_eq!(ah, 0); 150 | 151 | //TODO: check blocks transferred 152 | // dap = ptr::read(DISK_ADDRESS_PACKET_ADDR as *mut DiskAddressPacket); 153 | } 154 | 155 | ptr::copy(DISK_BIOS_ADDR as *const u8, chunk.as_mut_ptr(), chunk.len()); 156 | } 157 | 158 | Ok(buffer.len()) 159 | } 160 | 161 | unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result { 162 | log::error!( 163 | "DiskBios::write_at(0x{:X}, 0x{:X}:0x{:X}) not allowed", 164 | block, 165 | buffer.as_ptr() as usize, 166 | buffer.len() 167 | ); 168 | Err(Error::new(EIO)) 169 | } 170 | 171 | fn size(&mut self) -> Result { 172 | log::error!("DiskBios::size not implemented"); 173 | Err(Error::new(EIO)) 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/os/bios/macros.rs: -------------------------------------------------------------------------------- 1 | /// Print to console 2 | #[macro_export] 3 | macro_rules! print { 4 | ($($arg:tt)*) => ({ 5 | use core::fmt::Write; 6 | #[cfg(feature = "serial_debug")] 7 | { 8 | let _ = write!($crate::os::serial::COM1.lock(), $($arg)*); 9 | } 10 | let _ = write!($crate::os::VGA.lock(), $($arg)*); 11 | }); 12 | } 13 | 14 | /// Print with new line to console 15 | #[macro_export] 16 | macro_rules! println { 17 | () => (print!("\n")); 18 | ($fmt:expr) => (print!(concat!($fmt, "\n"))); 19 | ($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*)); 20 | } 21 | -------------------------------------------------------------------------------- /src/os/bios/memory_map.rs: -------------------------------------------------------------------------------- 1 | use core::{cmp, mem, ptr}; 2 | 3 | use crate::area_add; 4 | use crate::os::{OsMemoryEntry, OsMemoryKind}; 5 | 6 | use super::{thunk::ThunkData, MEMORY_MAP_ADDR}; 7 | 8 | #[repr(C, packed)] 9 | struct MemoryMapEntry { 10 | pub base: u64, 11 | pub size: u64, 12 | pub kind: u32, 13 | } 14 | 15 | pub struct MemoryMapIter { 16 | thunk15: extern "C" fn(), 17 | data: ThunkData, 18 | first: bool, 19 | } 20 | 21 | impl MemoryMapIter { 22 | pub fn new(thunk15: extern "C" fn()) -> Self { 23 | Self { 24 | thunk15, 25 | data: ThunkData::new(), 26 | first: true, 27 | } 28 | } 29 | } 30 | 31 | impl Iterator for MemoryMapIter { 32 | type Item = OsMemoryEntry; 33 | fn next(&mut self) -> Option { 34 | if self.first { 35 | self.first = false; 36 | } else if self.data.ebx == 0 { 37 | return None; 38 | } 39 | 40 | self.data.eax = 0xE820; 41 | self.data.ecx = mem::size_of::() as u32; 42 | self.data.edx = 0x534D4150; 43 | self.data.edi = MEMORY_MAP_ADDR as u32; 44 | 45 | unsafe { 46 | self.data.with(self.thunk15); 47 | } 48 | 49 | //TODO: return error? 50 | assert_eq!({ self.data.eax }, 0x534D4150); 51 | assert_eq!({ self.data.ecx }, mem::size_of::() as u32); 52 | 53 | let entry = unsafe { ptr::read(MEMORY_MAP_ADDR as *const MemoryMapEntry) }; 54 | Some(Self::Item { 55 | base: entry.base, 56 | size: entry.size, 57 | kind: match entry.kind { 58 | 0 => OsMemoryKind::Null, 59 | 1 => OsMemoryKind::Free, 60 | 3 => OsMemoryKind::Reclaim, 61 | _ => OsMemoryKind::Reserved, 62 | }, 63 | }) 64 | } 65 | } 66 | 67 | pub unsafe fn memory_map(thunk15: extern "C" fn()) -> Option<(usize, usize)> { 68 | let mut heap_limits = None; 69 | for entry in MemoryMapIter::new(thunk15) { 70 | let heap_start = 1024 * 1024; 71 | if { entry.kind } == OsMemoryKind::Free 72 | && entry.base <= heap_start as u64 73 | && (entry.base + entry.size) >= heap_start as u64 74 | { 75 | let heap_end = cmp::min(entry.base + entry.size, usize::MAX as u64) as usize; 76 | if heap_end >= heap_start { 77 | heap_limits = Some((heap_start, heap_end - heap_start)); 78 | } 79 | } 80 | 81 | area_add(entry); 82 | } 83 | heap_limits 84 | } 85 | -------------------------------------------------------------------------------- /src/os/bios/mod.rs: -------------------------------------------------------------------------------- 1 | use alloc::alloc::{alloc_zeroed, Layout}; 2 | use core::{convert::TryFrom, mem, ptr, slice}; 3 | use linked_list_allocator::LockedHeap; 4 | use spin::Mutex; 5 | 6 | use crate::logger::LOGGER; 7 | use crate::os::{Os, OsHwDesc, OsKey, OsVideoMode}; 8 | use crate::KernelArgs; 9 | 10 | use self::disk::DiskBios; 11 | use self::memory_map::memory_map; 12 | use self::thunk::ThunkData; 13 | use self::vbe::VideoModeIter; 14 | use self::vga::{Vga, VgaTextColor}; 15 | 16 | #[macro_use] 17 | mod macros; 18 | 19 | mod disk; 20 | mod memory_map; 21 | mod panic; 22 | pub(crate) mod serial; 23 | mod thunk; 24 | mod vbe; 25 | mod vga; 26 | 27 | // Real mode memory allocation, for use with thunk 28 | // 0x500 to 0x7BFF is free 29 | const DISK_BIOS_ADDR: usize = 0x70000; // 64 KiB at 448 KiB, ends at 512 KiB 30 | const VBE_CARD_INFO_ADDR: usize = 0x1000; // 512 bytes, ends at 0x11FF 31 | const VBE_MODE_INFO_ADDR: usize = 0x1200; // 256 bytes, ends at 0x12FF 32 | const VBE_EDID_ADDR: usize = 0x1300; // 128 bytes, ends at 0x137F 33 | const MEMORY_MAP_ADDR: usize = 0x1380; // 24 bytes, ends at 0x1397 34 | const DISK_ADDRESS_PACKET_ADDR: usize = 0x1398; // 16 bytes, ends at 0x13A7 35 | const THUNK_STACK_ADDR: usize = 0x7C00; // Grows downwards 36 | const VGA_ADDR: usize = 0xB8000; 37 | 38 | #[global_allocator] 39 | static ALLOCATOR: LockedHeap = LockedHeap::empty(); 40 | 41 | pub(crate) static VGA: Mutex = Mutex::new(unsafe { Vga::new(VGA_ADDR, 80, 25) }); 42 | 43 | pub struct OsBios { 44 | boot_disk: usize, 45 | thunk10: extern "C" fn(), 46 | thunk13: extern "C" fn(), 47 | thunk15: extern "C" fn(), 48 | thunk16: extern "C" fn(), 49 | } 50 | 51 | #[allow(dead_code)] 52 | #[derive(Copy, Clone, Debug)] 53 | #[repr(C, packed)] 54 | pub struct Rsdp { 55 | signature: [u8; 8], 56 | checksum: u8, 57 | oemid: [u8; 6], 58 | revision: u8, 59 | rsdt_address: u32, 60 | } 61 | 62 | #[allow(dead_code)] 63 | #[derive(Copy, Clone, Debug)] 64 | #[repr(C, packed)] 65 | pub struct Xsdp { 66 | rsdp: Rsdp, 67 | 68 | length: u32, 69 | xsdt_address: u64, 70 | extended_checksum: u8, 71 | reserved: [u8; 3], 72 | } 73 | 74 | unsafe fn search_rsdp(start: usize, end: usize) -> Option<(u64, u64)> { 75 | // Align start up to 16 bytes 76 | let mut addr = start.div_ceil(16) * 16; 77 | // Search until reading the end of the Rsdp would be past the end of the memory area 78 | while addr + mem::size_of::() <= end { 79 | let rsdp = ptr::read(addr as *const Rsdp); 80 | if &rsdp.signature == b"RSD PTR " { 81 | //TODO: check checksum? 82 | if rsdp.revision == 0 { 83 | return Some((addr as u64, mem::size_of::() as u64)); 84 | } else if rsdp.revision == 2 { 85 | let xsdp = ptr::read(addr as *const Xsdp); 86 | //TODO: check extended checksum? 87 | return Some((addr as u64, xsdp.length as u64)); 88 | } 89 | } 90 | 91 | // Rsdp is always aligned to 16 bytes 92 | addr += 16; 93 | } 94 | None 95 | } 96 | 97 | impl Os for OsBios { 98 | type D = DiskBios; 99 | type V = VideoModeIter; 100 | 101 | fn name(&self) -> &str { 102 | "x86/BIOS" 103 | } 104 | 105 | fn alloc_zeroed_page_aligned(&self, size: usize) -> *mut u8 { 106 | assert!(size != 0); 107 | 108 | let page_size = self.page_size(); 109 | let pages = size.div_ceil(page_size); 110 | 111 | let ptr = 112 | unsafe { alloc_zeroed(Layout::from_size_align(pages * page_size, page_size).unwrap()) }; 113 | 114 | assert!(!ptr.is_null()); 115 | ptr 116 | } 117 | 118 | fn page_size(&self) -> usize { 119 | 4096 120 | } 121 | 122 | fn filesystem( 123 | &self, 124 | password_opt: Option<&[u8]>, 125 | ) -> syscall::Result> { 126 | let disk = DiskBios::new(u8::try_from(self.boot_disk).unwrap(), self.thunk13); 127 | 128 | //TODO: get block from partition table 129 | let block = 2 * crate::MIBI as u64 / redoxfs::BLOCK_SIZE; 130 | redoxfs::FileSystem::open(disk, password_opt, Some(block), false) 131 | } 132 | 133 | fn hwdesc(&self) -> OsHwDesc { 134 | // See ACPI specification - Finding the RSDP on IA-PC Systems 135 | unsafe { 136 | let ebda_segment = ptr::read(0x40E as *const u16); 137 | let ebda_addr = (ebda_segment as usize) << 4; 138 | if let Some((addr, size)) = 139 | search_rsdp(ebda_addr, ebda_addr + 1024).or(search_rsdp(0xE0000, 0xFFFFF)) 140 | { 141 | // Copy to a page 142 | let page_aligned = self.alloc_zeroed_page_aligned(size as usize); 143 | ptr::copy(addr as *const u8, page_aligned, size as usize); 144 | return OsHwDesc::Acpi(page_aligned as u64, size); 145 | } 146 | } 147 | OsHwDesc::NotFound 148 | } 149 | 150 | fn video_outputs(&self) -> usize { 151 | //TODO: return 1 only if vbe supported? 152 | 1 153 | } 154 | 155 | fn video_modes(&self, _output_i: usize) -> VideoModeIter { 156 | VideoModeIter::new(self.thunk10) 157 | } 158 | 159 | fn set_video_mode(&self, _output_i: usize, mode: &mut OsVideoMode) { 160 | // Set video mode 161 | let mut data = ThunkData::new(); 162 | data.eax = 0x4F02; 163 | data.ebx = mode.id; 164 | unsafe { 165 | data.with(self.thunk10); 166 | } 167 | //TODO: check result 168 | } 169 | 170 | fn best_resolution(&self, _output_i: usize) -> Option<(u32, u32)> { 171 | let mut data = ThunkData::new(); 172 | data.eax = 0x4F15; 173 | data.ebx = 0x01; 174 | data.ecx = 0; 175 | data.edx = 0; 176 | data.edi = VBE_EDID_ADDR as u32; 177 | unsafe { 178 | data.with(self.thunk10); 179 | } 180 | 181 | if data.eax == 0x4F { 182 | let edid = unsafe { slice::from_raw_parts(VBE_EDID_ADDR as *const u8, 128) }; 183 | 184 | Some(( 185 | (edid[0x38] as u32) | (((edid[0x3A] as u32) & 0xF0) << 4), 186 | (edid[0x3B] as u32) | (((edid[0x3D] as u32) & 0xF0) << 4), 187 | )) 188 | } else { 189 | log::warn!("Failed to get VBE EDID: 0x{:X}", { data.eax }); 190 | None 191 | } 192 | } 193 | 194 | fn get_key(&self) -> OsKey { 195 | // Read keypress 196 | let mut data = ThunkData::new(); 197 | unsafe { 198 | data.with(self.thunk16); 199 | } 200 | match (data.eax >> 8) as u8 { 201 | 0x4B => OsKey::Left, 202 | 0x4D => OsKey::Right, 203 | 0x48 => OsKey::Up, 204 | 0x50 => OsKey::Down, 205 | 0x0E => OsKey::Backspace, 206 | 0x53 => OsKey::Delete, 207 | 0x1C => OsKey::Enter, 208 | _ => match data.eax as u8 { 209 | 0 => OsKey::Other, 210 | b => OsKey::Char(b as char), 211 | }, 212 | } 213 | } 214 | 215 | fn clear_text(&self) { 216 | //TODO: clear screen for VGA 217 | } 218 | 219 | fn get_text_position(&self) -> (usize, usize) { 220 | let vga = VGA.lock(); 221 | (vga.x, vga.y) 222 | } 223 | 224 | fn set_text_position(&self, x: usize, y: usize) { 225 | //TODO: ensure this is inside bounds! 226 | let mut vga = VGA.lock(); 227 | vga.x = x; 228 | vga.y = y; 229 | } 230 | 231 | fn set_text_highlight(&self, highlight: bool) { 232 | let mut vga = VGA.lock(); 233 | if highlight { 234 | vga.bg = VgaTextColor::Gray; 235 | vga.fg = VgaTextColor::Black; 236 | } else { 237 | vga.bg = VgaTextColor::Black; 238 | vga.fg = VgaTextColor::Gray; 239 | } 240 | } 241 | } 242 | 243 | #[no_mangle] 244 | pub unsafe extern "C" fn start( 245 | kernel_entry: extern "C" fn( 246 | page_table: usize, 247 | stack: u64, 248 | func: u64, 249 | args: *const KernelArgs, 250 | long_mode: usize, 251 | ) -> !, 252 | boot_disk: usize, 253 | thunk10: extern "C" fn(), 254 | thunk13: extern "C" fn(), 255 | thunk15: extern "C" fn(), 256 | thunk16: extern "C" fn(), 257 | ) -> ! { 258 | #[cfg(feature = "serial_debug")] 259 | { 260 | let mut com1 = serial::COM1.lock(); 261 | com1.init(); 262 | com1.write(b"SERIAL\n"); 263 | } 264 | 265 | { 266 | // Make sure we are in mode 3 (80x25 text mode) 267 | let mut data = ThunkData::new(); 268 | data.eax = 0x03; 269 | data.with(thunk10); 270 | } 271 | 272 | { 273 | // Disable cursor 274 | let mut data = ThunkData::new(); 275 | data.eax = 0x0100; 276 | data.ecx = 0x3F00; 277 | data.with(thunk10); 278 | } 279 | 280 | // Clear screen 281 | VGA.lock().clear(); 282 | 283 | // Set logger 284 | LOGGER.init(); 285 | 286 | let mut os = OsBios { 287 | boot_disk, 288 | thunk10, 289 | thunk13, 290 | thunk15, 291 | thunk16, 292 | }; 293 | 294 | let (heap_start, heap_size) = memory_map(os.thunk15).expect("No memory for heap"); 295 | 296 | ALLOCATOR.lock().init(heap_start as *mut u8, heap_size); 297 | 298 | let (page_phys, func, args) = crate::main(&mut os); 299 | 300 | kernel_entry( 301 | page_phys, 302 | args.stack_base 303 | + args.stack_size 304 | + if crate::KERNEL_64BIT { 305 | crate::arch::x64::PHYS_OFFSET 306 | } else { 307 | crate::arch::x32::PHYS_OFFSET as u64 308 | }, 309 | func, 310 | &args, 311 | if crate::KERNEL_64BIT { 1 } else { 0 }, 312 | ); 313 | } 314 | -------------------------------------------------------------------------------- /src/os/bios/panic.rs: -------------------------------------------------------------------------------- 1 | //! Intrinsics for panic handling 2 | 3 | use core::alloc::Layout; 4 | use core::arch::asm; 5 | use core::panic::PanicInfo; 6 | 7 | #[lang = "eh_personality"] 8 | pub extern "C" fn rust_eh_personality() {} 9 | 10 | /// Required to handle panics 11 | #[panic_handler] 12 | pub fn rust_begin_unwind(info: &PanicInfo) -> ! { 13 | unsafe { 14 | println!("BOOTLOADER PANIC:\n{}", info); 15 | loop { 16 | asm!("hlt"); 17 | } 18 | } 19 | } 20 | 21 | #[alloc_error_handler] 22 | #[allow(improper_ctypes_definitions)] // Layout is not repr(C) 23 | pub extern "C" fn rust_oom(_layout: Layout) -> ! { 24 | panic!("memory allocation failed"); 25 | } 26 | -------------------------------------------------------------------------------- /src/os/bios/serial.rs: -------------------------------------------------------------------------------- 1 | use spin::Mutex; 2 | use syscall::Pio; 3 | 4 | use crate::serial_16550::SerialPort; 5 | 6 | pub static COM1: Mutex>> = Mutex::new(SerialPort::>::new(0x3F8)); 7 | pub static COM2: Mutex>> = Mutex::new(SerialPort::>::new(0x2F8)); 8 | pub static COM3: Mutex>> = Mutex::new(SerialPort::>::new(0x3E8)); 9 | pub static COM4: Mutex>> = Mutex::new(SerialPort::>::new(0x2E8)); 10 | -------------------------------------------------------------------------------- /src/os/bios/thunk.rs: -------------------------------------------------------------------------------- 1 | use core::ptr; 2 | 3 | use super::THUNK_STACK_ADDR; 4 | 5 | #[allow(dead_code)] 6 | #[derive(Clone, Copy, Debug)] 7 | #[repr(C, packed)] 8 | pub struct ThunkData { 9 | pub es: u16, 10 | pub edi: u32, 11 | pub esi: u32, 12 | pub ebp: u32, 13 | pub ebx: u32, 14 | pub edx: u32, 15 | pub ecx: u32, 16 | pub eax: u32, 17 | } 18 | 19 | impl ThunkData { 20 | pub fn new() -> Self { 21 | Self { 22 | es: 0, 23 | edi: 0, 24 | esi: 0, 25 | ebp: 0, 26 | ebx: 0, 27 | edx: 0, 28 | ecx: 0, 29 | eax: 0, 30 | } 31 | } 32 | 33 | pub unsafe fn save(&self) { 34 | ptr::write((THUNK_STACK_ADDR - 64) as *mut ThunkData, *self); 35 | } 36 | 37 | pub unsafe fn load(&mut self) { 38 | *self = ptr::read((THUNK_STACK_ADDR - 64) as *const ThunkData); 39 | } 40 | 41 | pub unsafe fn with(&mut self, f: extern "C" fn()) { 42 | self.save(); 43 | f(); 44 | self.load(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/os/bios/vbe.rs: -------------------------------------------------------------------------------- 1 | use core::ptr; 2 | use log::error; 3 | 4 | use crate::os::OsVideoMode; 5 | 6 | use super::{ThunkData, VBE_CARD_INFO_ADDR, VBE_MODE_INFO_ADDR}; 7 | 8 | #[derive(Clone, Copy, Debug)] 9 | #[repr(C, packed)] 10 | pub struct VbeFarPtr { 11 | pub offset: u16, 12 | pub segment: u16, 13 | } 14 | 15 | impl VbeFarPtr { 16 | pub unsafe fn as_ptr(&self) -> *const T { 17 | (((self.segment as usize) << 4) + (self.offset as usize)) as *const T 18 | } 19 | } 20 | 21 | #[derive(Clone, Copy, Debug)] 22 | #[repr(C, packed)] 23 | pub struct VbeCardInfo { 24 | pub signature: [u8; 4], 25 | pub version: u16, 26 | pub oemstring: VbeFarPtr, 27 | pub capabilities: [u8; 4], 28 | pub videomodeptr: VbeFarPtr, 29 | pub totalmemory: u16, 30 | pub oemsoftwarerev: u16, 31 | pub oemvendornameptr: VbeFarPtr, 32 | pub oemproductnameptr: VbeFarPtr, 33 | pub oemproductrevptr: VbeFarPtr, 34 | pub reserved: [u8; 222], 35 | pub oemdata: [u8; 256], 36 | } 37 | 38 | #[derive(Clone, Copy, Debug)] 39 | #[repr(C, packed)] 40 | pub struct VbeModeInfo { 41 | pub attributes: u16, 42 | pub win_a: u8, 43 | pub win_b: u8, 44 | pub granularity: u16, 45 | pub winsize: u16, 46 | pub segment_a: u16, 47 | pub segment_b: u16, 48 | pub winfuncptr: u32, 49 | pub bytesperscanline: u16, 50 | pub xresolution: u16, 51 | pub yresolution: u16, 52 | pub xcharsize: u8, 53 | pub ycharsize: u8, 54 | pub numberofplanes: u8, 55 | pub bitsperpixel: u8, 56 | pub numberofbanks: u8, 57 | pub memorymodel: u8, 58 | pub banksize: u8, 59 | pub numberofimagepages: u8, 60 | pub unused: u8, 61 | pub redmasksize: u8, 62 | pub redfieldposition: u8, 63 | pub greenmasksize: u8, 64 | pub greenfieldposition: u8, 65 | pub bluemasksize: u8, 66 | pub bluefieldposition: u8, 67 | pub rsvdmasksize: u8, 68 | pub rsvdfieldposition: u8, 69 | pub directcolormodeinfo: u8, 70 | pub physbaseptr: u32, 71 | pub offscreenmemoryoffset: u32, 72 | pub offscreenmemsize: u16, 73 | pub reserved: [u8; 206], 74 | } 75 | 76 | pub struct VideoModeIter { 77 | thunk10: extern "C" fn(), 78 | mode_ptr: *const u16, 79 | } 80 | 81 | impl VideoModeIter { 82 | pub fn new(thunk10: extern "C" fn()) -> Self { 83 | // Get card info 84 | let mut data = ThunkData::new(); 85 | data.eax = 0x4F00; 86 | data.edi = VBE_CARD_INFO_ADDR as u32; 87 | unsafe { 88 | data.with(thunk10); 89 | } 90 | let mode_ptr = if data.eax == 0x004F { 91 | let card_info = unsafe { ptr::read(VBE_CARD_INFO_ADDR as *const VbeCardInfo) }; 92 | unsafe { card_info.videomodeptr.as_ptr::() } 93 | } else { 94 | error!("Failed to read VBE card info: 0x{:04X}", { data.eax }); 95 | ptr::null() 96 | }; 97 | Self { thunk10, mode_ptr } 98 | } 99 | } 100 | 101 | impl Iterator for VideoModeIter { 102 | type Item = OsVideoMode; 103 | fn next(&mut self) -> Option { 104 | if self.mode_ptr.is_null() { 105 | return None; 106 | } 107 | 108 | loop { 109 | // Set bit 14 to get linear frame buffer 110 | let mode = unsafe { *self.mode_ptr } | (1 << 14); 111 | if mode == 0xFFFF { 112 | return None; 113 | } 114 | self.mode_ptr = unsafe { self.mode_ptr.add(1) }; 115 | 116 | // Get mode info 117 | let mut data = ThunkData::new(); 118 | data.eax = 0x4F01; 119 | data.ecx = mode as u32; 120 | data.edi = VBE_MODE_INFO_ADDR as u32; 121 | unsafe { 122 | data.with(self.thunk10); 123 | } 124 | if data.eax == 0x004F { 125 | let mode_info = unsafe { ptr::read(VBE_MODE_INFO_ADDR as *const VbeModeInfo) }; 126 | 127 | // We only support 32-bits per pixel modes 128 | if mode_info.bitsperpixel != 32 { 129 | continue; 130 | } 131 | 132 | let width = mode_info.xresolution as u32; 133 | let height = mode_info.yresolution as u32; 134 | //TODO: support stride that is not a multiple of 4 135 | let stride = mode_info.bytesperscanline as u32 / 4; 136 | 137 | return Some(OsVideoMode { 138 | id: mode as u32, 139 | width, 140 | height, 141 | stride, 142 | base: mode_info.physbaseptr as u64, 143 | }); 144 | } else { 145 | error!("Failed to read VBE mode 0x{:04X} info: 0x{:04X}", mode, { 146 | data.eax 147 | }); 148 | } 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/os/bios/vga.rs: -------------------------------------------------------------------------------- 1 | use core::{fmt, slice}; 2 | 3 | #[derive(Clone, Copy)] 4 | #[repr(C, packed)] 5 | pub struct VgaTextBlock { 6 | pub char: u8, 7 | pub color: u8, 8 | } 9 | 10 | #[allow(dead_code)] 11 | #[derive(Clone, Copy)] 12 | #[repr(u8)] 13 | pub enum VgaTextColor { 14 | Black = 0, 15 | Blue = 1, 16 | Green = 2, 17 | Cyan = 3, 18 | Red = 4, 19 | Purple = 5, 20 | Brown = 6, 21 | Gray = 7, 22 | DarkGray = 8, 23 | LightBlue = 9, 24 | LightGreen = 10, 25 | LightCyan = 11, 26 | LightRed = 12, 27 | LightPurple = 13, 28 | Yellow = 14, 29 | White = 15, 30 | } 31 | 32 | pub struct Vga { 33 | pub base: usize, 34 | pub width: usize, 35 | pub height: usize, 36 | pub x: usize, 37 | pub y: usize, 38 | pub bg: VgaTextColor, 39 | pub fg: VgaTextColor, 40 | } 41 | 42 | impl Vga { 43 | pub const unsafe fn new(base: usize, width: usize, height: usize) -> Self { 44 | Self { 45 | base, 46 | width, 47 | height, 48 | x: 0, 49 | y: 0, 50 | bg: VgaTextColor::Black, 51 | fg: VgaTextColor::Gray, 52 | } 53 | } 54 | 55 | pub unsafe fn blocks(&mut self) -> &'static mut [VgaTextBlock] { 56 | slice::from_raw_parts_mut(self.base as *mut VgaTextBlock, self.width * self.height) 57 | } 58 | 59 | pub fn clear(&mut self) { 60 | self.x = 0; 61 | self.y = 0; 62 | let blocks = unsafe { self.blocks() }; 63 | for i in 0..blocks.len() { 64 | blocks[i] = VgaTextBlock { 65 | char: 0, 66 | color: ((self.bg as u8) << 4) | (self.fg as u8), 67 | }; 68 | } 69 | } 70 | } 71 | 72 | impl fmt::Write for Vga { 73 | fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { 74 | let blocks = unsafe { self.blocks() }; 75 | for c in s.chars() { 76 | if self.x >= self.width { 77 | self.x = 0; 78 | self.y += 1; 79 | } 80 | while self.y >= self.height { 81 | for y in 1..self.height { 82 | for x in 0..self.width { 83 | let i = y * self.width + x; 84 | let j = i - self.width; 85 | blocks[j] = blocks[i]; 86 | if y + 1 == self.height { 87 | blocks[i].char = 0; 88 | } 89 | } 90 | } 91 | self.y -= 1; 92 | } 93 | match c { 94 | '\x08' => { 95 | if self.x > 0 { 96 | self.x -= 1; 97 | } 98 | } 99 | '\r' => { 100 | self.x = 0; 101 | } 102 | '\n' => { 103 | self.x = 0; 104 | self.y += 1; 105 | } 106 | _ => { 107 | let i = self.y * self.width + self.x; 108 | if let Some(block) = blocks.get_mut(i) { 109 | block.char = c as u8; 110 | block.color = ((self.bg as u8) << 4) | (self.fg as u8); 111 | } 112 | self.x += 1; 113 | } 114 | } 115 | } 116 | 117 | Ok(()) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/os/mod.rs: -------------------------------------------------------------------------------- 1 | use redoxfs::Disk; 2 | 3 | #[cfg(all(target_arch = "x86", target_os = "none"))] 4 | pub use self::bios::*; 5 | 6 | #[cfg(all(target_arch = "x86", target_os = "none"))] 7 | #[macro_use] 8 | mod bios; 9 | 10 | #[cfg(any(target_arch = "riscv64", target_os = "uefi"))] 11 | #[allow(unused_imports)] 12 | pub use self::uefi::*; 13 | 14 | #[cfg(any(target_arch = "riscv64", target_os = "uefi"))] 15 | #[macro_use] 16 | mod uefi; 17 | 18 | #[derive(Clone, Copy, Debug)] 19 | pub enum OsHwDesc { 20 | Acpi(u64, u64), 21 | DeviceTree(u64, u64), 22 | NotFound, 23 | } 24 | 25 | #[derive(Clone, Copy, Debug)] 26 | pub enum OsKey { 27 | Left, 28 | Right, 29 | Up, 30 | Down, 31 | Backspace, 32 | Delete, 33 | Enter, 34 | Char(char), 35 | Other, 36 | } 37 | 38 | // Keep synced with BootloaderMemoryKind in kernel 39 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 40 | #[repr(u64)] 41 | pub enum OsMemoryKind { 42 | Null = 0, 43 | Free = 1, 44 | Reclaim = 2, 45 | Reserved = 3, 46 | } 47 | 48 | // Keep synced with BootloaderMemoryEntry in kernel 49 | #[derive(Clone, Copy, Debug)] 50 | #[repr(C, packed(8))] 51 | pub struct OsMemoryEntry { 52 | pub base: u64, 53 | pub size: u64, 54 | pub kind: OsMemoryKind, 55 | } 56 | 57 | #[derive(Clone, Copy, Debug)] 58 | pub struct OsVideoMode { 59 | pub id: u32, 60 | pub width: u32, 61 | pub height: u32, 62 | pub stride: u32, 63 | pub base: u64, 64 | } 65 | 66 | pub trait Os { 67 | type D: Disk; 68 | type V: Iterator; 69 | 70 | fn name(&self) -> &str; 71 | 72 | fn alloc_zeroed_page_aligned(&self, size: usize) -> *mut u8; 73 | 74 | #[allow(dead_code)] 75 | fn page_size(&self) -> usize; 76 | 77 | fn filesystem( 78 | &self, 79 | password_opt: Option<&[u8]>, 80 | ) -> syscall::Result>; 81 | 82 | fn hwdesc(&self) -> OsHwDesc; 83 | 84 | fn video_outputs(&self) -> usize; 85 | fn video_modes(&self, output_i: usize) -> Self::V; 86 | fn set_video_mode(&self, output_i: usize, mode: &mut OsVideoMode); 87 | fn best_resolution(&self, output_i: usize) -> Option<(u32, u32)>; 88 | 89 | fn get_key(&self) -> OsKey; 90 | 91 | fn clear_text(&self); 92 | fn get_text_position(&self) -> (usize, usize); 93 | fn set_text_position(&self, x: usize, y: usize); 94 | fn set_text_highlight(&self, highlight: bool); 95 | } 96 | -------------------------------------------------------------------------------- /src/os/uefi/acpi.rs: -------------------------------------------------------------------------------- 1 | use core::slice; 2 | use uefi::guid::{ACPI_20_TABLE_GUID, ACPI_TABLE_GUID}; 3 | 4 | use crate::Os; 5 | 6 | struct Invalid; 7 | 8 | fn validate_rsdp(address: usize, _v2: bool) -> core::result::Result { 9 | #[repr(C, packed)] 10 | #[derive(Clone, Copy, Debug)] 11 | struct Rsdp { 12 | signature: [u8; 8], // b"RSD PTR " 13 | chksum: u8, 14 | oem_id: [u8; 6], 15 | revision: u8, 16 | rsdt_addr: u32, 17 | // the following fields are only available for ACPI 2.0, and are reserved otherwise 18 | length: u32, 19 | xsdt_addr: u64, 20 | extended_chksum: u8, 21 | _rsvd: [u8; 3], 22 | } 23 | // paging is not enabled at this stage; we can just read the physical address here. 24 | let rsdp_bytes = 25 | unsafe { core::slice::from_raw_parts(address as *const u8, core::mem::size_of::()) }; 26 | let rsdp = unsafe { 27 | (rsdp_bytes.as_ptr() as *const Rsdp) 28 | .as_ref::<'static>() 29 | .unwrap() 30 | }; 31 | 32 | log::debug!("RSDP: {:?}", rsdp); 33 | 34 | if rsdp.signature != *b"RSD PTR " { 35 | return Err(Invalid); 36 | } 37 | let mut base_sum = 0u8; 38 | for base_byte in &rsdp_bytes[..20] { 39 | base_sum = base_sum.wrapping_add(*base_byte); 40 | } 41 | if base_sum != 0 { 42 | return Err(Invalid); 43 | } 44 | 45 | if rsdp.revision == 2 { 46 | let mut extended_sum = 0u8; 47 | for byte in rsdp_bytes { 48 | extended_sum = extended_sum.wrapping_add(*byte); 49 | } 50 | 51 | if extended_sum != 0 { 52 | return Err(Invalid); 53 | } 54 | } 55 | 56 | let length = if rsdp.revision == 2 { 57 | rsdp.length as usize 58 | } else { 59 | core::mem::size_of::() 60 | }; 61 | 62 | Ok(length) 63 | } 64 | 65 | pub(crate) fn find_acpi_table_pointers(os: &impl Os) -> Option<(u64, u64)> { 66 | let cfg_tables = std::system_table().config_tables(); 67 | let mut acpi = None; 68 | let mut acpi2 = None; 69 | for cfg_table in cfg_tables.iter() { 70 | if cfg_table.VendorGuid == ACPI_TABLE_GUID { 71 | match validate_rsdp(cfg_table.VendorTable, false) { 72 | Ok(length) => { 73 | acpi = Some(unsafe { 74 | core::slice::from_raw_parts(cfg_table.VendorTable as *const u8, length) 75 | }); 76 | } 77 | Err(_) => log::warn!( 78 | "Found RSDP that was not valid at {:p}", 79 | cfg_table.VendorTable as *const u8 80 | ), 81 | } 82 | } else if cfg_table.VendorGuid == ACPI_20_TABLE_GUID { 83 | match validate_rsdp(cfg_table.VendorTable, true) { 84 | Ok(length) => { 85 | acpi2 = Some(unsafe { 86 | core::slice::from_raw_parts(cfg_table.VendorTable as *const u8, length) 87 | }); 88 | } 89 | Err(_) => log::warn!( 90 | "Found RSDP that was not valid at {:p}", 91 | cfg_table.VendorTable as *const u8 92 | ), 93 | } 94 | } 95 | } 96 | 97 | let rsdp_area = acpi2.or(acpi).unwrap_or(&[]); 98 | 99 | if !rsdp_area.is_empty() { 100 | unsafe { 101 | // Copy to page aligned area 102 | let size = rsdp_area.len(); 103 | let base = os.alloc_zeroed_page_aligned(size); 104 | slice::from_raw_parts_mut(base, size).copy_from_slice(rsdp_area); 105 | Some((base as u64, size as u64)) 106 | } 107 | } else { 108 | None 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/os/uefi/arch/aarch64.rs: -------------------------------------------------------------------------------- 1 | use core::{arch::asm, fmt::Write, mem, slice}; 2 | use uefi::status::Result; 3 | 4 | use crate::{ 5 | arch::{ENTRY_ADDRESS_MASK, PAGE_ENTRIES, PF_PRESENT, PF_TABLE, PHYS_OFFSET}, 6 | logger::LOGGER, 7 | KernelArgs, 8 | }; 9 | 10 | use super::super::{memory_map::memory_map, OsEfi}; 11 | 12 | unsafe fn dump_page_tables(table_phys: u64, table_virt: u64, table_level: u64) { 13 | let entries = slice::from_raw_parts(table_phys as *const u64, PAGE_ENTRIES); 14 | for (i, entry) in entries.iter().enumerate() { 15 | let phys = entry & ENTRY_ADDRESS_MASK; 16 | let flags = entry & !ENTRY_ADDRESS_MASK; 17 | if flags & PF_PRESENT == 0 { 18 | continue; 19 | } 20 | let mut shift = 39u64; 21 | for _ in 0..table_level { 22 | shift -= 9; 23 | print!("\t"); 24 | } 25 | let virt = table_virt + (i as u64) << shift; 26 | println!( 27 | "index {} virt {:#x}: phys {:#x} flags {:#x}", 28 | i, virt, phys, flags 29 | ); 30 | if table_level < 3 && flags & PF_TABLE == PF_TABLE { 31 | dump_page_tables(phys, virt, table_level + 1); 32 | } 33 | } 34 | } 35 | 36 | unsafe extern "C" fn kernel_entry( 37 | page_phys: usize, 38 | stack: u64, 39 | func: u64, 40 | args: *const KernelArgs, 41 | ) -> ! { 42 | // Read memory map and exit boot services 43 | memory_map().exit_boot_services(); 44 | 45 | let currentel: u64; 46 | asm!( 47 | "mrs {0}, currentel", // Read current exception level 48 | out(reg) currentel, 49 | ); 50 | if currentel == (2 << 2) { 51 | // Need to drop from EL2 to EL1 52 | 53 | // Allow access to timers 54 | asm!( 55 | "mrs {0}, cnthctl_el2", 56 | "orr {0}, {0}, #0x3", 57 | "msr cnthctl_el2, {0}", 58 | "msr cntvoff_el2, xzr", 59 | out(reg) _ 60 | ); 61 | 62 | // Initialize ID registers 63 | asm!( 64 | "mrs {0}, midr_el1", 65 | "msr vpidr_el2, {0}", 66 | "mrs {0}, mpidr_el1", 67 | "msr vmpidr_el2, {0}", 68 | out(reg) _ 69 | ); 70 | 71 | // Disable traps 72 | asm!( 73 | "msr cptr_el2, {0}", 74 | "msr hstr_el2, xzr", 75 | in(reg) 0x33FF as u64 76 | ); 77 | 78 | // Enable floating point 79 | asm!( 80 | "msr cpacr_el1, {0}", 81 | in(reg) (3 << 20) as u64 82 | ); 83 | 84 | // Set EL1 system control register 85 | asm!( 86 | "msr sctlr_el1, {0}", 87 | in(reg) 0x30d00800 as u64 88 | ); 89 | 90 | // Set EL1 stack and VBAR 91 | asm!( 92 | "mov {0}, sp", 93 | "msr sp_el1, {0}", 94 | "mrs {0}, vbar_el2", 95 | "msr vbar_el1, {0}", 96 | out(reg) _ 97 | ); 98 | 99 | // Configure execution state of EL1 as aarch64 and disable hypervisor call 100 | asm!( 101 | "msr hcr_el2, {0}", 102 | in(reg) ((1 << 31) | (1 << 29)) as u64, 103 | ); 104 | 105 | // Set saved program status register 106 | asm!( 107 | "msr spsr_el2, {0}", 108 | in(reg) 0x3C5 as u64 109 | ); 110 | 111 | // Switch to EL1 112 | asm!( 113 | "adr {0}, 1f", 114 | "msr elr_el2, {0}", 115 | "eret", 116 | "1:", 117 | out(reg) _ 118 | ); 119 | } else if currentel == (1 << 2) { 120 | // Already in EL1 121 | } else { 122 | //TODO: what to do if not EL2 or already EL1? 123 | loop { 124 | asm!("wfi"); 125 | } 126 | } 127 | 128 | // Disable MMU 129 | asm!( 130 | "mrs {0}, sctlr_el1", // Read system control register 131 | "bic {0}, {0}, 1", // Clear MMU enable bit 132 | "msr sctlr_el1, {0}", // Write system control register 133 | "isb", // Instruction sync barrier 134 | out(reg) _, 135 | ); 136 | 137 | // Set MAIR 138 | // You can think about MAIRs as of an array with 8 elements each of 8 bits long. 139 | // 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. 140 | // https://lowenware.com/blog/aarch64-mmu-programming/ 141 | // https://developer.arm.com/documentation/102376/0200/Describing-memory-in-AArch64 142 | // https://developer.arm.com/documentation/ddi0595/2021-06/AArch64-Registers/MAIR-EL1--Memory-Attribute-Indirection-Register--EL1- 143 | // Attribute 0 (0xFF) - normal memory, caches are enabled 144 | // 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) 145 | // Attribute 2 (0x00) - nGnRnE device memory, caches are disabled, gathering, re-ordering, and early write acknowledgement aren't allowed. 146 | asm!( 147 | "msr mair_el1, {0}", 148 | in(reg) 0x00000000000044FF as u64, // MAIR: Arrange for Device, Normal Non-Cache, Normal Write-Back access types 149 | ); 150 | 151 | // Set TCR 152 | asm!( 153 | "mrs {1}, id_aa64mmfr0_el1", // Read memory model feature register 154 | "bfi {0}, {1}, #32, #3", 155 | "msr tcr_el1, {0}", // Write translation control register 156 | "isb", // Instruction sync barrier 157 | in(reg) 0x1085100510u64, // TCR: (TxSZ, ASID_16, TG1_4K, Cache Attrs, SMP Attrs) 158 | out(reg) _, 159 | ); 160 | 161 | // Set page tables 162 | asm!( 163 | "dsb sy", // Data sync barrier 164 | "msr ttbr1_el1, {0}", // Set higher half page table 165 | "msr ttbr0_el1, {0}", // Set lower half page table 166 | "isb", // Instruction sync barrier 167 | "dsb ishst", // Data sync barrier, only for stores, and only for inner shareable domain 168 | "tlbi vmalle1is", // Invalidate TLB 169 | "dsb ish", // Dta sync bariar, only for inner shareable domain 170 | "isb", // Instruction sync barrier 171 | in(reg) page_phys, 172 | ); 173 | 174 | // Enable MMU 175 | asm!( 176 | "mrs {2}, sctlr_el1", // Read system control register 177 | "bic {2}, {2}, {0}", // Clear bits 178 | "orr {2}, {2}, {1}", // Set bits 179 | "msr sctlr_el1, {2}", // Write system control register 180 | "isb", // Instruction sync barrier 181 | in(reg) 0x32802c2u64, // Clear SCTLR bits: (EE, EOE, IESB, WXN, UMA, ITD, THEE, A) 182 | in(reg) 0x3485d13du64, // Set SCTLR bits: (LSMAOE, nTLSMD, UCI, SPAN, nTWW, nTWI, UCT, DZE, I, SED, SA0, SA, C, M, CP15BEN) 183 | out(reg) _, 184 | ); 185 | 186 | // Set stack 187 | asm!("mov sp, {}", in(reg) stack); 188 | 189 | // Call kernel entry 190 | let entry_fn: extern "C" fn(*const KernelArgs) -> ! = mem::transmute(func); 191 | entry_fn(args); 192 | } 193 | 194 | pub fn main() -> Result<()> { 195 | LOGGER.init(); 196 | 197 | let mut os = OsEfi::new(); 198 | 199 | // Disable cursor 200 | let _ = (os.st.ConsoleOut.EnableCursor)(os.st.ConsoleOut, false); 201 | 202 | let currentel: u64; 203 | unsafe { 204 | asm!( 205 | "mrs {0}, currentel", // Read current exception level 206 | out(reg) currentel, 207 | ); 208 | } 209 | log::info!("Currently in EL{}", (currentel >> 2) & 3); 210 | 211 | let (page_phys, func, args) = crate::main(&mut os); 212 | 213 | unsafe { 214 | let stack = args.stack_base + args.stack_size + PHYS_OFFSET; 215 | 216 | // dump_page_tables(page_phys as _, 0, 0); 217 | 218 | println!( 219 | "kernel_entry({:#x}, {:#x}, {:#x}, {:p})", 220 | page_phys, stack, func, &args 221 | ); 222 | println!("{:#x?}", args); 223 | 224 | kernel_entry(page_phys, stack, func, &args); 225 | } 226 | } 227 | 228 | pub fn disable_interrupts() { 229 | unsafe { 230 | asm!("msr daifset, #2"); 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/os/uefi/arch/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_arch = "aarch64")] 2 | mod aarch64; 3 | #[cfg(target_arch = "aarch64")] 4 | pub use self::aarch64::*; 5 | 6 | #[cfg(target_arch = "x86_64")] 7 | mod x86_64; 8 | #[cfg(target_arch = "x86_64")] 9 | pub use self::x86_64::*; 10 | 11 | #[cfg(target_arch = "riscv64")] 12 | mod riscv64; 13 | #[cfg(target_arch = "riscv64")] 14 | pub use self::riscv64::*; 15 | -------------------------------------------------------------------------------- /src/os/uefi/arch/riscv64/boot_protocol.rs: -------------------------------------------------------------------------------- 1 | use std::proto::Protocol; 2 | use uefi::guid::Guid; 3 | use uefi::status::{Result, Status}; 4 | 5 | #[derive(Debug)] 6 | #[repr(C)] 7 | struct RiscVEfiBootProtocol { 8 | pub revision: u64, 9 | pub efi_get_boot_hartid: 10 | unsafe extern "efiapi" fn(this: *mut Self, phartid: *mut usize) -> Status, 11 | } 12 | 13 | impl RiscVEfiBootProtocol { 14 | pub const GUID: Guid = Guid::parse_str("ccd15fec-6f73-4eec-8395-3e69e4b940bf"); 15 | // pub const REVISION: u64 = 0x00010000; 16 | } 17 | 18 | struct RiscVEfiBoot(pub &'static mut RiscVEfiBootProtocol); 19 | 20 | impl Protocol for RiscVEfiBoot { 21 | fn guid() -> Guid { 22 | RiscVEfiBootProtocol::GUID 23 | } 24 | 25 | fn new(inner: &'static mut RiscVEfiBootProtocol) -> Self { 26 | Self(inner) 27 | } 28 | } 29 | 30 | impl RiscVEfiBoot { 31 | pub fn efi_get_boot_hartid(&mut self) -> Result { 32 | let mut boot_hartid: usize = 0; 33 | match unsafe { (self.0.efi_get_boot_hartid)(self.0, &mut boot_hartid) } { 34 | ok if ok.is_success() => Ok(boot_hartid), 35 | err => Err(err), 36 | } 37 | } 38 | } 39 | 40 | pub fn efi_get_boot_hartid() -> Result { 41 | let handles = RiscVEfiBoot::locate_handle()?; 42 | let handle = handles.first().ok_or(Status::NOT_FOUND)?; 43 | let mut proto = RiscVEfiBoot::handle_protocol(*handle)?; 44 | proto.efi_get_boot_hartid() 45 | } 46 | -------------------------------------------------------------------------------- /src/os/uefi/arch/riscv64/coff_helper.rs: -------------------------------------------------------------------------------- 1 | use core::arch::{global_asm, naked_asm}; 2 | 3 | /// Unfortunately this can't be written in Rust because it might use some not-yet 4 | /// relocated data such as jump tables 5 | #[naked] 6 | #[no_mangle] 7 | extern "C" fn coff_relocate(dynentry: *const u8, base: usize) -> usize { 8 | unsafe { 9 | naked_asm!( 10 | " 11 | mv t4, zero // RELA 12 | li t5, -1 // RELASZ 13 | li t6, -1 // RELAENT 14 | 15 | 5: 16 | ld t0, 0(a0) 17 | beqz t0, 6f 18 | addi a0, a0, 16 19 | addi t0, t0, -4 20 | bltz t0, 3f // fail on DT_NEEDED=1, DT_PLTRELSZ=2, DT_PLTGOT=3 21 | addi t0, t0, -3 22 | bltz t0, 5b // skip DT_HASH=4, DT_STRTAB=5, DT_SYMTAB=6 23 | bnez t0, 2f 24 | ld t4, -8(a0) // DT_RELA=7 25 | j 5b 26 | 2: addi t0, t0, -1 // DT_RELASZ=8 27 | bnez t0, 2f 28 | ld t5, -8(a0) 29 | j 5b 30 | 2: addi t0, t0, -1 // DT_RELAENT=9 31 | bnez t0, 2f 32 | ld t6, -8(a0) 33 | j 5b 34 | 2: addi t0, t0, -3 35 | bltz t0, 5b // skip DT_STRSZ=10, DT_SYMENT=11 36 | addi t0, t0, -2 37 | bltz t0, 3f // fail on DT_INIT=12, DT_FINI=13 38 | beqz t0, 5b // skip DT_SONAME=14 39 | 2: addi t0, t0, -2 40 | bltz t0, 3f // fail on DT_RPATH 41 | beqz t0, 5b // skip SYMBOLIC=16 42 | li t1, 0x6ffffef5-16 43 | sub t0, t0, t1 44 | beqz t0, 5b // skip DT_GNU_HASH=0x6ffffef5 45 | nop 46 | 3: // error 47 | mv a0, zero 48 | ret 49 | 50 | 6: 51 | bnez t4, 2f 52 | 4: // success 53 | li a0, 1 54 | ret 55 | 2: bltz t5, 3b 56 | blez t6, 3b 57 | 58 | add t4, t4, a1 59 | add t5, t5, t4 60 | 7: 61 | bge t4, t5, 4b 62 | ld t0, 0(t4) // r_offset 63 | add t0, t0, a1 64 | lwu t1, 8(t4) // r_type 65 | ld t2, 16(t4) // r_addend 66 | add t4, t4, t6 67 | addi t1, t1, -3 // R_RISCV_RELATIVE=3 68 | bnez t1, 3b 69 | add t2, t2, a1 // RELATIVE: *value = base + addend 70 | sd t2, 0(t0) 71 | j 7b 72 | " 73 | ) 74 | } 75 | } 76 | 77 | global_asm!( 78 | r#" 79 | .global coff_start 80 | coff_start: 81 | .option norelax 82 | addi sp, sp, -24 83 | sd a0, 0(sp) 84 | sd a1, 8(sp) 85 | sd ra, 16(sp) 86 | lla a0, _DYNAMIC 87 | lla a1, ImageBase // actual loaded image base to relocate to 88 | jal coff_relocate 89 | .option relax 90 | mv t0, a0 91 | ld a0, 0(sp) 92 | ld a1, 8(sp) 93 | ld ra, 16(sp) 94 | addi sp, sp, 24 95 | beqz t0, 2f 96 | j efi_main 97 | 2: ret 98 | "# 99 | ); 100 | 101 | // GNU-EFI .reloc trick to make objcopy say we are relocatable 102 | global_asm!( 103 | r#" 104 | .section .data 105 | DUMMY_RELOCATION: .4byte 0 106 | .section .reloc, "a" 107 | 108 | 2: 109 | .4byte DUMMY_RELOCATION - ImageBase 110 | .4byte 12 111 | .4byte 0 112 | "# 113 | ); 114 | -------------------------------------------------------------------------------- /src/os/uefi/arch/riscv64/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::arch::PHYS_OFFSET; 2 | use crate::logger::LOGGER; 3 | use crate::os::uefi::memory_map::memory_map; 4 | use crate::os::OsEfi; 5 | use crate::KernelArgs; 6 | use core::arch::asm; 7 | use core::mem; 8 | use uefi::status::Result; 9 | 10 | mod boot_protocol; 11 | mod coff_helper; 12 | 13 | pub use boot_protocol::*; 14 | 15 | unsafe extern "C" fn kernel_entry( 16 | page_phys: usize, 17 | stack: u64, 18 | func: u64, 19 | args: *const KernelArgs, 20 | ) -> ! { 21 | // Set page tables 22 | asm!( 23 | "sfence.vma", 24 | "csrw satp, {0}", 25 | in(reg) (page_phys >> 12 | 9 << 60) // Sv48 mode 26 | ); 27 | 28 | let entry_fn: extern "C" fn(*const KernelArgs) -> ! = mem::transmute(func); 29 | 30 | // Set stack and go to kernel 31 | asm!("mv sp, {0}", 32 | "mv a0, {1}", 33 | "jalr {2}", 34 | in(reg) stack, 35 | in(reg) args, 36 | in(reg) entry_fn 37 | ); 38 | loop {} 39 | } 40 | 41 | pub fn main() -> Result<()> { 42 | LOGGER.init(); 43 | 44 | let mut os = OsEfi::new(); 45 | 46 | // Disable cursor 47 | let _ = (os.st.ConsoleOut.EnableCursor)(os.st.ConsoleOut, false); 48 | 49 | let (page_phys, func, args) = crate::main(&mut os); 50 | 51 | unsafe { 52 | memory_map().exit_boot_services(); 53 | 54 | kernel_entry( 55 | page_phys, 56 | args.stack_base + args.stack_size + PHYS_OFFSET, 57 | func, 58 | &args, 59 | ); 60 | } 61 | } 62 | 63 | pub fn disable_interrupts() { 64 | unsafe { 65 | asm!("csrci sstatus, 2"); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/os/uefi/arch/x86_64.rs: -------------------------------------------------------------------------------- 1 | use core::{arch::asm, mem}; 2 | use uefi::status::Result; 3 | use x86::{ 4 | controlregs::{self, Cr0, Cr4}, 5 | msr, 6 | }; 7 | 8 | use crate::{logger::LOGGER, KernelArgs}; 9 | 10 | use super::super::{memory_map::memory_map, OsEfi}; 11 | 12 | unsafe extern "C" fn kernel_entry( 13 | page_phys: usize, 14 | stack: u64, 15 | func: u64, 16 | args: *const KernelArgs, 17 | ) -> ! { 18 | // Read memory map and exit boot services 19 | memory_map().exit_boot_services(); 20 | 21 | // Enable FXSAVE/FXRSTOR, Page Global, Page Address Extension, and Page Size Extension 22 | let mut cr4 = controlregs::cr4(); 23 | cr4 |= Cr4::CR4_ENABLE_SSE 24 | | Cr4::CR4_ENABLE_GLOBAL_PAGES 25 | | Cr4::CR4_ENABLE_PAE 26 | | Cr4::CR4_ENABLE_PSE; 27 | controlregs::cr4_write(cr4); 28 | 29 | // Enable Long mode and NX bit 30 | let mut efer = msr::rdmsr(msr::IA32_EFER); 31 | efer |= 1 << 11 | 1 << 8; 32 | msr::wrmsr(msr::IA32_EFER, efer); 33 | 34 | // Set new page map 35 | controlregs::cr3_write(page_phys as u64); 36 | 37 | // Enable paging, write protect kernel, protected mode 38 | let mut cr0 = controlregs::cr0(); 39 | cr0 |= Cr0::CR0_ENABLE_PAGING | Cr0::CR0_WRITE_PROTECT | Cr0::CR0_PROTECTED_MODE; 40 | controlregs::cr0_write(cr0); 41 | 42 | // Set stack 43 | asm!("mov rsp, {}", in(reg) stack); 44 | 45 | // Call kernel entry 46 | let entry_fn: extern "sysv64" fn(*const KernelArgs) -> ! = mem::transmute(func); 47 | entry_fn(args); 48 | } 49 | 50 | pub fn main() -> Result<()> { 51 | LOGGER.init(); 52 | 53 | let mut os = OsEfi::new(); 54 | 55 | // Disable cursor 56 | let _ = (os.st.ConsoleOut.EnableCursor)(os.st.ConsoleOut, false); 57 | 58 | let (page_phys, func, args) = crate::main(&mut os); 59 | 60 | unsafe { 61 | kernel_entry( 62 | page_phys, 63 | args.stack_base 64 | + args.stack_size 65 | + if crate::KERNEL_64BIT { 66 | crate::arch::x64::PHYS_OFFSET 67 | } else { 68 | crate::arch::x32::PHYS_OFFSET as u64 69 | }, 70 | func, 71 | &args, 72 | ); 73 | } 74 | } 75 | 76 | pub fn disable_interrupts() { 77 | unsafe { 78 | asm!("cli"); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/os/uefi/device.rs: -------------------------------------------------------------------------------- 1 | use alloc::{string::String, vec, vec::Vec}; 2 | use core::{fmt::Write, mem, ptr, slice}; 3 | use uefi::{ 4 | device::{ 5 | DevicePath, DevicePathAcpiType, DevicePathBbsType, DevicePathEndType, 6 | DevicePathHardwareType, DevicePathMediaType, DevicePathMessagingType, DevicePathType, 7 | }, 8 | guid::Guid, 9 | status::Status, 10 | Handle, 11 | }; 12 | use uefi_std::{fs::FileSystem, loaded_image::LoadedImage, proto::Protocol}; 13 | 14 | use super::disk::{DiskEfi, DiskOrFileEfi}; 15 | 16 | #[derive(Debug)] 17 | enum DevicePathRelation { 18 | This, 19 | Parent(usize), 20 | Child(usize), 21 | None, 22 | } 23 | 24 | fn device_path_relation(a_path: &DevicePath, b_path: &DevicePath) -> DevicePathRelation { 25 | let mut a_iter = DevicePathIter::new(a_path); 26 | let mut b_iter = DevicePathIter::new(b_path); 27 | loop { 28 | match (a_iter.next(), b_iter.next()) { 29 | (None, None) => return DevicePathRelation::This, 30 | (None, Some(_)) => return DevicePathRelation::Parent(b_iter.count()), 31 | (Some(_), None) => return DevicePathRelation::Child(a_iter.count()), 32 | (Some((a_node, a_data)), Some((b_node, b_data))) => { 33 | if a_node.Type != b_node.Type { 34 | return DevicePathRelation::None; 35 | } 36 | 37 | if a_node.SubType != b_node.SubType { 38 | return DevicePathRelation::None; 39 | } 40 | 41 | if a_data != b_data { 42 | return DevicePathRelation::None; 43 | } 44 | } 45 | } 46 | } 47 | } 48 | 49 | fn esp_live_image(esp_handle: Handle, esp_device_path: &DevicePath) -> Option> { 50 | let mut esp_fs = match FileSystem::handle_protocol(esp_handle) { 51 | Ok(esp_fs) => esp_fs, 52 | Err(err) => { 53 | log::warn!("Failed to find SimpleFileSystem protocol: {:?}", err); 54 | return None; 55 | } 56 | }; 57 | 58 | let mut root = match esp_fs.root() { 59 | Ok(root) => root, 60 | Err(err) => { 61 | log::warn!("Failed to open ESP filesystem: {:?}", err); 62 | return None; 63 | } 64 | }; 65 | 66 | const fn as_utf16_str(s: [u8; N]) -> [u16; N] { 67 | let mut ret = [0; N]; 68 | let mut i = 0; 69 | while i < N { 70 | ret[i] = s[i] as u16; 71 | i += 1; 72 | } 73 | ret 74 | } 75 | 76 | let filename = const { &as_utf16_str(*b"redox-live.img\0") }; 77 | let mut live_image = match root.open(filename) { 78 | Ok(live_image) => live_image, 79 | Err(Status::NOT_FOUND) => return None, 80 | Err(err) => { 81 | log::warn!( 82 | "Failed to open {}\\redox-live.img: {:?}", 83 | device_path_to_string(esp_device_path), 84 | err 85 | ); 86 | return None; 87 | } 88 | }; 89 | 90 | let mut buffer = Vec::new(); 91 | 92 | live_image.read_to_end(&mut buffer).unwrap(); 93 | 94 | Some(buffer) 95 | } 96 | 97 | pub struct DiskDevice { 98 | pub handle: Handle, 99 | pub disk: DiskOrFileEfi, 100 | pub partition_offset: u64, 101 | pub device_path: DevicePathProtocol, 102 | pub file_path: Option<&'static str>, 103 | } 104 | 105 | pub fn disk_device_priority() -> Vec { 106 | // Get the handle of the partition this program was loaded from, which should be the ESP 107 | let esp_handle = match LoadedImage::handle_protocol(std::handle()) { 108 | Ok(loaded_image) => loaded_image.0.DeviceHandle, 109 | Err(err) => { 110 | log::warn!("Failed to find LoadedImage protocol: {:?}", err); 111 | return Vec::new(); 112 | } 113 | }; 114 | 115 | // Get the device path of the ESP 116 | let esp_device_path = match DevicePathProtocol::handle_protocol(esp_handle) { 117 | Ok(ok) => ok, 118 | Err(err) => { 119 | log::warn!( 120 | "Failed to find device path protocol on {:?}: {:?}", 121 | esp_handle, 122 | err 123 | ); 124 | return Vec::new(); 125 | } 126 | }; 127 | 128 | if cfg!(feature = "live") { 129 | // First try to get a live image from redox-live.img. This is required to support netbooting. 130 | if let Some(buffer) = esp_live_image(esp_handle, esp_device_path.0) { 131 | return vec![DiskDevice { 132 | handle: esp_handle, 133 | // Support both a copy of livedisk.iso and a standalone redoxfs partition 134 | partition_offset: if &buffer[512..520] == b"EFI PART" { 135 | //TODO: get block from partition table 136 | 2 * crate::MIBI as u64 137 | } else { 138 | 0 139 | }, 140 | disk: DiskOrFileEfi::File(buffer), 141 | device_path: esp_device_path, 142 | file_path: Some("redox-live.img"), 143 | }]; 144 | } 145 | } 146 | 147 | // Get all block I/O handles along with their block I/O implementations and device paths 148 | let handles = match DiskEfi::locate_handle() { 149 | Ok(ok) => ok, 150 | Err(err) => { 151 | log::warn!("Failed to find block I/O handles: {:?}", err); 152 | Vec::new() 153 | } 154 | }; 155 | let mut devices = Vec::with_capacity(handles.len()); 156 | for handle in handles { 157 | let disk = match DiskEfi::handle_protocol(handle) { 158 | Ok(ok) => ok, 159 | Err(err) => { 160 | log::warn!( 161 | "Failed to find block I/O protocol on {:?}: {:?}", 162 | handle, 163 | err 164 | ); 165 | continue; 166 | } 167 | }; 168 | 169 | if !disk.0.Media.MediaPresent { 170 | continue; 171 | } 172 | 173 | let device_path = match DevicePathProtocol::handle_protocol(handle) { 174 | Ok(ok) => ok, 175 | Err(err) => { 176 | log::warn!( 177 | "Failed to find device path protocol on {:?}: {:?}", 178 | handle, 179 | err 180 | ); 181 | continue; 182 | } 183 | }; 184 | 185 | devices.push(DiskDevice { 186 | handle, 187 | partition_offset: if disk.0.Media.LogicalPartition { 188 | 0 189 | } else { 190 | //TODO: get block from partition table 191 | 2 * crate::MIBI as u64 192 | }, 193 | disk: DiskOrFileEfi::Disk(disk), 194 | device_path, 195 | file_path: None, 196 | }); 197 | } 198 | 199 | // Find possible boot disks 200 | let mut boot_disks = Vec::with_capacity(1); 201 | { 202 | let mut i = 0; 203 | while i < devices.len() { 204 | if let DevicePathRelation::Parent(0) = 205 | device_path_relation(devices[i].device_path.0, esp_device_path.0) 206 | { 207 | boot_disks.push(devices.remove(i)); 208 | continue; 209 | } 210 | 211 | i += 1; 212 | } 213 | } 214 | 215 | // Find all children of possible boot devices 216 | let mut priority = Vec::with_capacity(devices.capacity()); 217 | for boot_disk in boot_disks { 218 | let mut i = 0; 219 | while i < devices.len() { 220 | // Only prioritize non-ESP devices 221 | if devices[i].handle != esp_handle { 222 | if let DevicePathRelation::Child(0) = 223 | device_path_relation(devices[i].device_path.0, boot_disk.device_path.0) 224 | { 225 | priority.push(devices.remove(i)); 226 | continue; 227 | } 228 | } 229 | 230 | i += 1; 231 | } 232 | 233 | priority.push(boot_disk); 234 | } 235 | 236 | // Add any remaining devices 237 | priority.extend(devices); 238 | 239 | priority 240 | } 241 | 242 | #[repr(C, packed)] 243 | #[allow(dead_code)] 244 | struct DevicePathHarddrive { 245 | partition_number: u32, 246 | partition_start: u64, 247 | partition_size: u64, 248 | partition_signature: [u8; 16], 249 | partition_format: u8, 250 | signature_type: u8, 251 | } 252 | 253 | pub fn device_path_to_string(device_path: &DevicePath) -> String { 254 | let mut s = String::new(); 255 | for (node, node_data) in DevicePathIter::new(device_path) { 256 | let read_u16 = |i: usize| -> u16 { (node_data[i] as u16) | (node_data[i + 1] as u16) << 8 }; 257 | 258 | let read_u32 = |i: usize| -> u32 { 259 | (node_data[i] as u32) 260 | | (node_data[i + 1] as u32) << 8 261 | | (node_data[i + 2] as u32) << 16 262 | | (node_data[i + 3] as u32) << 24 263 | }; 264 | 265 | if !s.is_empty() { 266 | s.push('/'); 267 | } 268 | 269 | let _ = match DevicePathType::try_from(node.Type) { 270 | Ok(path_type) => match path_type { 271 | DevicePathType::Hardware => match DevicePathHardwareType::try_from(node.SubType) { 272 | Ok(sub_type) => match sub_type { 273 | DevicePathHardwareType::Pci if node_data.len() == 2 => { 274 | let func = node_data[0]; 275 | let dev = node_data[1]; 276 | write!(s, "Pci(0x{dev:X},0x{func:X})") 277 | } 278 | _ => write!(s, "{path_type:?} {sub_type:?} {node_data:X?}"), 279 | }, 280 | Err(()) => write!(s, "{:?} 0x{:02X} {:X?}", path_type, node.SubType, node_data), 281 | }, 282 | DevicePathType::Acpi => match DevicePathAcpiType::try_from(node.SubType) { 283 | Ok(sub_type) => match sub_type { 284 | DevicePathAcpiType::Acpi if node_data.len() == 8 => { 285 | let hid = read_u32(0); 286 | let uid = read_u32(4); 287 | if hid & 0xFFFF == 0x41D0 { 288 | write!(s, "Acpi(PNP{:04X},0x{:X})", hid >> 16, uid) 289 | } else { 290 | write!(s, "Acpi(0x{hid:08X},0x{uid:X})") 291 | } 292 | } 293 | _ => write!(s, "{path_type:?} {sub_type:?} {node_data:X?}"), 294 | }, 295 | Err(()) => write!(s, "{:?} 0x{:02X} {:X?}", path_type, node.SubType, node_data), 296 | }, 297 | DevicePathType::Messaging => { 298 | match DevicePathMessagingType::try_from(node.SubType) { 299 | Ok(sub_type) => match sub_type { 300 | DevicePathMessagingType::Sata if node_data.len() == 6 => { 301 | let hba_port = read_u16(0); 302 | let multiplier_port = read_u16(2); 303 | let logical_unit = read_u16(4); 304 | if multiplier_port & (1 << 15) != 0 { 305 | write!(s, "Sata(0x{hba_port:X},0x{logical_unit:X})") 306 | } else { 307 | write!( 308 | s, 309 | "Sata(0x{hba_port:X},0x{multiplier_port:X},0x{logical_unit:X})" 310 | ) 311 | } 312 | } 313 | DevicePathMessagingType::Usb if node_data.len() == 2 => { 314 | let port = node_data[0]; 315 | let iface = node_data[1]; 316 | write!(s, "Usb(0x{port:X},0x{iface:X})") 317 | } 318 | DevicePathMessagingType::Nvme if node_data.len() == 12 => { 319 | let nsid = read_u32(0); 320 | let eui = &node_data[4..]; 321 | if eui == [0, 0, 0, 0, 0, 0, 0, 0] { 322 | write!(s, "NVMe(0x{nsid:X})") 323 | } else { 324 | write!( 325 | s, 326 | "NVMe(0x{:X},{:02X}-{:02X}-{:02X}-{:02X}-{:02X}-{:02X}-{:02X}-{:02X})", 327 | nsid, 328 | eui[0], 329 | eui[1], 330 | eui[2], 331 | eui[3], 332 | eui[4], 333 | eui[5], 334 | eui[6], 335 | eui[7], 336 | ) 337 | } 338 | } 339 | DevicePathMessagingType::Mac 340 | if node_data.len() == 33 && node_data[32] == 0 341 | || node_data[32] == 1 => 342 | { 343 | write!( 344 | s, 345 | "Mac({:02x}{:02x}{:02x}{:02x}{:02x}{:02x},{:#02x})", 346 | node_data[0], 347 | node_data[1], 348 | node_data[2], 349 | node_data[3], 350 | node_data[4], 351 | node_data[5], 352 | node_data[32], 353 | ) 354 | } 355 | _ => write!(s, "{path_type:?} {sub_type:?} {node_data:X?}"), 356 | }, 357 | Err(()) => { 358 | write!(s, "{:?} 0x{:02X} {:X?}", path_type, node.SubType, node_data) 359 | } 360 | } 361 | } 362 | DevicePathType::Media => match DevicePathMediaType::try_from(node.SubType) { 363 | Ok(sub_type) => { 364 | match sub_type { 365 | DevicePathMediaType::Harddrive 366 | if node_data.len() == mem::size_of::() => 367 | { 368 | let harddrive = unsafe { 369 | ptr::read(node_data.as_ptr() as *const DevicePathHarddrive) 370 | }; 371 | let partition_number = unsafe { 372 | ptr::read_unaligned(ptr::addr_of!(harddrive.partition_number)) 373 | }; 374 | match harddrive.signature_type { 375 | 1 => { 376 | let id = unsafe { 377 | ptr::read(harddrive.partition_signature.as_ptr() 378 | as *const u32) 379 | }; 380 | write!(s, "HD(0x{partition_number:X},MBR,0x{id:X})") 381 | } 382 | 2 => { 383 | let guid = unsafe { 384 | ptr::read(harddrive.partition_signature.as_ptr() 385 | as *const Guid) 386 | }; 387 | write!( 388 | s, 389 | "HD(0x{:X},GPT,{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X})", 390 | partition_number, 391 | guid.0, 392 | guid.1, 393 | guid.2, 394 | guid.3[0], 395 | guid.3[1], 396 | guid.3[2], 397 | guid.3[3], 398 | guid.3[4], 399 | guid.3[5], 400 | guid.3[6], 401 | guid.3[7], 402 | ) 403 | } 404 | _ => { 405 | write!( 406 | s, 407 | "HD(0x{:X},0x{:X},{:X?})", 408 | partition_number, 409 | harddrive.signature_type, 410 | harddrive.partition_signature 411 | ) 412 | } 413 | } 414 | } 415 | DevicePathMediaType::Filepath => { 416 | for chunk in node_data.chunks_exact(2) { 417 | let data = (chunk[0] as u16) | (chunk[1] as u16) << 8; 418 | match unsafe { char::from_u32_unchecked(data as u32) } { 419 | '\\' => s.push('/'), 420 | c => s.push(c), 421 | } 422 | } 423 | Ok(()) 424 | } 425 | _ => write!(s, "{path_type:?} {sub_type:?} {node_data:X?}"), 426 | } 427 | } 428 | Err(()) => write!(s, "{:?} 0x{:02X} {:X?}", path_type, node.SubType, node_data), 429 | }, 430 | DevicePathType::Bbs => match DevicePathBbsType::try_from(node.SubType) { 431 | Ok(sub_type) => write!(s, "{path_type:?} {sub_type:?} {node_data:X?}"), 432 | Err(()) => write!(s, "{:?} 0x{:02X} {:X?}", path_type, node.SubType, node_data), 433 | }, 434 | DevicePathType::End => match DevicePathEndType::try_from(node.SubType) { 435 | Ok(sub_type) => write!(s, "{path_type:?} {sub_type:?} {node_data:X?}"), 436 | Err(()) => write!(s, "{:?} 0x{:02X} {:X?}", path_type, node.SubType, node_data), 437 | }, 438 | }, 439 | Err(()) => { 440 | write!( 441 | s, 442 | "0x{:02X} 0x{:02X} {:X?}", 443 | node.Type, node.SubType, node_data 444 | ) 445 | } 446 | }; 447 | } 448 | s 449 | } 450 | 451 | pub struct DevicePathProtocol(pub &'static mut DevicePath); 452 | 453 | impl Protocol for DevicePathProtocol { 454 | fn guid() -> Guid { 455 | uefi::guid::DEVICE_PATH_GUID 456 | } 457 | 458 | fn new(inner: &'static mut DevicePath) -> Self { 459 | Self(inner) 460 | } 461 | } 462 | 463 | pub struct LoadedImageDevicePathProtocol(pub &'static mut DevicePath); 464 | 465 | impl Protocol for LoadedImageDevicePathProtocol { 466 | fn guid() -> Guid { 467 | uefi::guid::LOADED_IMAGE_DEVICE_PATH_GUID 468 | } 469 | 470 | fn new(inner: &'static mut DevicePath) -> Self { 471 | Self(inner) 472 | } 473 | } 474 | 475 | pub struct DevicePathIter<'a> { 476 | device_path: &'a DevicePath, 477 | node_ptr: *const DevicePath, 478 | } 479 | 480 | impl<'a> DevicePathIter<'a> { 481 | pub fn new(device_path: &'a DevicePath) -> Self { 482 | Self { 483 | device_path, 484 | node_ptr: device_path as *const DevicePath, 485 | } 486 | } 487 | } 488 | 489 | impl<'a> Iterator for DevicePathIter<'a> { 490 | type Item = (&'a DevicePath, &'a [u8]); 491 | fn next(&mut self) -> Option { 492 | let node = unsafe { &*self.node_ptr }; 493 | 494 | if node.Type == DevicePathType::End as u8 { 495 | return None; 496 | } 497 | 498 | let node_data = unsafe { 499 | slice::from_raw_parts( 500 | self.node_ptr.add(1) as *mut u8, 501 | node.Length.saturating_sub(4) as usize, 502 | ) 503 | }; 504 | 505 | self.node_ptr = (self.node_ptr as usize + node.Length as usize) as *const DevicePath; 506 | 507 | Some((node, node_data)) 508 | } 509 | } 510 | -------------------------------------------------------------------------------- /src/os/uefi/disk.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use core::slice; 3 | use redoxfs::{Disk, BLOCK_SIZE, RECORD_SIZE}; 4 | use std::proto::Protocol; 5 | use syscall::{Error, Result, EINVAL, EIO}; 6 | use uefi::block_io::BlockIo as UefiBlockIo; 7 | use uefi::guid::{Guid, BLOCK_IO_GUID}; 8 | 9 | pub enum DiskOrFileEfi { 10 | Disk(DiskEfi), 11 | File(Vec), 12 | } 13 | 14 | impl redoxfs::Disk for DiskOrFileEfi { 15 | unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> syscall::Result { 16 | match self { 17 | DiskOrFileEfi::Disk(disk_efi) => disk_efi.read_at(block, buffer), 18 | DiskOrFileEfi::File(data) => { 19 | buffer.copy_from_slice( 20 | &data[(block * redoxfs::BLOCK_SIZE) as usize 21 | ..(block * redoxfs::BLOCK_SIZE) as usize + buffer.len()], 22 | ); 23 | Ok(buffer.len()) 24 | } 25 | } 26 | } 27 | 28 | unsafe fn write_at(&mut self, _block: u64, _buffer: &[u8]) -> syscall::Result { 29 | unreachable!() 30 | } 31 | 32 | fn size(&mut self) -> syscall::Result { 33 | unreachable!() 34 | } 35 | } 36 | 37 | pub struct DiskEfi(pub &'static mut UefiBlockIo, &'static mut [u8]); 38 | 39 | impl Protocol for DiskEfi { 40 | fn guid() -> Guid { 41 | BLOCK_IO_GUID 42 | } 43 | 44 | fn new(inner: &'static mut UefiBlockIo) -> Self { 45 | // Hack to get aligned buffer 46 | let block = unsafe { 47 | let ptr = super::alloc_zeroed_page_aligned(RECORD_SIZE as usize); 48 | slice::from_raw_parts_mut(ptr, RECORD_SIZE as usize) 49 | }; 50 | 51 | Self(inner, block) 52 | } 53 | } 54 | 55 | impl Disk for DiskEfi { 56 | unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result { 57 | // Optimization for live disks 58 | if let Some(live) = crate::LIVE_OPT { 59 | if block >= live.0 { 60 | let start = ((block - live.0) * BLOCK_SIZE) as usize; 61 | let end = start + buffer.len(); 62 | if end <= live.1.len() { 63 | buffer.copy_from_slice(&live.1[start..end]); 64 | return Ok(buffer.len()); 65 | } 66 | } 67 | } 68 | 69 | // Use aligned buffer if necessary 70 | let mut ptr = buffer.as_mut_ptr(); 71 | if self.0.Media.IoAlign != 0 { 72 | if (ptr as usize) % (self.0.Media.IoAlign as usize) != 0 { 73 | if buffer.len() <= self.1.len() { 74 | ptr = self.1.as_mut_ptr(); 75 | } else { 76 | println!( 77 | "DiskEfi::read_at 0x{:X} requires alignment, ptr = 0x{:p}, len = 0x{:x}", 78 | block, 79 | ptr, 80 | buffer.len() 81 | ); 82 | return Err(Error::new(EINVAL)); 83 | } 84 | } 85 | } 86 | 87 | let block_size = self.0.Media.BlockSize as u64; 88 | let lba = block * BLOCK_SIZE / block_size; 89 | 90 | match (self.0.ReadBlocks)(self.0, self.0.Media.MediaId, lba, buffer.len(), ptr) { 91 | status if status.is_success() => { 92 | // Copy to original buffer if using aligned buffer 93 | if ptr != buffer.as_mut_ptr() { 94 | let (left, _) = self.1.split_at(buffer.len()); 95 | buffer.copy_from_slice(left); 96 | } 97 | Ok(buffer.len()) 98 | } 99 | err => { 100 | println!("DiskEfi::read_at 0x{:X} failed: {:?}", block, err); 101 | Err(Error::new(EIO)) 102 | } 103 | } 104 | } 105 | 106 | unsafe fn write_at(&mut self, block: u64, _buffer: &[u8]) -> Result { 107 | println!("DiskEfi::write_at 0x{:X} not implemented", block); 108 | Err(Error::new(EIO)) 109 | } 110 | 111 | fn size(&mut self) -> Result { 112 | println!("DiskEfi::size not implemented"); 113 | Err(Error::new(EIO)) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/os/uefi/display.rs: -------------------------------------------------------------------------------- 1 | use std::proto::Protocol; 2 | use uefi::graphics::GraphicsOutput; 3 | use uefi::guid::{Guid, GRAPHICS_OUTPUT_PROTOCOL_GUID}; 4 | 5 | pub struct Output(pub &'static mut GraphicsOutput); 6 | 7 | impl Protocol for Output { 8 | fn guid() -> Guid { 9 | GRAPHICS_OUTPUT_PROTOCOL_GUID 10 | } 11 | 12 | fn new(inner: &'static mut GraphicsOutput) -> Self { 13 | Output(inner) 14 | } 15 | } 16 | 17 | const EDID_ACTIVE_PROTOCOL_GUID: Guid = Guid( 18 | 0xbd8c1056, 19 | 0x9f36, 20 | 0x44ec, 21 | [0x92, 0xa8, 0xa6, 0x33, 0x7f, 0x81, 0x79, 0x86], 22 | ); 23 | 24 | #[allow(non_snake_case)] 25 | #[repr(C)] 26 | pub struct EdidActiveProtocol { 27 | pub SizeOfEdid: u32, 28 | pub Edid: *const u8, 29 | } 30 | 31 | pub struct EdidActive(pub &'static mut EdidActiveProtocol); 32 | 33 | impl Protocol for EdidActive { 34 | fn guid() -> Guid { 35 | EDID_ACTIVE_PROTOCOL_GUID 36 | } 37 | 38 | fn new(inner: &'static mut EdidActiveProtocol) -> Self { 39 | EdidActive(inner) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/os/uefi/dtb.rs: -------------------------------------------------------------------------------- 1 | use crate::Os; 2 | use alloc::vec::Vec; 3 | use byteorder::ByteOrder; 4 | use byteorder::BE; 5 | use core::slice; 6 | use fdt::Fdt; 7 | use uefi::guid::DEVICE_TREE_GUID; 8 | #[cfg(target_arch = "aarch64")] 9 | use uefi::{ 10 | guid::SMBIOS3_TABLE_GUID, 11 | status::{Result, Status}, 12 | }; 13 | 14 | pub static mut DEV_MEM_AREA: Vec<(usize, usize)> = Vec::new(); 15 | 16 | pub unsafe fn is_in_dev_mem_region(addr: usize) -> bool { 17 | if DEV_MEM_AREA.is_empty() { 18 | return false; 19 | } 20 | for item in DEV_MEM_AREA.iter() { 21 | if (addr >= item.0) && (addr < item.0 + item.1) { 22 | return true; 23 | } 24 | } 25 | return false; 26 | } 27 | 28 | unsafe fn get_dev_mem_region(fdt: &Fdt) { 29 | let Some(soc) = fdt.find_node("/soc") else { 30 | return; 31 | }; 32 | let Some(ranges) = soc.property("ranges") else { 33 | return; 34 | }; 35 | let cell_sizes = soc.cell_sizes(); 36 | let chunk_size = (cell_sizes.address_cells * 2 + cell_sizes.size_cells) * 4; 37 | for chunk in ranges.value.chunks(chunk_size) { 38 | let child_bus_addr = { 39 | if cell_sizes.address_cells == 1 { 40 | BE::read_u32(&chunk[0..4]) as u64 41 | } else if cell_sizes.address_cells == 2 { 42 | BE::read_u32(&chunk[0..8]) as u64 43 | } else { 44 | DEV_MEM_AREA.clear(); 45 | return; 46 | } 47 | }; 48 | let parent_bus_addr = { 49 | if cell_sizes.address_cells == 1 { 50 | BE::read_u32(&chunk[4..8]) as u64 51 | } else if cell_sizes.address_cells == 2 { 52 | BE::read_u32(&chunk[8..16]) as u64 53 | } else { 54 | DEV_MEM_AREA.clear(); 55 | return; 56 | } 57 | }; 58 | let addr_size = { 59 | if cell_sizes.size_cells == 1 { 60 | BE::read_u32(&chunk[8..12]) as u64 61 | } else if cell_sizes.size_cells == 2 { 62 | BE::read_u32(&chunk[16..20]) as u64 63 | } else { 64 | DEV_MEM_AREA.clear(); 65 | return; 66 | } 67 | }; 68 | println!( 69 | "dev mem 0x{:08x} 0x{:08x} 0x{:08x}", 70 | child_bus_addr, parent_bus_addr, addr_size 71 | ); 72 | DEV_MEM_AREA.push((parent_bus_addr as usize, addr_size as usize)); 73 | } 74 | } 75 | 76 | fn parse_dtb(os: &impl Os, address: *const u8) -> Option<(u64, u64)> { 77 | unsafe { 78 | if let Ok(fdt) = fdt::Fdt::from_ptr(address) { 79 | let mut rsdps_area = Vec::new(); 80 | //println!("DTB model = {}", fdt.root().model()); 81 | get_dev_mem_region(&fdt); 82 | let length = fdt.total_size(); 83 | let align = 8; 84 | rsdps_area.extend(core::slice::from_raw_parts(address, length)); 85 | rsdps_area.resize(((rsdps_area.len() + (align - 1)) / align) * align, 0u8); 86 | let size = rsdps_area.len(); 87 | let base = os.alloc_zeroed_page_aligned(size); 88 | slice::from_raw_parts_mut(base, size).copy_from_slice(&rsdps_area); 89 | Some((base as u64, size as u64)) 90 | } else { 91 | println!("Failed to parse DTB"); 92 | None 93 | } 94 | } 95 | } 96 | 97 | #[cfg(target_arch = "aarch64")] 98 | fn find_smbios3_system(address: *const u8) -> Result> { 99 | unsafe { 100 | let smb = core::slice::from_raw_parts(address, 24); 101 | if let Ok(smbios) = dmidecode::EntryPoint::search(smb) { 102 | let smb_structure_data = core::slice::from_raw_parts( 103 | smbios.smbios_address() as *const u8, 104 | smbios.smbios_len() as usize, 105 | ); 106 | for structure in smbios.structures(smb_structure_data) { 107 | if let Ok(sval) = structure { 108 | //println!("SMBIOS: {:#?}", sval); 109 | if let dmidecode::Structure::System(buf) = sval { 110 | return Ok(buf); 111 | } 112 | } 113 | } 114 | } 115 | } 116 | Err(Status::NOT_FOUND) 117 | } 118 | 119 | pub(crate) fn find_dtb(os: &impl Os) -> Option<(u64, u64)> { 120 | let cfg_tables = std::system_table().config_tables(); 121 | for cfg_table in cfg_tables.iter() { 122 | if cfg_table.VendorGuid == DEVICE_TREE_GUID { 123 | let addr = cfg_table.VendorTable; 124 | return parse_dtb(os, addr as *const u8); 125 | } 126 | } 127 | 128 | /* This hack is no longer needed, but can be re-enabled for testing 129 | #[cfg(target_arch = "aarch64")] 130 | for cfg_table in cfg_tables.iter() { 131 | if cfg_table.VendorGuid == SMBIOS3_TABLE_GUID { 132 | let addr = cfg_table.VendorTable; 133 | if let Ok(sys) = find_smbios3_system(addr as *const u8) { 134 | let get_dtb_addr = match (sys.manufacturer, sys.version) { 135 | ("QEMU", version) if version.starts_with("virt") => Some(0x4000_0000 as usize), 136 | _ => None, 137 | }; 138 | if let Some(dtb_addr) = get_dtb_addr { 139 | return parse_dtb(os, dtb_addr as *const u8); 140 | } 141 | } 142 | } 143 | } 144 | */ 145 | 146 | None 147 | } 148 | -------------------------------------------------------------------------------- /src/os/uefi/memory_map.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec; 2 | use alloc::vec::Vec; 3 | use core::{mem, ptr}; 4 | use uefi::memory::{MemoryDescriptor, MemoryType}; 5 | 6 | use crate::area_add; 7 | use crate::os::{OsMemoryEntry, OsMemoryKind}; 8 | 9 | use super::status_to_result; 10 | 11 | pub struct MemoryMapIter { 12 | map: Vec, 13 | map_key: usize, 14 | descriptor_size: usize, 15 | descriptor_version: u32, 16 | i: usize, 17 | } 18 | 19 | impl MemoryMapIter { 20 | pub fn new() -> Self { 21 | let uefi = std::system_table(); 22 | 23 | let mut map = vec![0; 65536]; 24 | let mut map_size = map.len(); 25 | let mut map_key = 0; 26 | let mut descriptor_size = 0; 27 | let mut descriptor_version = 0; 28 | status_to_result((uefi.BootServices.GetMemoryMap)( 29 | &mut map_size, 30 | map.as_mut_ptr() as *mut MemoryDescriptor, 31 | &mut map_key, 32 | &mut descriptor_size, 33 | &mut descriptor_version, 34 | )) 35 | .expect("Failed to get UEFI memory map"); 36 | 37 | // Ensure descriptor size is usable 38 | assert!(descriptor_size >= mem::size_of::()); 39 | 40 | // Ensure descriptor version is supported 41 | assert_eq!(descriptor_version, 1); 42 | 43 | // Reduce map size to returned value 44 | map.truncate(map_size); 45 | 46 | Self { 47 | map, 48 | map_key, 49 | descriptor_size, 50 | descriptor_version, 51 | i: 0, 52 | } 53 | } 54 | 55 | pub fn exit_boot_services(mut self) { 56 | let handle = std::handle(); 57 | let uefi = std::system_table(); 58 | 59 | // We are writing to the memory map that will be passed to 60 | // SetVirtualAddressMap before ExitBootServices as on some firmware 61 | // EfiLoaderData memory regions like this one are marked as read-only 62 | // after ExitBootServices 63 | for i in 0..self.map.len() / self.descriptor_size { 64 | let descriptor_ptr = unsafe { self.map.as_mut_ptr().add(i * self.descriptor_size) }; 65 | let descriptor = unsafe { &mut *(descriptor_ptr as *mut MemoryDescriptor) }; 66 | 67 | // Map all memory regions even when not marked as EFI_MEMORY_RUNTIME 68 | // as some firmware uses memory regions not marked as 69 | // EFI_MEMORY_RUNTIME in runtime services. Linux has a list of 70 | // exactly which memory regions need to be mapped, but for simplicity 71 | // we are mapping all regions here. 72 | 73 | // Identity map all memory regions as some firmware fails to update 74 | // all pointers in SetVirtualAddressMap. 75 | 76 | descriptor.VirtualStart.0 = descriptor.PhysicalStart.0; 77 | } 78 | 79 | status_to_result((uefi.BootServices.ExitBootServices)(handle, self.map_key)) 80 | .expect("Failed to exit UEFI boot services"); 81 | 82 | // Runtime services must be called with interrupts disabled 83 | super::arch::disable_interrupts(); 84 | 85 | status_to_result((uefi.RuntimeServices.SetVirtualAddressMap)( 86 | self.map.len(), 87 | self.descriptor_size, 88 | self.descriptor_version, 89 | self.map.as_ptr() as *const MemoryDescriptor, 90 | )) 91 | .expect("Failed to set UEFI runtime services virtual address map"); 92 | 93 | // After ExitBootServices, GlobalAlloc::dealloc() is not allowed anymore 94 | // as it uses boot services. 95 | mem::forget(self); 96 | } 97 | } 98 | 99 | impl Iterator for MemoryMapIter { 100 | type Item = OsMemoryEntry; 101 | fn next(&mut self) -> Option { 102 | if self.i < self.map.len() / self.descriptor_size { 103 | let descriptor_ptr = unsafe { self.map.as_ptr().add(self.i * self.descriptor_size) }; 104 | self.i += 1; 105 | 106 | let descriptor = unsafe { ptr::read(descriptor_ptr as *const MemoryDescriptor) }; 107 | let descriptor_type: MemoryType = unsafe { mem::transmute(descriptor.Type) }; 108 | 109 | Some(OsMemoryEntry { 110 | base: descriptor.PhysicalStart.0, 111 | //TODO: do not hard code page size 112 | size: descriptor.NumberOfPages * 4096, 113 | kind: match descriptor_type { 114 | MemoryType::EfiLoaderCode 115 | | MemoryType::EfiLoaderData 116 | | MemoryType::EfiBootServicesCode 117 | | MemoryType::EfiBootServicesData 118 | | MemoryType::EfiConventionalMemory => OsMemoryKind::Free, 119 | //TODO: mark ACPI memory as reclaim 120 | _ => OsMemoryKind::Reserved, 121 | }, 122 | }) 123 | } else { 124 | None 125 | } 126 | } 127 | } 128 | 129 | pub unsafe fn memory_map() -> MemoryMapIter { 130 | let mut iter = MemoryMapIter::new(); 131 | 132 | // Using next to avoid consuming iterator 133 | while let Some(entry) = iter.next() { 134 | area_add(entry); 135 | } 136 | 137 | // Rewind iterator 138 | iter.i = 0; 139 | 140 | iter 141 | } 142 | -------------------------------------------------------------------------------- /src/os/uefi/mod.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use core::{cell::RefCell, mem, ptr, slice}; 3 | use std::proto::Protocol; 4 | use uefi::{ 5 | boot::LocateSearchType, 6 | memory::MemoryType, 7 | reset::ResetType, 8 | status::{Result, Status}, 9 | system::SystemTable, 10 | text::TextInputKey, 11 | Handle, 12 | }; 13 | 14 | use crate::os::{Os, OsHwDesc, OsKey, OsVideoMode}; 15 | 16 | use self::{ 17 | device::{device_path_to_string, disk_device_priority}, 18 | disk::DiskOrFileEfi, 19 | display::{EdidActive, Output}, 20 | video_mode::VideoModeIter, 21 | }; 22 | 23 | mod acpi; 24 | mod arch; 25 | mod device; 26 | mod disk; 27 | mod display; 28 | #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] 29 | pub mod dtb; 30 | mod memory_map; 31 | mod video_mode; 32 | 33 | #[cfg(target_arch = "riscv64")] 34 | pub use arch::efi_get_boot_hartid; 35 | 36 | pub(crate) fn page_size() -> usize { 37 | // EDK2 always uses 4096 as the page size 38 | 4096 39 | } 40 | 41 | pub(crate) fn alloc_zeroed_page_aligned(size: usize) -> *mut u8 { 42 | assert!(size != 0); 43 | 44 | let page_size = page_size(); 45 | let pages = size.div_ceil(page_size); 46 | 47 | let ptr = { 48 | // Max address mapped by src/arch paging code (8 GiB) 49 | let mut ptr = 0x2_0000_0000; 50 | status_to_result((std::system_table().BootServices.AllocatePages)( 51 | 1, // AllocateMaxAddress 52 | MemoryType::EfiRuntimeServicesData, // Keeps this memory out of free space list 53 | pages, 54 | &mut ptr, 55 | )) 56 | .unwrap(); 57 | ptr as *mut u8 58 | }; 59 | 60 | assert!(!ptr.is_null()); 61 | unsafe { ptr::write_bytes(ptr, 0, pages * page_size) }; 62 | ptr 63 | } 64 | 65 | pub struct OsEfi { 66 | st: &'static SystemTable, 67 | outputs: RefCell)>>, 68 | } 69 | 70 | impl OsEfi { 71 | pub fn new() -> Self { 72 | let st = std::system_table(); 73 | let mut outputs = Vec::<(Output, Option)>::new(); 74 | { 75 | let guid = Output::guid(); 76 | let mut handles = Vec::with_capacity(256); 77 | let mut len = handles.capacity() * mem::size_of::(); 78 | match status_to_result((st.BootServices.LocateHandle)( 79 | LocateSearchType::ByProtocol, 80 | &guid, 81 | 0, 82 | &mut len, 83 | handles.as_mut_ptr(), 84 | )) { 85 | Ok(_) => { 86 | unsafe { 87 | handles.set_len(len / mem::size_of::()); 88 | } 89 | 'handles: for handle in handles { 90 | //TODO: do we have to query all modes to get good edid? 91 | match Output::handle_protocol(handle) { 92 | Ok(output) => { 93 | log::debug!( 94 | "Output {:?} at {:x}", 95 | handle, 96 | output.0.Mode.FrameBufferBase 97 | ); 98 | 99 | if output.0.Mode.FrameBufferBase == 0 { 100 | log::debug!("Skipping output with frame buffer base of 0"); 101 | continue 'handles; 102 | } 103 | 104 | for other_output in outputs.iter() { 105 | if output.0.Mode.FrameBufferBase 106 | == other_output.0 .0.Mode.FrameBufferBase 107 | { 108 | log::debug!("Skipping output with frame buffer base matching another output"); 109 | continue 'handles; 110 | } 111 | } 112 | 113 | outputs.push(( 114 | output, 115 | match EdidActive::handle_protocol(handle) { 116 | Ok(efi_edid) => Some(efi_edid), 117 | Err(err) => { 118 | log::warn!( 119 | "Failed to get EFI EDID from handle {:?}: {:?}", 120 | handle, 121 | err 122 | ); 123 | None 124 | } 125 | }, 126 | )); 127 | } 128 | Err(err) => { 129 | log::warn!( 130 | "Failed to get Output from handle {:?}: {:?}", 131 | handle, 132 | err 133 | ); 134 | } 135 | } 136 | } 137 | } 138 | Err(err) => { 139 | log::warn!("Failed to locate Outputs: {:?}", err); 140 | } 141 | } 142 | } 143 | Self { 144 | st, 145 | outputs: RefCell::new(outputs), 146 | } 147 | } 148 | } 149 | 150 | impl Os for OsEfi { 151 | type D = DiskOrFileEfi; 152 | type V = VideoModeIter; 153 | 154 | #[cfg(target_arch = "aarch64")] 155 | fn name(&self) -> &str { 156 | "aarch64/UEFI" 157 | } 158 | 159 | #[cfg(target_arch = "x86_64")] 160 | fn name(&self) -> &str { 161 | "x86_64/UEFI" 162 | } 163 | 164 | #[cfg(target_arch = "riscv64")] 165 | fn name(&self) -> &str { 166 | "riscv64/UEFI" 167 | } 168 | 169 | fn alloc_zeroed_page_aligned(&self, size: usize) -> *mut u8 { 170 | alloc_zeroed_page_aligned(size) 171 | } 172 | 173 | fn page_size(&self) -> usize { 174 | page_size() 175 | } 176 | 177 | fn filesystem( 178 | &self, 179 | password_opt: Option<&[u8]>, 180 | ) -> syscall::Result> { 181 | // Search for RedoxFS on disks in prioritized order 182 | println!("Looking for RedoxFS:"); 183 | for device in disk_device_priority() { 184 | if let Some(file_path) = device.file_path { 185 | println!( 186 | " - {}\\{}", 187 | device_path_to_string(device.device_path.0), 188 | file_path 189 | ) 190 | } else { 191 | println!(" - {}", device_path_to_string(device.device_path.0)) 192 | } 193 | 194 | let block = device.partition_offset / redoxfs::BLOCK_SIZE; 195 | 196 | match redoxfs::FileSystem::open(device.disk, password_opt, Some(block), false) { 197 | Ok(ok) => return Ok(ok), 198 | Err(err) => match err.errno { 199 | // Ignore header not found error 200 | syscall::ENOENT => (), 201 | // Print any other errors 202 | _ => { 203 | log::warn!("BlockIo error: {:?}", err); 204 | } 205 | }, 206 | } 207 | } 208 | 209 | log::warn!("No RedoxFS partitions found"); 210 | Err(syscall::Error::new(syscall::ENOENT)) 211 | } 212 | 213 | fn hwdesc(&self) -> OsHwDesc { 214 | //TODO: if both DTB and ACPI are found, we should probably let the OS choose what to use? 215 | 216 | // For now we will prefer DTB on platforms that have it 217 | #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] 218 | if let Some((addr, size)) = dtb::find_dtb(self) { 219 | return OsHwDesc::DeviceTree(addr, size); 220 | } 221 | 222 | if let Some((addr, size)) = acpi::find_acpi_table_pointers(self) { 223 | return OsHwDesc::Acpi(addr, size); 224 | } 225 | 226 | OsHwDesc::NotFound 227 | } 228 | 229 | fn video_outputs(&self) -> usize { 230 | self.outputs.borrow().len() 231 | } 232 | 233 | fn video_modes(&self, output_i: usize) -> VideoModeIter { 234 | let output_opt = match self.outputs.borrow_mut().get_mut(output_i) { 235 | Some(output) => unsafe { 236 | // Hack to enable clone 237 | let ptr = output.0 .0 as *mut _; 238 | Some(Output::new(&mut *ptr)) 239 | }, 240 | None => None, 241 | }; 242 | VideoModeIter::new(output_opt) 243 | } 244 | 245 | fn set_video_mode(&self, output_i: usize, mode: &mut OsVideoMode) { 246 | //TODO: return error? 247 | let mut outputs = self.outputs.borrow_mut(); 248 | let (output, _efi_edid_opt) = &mut outputs[output_i]; 249 | status_to_result((output.0.SetMode)(output.0, mode.id)).unwrap(); 250 | 251 | // Update with actual mode information 252 | mode.width = output.0.Mode.Info.HorizontalResolution; 253 | mode.height = output.0.Mode.Info.VerticalResolution; 254 | mode.base = output.0.Mode.FrameBufferBase as u64; 255 | } 256 | 257 | fn best_resolution(&self, output_i: usize) -> Option<(u32, u32)> { 258 | let mut outputs = self.outputs.borrow_mut(); 259 | let (output, efi_edid_opt) = outputs.get_mut(output_i)?; 260 | 261 | if let Some(efi_edid) = efi_edid_opt { 262 | let edid = 263 | unsafe { slice::from_raw_parts(efi_edid.0.Edid, efi_edid.0.SizeOfEdid as usize) }; 264 | 265 | if edid.len() > 0x3D { 266 | return Some(( 267 | (edid[0x38] as u32) | (((edid[0x3A] as u32) & 0xF0) << 4), 268 | (edid[0x3B] as u32) | (((edid[0x3D] as u32) & 0xF0) << 4), 269 | )); 270 | } else { 271 | log::warn!("EFI EDID too small: {}", edid.len()); 272 | } 273 | } 274 | 275 | // Fallback to the current output resolution 276 | Some(( 277 | output.0.Mode.Info.HorizontalResolution, 278 | output.0.Mode.Info.VerticalResolution, 279 | )) 280 | } 281 | 282 | fn get_key(&self) -> OsKey { 283 | //TODO: do not unwrap 284 | 285 | let mut index = 0; 286 | status_to_result((self.st.BootServices.WaitForEvent)( 287 | 1, 288 | &self.st.ConsoleIn.WaitForKey, 289 | &mut index, 290 | )) 291 | .unwrap(); 292 | 293 | let mut key = TextInputKey { 294 | ScanCode: 0, 295 | UnicodeChar: 0, 296 | }; 297 | status_to_result((self.st.ConsoleIn.ReadKeyStroke)( 298 | self.st.ConsoleIn, 299 | &mut key, 300 | )) 301 | .unwrap(); 302 | 303 | match key.ScanCode { 304 | 0 => match key.UnicodeChar { 305 | 8 => OsKey::Backspace, 306 | 13 => OsKey::Enter, 307 | w => match char::from_u32(w as u32) { 308 | Some(c) => OsKey::Char(c), 309 | None => OsKey::Other, 310 | }, 311 | }, 312 | 1 => OsKey::Up, 313 | 2 => OsKey::Down, 314 | 3 => OsKey::Right, 315 | 4 => OsKey::Left, 316 | 8 => OsKey::Delete, 317 | _ => OsKey::Other, 318 | } 319 | } 320 | 321 | fn clear_text(&self) { 322 | //TODO: why does this sometimes return InvalidParameter, but otherwise appear to work? 323 | let _ = status_to_result((self.st.ConsoleOut.ClearScreen)(self.st.ConsoleOut)); 324 | } 325 | 326 | fn get_text_position(&self) -> (usize, usize) { 327 | ( 328 | self.st.ConsoleOut.Mode.CursorColumn as usize, 329 | self.st.ConsoleOut.Mode.CursorRow as usize, 330 | ) 331 | } 332 | 333 | fn set_text_position(&self, x: usize, y: usize) { 334 | // Ignore error because Tow-Boot appears to not implement this 335 | let _ = status_to_result((self.st.ConsoleOut.SetCursorPosition)( 336 | self.st.ConsoleOut, 337 | x, 338 | y, 339 | )); 340 | } 341 | 342 | fn set_text_highlight(&self, highlight: bool) { 343 | let attr = if highlight { 0x70 } else { 0x07 }; 344 | status_to_result((self.st.ConsoleOut.SetAttribute)(self.st.ConsoleOut, attr)).unwrap(); 345 | } 346 | } 347 | 348 | fn status_to_result(status: Status) -> Result { 349 | match status { 350 | Status(ok) if status.is_success() => Ok(ok), 351 | err => Err(err), 352 | } 353 | } 354 | 355 | fn set_max_mode(output: &uefi::text::TextOutput) -> Result<()> { 356 | let mut max_i = None; 357 | let mut max_w = 0; 358 | let mut max_h = 0; 359 | 360 | for i in 0..output.Mode.MaxMode as usize { 361 | let mut w = 0; 362 | let mut h = 0; 363 | if (output.QueryMode)(output, i, &mut w, &mut h).is_success() { 364 | if w >= max_w && h >= max_h { 365 | max_i = Some(i); 366 | max_w = w; 367 | max_h = h; 368 | } 369 | } 370 | } 371 | 372 | if let Some(i) = max_i { 373 | status_to_result((output.SetMode)(output, i))?; 374 | } 375 | 376 | Ok(()) 377 | } 378 | 379 | #[no_mangle] 380 | pub extern "C" fn main() -> Status { 381 | let uefi = std::system_table(); 382 | 383 | let _ = (uefi.BootServices.SetWatchdogTimer)(0, 0, 0, ptr::null()); 384 | 385 | if let Err(err) = set_max_mode(uefi.ConsoleOut) { 386 | println!("Failed to set max mode: {:?}", err); 387 | } 388 | 389 | if let Err(err) = arch::main() { 390 | panic!("App error: {:?}", err); 391 | } 392 | 393 | (uefi.RuntimeServices.ResetSystem)(ResetType::Cold, Status(0), 0, ptr::null()); 394 | } 395 | -------------------------------------------------------------------------------- /src/os/uefi/video_mode.rs: -------------------------------------------------------------------------------- 1 | use core::ptr; 2 | use log::error; 3 | use uefi::status::Status; 4 | 5 | use crate::os::uefi::display::Output; 6 | use crate::os::OsVideoMode; 7 | 8 | pub struct VideoModeIter { 9 | output_opt: Option, 10 | i: u32, 11 | } 12 | 13 | impl VideoModeIter { 14 | pub fn new(output_opt: Option) -> Self { 15 | Self { output_opt, i: 0 } 16 | } 17 | } 18 | 19 | impl Iterator for VideoModeIter { 20 | type Item = OsVideoMode; 21 | fn next(&mut self) -> Option { 22 | if let Some(ref mut output) = self.output_opt { 23 | while self.i < output.0.Mode.MaxMode { 24 | let id = self.i; 25 | self.i += 1; 26 | 27 | let mut mode_ptr = ::core::ptr::null_mut(); 28 | let mut mode_size = 0; 29 | match (output.0.QueryMode)(output.0, id, &mut mode_size, &mut mode_ptr) { 30 | Status::SUCCESS => (), 31 | err => { 32 | error!("Failed to read mode {}: {:?}", id, err); 33 | continue; 34 | } 35 | } 36 | 37 | //TODO: ensure mode_size is set correctly 38 | let mode = unsafe { ptr::read(mode_ptr) }; 39 | 40 | let width = mode.HorizontalResolution; 41 | let height = mode.VerticalResolution; 42 | let stride = mode.PixelsPerScanLine; 43 | 44 | return Some(OsVideoMode { 45 | id, 46 | width, 47 | height, 48 | stride, 49 | // Base is retrieved later by setting the mode 50 | base: 0, 51 | }); 52 | } 53 | } 54 | 55 | None 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/serial_16550.rs: -------------------------------------------------------------------------------- 1 | use bitflags::bitflags; 2 | use core::convert::TryInto; 3 | use core::fmt; 4 | use core::ptr::{addr_of, addr_of_mut}; 5 | #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 6 | use syscall::io::Pio; 7 | use syscall::io::{Io, Mmio, ReadOnly}; 8 | 9 | bitflags! { 10 | /// Interrupt enable flags 11 | struct IntEnFlags: u8 { 12 | const RECEIVED = 1; 13 | const SENT = 1 << 1; 14 | const ERRORED = 1 << 2; 15 | const STATUS_CHANGE = 1 << 3; 16 | // 4 to 7 are unused 17 | } 18 | } 19 | 20 | bitflags! { 21 | /// Line status flags 22 | struct LineStsFlags: u8 { 23 | const INPUT_FULL = 1; 24 | // 1 to 4 unknown 25 | const OUTPUT_EMPTY = 1 << 5; 26 | // 6 and 7 unknown 27 | } 28 | } 29 | 30 | #[allow(dead_code)] 31 | #[repr(C, packed)] 32 | pub struct SerialPort { 33 | /// Data register, read to receive, write to send 34 | data: T, 35 | /// Interrupt enable 36 | int_en: T, 37 | /// FIFO control 38 | fifo_ctrl: T, 39 | /// Line control 40 | line_ctrl: T, 41 | /// Modem control 42 | modem_ctrl: T, 43 | /// Line status 44 | line_sts: ReadOnly, 45 | /// Modem status 46 | modem_sts: ReadOnly, 47 | } 48 | 49 | #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 50 | impl SerialPort> { 51 | pub const fn new(base: u16) -> SerialPort> { 52 | SerialPort { 53 | data: Pio::new(base), 54 | int_en: Pio::new(base + 1), 55 | fifo_ctrl: Pio::new(base + 2), 56 | line_ctrl: Pio::new(base + 3), 57 | modem_ctrl: Pio::new(base + 4), 58 | line_sts: ReadOnly::new(Pio::new(base + 5)), 59 | modem_sts: ReadOnly::new(Pio::new(base + 6)), 60 | } 61 | } 62 | } 63 | 64 | impl SerialPort> { 65 | pub unsafe fn new(base: usize) -> &'static mut SerialPort> { 66 | &mut *(base as *mut Self) 67 | } 68 | } 69 | 70 | impl SerialPort 71 | where 72 | T::Value: From + TryInto, 73 | { 74 | pub fn init(&mut self) { 75 | unsafe { 76 | //TODO: Cleanup 77 | // FIXME: Fix UB if unaligned 78 | (*addr_of_mut!(self.int_en)).write(0x00.into()); 79 | (*addr_of_mut!(self.line_ctrl)).write(0x80.into()); 80 | (*addr_of_mut!(self.data)).write(0x01.into()); 81 | (*addr_of_mut!(self.int_en)).write(0x00.into()); 82 | (*addr_of_mut!(self.line_ctrl)).write(0x03.into()); 83 | (*addr_of_mut!(self.fifo_ctrl)).write(0xC7.into()); 84 | (*addr_of_mut!(self.modem_ctrl)).write(0x0B.into()); 85 | (*addr_of_mut!(self.int_en)).write(0x01.into()); 86 | } 87 | } 88 | 89 | fn line_sts(&self) -> LineStsFlags { 90 | LineStsFlags::from_bits_truncate( 91 | (unsafe { &*addr_of!(self.line_sts) }.read() & 0xFF.into()) 92 | .try_into() 93 | .unwrap_or(0), 94 | ) 95 | } 96 | 97 | pub fn receive(&mut self) -> Option { 98 | if self.line_sts().contains(LineStsFlags::INPUT_FULL) { 99 | Some( 100 | (unsafe { &*addr_of!(self.data) }.read() & 0xFF.into()) 101 | .try_into() 102 | .unwrap_or(0), 103 | ) 104 | } else { 105 | None 106 | } 107 | } 108 | 109 | pub fn send(&mut self, data: u8) { 110 | while !self.line_sts().contains(LineStsFlags::OUTPUT_EMPTY) {} 111 | unsafe { &mut *addr_of_mut!(self.data) }.write(data.into()) 112 | } 113 | 114 | pub fn write(&mut self, buf: &[u8]) { 115 | for &b in buf { 116 | match b { 117 | 8 | 0x7F => { 118 | self.send(8); 119 | self.send(b' '); 120 | self.send(8); 121 | } 122 | b'\n' => { 123 | self.send(b'\r'); 124 | self.send(b'\n'); 125 | } 126 | _ => { 127 | self.send(b); 128 | } 129 | } 130 | } 131 | } 132 | } 133 | 134 | impl fmt::Write for SerialPort 135 | where 136 | T::Value: From + TryInto, 137 | { 138 | fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { 139 | self.write(s.as_bytes()); 140 | Ok(()) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /targets/aarch64-unknown-uefi.json: -------------------------------------------------------------------------------- 1 | { 2 | "arch": "aarch64", 3 | "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", 4 | "default-hidden-visibility": true, 5 | "emit-debug-gdb-scripts": false, 6 | "exe-suffix": ".efi", 7 | "executables": true, 8 | "is-like-windows": true, 9 | "linker": "rust-lld", 10 | "linker-flavor": "lld-link", 11 | "llvm-target": "aarch64-pc-windows-msvc", 12 | "os": "uefi", 13 | "panic-strategy": "abort", 14 | "pre-link-args": { 15 | "lld-link": [ 16 | "/subsystem:EFI_Application", 17 | "/entry:efi_main", 18 | "/machine:arm64" 19 | ] 20 | }, 21 | "stack_probes": true, 22 | "target-c-int-width": "32", 23 | "target-endian": "little", 24 | "target-pointer-width": "64" 25 | } 26 | -------------------------------------------------------------------------------- /targets/riscv64gc-unknown-uefi.json: -------------------------------------------------------------------------------- 1 | { 2 | "arch": "riscv64", 3 | "code-model": "medium", 4 | "cpu": "generic-rv64", 5 | "data-layout": "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128", 6 | "default-hidden-visibility": true, 7 | "emit-debug-gdb-scripts": false, 8 | "exe-suffix": ".elf", 9 | "executables": true, 10 | "linker-flavor": "gnu-cc", 11 | "linker": "riscv64-unknown-redox-gcc", 12 | "llvm-abiname": "lp64d", 13 | "features": "+m,+a,+f,+d,+c", 14 | "llvm-target": "riscv64-unknown-none-elf", 15 | "os": "none", 16 | "metadata": { 17 | "description": null, 18 | "host_tools": null, 19 | "std": null, 20 | "tier": null 21 | }, 22 | "panic-strategy": "abort", 23 | "target-c-int-width": "32", 24 | "target-endian": "little", 25 | "target-pointer-width": "64" 26 | } 27 | -------------------------------------------------------------------------------- /targets/x86-unknown-none.json: -------------------------------------------------------------------------------- 1 | { 2 | "llvm-target": "i686-unknown-none", 3 | "target-endian": "little", 4 | "target-pointer-width": "32", 5 | "target-c-int-width": "32", 6 | "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", 7 | "arch": "x86", 8 | "os": "none", 9 | "env": "", 10 | "vendor": "unknown", 11 | "linker-flavor": "gcc", 12 | "panic-strategy": "abort", 13 | "pre-link-args": { 14 | "gcc": ["-m32", "-nostdlib", "-static"] 15 | }, 16 | "features": "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-3dnow,-3dnowa,-avx,-avx2,+soft-float", 17 | "rustc-abi": "x86-softfloat", 18 | "dynamic-linking": false, 19 | "executables": false, 20 | "relocation-model": "static", 21 | "code-model": "large", 22 | "disable-redzone": true, 23 | "frame-pointer": "always", 24 | "exe-suffix": "", 25 | "has-rpath": false, 26 | "no-default-libraries": true, 27 | "position-independent-executables": false, 28 | "tls-model": "global-dynamic" 29 | } 30 | --------------------------------------------------------------------------------