├── .cargo └── config.toml ├── .github └── workflows │ ├── ci.yaml │ └── license-check.yaml ├── .gitignore ├── .licenserc.yaml ├── Cargo.lock ├── Cargo.toml ├── LICENSE.txt ├── README.md ├── build.rs ├── rust-toolchain.toml ├── rustfmt.toml ├── src ├── allocator.rs ├── fakes.rs ├── idt.rs ├── iomux.rs ├── loader.rs ├── main.rs ├── mem.rs ├── mmu.rs ├── phbl.ld ├── phbl.rs ├── start.S └── uart.rs ├── x86_64-oxide-none-elf.json └── xtask ├── Cargo.toml └── src └── main.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xtask = "run --manifest-path ./xtask/Cargo.toml --" 3 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: phbl 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - "**" 10 | 11 | env: 12 | CARGO_TERM_COLOR: always 13 | 14 | jobs: 15 | test: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Run phbl tests 20 | run: cargo xtask test --locked 21 | 22 | miri: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v3 26 | - name: Run phbl miri tests 27 | run: cargo --locked miri test 28 | 29 | format: 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@v3 33 | - uses: mbrobbel/rustfmt-check@master 34 | with: 35 | token: ${{ secrets.GITHUB_TOKEN }} 36 | 37 | lint: 38 | runs-on: ubuntu-latest 39 | steps: 40 | - uses: actions/checkout@v3 41 | - run: rustup component add clippy 42 | - uses: actions-rs/clippy-check@v1 43 | with: 44 | token: ${{ secrets.GITHUB_TOKEN }} 45 | args: --all-features 46 | -------------------------------------------------------------------------------- /.github/workflows/license-check.yaml: -------------------------------------------------------------------------------- 1 | name: license-check 2 | on: pull_request 3 | jobs: 4 | license: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@master 8 | - name: Check License Header 9 | uses: apache/skywalking-eyes/header@501a28d2fb4a9b962661987e50cf0219631b32ff 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.vscode 3 | -------------------------------------------------------------------------------- /.licenserc.yaml: -------------------------------------------------------------------------------- 1 | header: 2 | license: 3 | spdx-id: MPL-2.0 4 | 5 | content: | 6 | This Source Code Form is subject to the terms of the Mozilla Public 7 | License, v. 2.0. If a copy of the MPL was not distributed with this 8 | file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | paths: 10 | - '**/*.rs' 11 | 12 | comment: on-failure 13 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "adler2" 7 | version = "2.0.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 10 | 11 | [[package]] 12 | name = "anstream" 13 | version = "0.6.18" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 16 | dependencies = [ 17 | "anstyle", 18 | "anstyle-parse", 19 | "anstyle-query", 20 | "anstyle-wincon", 21 | "colorchoice", 22 | "is_terminal_polyfill", 23 | "utf8parse", 24 | ] 25 | 26 | [[package]] 27 | name = "anstyle" 28 | version = "1.0.10" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 31 | 32 | [[package]] 33 | name = "anstyle-parse" 34 | version = "0.2.6" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 37 | dependencies = [ 38 | "utf8parse", 39 | ] 40 | 41 | [[package]] 42 | name = "anstyle-query" 43 | version = "1.1.2" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 46 | dependencies = [ 47 | "windows-sys", 48 | ] 49 | 50 | [[package]] 51 | name = "anstyle-wincon" 52 | version = "3.0.8" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" 55 | dependencies = [ 56 | "anstyle", 57 | "once_cell_polyfill", 58 | "windows-sys", 59 | ] 60 | 61 | [[package]] 62 | name = "bit_field" 63 | version = "0.10.2" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" 66 | 67 | [[package]] 68 | name = "bitflags" 69 | version = "1.3.2" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 72 | 73 | [[package]] 74 | name = "bitflags" 75 | version = "2.9.1" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" 78 | 79 | [[package]] 80 | name = "bitstruct" 81 | version = "0.1.1" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "a1b10c3912af09af44ea1dafe307edb5ed374b2a32658eb610e372270c9017b4" 84 | dependencies = [ 85 | "bitstruct_derive", 86 | ] 87 | 88 | [[package]] 89 | name = "bitstruct_derive" 90 | version = "0.1.0" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "35fd19022c2b750d14eb9724c204d08ab7544570105b3b466d8a9f2f3feded27" 93 | dependencies = [ 94 | "proc-macro2", 95 | "quote", 96 | "syn 1.0.109", 97 | ] 98 | 99 | [[package]] 100 | name = "clap" 101 | version = "4.5.38" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" 104 | dependencies = [ 105 | "clap_builder", 106 | "clap_derive", 107 | ] 108 | 109 | [[package]] 110 | name = "clap_builder" 111 | version = "4.5.38" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" 114 | dependencies = [ 115 | "anstream", 116 | "anstyle", 117 | "clap_lex", 118 | "strsim", 119 | ] 120 | 121 | [[package]] 122 | name = "clap_derive" 123 | version = "4.5.32" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" 126 | dependencies = [ 127 | "heck", 128 | "proc-macro2", 129 | "quote", 130 | "syn 2.0.101", 131 | ] 132 | 133 | [[package]] 134 | name = "clap_lex" 135 | version = "0.7.4" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 138 | 139 | [[package]] 140 | name = "colorchoice" 141 | version = "1.0.3" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 144 | 145 | [[package]] 146 | name = "cpio_reader" 147 | version = "0.1.2" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "5793d74f50cf1460a969f028d40963d8d6e01ebae049dea976e944335c7df492" 150 | dependencies = [ 151 | "bitflags 2.9.1", 152 | ] 153 | 154 | [[package]] 155 | name = "duct" 156 | version = "1.0.0" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "b6ce170a0e8454fa0f9b0e5ca38a6ba17ed76a50916839d217eb5357e05cdfde" 159 | dependencies = [ 160 | "libc", 161 | "os_pipe", 162 | "shared_child", 163 | "shared_thread", 164 | ] 165 | 166 | [[package]] 167 | name = "goblin" 168 | version = "0.9.3" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "daa0a64d21a7eb230583b4c5f4e23b7e4e57974f96620f42a7e75e08ae66d745" 171 | dependencies = [ 172 | "log", 173 | "plain", 174 | "scroll", 175 | ] 176 | 177 | [[package]] 178 | name = "heck" 179 | version = "0.5.0" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 182 | 183 | [[package]] 184 | name = "is_terminal_polyfill" 185 | version = "1.70.1" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 188 | 189 | [[package]] 190 | name = "libc" 191 | version = "0.2.172" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 194 | 195 | [[package]] 196 | name = "log" 197 | version = "0.4.27" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 200 | 201 | [[package]] 202 | name = "miniz_oxide" 203 | version = "0.8.8" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" 206 | dependencies = [ 207 | "adler2", 208 | ] 209 | 210 | [[package]] 211 | name = "once_cell_polyfill" 212 | version = "1.70.1" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" 215 | 216 | [[package]] 217 | name = "os_pipe" 218 | version = "1.2.2" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "db335f4760b14ead6290116f2427bf33a14d4f0617d49f78a246de10c1831224" 221 | dependencies = [ 222 | "libc", 223 | "windows-sys", 224 | ] 225 | 226 | [[package]] 227 | name = "phbl" 228 | version = "0.1.0" 229 | dependencies = [ 230 | "bit_field", 231 | "bitstruct", 232 | "cpio_reader", 233 | "goblin", 234 | "miniz_oxide", 235 | "seq-macro", 236 | "static_assertions", 237 | "x86", 238 | ] 239 | 240 | [[package]] 241 | name = "plain" 242 | version = "0.2.3" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" 245 | 246 | [[package]] 247 | name = "proc-macro2" 248 | version = "1.0.95" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 251 | dependencies = [ 252 | "unicode-ident", 253 | ] 254 | 255 | [[package]] 256 | name = "quote" 257 | version = "1.0.40" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 260 | dependencies = [ 261 | "proc-macro2", 262 | ] 263 | 264 | [[package]] 265 | name = "raw-cpuid" 266 | version = "10.7.0" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" 269 | dependencies = [ 270 | "bitflags 1.3.2", 271 | ] 272 | 273 | [[package]] 274 | name = "scroll" 275 | version = "0.12.0" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" 278 | dependencies = [ 279 | "scroll_derive", 280 | ] 281 | 282 | [[package]] 283 | name = "scroll_derive" 284 | version = "0.12.1" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" 287 | dependencies = [ 288 | "proc-macro2", 289 | "quote", 290 | "syn 2.0.101", 291 | ] 292 | 293 | [[package]] 294 | name = "seq-macro" 295 | version = "0.3.6" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" 298 | 299 | [[package]] 300 | name = "shared_child" 301 | version = "1.0.2" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "7e297bd52991bbe0686c086957bee142f13df85d1e79b0b21630a99d374ae9dc" 304 | dependencies = [ 305 | "libc", 306 | "windows-sys", 307 | ] 308 | 309 | [[package]] 310 | name = "shared_thread" 311 | version = "0.1.0" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "c7a6f98357c6bb0ebace19b22220e5543801d9de90ffe77f8abb27c056bac064" 314 | 315 | [[package]] 316 | name = "static_assertions" 317 | version = "1.1.0" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 320 | 321 | [[package]] 322 | name = "strsim" 323 | version = "0.11.1" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 326 | 327 | [[package]] 328 | name = "syn" 329 | version = "1.0.109" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 332 | dependencies = [ 333 | "proc-macro2", 334 | "quote", 335 | "unicode-ident", 336 | ] 337 | 338 | [[package]] 339 | name = "syn" 340 | version = "2.0.101" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" 343 | dependencies = [ 344 | "proc-macro2", 345 | "quote", 346 | "unicode-ident", 347 | ] 348 | 349 | [[package]] 350 | name = "unicode-ident" 351 | version = "1.0.18" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 354 | 355 | [[package]] 356 | name = "utf8parse" 357 | version = "0.2.2" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 360 | 361 | [[package]] 362 | name = "windows-sys" 363 | version = "0.59.0" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 366 | dependencies = [ 367 | "windows-targets", 368 | ] 369 | 370 | [[package]] 371 | name = "windows-targets" 372 | version = "0.52.6" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 375 | dependencies = [ 376 | "windows_aarch64_gnullvm", 377 | "windows_aarch64_msvc", 378 | "windows_i686_gnu", 379 | "windows_i686_gnullvm", 380 | "windows_i686_msvc", 381 | "windows_x86_64_gnu", 382 | "windows_x86_64_gnullvm", 383 | "windows_x86_64_msvc", 384 | ] 385 | 386 | [[package]] 387 | name = "windows_aarch64_gnullvm" 388 | version = "0.52.6" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 391 | 392 | [[package]] 393 | name = "windows_aarch64_msvc" 394 | version = "0.52.6" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 397 | 398 | [[package]] 399 | name = "windows_i686_gnu" 400 | version = "0.52.6" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 403 | 404 | [[package]] 405 | name = "windows_i686_gnullvm" 406 | version = "0.52.6" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 409 | 410 | [[package]] 411 | name = "windows_i686_msvc" 412 | version = "0.52.6" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 415 | 416 | [[package]] 417 | name = "windows_x86_64_gnu" 418 | version = "0.52.6" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 421 | 422 | [[package]] 423 | name = "windows_x86_64_gnullvm" 424 | version = "0.52.6" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 427 | 428 | [[package]] 429 | name = "windows_x86_64_msvc" 430 | version = "0.52.6" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 433 | 434 | [[package]] 435 | name = "x86" 436 | version = "0.52.0" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "2781db97787217ad2a2845c396a5efe286f87467a5810836db6d74926e94a385" 439 | dependencies = [ 440 | "bit_field", 441 | "bitflags 1.3.2", 442 | "raw-cpuid", 443 | ] 444 | 445 | [[package]] 446 | name = "xtask" 447 | version = "0.1.0" 448 | dependencies = [ 449 | "clap", 450 | "duct", 451 | ] 452 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | workspace = { members = ["xtask"] } 2 | 3 | [package] 4 | name = "phbl" 5 | authors = ["Oxide Computer Company"] 6 | version = "0.1.0" 7 | edition = "2024" 8 | license = "MPL-2.0" 9 | 10 | [dependencies] 11 | bit_field = "0.10" 12 | bitstruct = "0.1" 13 | cpio_reader = "0.1" 14 | goblin = { version = "0.9", default-features = false, features = [ 15 | "endian_fd", 16 | "elf64", 17 | "elf32", 18 | "alloc", 19 | ] } 20 | miniz_oxide = "0.8" 21 | seq-macro = "0.3" 22 | static_assertions = "1.1" 23 | x86 = "0.52" 24 | 25 | [profile.dev] 26 | panic = "abort" 27 | 28 | [profile.release] 29 | panic = "abort" 30 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pico Host Boot Loader 2 | 3 | `phbl` (pronounced "foible") is the program run from the x86 4 | reset vector that loads and invokes the phase1 host operating 5 | system package, consisting of the host kernel and phase1 cpio 6 | archive. 7 | 8 | It is loaded from SPI flash by the PSP, and execution starts in 9 | 16-bit real mode. It is responsible for: 10 | 11 | * bringing the bootstrap core up into 64-bit long mode with 12 | paging enabled 13 | * decompressing the phase1 cpio archive into physical memory 14 | * locating the kernel executable ELF image inside the archive 15 | * loading the binary image into physical memory mapped at its 16 | linked addresses, 17 | * and finally invoking the kernel's ELF entry point 18 | 19 | The ZLIB compressed phase1 archive is compiled into `phbl` as a 20 | binary blob of bytes. 21 | 22 | The implementation and steps that `phbl` takes are described in 23 | detail in [rfd284][1]. 24 | 25 | ## Building phbl 26 | 27 | We use `cargo` and the [`xtask`][2] pattern for builds. Note 28 | that the compressed cpio archive containing the phase1 bootstrap 29 | is used as part of the build process, and so one must build that 30 | and compress it first before building `phbl`: the process for 31 | building a phase1 archive is beyond the scope of this document; 32 | refer to the [Helios][3] documentation for details. But note 33 | that the [pinprick][4] utility is suitable for compressing a 34 | phase1 archive so that it is compatible with `phbl`. 35 | 36 | Let us assume that the compressed cpio archive is in a file 37 | called `phase1.cpio.z` and that the `$CPIOZ` environment 38 | variable points to it. Then we may build `phbl` with: 39 | 40 | ``` 41 | cargo xtask build --cpioz=$CPIOZ 42 | ``` 43 | 44 | This generates a "Debug" binary in the file 45 | `target/x86_64-oxide-none-elf/debug/phbl`. 46 | 47 | **Note**: Linking `phbl` requires using the GNU linker. 48 | By default, Oxide's build systems install GNU ld as `gld`, which is 49 | configured in `x86_64-oxide-none-elf.json`. If your OS installation 50 | calls it something else, set the [environment variable]( 51 | https://doc.rust-lang.org/cargo/reference/environment-variables.html) 52 | `CARGO_TARGET_X86_64_OXIDE_NONE_ELF_LINKER` to use yours. On most GNU 53 | systems, that will be `CARGO_TARGET_X86_64_OXIDE_NONE_ELF_LINKER=ld`. 54 | 55 | ## Phbl development 56 | 57 | Modifying `phbl` follows the typical development patterns of 58 | most Rust programs, and we have several `cargo xtask` targets to 59 | help with common tasks. Typically, one might use: 60 | 61 | * `cargo xtask test` to run unit tests 62 | * `cargo xtask clippy` to run the linter 63 | * `cargo xtask clean` to remove build artifacts and intermediate 64 | files. 65 | * `cargo xtask expand` to expand macros 66 | * `cargo xtask disasm` to build the phbl image and dump a 67 | disassembly listing of it 68 | 69 | `cargo check` is fully supported for e.g. editor integration, 70 | and formatting should be kept consistent via `cargo fmt`. 71 | 72 | Most targets will also accept either a `--release` or `--debug` 73 | argument to get either an optimized or debugging build; debug 74 | is the default. To build a release version for production, run: 75 | 76 | ``` 77 | cargo xtask build --cpioz=$CPIOZ --release 78 | ``` 79 | 80 | This will produce an optimized standalone binary named 81 | `target/x86_64-oxide-none-elf/release/phbl`. If one builds 82 | a debug binary, as in the previous section, it will be named, 83 | `target/x86_64-oxide-none-elf/debug/phbl`. 84 | 85 | These binaries are suitable for use with the 86 | [amd-host-image-builder][5] tool. For example, to create an 87 | image suitable for writing to flash on a gimlet from a debug 88 | `phbl` binary, one may change to the `amd-host-image-builder` 89 | repository and run: 90 | 91 | ``` 92 | cargo run -- \ 93 | -B amd-firmware/GN/1.0.0.1 \ 94 | -B amd-firmware/GN/1.0.0.6 \ 95 | -c etc/milan-gimlet-b.efs.json5 \ 96 | -r ${PHBL_REPO_ROOT}/target/x86_64-oxide-none-elf/debug/phbl \ 97 | -o milan-gimlet-b-phbl.img 98 | ``` 99 | 100 | The resulting `milan-gimlet-b-phbl.img` is suitable for writing 101 | into a gimlet's SPI ROM. 102 | 103 | Changes are submitted and reviewed using the GitHub pull request 104 | model. Because `phbl` is load bearing, all changes must be 105 | reviewed. CI triggered by github actions ensures that tests 106 | pass. 107 | 108 | [1]: https://rfd.shared.oxide.computer/rfd/0284 109 | [2]: https://github.com/matklad/cargo-xtask 110 | [3]: https://github.com/oxidecomputer/helios 111 | [4]: https://github.com/oxidecomputer/pinprick/ 112 | [5]: https://github.com/oxidecomputer/amd-host-image-builder/ 113 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | fn main() { 6 | println!("cargo:rerun-if-changed=src/phbl.ld"); 7 | } 8 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly-2025-05-23" 3 | components = [ "rustfmt", "rust-src", "llvm-tools", "clippy", "miri", "rust-analyzer" ] 4 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2024" 2 | use_small_heuristics = "Max" 3 | newline_style = "Unix" 4 | max_width = 80 5 | -------------------------------------------------------------------------------- /src/allocator.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // This file incorporates work covered by the following copyright and 6 | // permission notice: 7 | // 8 | // Copyright 2021 The Hypatia Authors 9 | // All rights reserved 10 | // 11 | // Use of this source code is governed by an MIT-style 12 | // license that can be found in the LICENSE file or at 13 | // https://opensource.org/licenses/MIT. 14 | 15 | extern crate alloc; 16 | 17 | use alloc::alloc::{AllocError, Allocator, GlobalAlloc, Layout}; 18 | use core::cell::UnsafeCell; 19 | use core::ops::Range; 20 | use core::ptr; 21 | use core::sync::atomic::{AtomicUsize, Ordering}; 22 | 23 | /// A simple bump allocator for use as a global allocator 24 | /// (for Goblin) as well as implementing the specific 25 | /// allocator interface (for page tables). 26 | #[repr(C, align(4096))] 27 | pub(crate) struct BumpAlloc { 28 | heap: UnsafeCell<[u8; SIZE]>, 29 | offset: AtomicUsize, 30 | } 31 | 32 | impl BumpAlloc { 33 | pub(crate) const fn new(buffer: [u8; SIZE]) -> BumpAlloc { 34 | BumpAlloc { heap: UnsafeCell::new(buffer), offset: AtomicUsize::new(0) } 35 | } 36 | 37 | /// Allocates a region of memory of the given alignment and 38 | /// size. 39 | /// 40 | /// Note that allocators are an explicit example of a use 41 | /// case where the clippy `mut_from_ref` lint is known to 42 | /// give false-positives. 43 | #[allow(clippy::mut_from_ref)] 44 | pub(crate) fn alloc_bytes( 45 | &self, 46 | align: usize, 47 | size: usize, 48 | ) -> Option<&mut [u8]> { 49 | let heap = self.base(); 50 | let mut pos = 0; 51 | self.offset 52 | .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |offset| { 53 | let current = unsafe { heap.add(offset) }; 54 | let adjust = current.align_offset(align); 55 | pos = offset.checked_add(adjust).expect("alignment overflow"); 56 | let next = pos.checked_add(size).expect("size overflow"); 57 | if next > SIZE { 58 | return None; 59 | } 60 | Some(next) 61 | }) 62 | .ok()?; 63 | let ptr = unsafe { heap.add(pos) }; 64 | Some(unsafe { core::slice::from_raw_parts_mut(ptr, size) }) 65 | } 66 | 67 | /// Returns a raw pointer to the heap. Useful for 68 | /// reconstructing provenance. 69 | pub(crate) fn base(&self) -> *mut u8 { 70 | self.heap.get() as *mut u8 71 | } 72 | 73 | /// Returns the range of addresses in the heap, for 74 | /// validating that an integral value lies within 75 | /// the heap. 76 | pub(crate) fn addr_range(&self) -> Range { 77 | let start = self.base().addr(); 78 | let end = start + SIZE; 79 | start..end 80 | } 81 | } 82 | 83 | /// By implementing the Allocator interface, we can allocate in 84 | /// a static region, e.g., such as that owned by the page 85 | /// allocator used for creating page tables. 86 | unsafe impl Allocator for BumpAlloc { 87 | fn allocate( 88 | &self, 89 | layout: Layout, 90 | ) -> Result, AllocError> { 91 | let p = self 92 | .alloc_bytes(layout.align(), layout.size()) 93 | .ok_or(AllocError)?; 94 | Ok(p.into()) 95 | } 96 | unsafe fn deallocate(&self, _ptr: ptr::NonNull, _layout: Layout) {} 97 | } 98 | 99 | /// By implementing GlobalAlloc, we can use an ELF parsing 100 | /// library that uses Vec etc. 101 | unsafe impl GlobalAlloc for BumpAlloc { 102 | unsafe fn alloc(&self, layout: Layout) -> *mut u8 { 103 | self.alloc_bytes(layout.align(), layout.size()) 104 | .map_or(ptr::null_mut(), |p| p.as_mut_ptr()) 105 | } 106 | unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {} 107 | } 108 | 109 | #[cfg(test)] 110 | mod bump_tests { 111 | use super::BumpAlloc; 112 | 113 | #[test] 114 | fn simple_alloc() { 115 | let allocator = BumpAlloc::new([0; 128]); 116 | 117 | let a = allocator.alloc_bytes(4, 4).unwrap().as_ptr(); 118 | let b = allocator.alloc_bytes(4, 4).unwrap().as_ptr(); 119 | assert_eq!(a.addr() + 4, b.addr()); 120 | } 121 | } 122 | 123 | #[cfg(not(any(test, clippy)))] 124 | mod global { 125 | use super::BumpAlloc; 126 | 127 | const HEAP_SIZE: usize = 2 * 1024 * 1024; 128 | 129 | #[global_allocator] 130 | static mut BUMP_ALLOCATOR: BumpAlloc = 131 | BumpAlloc::new([0; HEAP_SIZE]); 132 | } 133 | -------------------------------------------------------------------------------- /src/fakes.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | //! Stub out things that are not ordinarily available in tests. 6 | //! For instance, linker-provided symbols. 7 | 8 | /// Linker symbol. 9 | #[unsafe(no_mangle)] 10 | static __sloader: usize = 4096; 11 | /// Linker symbol. 12 | #[unsafe(no_mangle)] 13 | static etext: usize = 8192; 14 | /// Linker symbol. 15 | #[unsafe(no_mangle)] 16 | static erodata: usize = 16384; 17 | /// Linker symbol. 18 | #[unsafe(no_mangle)] 19 | static edata: usize = 32768; 20 | /// Linker symbol. 21 | #[unsafe(no_mangle)] 22 | static sbss: usize = 32768 + 16384; 23 | /// Linker symbol. 24 | #[unsafe(no_mangle)] 25 | static ebss: usize = 65536; 26 | /// Defined in assembly. 27 | #[unsafe(no_mangle)] 28 | static stack: usize = 65536; 29 | /// Defined in the loader. 30 | #[unsafe(no_mangle)] 31 | static bootblock: usize = 65536 + 4096; 32 | /// Defined in the loader. 33 | #[unsafe(no_mangle)] 34 | static __eloader: usize = 65536 + 8192; 35 | /// Defined in assembly. 36 | #[unsafe(no_mangle)] 37 | static MMIO_BASE: usize = 65536 + 16384; 38 | /// Defined in assembly. 39 | #[unsafe(no_mangle)] 40 | static STACK_SIZE: u64 = 8 * 4096; 41 | /// Defined in assembly. 42 | #[unsafe(no_mangle)] 43 | static GDT_CODE64: usize = 0x28; 44 | /// Defined in assembly. 45 | #[unsafe(no_mangle)] 46 | pub unsafe extern "C" fn dnr() { 47 | loop {} 48 | } 49 | -------------------------------------------------------------------------------- /src/idt.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | // Derived from the rxv64 operating system. 6 | 7 | use crate::println; 8 | use bit_field::BitField; 9 | use bitstruct::bitstruct; 10 | use core::arch::{asm, naked_asm}; 11 | use core::ptr; 12 | use seq_macro::seq; 13 | 14 | /// Returns the selector for the 64-bit code segment in the GDT. 15 | fn code64() -> u16 { 16 | unsafe extern "C" { 17 | static GDT_CODE64: [u8; 0]; // The selector as an absolute symbol. 18 | } 19 | unsafe { GDT_CODE64.as_ptr() as u16 } 20 | } 21 | 22 | bitstruct! { 23 | /// An interrupt gate descriptor. 24 | #[derive(Clone, Copy, Default)] 25 | pub struct GateDesc(u128) { 26 | pub offset0: u16 = 0..16; 27 | pub segment_selector: u16 = 16..32; 28 | pub stack_table_index: u8 = 32..35; 29 | mbz0: bool = 35; 30 | mbz1: bool = 36; 31 | mbz2: u8 = 37..40; 32 | pub fixed_type: u8 = 40..44; 33 | mbz3: bool = 44; 34 | pub privilege_level: u8 = 45..47; 35 | pub present: bool = 47; 36 | pub offset16: u16 = 48..64; 37 | pub offset32: u32 = 64..96; 38 | reserved: u32 = 96..128; 39 | } 40 | } 41 | 42 | impl GateDesc { 43 | /// Returns an empty interrupt gate descriptor. 44 | pub const fn empty() -> GateDesc { 45 | const TYPE_INTERRUPT_GATE: u8 = 0b1110; 46 | GateDesc(0).with_fixed_type(TYPE_INTERRUPT_GATE) 47 | } 48 | 49 | /// Returns a new interrupt gate descriptor that dispatches 50 | /// to the given thunk. 51 | pub fn new(thunk: unsafe extern "C" fn() -> !) -> GateDesc { 52 | const DPL_KERN: u8 = 0b00; 53 | const RSP0: u8 = 0; 54 | let va = thunk as usize; 55 | GateDesc::empty() 56 | .with_offset0(va.get_bits(0..16) as u16) 57 | .with_offset16(va.get_bits(16..32) as u16) 58 | .with_offset32(va.get_bits(32..64) as u32) 59 | .with_stack_table_index(RSP0) 60 | .with_segment_selector(code64()) 61 | .with_present(true) 62 | .with_privilege_level(DPL_KERN) 63 | } 64 | } 65 | 66 | /// The trap frame captured by software on exceptions 67 | #[derive(Copy, Clone, Debug)] 68 | #[repr(C)] 69 | struct TrapFrame { 70 | // Pushed by software. 71 | rax: u64, 72 | rbx: u64, 73 | rcx: u64, 74 | rdx: u64, 75 | rsi: u64, 76 | rdi: u64, 77 | rbp: u64, 78 | r8: u64, 79 | r9: u64, 80 | r10: u64, 81 | r11: u64, 82 | r12: u64, 83 | r13: u64, 84 | r14: u64, 85 | r15: u64, 86 | 87 | // %ds and %es are not used in 64-bit mode, but they exist, 88 | // so we save and restore them. 89 | ds: u64, // Really these are u16s, but 90 | es: u64, // we waste a few bytes to keep 91 | fs: u64, // the stack aligned. Thank 92 | gs: u64, // you, x86 segmentation. 93 | 94 | vector: u64, 95 | 96 | // Sometimes pushed by hardware. 97 | error: u64, 98 | 99 | // Pushed by hardware. 100 | rip: u64, 101 | cs: u64, 102 | rflags: u64, 103 | rsp: u64, 104 | ss: u64, 105 | } 106 | 107 | macro_rules! gen_stub { 108 | ($name:ident, $vecnum:expr) => { 109 | #[unsafe(naked)] 110 | unsafe extern "C" fn $name() -> ! { 111 | naked_asm!("pushq $0; pushq ${}; jmp {}", 112 | const $vecnum, sym alltraps, 113 | options(att_syntax)) 114 | } 115 | }; 116 | ($name:ident, $vecnum:expr, err) => { 117 | #[unsafe(naked)] 118 | unsafe extern "C" fn $name() -> ! { 119 | naked_asm!("pushq ${}; jmp {}", 120 | const $vecnum, sym alltraps, 121 | options(att_syntax)) 122 | } 123 | }; 124 | } 125 | 126 | macro_rules! gen_vector_stub { 127 | // These cases include hardware-generated error words 128 | // on the trap frame. 129 | (vector8, 8) => { 130 | gen_stub!(vector8, 8, err); 131 | }; 132 | (vector10, 10) => { 133 | gen_stub!(vector10, 10, err); 134 | }; 135 | (vector11, 11) => { 136 | gen_stub!(vector11, 11, err); 137 | }; 138 | (vector12, 12) => { 139 | gen_stub!(vector12, 12, err); 140 | }; 141 | (vector13, 13) => { 142 | gen_stub!(vector13, 13, err); 143 | }; 144 | (vector14, 14) => { 145 | gen_stub!(vector14, 14, err); 146 | }; 147 | (vector17, 17) => { 148 | gen_stub!(vector17, 17, err); 149 | }; 150 | // No hardware error. 151 | ($vector:ident, $num:expr) => { 152 | gen_stub!($vector, $num); 153 | }; 154 | } 155 | 156 | seq!(N in 0..=255 { 157 | gen_vector_stub!(vector~N, N); 158 | }); 159 | 160 | /// The common trap routine that all vectors dispatch to. Saves 161 | /// hardware state and invokes `trap`. Note that the vector 162 | /// number and a padding zero for exceptions that don't push a 163 | /// hardware error are pushed by the trap stubs before jumping 164 | /// here. 165 | #[unsafe(naked)] 166 | unsafe extern "C" fn alltraps() -> ! { 167 | naked_asm!(r#" 168 | // Save the x86 segmentation registers. 169 | subq $32, %rsp 170 | movq $0, 24(%rsp); 171 | movw %gs, 24(%rsp); 172 | movq $0, 16(%rsp); 173 | movw %fs, 16(%rsp); 174 | movq $0, 8(%rsp); 175 | movw %es, 8(%rsp); 176 | movq $0, (%rsp); 177 | movw %ds, (%rsp); 178 | pushq %r15; 179 | pushq %r14; 180 | pushq %r13; 181 | pushq %r12; 182 | pushq %r11; 183 | pushq %r10; 184 | pushq %r9; 185 | pushq %r8; 186 | pushq %rbp; 187 | pushq %rdi; 188 | pushq %rsi; 189 | pushq %rdx; 190 | pushq %rcx; 191 | pushq %rbx; 192 | pushq %rax; 193 | movq %rsp, %rdi; 194 | callq {trap}; 195 | popq %rax; 196 | popq %rbx; 197 | popq %rcx; 198 | popq %rdx; 199 | popq %rsi; 200 | popq %rdi; 201 | popq %rbp; 202 | popq %r8; 203 | popq %r9; 204 | popq %r10; 205 | popq %r11; 206 | popq %r12; 207 | popq %r13; 208 | popq %r14; 209 | popq %r15; 210 | movw (%rsp), %ds; 211 | movw 8(%rsp), %es; 212 | movw 16(%rsp), %fs; 213 | movw 24(%rsp), %gs; 214 | addq $32, %rsp; 215 | // Pop vector and error word. 216 | addq $16, %rsp; 217 | iretq; 218 | "#, 219 | trap = sym trap, 220 | options(att_syntax)) 221 | } 222 | 223 | /// The Interrupt Descriptor Table. 224 | #[repr(C, align(4096))] 225 | struct Idt { 226 | entries: [GateDesc; 256], 227 | } 228 | 229 | impl Idt { 230 | /// Returns an empty IDT for initializing the static global. 231 | const fn empty() -> Idt { 232 | Idt { entries: [GateDesc::empty(); 256] } 233 | } 234 | 235 | /// Initializes the IDT by writing the gates to refer to the 236 | /// vector stub routines. 237 | fn init(&mut self) { 238 | self.entries = seq!(N in 0..=255 { 239 | [#( 240 | GateDesc::new(vector~N), 241 | )*] 242 | }); 243 | } 244 | 245 | /// Loads the IDT into the CPU. Creates an IDT descriptor 246 | /// on the stack and invokes the `lidt` instruction. 247 | unsafe fn load(&'static mut self) { 248 | const LIMIT: u16 = core::mem::size_of::() as u16 - 1; 249 | unsafe { 250 | asm!(r#" 251 | subq $16, %rsp; 252 | movq {}, 8(%rsp); 253 | movw ${}, 6(%rsp); 254 | lidt 6(%rsp); 255 | addq $16, %rsp; 256 | "#, in(reg) self, const LIMIT, options(att_syntax)); 257 | } 258 | } 259 | } 260 | 261 | extern "C" fn trap(frame: &mut TrapFrame) { 262 | println!("Exception:"); 263 | println!("{frame:#x?}"); 264 | println!("cr0: {:#x}", unsafe { x86::controlregs::cr0() }); 265 | println!("cr2: {:#x}", unsafe { x86::controlregs::cr2() }); 266 | println!("cr3: {:#x}", unsafe { x86::controlregs::cr3() }); 267 | println!("cr4: {:#x}", unsafe { x86::controlregs::cr4() }); 268 | println!("efer: {:#x}", unsafe { x86::msr::rdmsr(x86::msr::IA32_EFER) }); 269 | unsafe { 270 | backtrace(frame.rbp); 271 | } 272 | // Arrange for the exception return to land in a halt loop. 273 | // The seemingly superfluous cast to usize and then again to 274 | // u64 keeps clippy happy. 275 | frame.rip = crate::phbl::dnr as usize as u64; 276 | } 277 | 278 | /// Prints a call backtrace starting from the given frame 279 | /// pointer. 280 | /// 281 | /// # Safety 282 | /// Be sure to call this with something you are fairly certain 283 | /// is a valid stack frame that does not alias the current stack. 284 | unsafe fn backtrace(mut rbp: u64) { 285 | unsafe extern "C" { 286 | static stack: [u8; 0]; 287 | static STACK_SIZE: [u8; 0]; // Really the size, but an absolute symbol 288 | } 289 | let base = unsafe { stack.as_ptr() } as *const u64; 290 | let sstack = base.addr() as u64; 291 | let estack = sstack + unsafe { STACK_SIZE.as_ptr().addr() as u64 }; 292 | println!("stack [{sstack:x}..{estack:x}) %rip trace:"); 293 | while rbp != 0 { 294 | if rbp < sstack || estack < rbp + 16 || rbp & 0b1111 != 0 { 295 | println!( 296 | "bogus frame pointer {rbp:#x} (stack {sstack:#x}..{estack:#x})" 297 | ); 298 | break; 299 | } 300 | let p = base.with_addr(rbp as usize); 301 | let next_rbp = unsafe { ptr::read(p) }; 302 | if next_rbp != 0 && next_rbp < rbp + 16 { 303 | println!("stack is corrupt {next_rbp:#x} <= {rbp:#x}"); 304 | break; 305 | } 306 | let rip = unsafe { ptr::read(p.add(1)) }; 307 | rbp = next_rbp; 308 | println!("{rip:#x}"); 309 | } 310 | } 311 | 312 | /// Initialize and load the IDT. 313 | /// Should be called exactly once, early in boot. 314 | pub(crate) fn init() { 315 | use core::cell::SyncUnsafeCell; 316 | use core::sync::atomic::{AtomicBool, Ordering}; 317 | static INITED: AtomicBool = AtomicBool::new(false); 318 | if INITED.swap(true, Ordering::AcqRel) { 319 | panic!("IDT already initialized"); 320 | } 321 | static IDT: SyncUnsafeCell = SyncUnsafeCell::new(Idt::empty()); 322 | let idt = unsafe { &mut *IDT.get() }; 323 | idt.init(); 324 | unsafe { 325 | idt.load(); 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /src/iomux.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | /// Supported pin functions. 6 | #[derive(Clone, Copy)] 7 | #[repr(u8)] 8 | enum GpioX { 9 | F0 = 0b00, 10 | _F1 = 0b01, 11 | _F2 = 0b10, 12 | _F3 = 0b11, 13 | } 14 | 15 | /// Initializes the IO mux so that pins for UART 0 are mapped to 16 | /// UART functions. In some cases, the values observed are 17 | /// different from the documented reset values, so we force them 18 | /// to our desired settings. 19 | /// 20 | /// # Safety 21 | /// The caller must ensure that the IO mux MMIO region is in the 22 | /// current address space. 23 | pub unsafe fn init() { 24 | if let Some(settings) = mux_settings() { 25 | use core::ptr; 26 | const GPIO_BASE_ADDR: usize = 0xFED8_0000; 27 | const IOMUX_BASE_ADDR: usize = GPIO_BASE_ADDR + 0x0D00; 28 | let iomux = ptr::with_exposed_provenance_mut::(IOMUX_BASE_ADDR); 29 | for (pin, function) in settings.iter() { 30 | unsafe { 31 | ptr::write_volatile(iomux.offset(*pin), *function as u8); 32 | } 33 | } 34 | } 35 | } 36 | 37 | /// Returns the correct IO mux settings for the current system, 38 | /// if any. 39 | fn mux_settings() -> Option<&'static [(isize, GpioX)]> { 40 | const SP5: u32 = 4; 41 | 42 | match cpuinfo()? { 43 | // We really ought to explicitly check socket type 44 | // for the earlier processor models here. 45 | (0x17, 0x00..=0x0f, 0x0..=0xf, _) | // Naples 46 | (0x17, 0x30..=0x3f, 0x0..=0xf, _) | // Rome 47 | (0x19, 0x00..=0x0f, 0x0..=0xf, _) | // Milan 48 | (0x19, 0x10..=0x1f, 0x0..=0xf, Some(SP5)) | // Genoa 49 | (0x19, 0xa0..=0xaf, 0x0..=0xf, Some(SP5)) | // Bergamo and Sienna 50 | (0x1a, 0x00..=0x1f, 0x0..=0xf, Some(SP5)) => { // Turin 51 | Some(&[ 52 | (135, GpioX::F0), // UART0 CTS 53 | (136, GpioX::F0), // UART0 RXD 54 | (137, GpioX::F0), // UART0 RTS 55 | (138, GpioX::F0), // UART0 TXD 56 | ]) 57 | }, 58 | _ => None, 59 | } 60 | } 61 | 62 | /// Returns information about the current processor and its 63 | /// package. 64 | fn cpuinfo() -> Option<(u8, u8, u8, Option)> { 65 | let cpuid = x86::cpuid::CpuId::new(); 66 | let features = cpuid.get_feature_info()?; 67 | let family = features.family_id(); 68 | let ext = cpuid.get_extended_processor_and_feature_identifiers()?; 69 | let pkg_type = (family > 0x10).then_some(ext.pkg_type()); 70 | Some((family, features.model_id(), features.stepping_id(), pkg_type)) 71 | } 72 | -------------------------------------------------------------------------------- /src/loader.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | //! Parses and loads a binary image, represented as a slice of 6 | //! bytes, into its executable form in RAM. 7 | 8 | extern crate alloc; 9 | 10 | use crate::Result; 11 | use crate::mem; 12 | use crate::mmu::LoaderPageTable; 13 | #[cfg(not(any(test, clippy)))] 14 | use alloc::vec::Vec; 15 | use goblin::container::{Container, Ctx, Endian}; 16 | use goblin::elf::ProgramHeader; 17 | use goblin::elf::program_header::PT_LOAD; 18 | use goblin::elf::{self, Elf}; 19 | 20 | type Thunk = unsafe extern "C" fn( 21 | ramdisk_paddr: u64, 22 | ramdisk_len: usize, 23 | _rdx: u64, 24 | _rcx: u64, 25 | _r8: u64, 26 | _r9: u64, 27 | ); 28 | 29 | /// Loads an executable image contained in the given byte slice, 30 | /// creating virtual mappings as required. Returns the image's 31 | /// ELF entry point on success. 32 | pub(crate) fn load( 33 | page_table: &mut LoaderPageTable, 34 | bytes: &[u8], 35 | ) -> Result { 36 | let elf = parse_elf(bytes)?; 37 | for section in elf.program_headers.iter().filter(|&h| h.p_type == PT_LOAD) { 38 | let file_range = section.file_range(); 39 | if bytes.len() < file_range.end { 40 | return Err("load: truncated executable"); 41 | } 42 | load_segment(page_table, section, &bytes[file_range])?; 43 | } 44 | let entry = unsafe { core::mem::transmute::(elf.entry) }; 45 | Ok(move |ramdisk_paddr: u64, ramdisk_len: usize| unsafe { 46 | entry(ramdisk_paddr, ramdisk_len, 0, 0, 0, 0) 47 | }) 48 | } 49 | 50 | /// Parses the ELF executable contained in the given byte slice. 51 | fn parse_elf(bytes: &[u8]) -> Result { 52 | let header = parse_header(bytes)?; 53 | let mut elf = Elf::lazy_parse(header).map_err(|_| "parsed ELF binary")?; 54 | elf.program_headers = parse_program_headers(bytes, header)?; 55 | Ok(elf) 56 | } 57 | 58 | /// Parses and validates the ELF header from the given byte 59 | /// slice. Note that much of the heavy lifting of validating 60 | /// the ELF header is done by the parsing library. 61 | fn parse_header(bytes: &[u8]) -> Result { 62 | let binary = Elf::parse_header(bytes).map_err(|_| "parsed ELF header")?; 63 | if binary.e_machine != elf::header::EM_X86_64 { 64 | return Err("ELF: incorrect machine architecture"); 65 | } 66 | let container = binary.container().map_err(|_| "ELF: bad class")?; 67 | if container != Container::Big { 68 | return Err("ELF: object file is not 64-bit"); 69 | } 70 | let endian = binary.endianness().map_err(|_| "ELF: bad endianness")?; 71 | if endian != Endian::Little { 72 | return Err("ELF: object file is not little-endian"); 73 | } 74 | if binary.e_type != elf::header::ET_EXEC { 75 | return Err("ELF: object file is not executable"); 76 | } 77 | if binary.e_entry == 0 { 78 | return Err("ELF: binary has nil entry point"); 79 | } 80 | if binary.e_ident[elf::header::EI_VERSION] != elf::header::EV_CURRENT 81 | || binary.e_version != elf::header::EV_CURRENT.into() 82 | { 83 | return Err("ELF: bad ELF version number"); 84 | } 85 | // Apparently, illumos uses the 'ELFOSABI_SOLARIS' ABI type 86 | // for the kernel. Ignore this for now. 87 | // if binary.e_ident[elf::header::EI_OSABI] != elf::header::ELFOSABI_NONE { 88 | // return Err("ELF: bad image ABI (is not NONE)"); 89 | // } 90 | Ok(binary) 91 | } 92 | 93 | /// Parses the ELF program headers in the contained given image 94 | /// and header. Separated from parsing the rest of the image 95 | /// as we want to avoid excessive allocations for things that we 96 | /// do not use, such as the symbol and strings tables. 97 | fn parse_program_headers( 98 | bytes: &[u8], 99 | header: elf::Header, 100 | ) -> Result> { 101 | let container = header.container().map_err(|_| "ELF: Bad container")?; 102 | let endian = header.endianness().map_err(|_| "ELF: Bad endianness")?; 103 | let ctx = Ctx::new(container, endian); 104 | ProgramHeader::parse( 105 | bytes, 106 | header.e_phoff as usize, 107 | header.e_phnum as usize, 108 | ctx, 109 | ) 110 | .map_err(|_| "cannot parse ELF program headers") 111 | } 112 | 113 | /// Loads the given ELF segment, creating virtual mappings for 114 | /// it as required. 115 | fn load_segment( 116 | page_table: &mut LoaderPageTable, 117 | section: &ProgramHeader, 118 | bytes: &[u8], 119 | ) -> Result<()> { 120 | let pa = section.p_paddr; 121 | if pa % mem::P4KA::ALIGN != 0 { 122 | return Err("Program section is not physically 4KiB aligned"); 123 | } 124 | let vm = section.vm_range(); 125 | if vm.contains(&mem::LOW_CANON_SUP) || vm.contains(&mem::HI_CANON_INF) { 126 | return Err("Program section is not canonical"); 127 | } 128 | if vm.start % mem::V4KA::ALIGN != 0 { 129 | return Err("Program section not virtually 4KiB aligned"); 130 | } 131 | if vm.end <= vm.start { 132 | return Err("Program section ends before start or is empty"); 133 | } 134 | let start = mem::V4KA::new(vm.start); 135 | let end = mem::V4KA::new(round_up_4k(vm.end)); 136 | let region = start..end; 137 | let pa = mem::P4KA::new(pa); 138 | { 139 | let dst = unsafe { 140 | page_table 141 | .map_region(region.clone(), mem::Attrs::new_data(), pa) 142 | .expect("mapped region read-write"); 143 | let p = page_table.try_with_addr(start.addr()).unwrap(); 144 | let len = end.addr() - start.addr(); 145 | core::ptr::write_bytes(p, 0, len); 146 | core::slice::from_raw_parts_mut(p, len) 147 | }; 148 | let len = usize::min(bytes.len(), dst.len()); 149 | if len > 0 { 150 | dst[..len].copy_from_slice(&bytes[..len]); 151 | } 152 | } 153 | let attrs = mem::Attrs::new_kernel( 154 | section.is_read(), 155 | section.is_write(), 156 | section.is_executable(), 157 | ); 158 | unsafe { 159 | page_table 160 | .map_region(region, attrs, pa) 161 | .expect("remapped region with attrs"); 162 | } 163 | Ok(()) 164 | } 165 | 166 | /// Aligns the given address up to the next higher 4KiB 167 | /// boundary, possibly wrapping around to 0. 168 | fn round_up_4k(va: usize) -> usize { 169 | const MASK: usize = 0b1111_1111_1111; 170 | va.wrapping_add(MASK) & !MASK 171 | } 172 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | #![feature(allocator_api)] 6 | #![feature(sync_unsafe_cell)] 7 | #![cfg_attr(not(any(test, clippy)), no_std)] 8 | #![cfg_attr(not(test), no_main)] 9 | #![forbid(unsafe_op_in_unsafe_fn)] 10 | 11 | mod allocator; 12 | mod idt; 13 | mod iomux; 14 | mod loader; 15 | mod mem; 16 | mod mmu; 17 | mod phbl; 18 | mod uart; 19 | 20 | type Result = core::result::Result; 21 | 22 | /// The main entry point, called from assembler. 23 | #[unsafe(no_mangle)] 24 | pub(crate) extern "C" fn entry(config: &mut phbl::Config) { 25 | println!(); 26 | println!("Oxide Pico Host Boot Loader"); 27 | println!("{config:#x?}"); 28 | let ramdisk = expand_ramdisk(); 29 | let kernel = find_kernel(ramdisk); 30 | let entry = 31 | loader::load(&mut config.page_table, kernel).expect("loaded kernel"); 32 | println!("jumping into kernel..."); 33 | entry(ramdisk.as_ptr().addr() as u64, ramdisk.len()); 34 | panic!("main returning"); 35 | } 36 | 37 | /// Expands the compressed cpio archive image (basically a 38 | /// ramdisk) into a dedicated RAM region. Note that the ramdisk 39 | /// is compiled into the loader image. Returns a slice around 40 | /// the ramdisk contents. 41 | fn expand_ramdisk() -> &'static [u8] { 42 | use miniz_oxide::inflate::TINFLStatus; 43 | use miniz_oxide::inflate::core::DecompressorOxide; 44 | use miniz_oxide::inflate::core::decompress; 45 | use miniz_oxide::inflate::core::inflate_flags::TINFL_FLAG_PARSE_ZLIB_HEADER; 46 | 47 | #[cfg(target_os = "none")] 48 | let cpio = include_bytes!(env!("PHBL_PHASE1_COMPRESSED_CPIO_ARCHIVE_PATH")); 49 | #[cfg(not(target_os = "none"))] 50 | let cpio = [0u8; 1]; 51 | 52 | let dst = phbl::ramdisk_region_init_mut(); 53 | let mut r = DecompressorOxide::new(); 54 | let flags = TINFL_FLAG_PARSE_ZLIB_HEADER; 55 | print!( 56 | "Decompressing cpio archive to {:#x}..{:#x}...", 57 | dst.as_ptr().addr(), 58 | dst.len() + dst.as_ptr().addr(), 59 | ); 60 | let (s, _, o) = decompress(&mut r, &cpio[..], dst, 0, flags); 61 | assert!(s == TINFLStatus::Done); 62 | println!("Done."); 63 | &dst[..o] 64 | } 65 | 66 | fn find_kernel(cpio: &[u8]) -> &[u8] { 67 | for entry in cpio_reader::iter_files(cpio) { 68 | if entry.name() == "platform/oxide/kernel/amd64/unix" { 69 | return entry.file(); 70 | } 71 | } 72 | panic!("could not locate unix in cpio archive"); 73 | } 74 | 75 | #[cfg(not(any(test, clippy)))] 76 | mod no_std { 77 | #[panic_handler] 78 | pub fn panic(info: &core::panic::PanicInfo) -> ! { 79 | crate::println!("Panic: {:#?}", info); 80 | unsafe { 81 | crate::phbl::dnr(); 82 | } 83 | } 84 | } 85 | #[cfg(test)] 86 | mod fakes; 87 | -------------------------------------------------------------------------------- /src/mem.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | use core::ops::Range; 6 | 7 | pub(crate) const KIB: usize = 1024; 8 | pub(crate) const MIB: usize = 1024 * KIB; 9 | pub(crate) const GIB: usize = 1024 * MIB; 10 | 11 | /// A V4KA represents a 4KiB aligned, canonical virtual memory 12 | /// address. The address may or may not be mapped. 13 | #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd)] 14 | pub(crate) struct V4KA(usize); 15 | 16 | /// Lower canonical address space supremum. 17 | pub const LOW_CANON_SUP: usize = 0x0000_7FFF_FFFF_FFFF + 1; 18 | // Higher canonical address space infimum. 19 | pub const HI_CANON_INF: usize = 0xFFFF_8000_0000_0000 - 1; 20 | 21 | /// Returns true IFF the given address is canonical. 22 | const fn is_canonical(va: usize) -> bool { 23 | va <= 0x0000_7FFF_FFFF_FFFF || 0xFFFF_8000_0000_0000 <= va 24 | } 25 | 26 | /// Returns true IFF the address is a valid physical address. 27 | pub const fn is_physical(pa: u64) -> bool { 28 | pa < (1 << 46) 29 | } 30 | 31 | /// Returns true IFF the range of virtual addresses 32 | /// in [start, end) is canonical. 33 | pub const fn is_canonical_range(start: usize, end: usize) -> bool { 34 | // If the range ends before it starts and end is not exactly 35 | // zero, the range is not canonical. 36 | if end < start && end != 0 { 37 | return false; 38 | } 39 | // If in the lower portion of the canonical address space, 40 | // end is permitted to be exactly one beyond the supremum. 41 | if start < LOW_CANON_SUP && end <= LOW_CANON_SUP { 42 | return true; 43 | } 44 | // Otherwise, the range is valid IFF it is in the upper 45 | // portion of the canonical address space, or end is 0. 46 | HI_CANON_INF < start && (HI_CANON_INF < end || end == 0) 47 | } 48 | 49 | impl V4KA { 50 | /// The alignment factor. 51 | pub(crate) const ALIGN: usize = 4096; 52 | pub(crate) const MASK: usize = Self::ALIGN - 1; 53 | pub(crate) const SIZE: usize = Self::ALIGN; 54 | 55 | /// Returns a new V4KA constructed from the given virtual 56 | /// address, which must be both canonical and properly 57 | /// aligned. 58 | pub(crate) const fn new(va: usize) -> V4KA { 59 | assert!(is_canonical(va)); 60 | assert!(va & Self::MASK == 0); 61 | V4KA(va) 62 | } 63 | 64 | /// Returns the integer value of the raw virtual address. 65 | pub(crate) const fn addr(self) -> usize { 66 | self.0 67 | } 68 | } 69 | 70 | /// A P4KA represents a 4KiB aligned, valid address in the 71 | /// physical address space. 72 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 73 | pub(crate) struct P4KA(u64); 74 | 75 | impl P4KA { 76 | /// The alignment factor. 77 | pub(crate) const ALIGN: u64 = 4096; 78 | pub(crate) const MASK: u64 = Self::ALIGN - 1; 79 | 80 | /// Constructs a new P4KA from the given physical address, 81 | /// must be properly aligned and lie within the range of the 82 | /// physical address space. 83 | pub(crate) const fn new(pa: u64) -> P4KA { 84 | assert!(is_physical(pa)); 85 | assert!(pa & Self::MASK == 0); 86 | P4KA(pa) 87 | } 88 | 89 | /// Returns the integer value of the raw physical address. 90 | pub(crate) const fn phys_addr(self) -> u64 { 91 | self.0 92 | } 93 | } 94 | 95 | /// Records the permissions of a mapped into the virtual address 96 | /// space. 97 | #[derive(Clone, Copy, Debug)] 98 | pub(crate) struct Attrs { 99 | /// True if readable. 100 | r: bool, 101 | /// True if writable. 102 | w: bool, 103 | /// True if executable. 104 | x: bool, 105 | /// True if cacheable. 106 | c: bool, 107 | /// True if part of the kernel nucleus 108 | k: bool, 109 | } 110 | 111 | impl Attrs { 112 | /// Returns a new Attrs structure with the given permissions. 113 | pub(crate) fn new(r: bool, w: bool, x: bool, c: bool, k: bool) -> Attrs { 114 | Attrs { r, w, x, c, k } 115 | } 116 | 117 | /// Returns new attributes suitable for the loader. 118 | fn new_loader(r: bool, w: bool, x: bool) -> Attrs { 119 | Self::new(r, w, x, true, false) 120 | } 121 | 122 | /// Returns a new Attrs specialized for loader text. 123 | pub(crate) fn new_text() -> Attrs { 124 | Self::new_loader(true, false, true) 125 | } 126 | 127 | /// Returns a new Attrs specialized for loader read-only 128 | /// data. 129 | pub(crate) fn new_rodata() -> Attrs { 130 | Self::new_loader(true, false, false) 131 | } 132 | 133 | /// Returns a new Attrs specialized for loader read/write 134 | /// data. 135 | pub(crate) fn new_data() -> Attrs { 136 | Self::new_loader(true, true, false) 137 | } 138 | 139 | /// Returns new Attrs specialized for loader BSS. These are 140 | /// functionally identical to data attributes. 141 | pub(crate) fn new_bss() -> Attrs { 142 | Self::new_data() 143 | } 144 | 145 | /// Returns new Attrs specialized for MMIO regions. Notably, 146 | /// these are uncached. 147 | pub(crate) fn new_mmio() -> Attrs { 148 | Self::new(true, true, false, false, false) 149 | } 150 | 151 | /// Returns new Attrs suitable for the host kernel nucleus. 152 | pub(crate) fn new_kernel(r: bool, w: bool, x: bool) -> Attrs { 153 | Self::new(r, w, x, true, true) 154 | } 155 | 156 | /// Returns true IFF readable. 157 | pub(crate) fn r(&self) -> bool { 158 | self.r 159 | } 160 | 161 | /// Returns true IFF writeable. 162 | pub(crate) fn w(&self) -> bool { 163 | self.w 164 | } 165 | 166 | /// Returns true IFF executable. 167 | pub(crate) fn x(&self) -> bool { 168 | self.x 169 | } 170 | 171 | /// Returns true IFF cacheable. 172 | pub(crate) fn c(&self) -> bool { 173 | self.c 174 | } 175 | 176 | /// Returns true IFF part of the host kernel. 177 | pub(crate) fn k(&self) -> bool { 178 | self.k 179 | } 180 | } 181 | 182 | /// A region of virtual memory. 183 | #[derive(Clone, Debug)] 184 | pub(crate) struct Region { 185 | range: Range, 186 | attrs: Attrs, 187 | } 188 | 189 | impl Region { 190 | /// Returns a new region spanning the given [start, end) 191 | /// address pair and attributes. 192 | pub fn new(range: Range, attrs: Attrs) -> Region { 193 | Region { range, attrs } 194 | } 195 | 196 | /// Returns the range start address. 197 | pub fn start(&self) -> V4KA { 198 | self.range.start 199 | } 200 | 201 | /// Returns the range end address. 202 | pub fn end(&self) -> V4KA { 203 | self.range.end 204 | } 205 | 206 | /// Returns the range attributes. 207 | pub fn attrs(&self) -> Attrs { 208 | self.attrs 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/mmu.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | //! # Page tables and the MMU. 6 | //! 7 | //! We support paging for 64-bit operation in early boot, but 8 | //! not anything close to approaching the generality of virtual 9 | //! memory that would be supported in a full operating system. 10 | //! While limiting in some respects, we do not need anything 11 | //! more complex, and this allows us to make a number of 12 | //! simplifying assumptions: 13 | //! 14 | //! * The loader virtual address space is identity mapped. That 15 | //! is, every mapped loader address is in bijection with the 16 | //! corresponding physical addresses that map to them. 17 | //! * Pages used to define the paging structures are allocated 18 | //! from the loader and mapped into its virtual address space. 19 | //! * As a consequence of the above two points, every page in 20 | //! the page table radix tree is mapped in the loader at its 21 | //! physical address. We may take any PTE in an inner node in 22 | //! the table, extract its physical address, and cast that to 23 | //! a valid pointer to a Table structure. 24 | //! * While we support the creation of multiple address spaces 25 | //! (we need to remap the loader itself on entry to Rust code) 26 | //! we run on a single CPU in a single-threaded environment. 27 | //! Exactly one virtual address space is active at any given 28 | //! time globally across the machine. 29 | //! 30 | //! ## Notes on Types and Traits 31 | //! 32 | //! We endeavor to use the type system to statically prevent 33 | //! common error categories. For instance, we enforce at 34 | //! compile time that pages of a particular type (4KiB, 2MiB, 35 | //! 1GiB) are only mapped to physical page frames of the 36 | //! corresponding type. 37 | //! 38 | //! Generally, we try to adhere to the, "Parse Don't Validate" 39 | //! type-driven design philosophy. Once we have parsed data 40 | //! into a type that represents some invariant, we do not 41 | //! continually recheck that invariant. For example, we may 42 | //! parse a virtual memory address into a, `Page2M` variable; 43 | //! we then trust that the contained address is appropriately 44 | //! aligned and canonical. 45 | //! 46 | //! The major traits involved in MMU handling are: 47 | //! 48 | //! * `Frame` --- Describes facets of physical page frames that 49 | //! are used when creating page table entries. These include 50 | //! the frame's physical address and whether it is "big" 51 | //! (Large or Huge in x86 parlance). Concrete frame types are 52 | //! defined for various sizes/alignments. 53 | //! * `Page` --- Describes pages of virtual memory, including 54 | //! associating them with their corresponding physical frame 55 | //! type. Concrete page types are defined for various 56 | //! sizes/alignments. 57 | //! * `Table` --- A page table is really a hardware-defined 58 | //! radix tree. The `Table` trait describes behaviors at a 59 | //! particular level in the tree. Concrete types exist for 60 | //! tables at each tree level. 61 | //! * `TableInner` --- An interior node in the tree can, 62 | //! depending on its specific type, either map to a "big" page 63 | //! or point to a next-level page table. This trait describes 64 | //! behaviors of table types that can point to other nodes. 65 | //! * `Mapping` and the `Mapping(1|2|3|4)` enumerations --- 66 | //! These types tie `Page`/`Frame` pairs to `Table` types and 67 | //! are used when establishing mappings, as well as defining 68 | //! page attributes (readability, cacheability, etc). 69 | //! 70 | //! These details are hidden from the consumer, which interacts 71 | //! with page tables via the `PageTable` type, which is a 72 | //! wrapper around a `PML4` (the root of the paging radix tree). 73 | //! This exposes the various kinds of mapping methods that can 74 | //! make use of the above to enforce invariants. 75 | //! 76 | //! With these things in place, we can statically prevent many 77 | //! paging errors; interior nodes in the tree always have the 78 | //! correct permissions, frame and page sizes and alignment 79 | //! always correspond, etc. Regardless, there are some errors 80 | //! we make no attempt to prevent: for instance, nothing 81 | //! prevents us from mapping MMIO space onto the loader text 82 | //! segment, or otherwise overwriting existing mappings, etc, 83 | //! and so the `map` operation is unsafe. 84 | //! 85 | //! ## Physical Memory and Interaction with the Host OS 86 | //! 87 | //! The loader has a contract with the host operating system 88 | //! that imposes some constraints on memory consumed by the MMU 89 | //! code. In particular, for the page table that we enter the 90 | //! host operating system on, the host OS requires that, 91 | //! 92 | //! * All memory frames in the page table come from a physically 93 | //! contiguous region, 94 | //! * That the root table (PML4) must be at the lowest physical 95 | //! address in that contiguous range, 96 | //! * That the range must has no fewer 16 4KiB pages (but may 97 | //! have more). 98 | //! * All non-page-table pages that map part of the host OS 99 | //! "kernel nucleus" image must set bit 11 in their PTEs. 100 | //! 101 | //! See RFD 215 for details. 102 | //! 103 | //! In order to maintain these properties, we define a special 104 | //! memory allocator specific to page table allocation that 105 | //! draws from a 4KiB aligned static buffer. 106 | 107 | extern crate alloc; 108 | 109 | use crate::mem; 110 | #[cfg(not(any(test, clippy)))] 111 | use alloc::boxed::Box; 112 | #[cfg(not(any(test, clippy)))] 113 | use alloc::vec::Vec; 114 | use bitstruct::bitstruct; 115 | use core::ops::Range; 116 | 117 | #[derive(Clone, Copy, Debug)] 118 | pub(super) enum Error { 119 | BadPointer, 120 | } 121 | 122 | // We start with basic page and frame types. 123 | 124 | /// Traits common to page frame numbers. PFNs of different 125 | /// sizes represent aligned frames of physical address space. 126 | trait Frame { 127 | /// True if the frame is larger than 4KiB. 128 | const BIG: bool; 129 | const SIZE: usize; 130 | 131 | /// Returns a new Frame of the given type. 132 | fn new(addr: u64) -> Self; 133 | 134 | /// Returns the physical address of the frame. 135 | fn phys_addr(self) -> u64; 136 | } 137 | 138 | /// An aligned 4KiB span of physical address space. 139 | #[derive(Clone, Copy, Debug)] 140 | #[repr(transparent)] 141 | struct PFN4K(u64); 142 | impl Frame for PFN4K { 143 | const BIG: bool = false; 144 | const SIZE: usize = 1 << 12; 145 | 146 | fn new(addr: u64) -> PFN4K { 147 | assert_eq!(addr % Self::SIZE as u64, 0); 148 | PFN4K(addr) 149 | } 150 | 151 | fn phys_addr(self) -> u64 { 152 | self.0 153 | } 154 | } 155 | 156 | /// An aligned 2MiB span of physical address space. 157 | #[derive(Clone, Copy, Debug)] 158 | #[repr(transparent)] 159 | struct PFN2M(u64); 160 | impl Frame for PFN2M { 161 | const BIG: bool = true; 162 | const SIZE: usize = 1 << 21; 163 | 164 | fn new(addr: u64) -> PFN2M { 165 | assert_eq!(addr % Self::SIZE as u64, 0); 166 | PFN2M(addr) 167 | } 168 | 169 | fn phys_addr(self) -> u64 { 170 | self.0 171 | } 172 | } 173 | 174 | /// An aligned 1GiB span of physical address space. 175 | #[derive(Clone, Copy, Debug)] 176 | #[repr(transparent)] 177 | struct PFN1G(u64); 178 | impl Frame for PFN1G { 179 | const BIG: bool = true; 180 | const SIZE: usize = 1 << 30; 181 | 182 | fn new(addr: u64) -> PFN1G { 183 | assert_eq!(addr % Self::SIZE as u64, 0); 184 | PFN1G(addr) 185 | } 186 | 187 | fn phys_addr(self) -> u64 { 188 | self.0 189 | } 190 | } 191 | 192 | /// Represents a 4KiB page of virtual memory, aligned on a 4KiB 193 | /// boundary. 194 | #[derive(Clone)] 195 | struct Page4K(usize); 196 | impl Page4K { 197 | fn new(va: usize) -> Self { 198 | assert_eq!(va % ::FrameType::SIZE, 0); 199 | Self(va) 200 | } 201 | } 202 | 203 | /// Represents a 2MiB page of virtual memory, aligned on a 2MiB 204 | /// boundary. 205 | struct Page2M(usize); 206 | impl Page2M { 207 | fn new(va: usize) -> Self { 208 | assert_eq!(va % ::FrameType::SIZE, 0); 209 | Self(va) 210 | } 211 | } 212 | 213 | /// Represents a 1GiB page of virtual memory, aligned on a 1GiB 214 | /// boundary. 215 | struct Page1G(usize); 216 | impl Page1G { 217 | fn new(va: usize) -> Self { 218 | assert_eq!(va % ::FrameType::SIZE, 0); 219 | Self(va) 220 | } 221 | } 222 | 223 | /// Represents some mapping from a virtual page to a physical 224 | /// frame of the corresponding type. 225 | trait Mapping { 226 | fn virt_addr(&self) -> *const (); 227 | } 228 | 229 | /// Representable mappings at the PML1 level. 230 | enum Mapping1 { 231 | Map4K(Page4K, PFN4K, mem::Attrs), 232 | } 233 | 234 | impl Mapping for Mapping1 { 235 | fn virt_addr(&self) -> *const () { 236 | match self { 237 | Mapping1::Map4K(page, _, _) => { 238 | core::ptr::without_provenance(page.addr()) 239 | } 240 | } 241 | } 242 | } 243 | 244 | /// Mappings representable at the PML2 level. 245 | enum Mapping2 { 246 | Map2M(Page2M, PFN2M, mem::Attrs), 247 | Next(Mapping1), 248 | } 249 | 250 | impl Mapping for Mapping2 { 251 | fn virt_addr(&self) -> *const () { 252 | match self { 253 | Mapping2::Map2M(page, _, _) => { 254 | core::ptr::without_provenance(page.addr()) 255 | } 256 | Mapping2::Next(mapping1) => mapping1.virt_addr(), 257 | } 258 | } 259 | } 260 | 261 | /// Representable mappings at the PML3 level. 262 | enum Mapping3 { 263 | Map1G(Page1G, PFN1G, mem::Attrs), 264 | Next(Mapping2), 265 | } 266 | 267 | impl Mapping for Mapping3 { 268 | fn virt_addr(&self) -> *const () { 269 | match self { 270 | Mapping3::Map1G(page, _, _) => { 271 | core::ptr::without_provenance(page.addr()) 272 | } 273 | Mapping3::Next(mapping2) => mapping2.virt_addr(), 274 | } 275 | } 276 | } 277 | 278 | /// Representable mappings at the PML4 (root) level. 279 | enum Mapping4 { 280 | Next(Mapping3), 281 | } 282 | 283 | impl Mapping for Mapping4 { 284 | fn virt_addr(&self) -> *const () { 285 | match self { 286 | Mapping4::Next(mapping3) => mapping3.virt_addr(), 287 | } 288 | } 289 | } 290 | 291 | /// Traits shared by pages of all types. 292 | trait Page { 293 | /// The associated frame type for this page type. 294 | type FrameType: Frame; 295 | 296 | /// Creates a Mapping enumeration binding a typed page and 297 | /// frame for this type of page. 298 | fn mapping( 299 | page: Self, 300 | frame: Self::FrameType, 301 | attrs: mem::Attrs, 302 | ) -> Mapping4; 303 | 304 | // Returns the virtual address of the page. 305 | fn addr(&self) -> usize; 306 | } 307 | 308 | impl Page for Page4K { 309 | type FrameType = PFN4K; 310 | 311 | fn mapping( 312 | page: Self, 313 | frame: Self::FrameType, 314 | attrs: mem::Attrs, 315 | ) -> Mapping4 { 316 | let mapping = Mapping1::Map4K(page, frame, attrs); 317 | Mapping4::Next(Mapping3::Next(Mapping2::Next(mapping))) 318 | } 319 | 320 | fn addr(&self) -> usize { 321 | self.0 322 | } 323 | } 324 | 325 | impl Page for Page2M { 326 | type FrameType = PFN2M; 327 | 328 | fn mapping( 329 | page: Self, 330 | frame: Self::FrameType, 331 | attrs: mem::Attrs, 332 | ) -> Mapping4 { 333 | Mapping4::Next(Mapping3::Next(Mapping2::Map2M(page, frame, attrs))) 334 | } 335 | 336 | fn addr(&self) -> usize { 337 | self.0 338 | } 339 | } 340 | 341 | impl Page for Page1G { 342 | type FrameType = PFN1G; 343 | 344 | fn mapping( 345 | page: Self, 346 | frame: Self::FrameType, 347 | attrs: mem::Attrs, 348 | ) -> Mapping4 { 349 | Mapping4::Next(Mapping3::Map1G(page, frame, attrs)) 350 | } 351 | 352 | fn addr(&self) -> usize { 353 | self.0 354 | } 355 | } 356 | 357 | bitstruct! { 358 | /// A basic page table entry used at any level of the paging 359 | /// hierarchy. Note that the loader only uses a small subset 360 | /// of paging functionality, so we don't define every bit 361 | /// defined by the hardware. 362 | /// 363 | /// Bit 11 is special. This is one of the architecturally 364 | /// "ignored" bits available for use by system software; we 365 | /// use it as part of the contract with the host operating 366 | /// system: setting it on a leaf marks a page containing 367 | /// part of the host OS kernel. 368 | /// 369 | /// We don't use the user bit, but the host OS expects it to 370 | /// be set on the interior paging structures, so we define 371 | /// it here. 372 | #[derive(Copy, Clone, Debug)] 373 | struct PTE(u64) { 374 | p: bool = 0; 375 | w: bool = 1; 376 | u: bool = 2; 377 | wt: bool = 3; 378 | nc: bool = 4; 379 | // a: bool = 5; 380 | // d: bool = 6; 381 | h: bool = 7; // Large or Huge page. 382 | // g: bool = 8; 383 | //ign: u8 = 9..12; 384 | k: bool = 11; 385 | pfn: u64 = 12..51; 386 | nx: bool = 63; 387 | } 388 | } 389 | 390 | impl PTE { 391 | /// Returns an empty PTE. 392 | const fn empty() -> PTE { 393 | PTE(0) 394 | } 395 | 396 | const fn from_phys_addr(pa: u64) -> PTE { 397 | PTE(pa) 398 | } 399 | 400 | fn phys_addr(&self) -> u64 { 401 | self.pfn() << 12 402 | } 403 | 404 | /// Creates a new PTE for the given page frame number and 405 | /// permissions. 406 | fn new(pa: F, attrs: mem::Attrs) -> PTE { 407 | PTE::from_phys_addr(pa.phys_addr()) 408 | .with_p(attrs.r()) 409 | .with_w(attrs.w()) 410 | .with_nx(!attrs.x()) 411 | .with_wt(!attrs.c()) 412 | .with_nc(!attrs.c()) 413 | .with_k(attrs.k()) 414 | .with_h(F::BIG) 415 | } 416 | 417 | /// Creates a new PTE for a table at any level in the radix 418 | /// tree. 419 | fn new_for_table(table: &T) -> PTE { 420 | let ptr: *const T = table; 421 | // Note that tables are identity mapped. 422 | let pa = ptr.addr() as u64; 423 | PTE::from_phys_addr(pa) 424 | .with_p(true) 425 | .with_w(true) 426 | .with_wt(false) 427 | .with_nc(false) 428 | .with_nx(false) 429 | .with_u(true) 430 | } 431 | 432 | /// Returns the permissions of the given entry (if any). 433 | fn attrs(self) -> mem::Attrs { 434 | mem::Attrs::new(self.p(), self.w(), !self.nx(), !self.nc(), self.k()) 435 | } 436 | 437 | /// Returns the virtual address of the table mapped by this address. 438 | /// 439 | /// # Safety 440 | /// Tables are taken from the identity mapped region of the 441 | /// address space. 442 | unsafe fn virt_addr(self) -> *const () { 443 | core::ptr::without_provenance(self.phys_addr() as usize) 444 | } 445 | } 446 | 447 | #[cfg(test)] 448 | mod pte_tests { 449 | use super::{Frame, PFN2M, PFN4K, PTE}; 450 | use crate::mem; 451 | 452 | #[test] 453 | fn simple() { 454 | let pte = PTE::from_phys_addr(0xF00F_F000) 455 | .with_p(true) 456 | .with_w(true) 457 | .with_u(true); 458 | assert_eq!(pte.0, 0xF00F_F007); 459 | assert_eq!(pte.pfn(), 0xF_00FF); 460 | assert!(!pte.nc()); 461 | assert!(!pte.nx()); 462 | } 463 | 464 | #[test] 465 | fn nx() { 466 | let pte = PTE(0).with_pfn(0xF_00FF).with_nx(true); 467 | assert_eq!(pte.0, 0x8000_0000_F00F_F000); 468 | } 469 | 470 | #[test] 471 | fn constructed() { 472 | let frame = PFN4K::new(0xF00D_F000); 473 | let attrs = mem::Attrs::new(true, true, true, true, true); 474 | let pte = PTE::new(frame, attrs); 475 | assert_eq!(pte.0, 0b1111_0000_0000_1101_1111_1000_0000_0011); 476 | } 477 | 478 | #[test] 479 | fn constructed_large() { 480 | let frame = PFN2M::new(0xF000_0000); 481 | let attrs = mem::Attrs::new(true, true, false, false, true); 482 | let pte = PTE::new(frame, attrs); 483 | const NX: u64 = 1 << 63; 484 | assert_eq!(pte.0, NX | 0b1111_0000_0000_0000_0000_1000_1001_1011); 485 | } 486 | } 487 | 488 | /// Traits shared by tables at all levels in the paging radix 489 | /// tree. 490 | trait Table: Sized { 491 | /// The associated entry type mapped by this table type. 492 | type EntryType; 493 | 494 | /// The mapping type supported by this level of the tree. 495 | type MappingType: Mapping; 496 | 497 | /// The number of bits required to shift a virtual address 498 | /// to find its index in a table of this type. 499 | const INDEX_SHIFT: usize; 500 | 501 | /// Creates a new table of the current type. This is 502 | /// allocated from the special paging-specific table 503 | /// allocator. 504 | fn new() -> &'static mut Self { 505 | let table = Box::::new_zeroed_in(TableAlloc); 506 | Box::leak(unsafe { table.assume_init() }) 507 | } 508 | 509 | /// Returns an entry in the current table for the given 510 | /// virtual address. 511 | fn entry(&mut self, va: *const ()) -> Option; 512 | 513 | /// Sets the entry corresponding to the given virtual 514 | /// address. 515 | /// 516 | /// # Safety 517 | /// The caller must ensure that the given entry type and 518 | /// permissions are appropriate for the virtual address 519 | /// space. This method does not ensure that one does not 520 | /// overwrite part of the loader, or map a cached-page onto 521 | /// MMIO space, for example. 522 | unsafe fn set_entry( 523 | &mut self, 524 | va: *const (), 525 | entry: Option, 526 | ); 527 | 528 | /// Establishes a mapping of the appropriate type for this 529 | /// level of the tree in the table. 530 | /// 531 | /// # Safety 532 | /// The caller must ensure that the given mapping is 533 | /// appropriate for the virtual address space. This method 534 | /// will overwrite any existing mappings. Be sure not to 535 | /// overwrite the loader or inappropriately map MMIO space. 536 | unsafe fn map(&mut self, mapping: Self::MappingType); 537 | 538 | /// Computes the table entry index for the given virtual 539 | /// address in the current table. 540 | fn index(va: *const ()) -> usize { 541 | (va.addr() >> Self::INDEX_SHIFT) & 0x1FF 542 | } 543 | } 544 | 545 | /// Interior table types in the radix tree implement this trait 546 | /// to establish behaviors specific to nodes that can point to 547 | /// other nodes. 548 | trait InnerTable: Table { 549 | /// The type of table at the next lower level in the paging 550 | /// radix tree. 551 | type NextTableType: Table; 552 | 553 | /// Returns a mutable reference to the next-level page table 554 | /// for the given virtual address, or None if no such table 555 | /// exists. 556 | fn next_mut( 557 | &mut self, 558 | va: *const (), 559 | ) -> Option<&'static mut Self::NextTableType>; 560 | } 561 | 562 | /// A PML4 is the highest level of the paging radix tree. 563 | #[repr(C, align(4096))] 564 | struct PML4 { 565 | entries: [PTE; 512], 566 | } 567 | 568 | /// The only valid entries in the PML4 are pointers to PML3s. 569 | enum PML4E { 570 | Next(&'static mut PML3), 571 | } 572 | 573 | impl InnerTable for PML4 { 574 | type NextTableType = PML3; 575 | 576 | fn next_mut(&mut self, va: *const ()) -> Option<&'static mut PML3> { 577 | let entry = self.entries[Self::index(va)]; 578 | entry.p().then(|| { 579 | let p = unsafe { entry.virt_addr() }; 580 | assert!(!p.is_null() && p.cast::().is_aligned()); 581 | unsafe { &mut *TableAlloc::try_with_addr(p.addr()).unwrap() } 582 | }) 583 | } 584 | } 585 | 586 | impl Table for PML4 { 587 | type EntryType = PML4E; 588 | type MappingType = Mapping4; 589 | const INDEX_SHIFT: usize = 39; 590 | 591 | fn entry(&mut self, va: *const ()) -> Option { 592 | self.next_mut(va).map(PML4E::Next) 593 | } 594 | 595 | unsafe fn set_entry(&mut self, va: *const (), entry: Option) { 596 | self.entries[PML4::index(va)] = match entry { 597 | None => PTE::empty(), 598 | Some(PML4E::Next(table)) => PTE::new_for_table(table), 599 | }; 600 | } 601 | 602 | unsafe fn map(&mut self, mapping: Mapping4) { 603 | let va = mapping.virt_addr(); 604 | if self.entry(va).is_none() { 605 | unsafe { 606 | self.set_entry(va, Some(PML4E::Next(PML3::new()))); 607 | } 608 | } 609 | if let Some(table) = self.next_mut(va) { 610 | let Mapping4::Next(mapping3) = mapping; 611 | unsafe { 612 | table.map(mapping3); 613 | } 614 | } 615 | } 616 | } 617 | 618 | /// The PML3 is the second highest level in the paging radix 619 | /// tree. It can either map 1GiB "huge" pages. 620 | #[repr(C, align(4096))] 621 | struct PML3 { 622 | entries: [PTE; 512], 623 | } 624 | 625 | /// PML3 entries either point to a 1GiB page frame, or to a 626 | /// PML2. 627 | enum PML3E { 628 | Next(&'static mut PML2), 629 | Page(PFN1G, mem::Attrs), 630 | } 631 | 632 | impl InnerTable for PML3 { 633 | type NextTableType = PML2; 634 | 635 | fn next_mut(&mut self, va: *const ()) -> Option<&'static mut PML2> { 636 | let entry = self.entries[Self::index(va)]; 637 | (entry.p() && !entry.h()).then(|| { 638 | let p = unsafe { entry.virt_addr() }; 639 | assert!(!p.is_null() && p.cast::().is_aligned()); 640 | unsafe { &mut *TableAlloc::try_with_addr(p.addr()).unwrap() } 641 | }) 642 | } 643 | } 644 | 645 | impl Table for PML3 { 646 | type EntryType = PML3E; 647 | type MappingType = Mapping3; 648 | const INDEX_SHIFT: usize = 30; 649 | 650 | fn entry(&mut self, va: *const ()) -> Option { 651 | let entry = self.entries[Self::index(va)]; 652 | match (entry.p(), entry.h()) { 653 | (false, _) => None, 654 | (_, false) => self.next_mut(va).map(PML3E::Next), 655 | (_, true) => { 656 | Some(PML3E::Page(PFN1G::new(entry.phys_addr()), entry.attrs())) 657 | } 658 | } 659 | } 660 | 661 | unsafe fn set_entry(&mut self, va: *const (), entry: Option) { 662 | self.entries[PML3::index(va)] = match entry { 663 | None => PTE::empty(), 664 | Some(PML3E::Next(table)) => PTE::new_for_table(table), 665 | Some(PML3E::Page(page, attrs)) => PTE::new(page, attrs), 666 | }; 667 | } 668 | 669 | unsafe fn map(&mut self, mapping: Mapping3) { 670 | let va = mapping.virt_addr(); 671 | match mapping { 672 | Mapping3::Map1G(_, frame, attrs) => unsafe { 673 | self.set_entry(va, Some(PML3E::Page(frame, attrs))); 674 | }, 675 | Mapping3::Next(mapping2) => { 676 | if self.entry(va).is_none() { 677 | unsafe { 678 | self.set_entry(va, Some(PML3E::Next(PML2::new()))); 679 | } 680 | } 681 | if let Some(table) = self.next_mut(va) { 682 | unsafe { 683 | table.map(mapping2); 684 | } 685 | } 686 | } 687 | } 688 | } 689 | } 690 | 691 | /// The PML2 is the third-highest type of table in the paging 692 | /// tree. 693 | #[repr(C, align(4096))] 694 | struct PML2 { 695 | entries: [PTE; 512], 696 | } 697 | 698 | /// PML2 entries can either point to a PML1, or to a 2MiB 699 | /// "large" page. 700 | enum PML2E { 701 | Next(&'static mut PML1), 702 | Page(PFN2M, mem::Attrs), 703 | } 704 | 705 | impl InnerTable for PML2 { 706 | type NextTableType = PML1; 707 | 708 | fn next_mut(&mut self, va: *const ()) -> Option<&'static mut PML1> { 709 | let entry = self.entries[Self::index(va)]; 710 | (entry.p() && !entry.h()).then(|| { 711 | let p = unsafe { entry.virt_addr() }; 712 | assert!(!p.is_null() && p.cast::().is_aligned()); 713 | unsafe { &mut *TableAlloc::try_with_addr(p.addr()).unwrap() } 714 | }) 715 | } 716 | } 717 | 718 | impl Table for PML2 { 719 | type EntryType = PML2E; 720 | type MappingType = Mapping2; 721 | const INDEX_SHIFT: usize = 21; 722 | 723 | fn entry(&mut self, va: *const ()) -> Option { 724 | let entry = self.entries[Self::index(va)]; 725 | match (entry.p(), entry.h()) { 726 | (false, _) => None, 727 | (_, false) => self.next_mut(va).map(PML2E::Next), 728 | (_, true) => { 729 | Some(PML2E::Page(PFN2M::new(entry.phys_addr()), entry.attrs())) 730 | } 731 | } 732 | } 733 | 734 | unsafe fn set_entry(&mut self, va: *const (), entry: Option) { 735 | self.entries[PML2::index(va)] = match entry { 736 | None => PTE::empty(), 737 | Some(PML2E::Next(table)) => PTE::new_for_table(table), 738 | Some(PML2E::Page(page, attrs)) => PTE::new(page, attrs), 739 | } 740 | } 741 | 742 | unsafe fn map(&mut self, mapping: Mapping2) { 743 | let va = mapping.virt_addr(); 744 | match mapping { 745 | Mapping2::Map2M(_, frame, attrs) => unsafe { 746 | self.set_entry(va, Some(PML2E::Page(frame, attrs))); 747 | }, 748 | Mapping2::Next(mapping1) => { 749 | if self.entry(va).is_none() { 750 | unsafe { 751 | self.set_entry(va, Some(PML2E::Next(PML1::new()))); 752 | } 753 | } 754 | if let Some(table) = self.next_mut(va) { 755 | unsafe { 756 | table.map(mapping1); 757 | } 758 | } 759 | } 760 | } 761 | } 762 | } 763 | 764 | /// The PML1 represents a terminal leaf note in the paging radix 765 | /// tree. 766 | #[repr(C, align(4096))] 767 | struct PML1 { 768 | entries: [PTE; 512], 769 | } 770 | 771 | /// Valid PML1 entries can only point to 4KiB page frames. 772 | enum PML1E { 773 | Page(PFN4K, mem::Attrs), 774 | } 775 | 776 | impl Table for PML1 { 777 | type EntryType = PML1E; 778 | type MappingType = Mapping1; 779 | const INDEX_SHIFT: usize = 12; 780 | 781 | fn entry(&mut self, va: *const ()) -> Option { 782 | let entry = self.entries[Self::index(va)]; 783 | entry 784 | .p() 785 | .then(|| PML1E::Page(PFN4K::new(entry.phys_addr()), entry.attrs())) 786 | } 787 | 788 | unsafe fn set_entry(&mut self, va: *const (), entry: Option) { 789 | self.entries[PML1::index(va)] = match entry { 790 | None => PTE::empty(), 791 | Some(PML1E::Page(page, attrs)) => PTE::new(page, attrs), 792 | }; 793 | } 794 | 795 | unsafe fn map(&mut self, mapping: Mapping1) { 796 | let Mapping1::Map4K(_, frame, attrs) = mapping; 797 | unsafe { 798 | self.set_entry( 799 | mapping.virt_addr(), 800 | Some(PML1E::Page(frame, attrs)), 801 | ); 802 | } 803 | } 804 | } 805 | 806 | /// Represents a complete page table. 807 | #[repr(C, align(4096))] 808 | pub(crate) struct PageTable { 809 | pml4: PML4, 810 | } 811 | 812 | impl PageTable { 813 | /// Creates a new static page table, zero it, and returns 814 | /// a reference to it. 815 | pub(crate) fn new() -> &'static mut PageTable { 816 | let table = Box::::new_zeroed_in(TableAlloc); 817 | Box::leak(unsafe { table.assume_init() }) 818 | } 819 | 820 | /// Loads the page table into the MMU. 821 | pub(crate) unsafe fn activate(&'static mut self) -> &'static mut PageTable { 822 | let pa = self.phys_addr(); 823 | unsafe { 824 | core::arch::asm!("movq {pa}, %cr3", pa = in(reg) pa, options(att_syntax)); 825 | } 826 | self 827 | } 828 | 829 | /// Returns the physical address of the root of the page 830 | /// table radix tree. 831 | pub(crate) fn phys_addr(&self) -> u64 { 832 | let ptr: *const PML4 = &self.pml4; 833 | // Note that the PML4 is identity mapped. 834 | ptr.addr() as u64 835 | } 836 | 837 | /// Identity maps an address space. 838 | pub(crate) unsafe fn identity_map(&mut self, regions: &[mem::Region]) { 839 | for region in regions { 840 | let pa = mem::P4KA::new(region.start().addr() as u64); 841 | unsafe { 842 | self.map_region(region, pa); 843 | } 844 | } 845 | } 846 | 847 | /// Maps a single region of virtual address space to some 848 | /// region of contiguous physical address space. Permits 849 | /// mapping at the end of the address range. 850 | unsafe fn map_region(&mut self, region: &mem::Region, pa: mem::P4KA) { 851 | let mut start = region.start().addr(); 852 | let end = region.end().addr(); 853 | assert!(mem::is_canonical_range(start, end)); 854 | let mut pa = pa.phys_addr(); 855 | assert!(mem::is_physical( 856 | pa.checked_add(end.wrapping_sub(start) as u64).unwrap() 857 | )); 858 | while start != end { 859 | let attrs = region.attrs(); 860 | let len = if end.wrapping_sub(start) >= PFN1G::SIZE 861 | && start % PFN1G::SIZE == 0 862 | && (pa as usize) % PFN1G::SIZE == 0 863 | { 864 | unsafe { 865 | self.map(Page1G::new(start), PFN1G::new(pa), attrs); 866 | } 867 | PFN1G::SIZE 868 | } else if end.wrapping_sub(start) >= PFN2M::SIZE 869 | && start % PFN2M::SIZE == 0 870 | && (pa as usize) % PFN2M::SIZE == 0 871 | { 872 | unsafe { 873 | self.map(Page2M::new(start), PFN2M::new(pa), attrs); 874 | } 875 | PFN2M::SIZE 876 | } else if end.wrapping_sub(start) >= PFN4K::SIZE 877 | && start % PFN4K::SIZE == 0 878 | && (pa as usize) % PFN4K::SIZE == 0 879 | { 880 | unsafe { 881 | self.map(Page4K::new(start), PFN4K::new(pa), attrs); 882 | } 883 | PFN4K::SIZE 884 | } else { 885 | panic!("bad page size"); 886 | }; 887 | start = start.wrapping_add(len); 888 | pa = pa.checked_add(len as u64).unwrap(); 889 | } 890 | } 891 | 892 | /// Map a page of some size and alignment to the 893 | /// corresponding frame type, with the given attributes. 894 | unsafe fn map( 895 | &mut self, 896 | page: P, 897 | frame: P::FrameType, 898 | attrs: mem::Attrs, 899 | ) { 900 | unsafe { 901 | self.pml4.map(P::mapping(page, frame, attrs)); 902 | } 903 | } 904 | 905 | /// XXX(cross): Make this actually walk the table to make sure 906 | /// the VA is really mapped. 907 | fn is_mapped(&self, va: usize) -> bool { 908 | va != 0 909 | } 910 | 911 | /// Returns a raw pointer to a virtual address mapped by 912 | /// this table. 913 | pub(crate) fn try_with_addr(&self, va: usize) -> Result<*mut T, Error> { 914 | if !self.is_mapped(va) { 915 | return Err(Error::BadPointer); 916 | } 917 | let ptr = core::ptr::with_exposed_provenance_mut::<()>(va); 918 | if !ptr.cast::().is_aligned() { 919 | return Err(Error::BadPointer); 920 | } 921 | Ok(ptr as *mut T) 922 | } 923 | } 924 | 925 | #[cfg(test)] 926 | mod tests { 927 | use super::*; 928 | 929 | // This test is unfortunately long, but we construct an 930 | // entire address space and then probe it completely. 931 | #[test] 932 | fn test_an_addr_space() { 933 | let regions = &[ 934 | mem::Region::new( 935 | mem::V4KA::new(0x1000_0000)..mem::V4KA::new(0x1000_1000), 936 | mem::Attrs::new_text(), 937 | ), 938 | mem::Region::new( 939 | mem::V4KA::new(0x1000_2000)..mem::V4KA::new(0x1000_4000), 940 | mem::Attrs::new_rodata(), 941 | ), 942 | mem::Region::new( 943 | mem::V4KA::new(0x1000_F000)..mem::V4KA::new(0x1200_0000), 944 | mem::Attrs::new_data(), 945 | ), 946 | mem::Region::new( 947 | mem::V4KA::new(0x1200_0000)..mem::V4KA::new(0x4000_0000), 948 | mem::Attrs::new_bss(), 949 | ), 950 | mem::Region::new( 951 | mem::V4KA::new(0x8000_0000)..mem::V4KA::new(0x1_0000_0000), 952 | mem::Attrs::new_mmio(), 953 | ), 954 | ]; 955 | let page_table = PageTable::new(); 956 | unsafe { 957 | page_table.identity_map(regions); 958 | } 959 | 960 | // Examine the PML4 entries. 961 | let pml4 = &mut page_table.pml4; 962 | 963 | let pml4es = pml4 964 | .entries 965 | .iter() 966 | .enumerate() 967 | .filter(|&(_, e)| e.p()) 968 | .collect::>(); 969 | assert_eq!(pml4es.len(), 1); 970 | let (index, &entry) = pml4es[0]; 971 | assert_eq!(index, 0); 972 | assert!(entry.p()); 973 | assert!(entry.w()); 974 | assert!(!entry.nx()); 975 | assert!(!entry.nc()); 976 | assert!(entry.u()); 977 | 978 | // Examine the PML3 entries. There should be a single 979 | // entry pointing to a PML2 for the loader, and two huge 980 | // pages for MMIO space. 981 | let pml3 = 982 | pml4.next_mut(core::ptr::without_provenance(0x8000_0000)).unwrap(); 983 | let n = pml3.entries.iter().filter(|&e| e.p()).count(); 984 | assert_eq!(n, 3); 985 | let l0g = pml3.entries[0]; 986 | assert!(l0g.p()); 987 | assert!(l0g.w()); 988 | assert!(!l0g.nc()); 989 | assert!(!l0g.nx()); 990 | assert!(l0g.u()); 991 | let g1 = pml3.entries[1]; 992 | assert!(!g1.p()); 993 | let mmio2g = pml3.entries[2]; 994 | assert!(mmio2g.p()); 995 | assert!(mmio2g.w()); 996 | assert!(mmio2g.h()); 997 | assert!(mmio2g.nc()); 998 | assert!(mmio2g.nx()); 999 | assert!(!mmio2g.k()); 1000 | assert!(!mmio2g.u()); 1001 | assert_eq!(mmio2g.phys_addr(), 0x8000_0000); 1002 | let mmio3g = pml3.entries[3]; 1003 | assert!(mmio3g.p()); 1004 | assert!(mmio3g.w()); 1005 | assert!(mmio3g.h()); 1006 | assert!(mmio3g.nc()); 1007 | assert!(mmio3g.nx()); 1008 | assert!(!mmio3g.k()); 1009 | assert!(!mmio3g.u()); 1010 | assert_eq!(mmio3g.phys_addr(), 0xC000_0000); 1011 | 1012 | // Check the PML2 entries. The PML2 maps a gigabyte of 1013 | // address space from 0 to 0x4000_0000. 1014 | let pml2 = 1015 | pml3.next_mut(core::ptr::without_provenance(0x1000_0000)).unwrap(); 1016 | let n = pml2.entries.iter().filter(|&e| e.p()).count(); 1017 | assert_eq!(n, 512 - 512 / 4); 1018 | // The lower quarter of the PML2 should be empty. 1019 | for e in &pml2.entries[..128] { 1020 | assert!(!e.p()); 1021 | } 1022 | // Where the 2MiB entries start. 1023 | let start2m = 0x1020_0000 >> 21; 1024 | assert_eq!(start2m, 129); 1025 | // There should be one PML1 mapping kernel text, rodata 1026 | // and the first part of kernel data. 1027 | for (k, e) in pml2.entries[128..start2m].iter().enumerate() { 1028 | assert!(e.p()); 1029 | assert!(e.w()); 1030 | assert!(!e.nx(), "!e.nx() at {k}"); 1031 | assert!(!e.nc()); 1032 | assert!(e.u()); 1033 | assert!(!e.k()); 1034 | } 1035 | // Check the 2MiB entries covering K data and K BSS. 1036 | assert_eq!(0x40000000 >> 21, 512); 1037 | for (k, e) in pml2.entries[start2m..].iter().enumerate() { 1038 | assert!(e.p()); 1039 | assert!(e.w()); 1040 | assert!(e.h()); 1041 | assert!(e.nx()); 1042 | assert!(!e.nc()); 1043 | assert!(!e.k()); 1044 | assert!(!e.u()); 1045 | let expected_addr = start2m * (1 << 21) + k * (1 << 21); 1046 | assert_eq!(e.phys_addr(), expected_addr as u64); 1047 | } 1048 | // Check the 4KiB PML1 entries. There should be one 1049 | // text page, two RO data pages, and a bunch of RW 1050 | // data pages. 1051 | let pml1 = 1052 | pml2.next_mut(core::ptr::without_provenance(0x1000_0000)).unwrap(); 1053 | // Text. 1054 | assert!(pml1.entries[0].p()); 1055 | assert!(!pml1.entries[0].w()); 1056 | assert!(!pml1.entries[0].nx()); 1057 | assert!(!pml1.entries[0].h()); 1058 | assert!(!pml1.entries[0].u()); 1059 | assert!(!pml1.entries[0].nc()); 1060 | assert!(!pml1.entries[0].k()); 1061 | assert_eq!(pml1.entries[0].phys_addr(), 0x1000_0000); 1062 | // Empty. 1063 | assert!(!pml1.entries[1].p()); 1064 | // RO data. 1065 | for (k, e) in pml1.entries[2..4].iter().enumerate() { 1066 | assert!(e.p()); 1067 | assert!(!e.w()); 1068 | assert!(e.nx()); 1069 | assert!(!e.h()); 1070 | assert!(!e.u()); 1071 | assert!(!e.nc()); 1072 | assert!(!e.k()); 1073 | let offset = k as u64 * 4096; 1074 | assert_eq!(e.phys_addr(), 0x1000_2000 + offset); 1075 | } 1076 | // A few more empty entries for 0x1000_4000..0x1000_F000 1077 | for e in &pml1.entries[4..15] { 1078 | assert!(!e.p()); 1079 | } 1080 | // And finally, we should map 4KiB R/W data pages for 1081 | // 0x1000_F000..0x1020_0000. 1082 | for (k, e) in pml1.entries[15..].iter().enumerate() { 1083 | assert!(e.p()); 1084 | assert!(e.w()); 1085 | assert!(e.nx()); 1086 | assert!(!e.h()); 1087 | assert!(!e.u()); 1088 | assert!(!e.nc()); 1089 | assert!(!e.k()); 1090 | let offset = k as u64 * 4096; 1091 | assert_eq!(e.phys_addr(), 0x1000_F000 + offset); 1092 | } 1093 | } 1094 | } 1095 | 1096 | /// A LoaderPageTable is a newtype around a PageTable that 1097 | /// prohibits some types of mappings. In particular, it 1098 | /// maintains a list of regions that the consumer cannot 1099 | /// creating mappings in. 1100 | pub(crate) struct LoaderPageTable { 1101 | page_table: &'static mut PageTable, 1102 | reserved: Vec>, 1103 | } 1104 | 1105 | impl LoaderPageTable { 1106 | /// Creates a new LoaderPageTable from the given PageTable. 1107 | pub(crate) fn new( 1108 | page_table: &'static mut PageTable, 1109 | reserved: &[Range], 1110 | ) -> LoaderPageTable { 1111 | LoaderPageTable { page_table, reserved: reserved.into() } 1112 | } 1113 | 1114 | /// Maps the given virtual region to the given physical 1115 | /// address with the given attributes. 1116 | pub(crate) unsafe fn map_region( 1117 | &mut self, 1118 | range: Range, 1119 | attrs: mem::Attrs, 1120 | pa: mem::P4KA, 1121 | ) -> crate::Result<()> { 1122 | if Self::overlaps(&self.reserved, &range) { 1123 | return Err("range overlaps reserved regions"); 1124 | } 1125 | let len = range.end.addr().wrapping_sub(range.start.addr()); 1126 | let phys_addr = pa.phys_addr() as usize; 1127 | let pstart = mem::V4KA::new(phys_addr); 1128 | let pend = mem::V4KA::new(phys_addr.wrapping_add(len)); 1129 | let prange = pstart..pend; 1130 | if Self::overlaps(&self.reserved, &prange) { 1131 | return Err("physical range overlaps reserved regions"); 1132 | } 1133 | let region = mem::Region::new(range, attrs); 1134 | unsafe { 1135 | self.page_table.map_region(®ion, pa); 1136 | } 1137 | Ok(()) 1138 | } 1139 | 1140 | /// Returns true iff region `a` overlaps any of the regions 1141 | /// in `rs`. 1142 | /// 1143 | /// Two regions `a` and `b` overlap iff `a` contains `b`'s 1144 | /// start or `b` contains `a`'s start. Note, however, that 1145 | /// because address ranges in the loader are half-open and 1146 | /// can wrap around the address space to (exactly) 0, we 1147 | /// first convert the ranges to closed, inclusive ranges. 1148 | fn overlaps(rs: &[Range], a: &Range) -> bool { 1149 | let aa = a.start.addr()..=(a.end.addr().wrapping_sub(1)); 1150 | rs.iter().any(|range| { 1151 | let rr = range.start.addr()..=(range.end.addr().wrapping_sub(1)); 1152 | rr.contains(aa.start()) || aa.contains(rr.start()) 1153 | }) 1154 | } 1155 | 1156 | /// Returns a pointer from a virtual address mapped by this 1157 | /// table. 1158 | pub(crate) fn try_with_addr(&self, va: usize) -> Result<*mut T, Error> { 1159 | self.page_table.try_with_addr(va) 1160 | } 1161 | 1162 | /// Returns the physical address of the page table root. 1163 | pub(crate) fn phys_addr(&self) -> u64 { 1164 | self.page_table.phys_addr() 1165 | } 1166 | } 1167 | 1168 | #[cfg(test)] 1169 | mod loader_page_table_tests { 1170 | use super::*; 1171 | 1172 | #[test] 1173 | fn map_non_overlapping_reserved() { 1174 | let page_table = PageTable::new(); 1175 | let reserved = &[mem::V4KA::new(0x1000)..mem::V4KA::new(0x8000)]; 1176 | let mut loader_page_table = LoaderPageTable::new(page_table, reserved); 1177 | let region = mem::V4KA::new(0x8000)..mem::V4KA::new(0xa000); 1178 | assert!(unsafe { 1179 | loader_page_table 1180 | .map_region( 1181 | region, 1182 | mem::Attrs::new_text(), 1183 | mem::P4KA::new(0x8000), 1184 | ) 1185 | .is_ok() 1186 | }); 1187 | } 1188 | 1189 | #[test] 1190 | fn map_overlapping_reserved_fail() { 1191 | let page_table = PageTable::new(); 1192 | let reserved = &[mem::V4KA::new(0x1000)..mem::V4KA::new(0x8000)]; 1193 | let mut loader_page_table = LoaderPageTable::new(page_table, reserved); 1194 | let overlapping = mem::V4KA::new(0x6000)..mem::V4KA::new(0x9000); 1195 | assert!(unsafe { 1196 | loader_page_table 1197 | .map_region( 1198 | overlapping, 1199 | mem::Attrs::new_rodata(), 1200 | mem::P4KA::new(0x2000), 1201 | ) 1202 | .is_err() 1203 | }); 1204 | } 1205 | } 1206 | 1207 | mod arena { 1208 | extern crate alloc; 1209 | 1210 | use super::{Error, Table}; 1211 | use crate::allocator::BumpAlloc; 1212 | use alloc::alloc::{AllocError, Allocator, Layout}; 1213 | use core::cell::SyncUnsafeCell; 1214 | use core::ptr; 1215 | use static_assertions::const_assert; 1216 | 1217 | const PAGE_SIZE: usize = 4096; 1218 | const PAGE_ARENA_SIZE: usize = 128 * PAGE_SIZE; 1219 | // This is trivially true, but keep the assert as 1220 | // documentation of the minimum arena size invariant. 1221 | // See RFD215 for details. 1222 | const_assert!(PAGE_ARENA_SIZE > 16 * PAGE_SIZE); 1223 | 1224 | unsafe impl Sync for BumpAlloc<{ PAGE_ARENA_SIZE }> {} 1225 | 1226 | static PAGE_ALLOCATOR: SyncUnsafeCell> = 1227 | SyncUnsafeCell::new(BumpAlloc::new([0; PAGE_ARENA_SIZE])); 1228 | 1229 | /// An allocator specialized for MMU page allocations. 1230 | /// 1231 | /// # Safety 1232 | /// This is visibility restricted to this module, and 1233 | /// the only allocations we take from it are PAGE_SIZE 1234 | /// size and aligned. 1235 | pub(super) struct TableAlloc; 1236 | 1237 | impl TableAlloc { 1238 | /// Try and convert an integer to a pointer. 1239 | pub(super) fn try_with_addr( 1240 | addr: usize, 1241 | ) -> Result<*mut T, Error> { 1242 | let page_allocator = unsafe { &*PAGE_ALLOCATOR.get() }; 1243 | let range = page_allocator.addr_range(); 1244 | if !range.contains(&addr) { 1245 | return Err(Error::BadPointer); 1246 | } 1247 | let base = page_allocator.base(); 1248 | let ptr = base.with_addr(addr); 1249 | if !ptr.cast::().is_aligned() { 1250 | return Err(Error::BadPointer); 1251 | } 1252 | Ok(ptr as *mut T) 1253 | } 1254 | } 1255 | 1256 | unsafe impl Allocator for TableAlloc { 1257 | fn allocate( 1258 | &self, 1259 | layout: Layout, 1260 | ) -> Result, AllocError> { 1261 | let align = layout.align(); 1262 | let size = layout.size(); 1263 | assert_eq!(align, PAGE_SIZE); 1264 | assert_eq!(size, PAGE_SIZE); 1265 | let page_allocator = unsafe { &*PAGE_ALLOCATOR.get() }; 1266 | let a = page_allocator.alloc_bytes(align, size); 1267 | let p = a.ok_or(AllocError)?; 1268 | Ok(p.into()) 1269 | } 1270 | unsafe fn deallocate(&self, _ptr: ptr::NonNull, _layout: Layout) {} 1271 | } 1272 | } 1273 | 1274 | use arena::TableAlloc; 1275 | -------------------------------------------------------------------------------- /src/phbl.ld: -------------------------------------------------------------------------------- 1 | /* 2 | * Linker script for the Pico Host Boot Loader 3 | */ 4 | ENTRY(reset); 5 | 6 | HIDDEN(bootblock = 0x000000007ffef000); 7 | HIDDEN(resetaddr = 0x000000007ffefff0); 8 | 9 | SECTIONS { 10 | .start bootblock : { 11 | FILL(0xffffffff); 12 | *(.start) 13 | } :srodata 14 | .start.rodata ALIGN(64) : { 15 | *(.start.rodata) 16 | } :srodata 17 | .reset resetaddr : { 18 | FILL(0xffffffff); 19 | *(.reset) 20 | __eloader = ALIGN(65536); 21 | } :srodata 22 | 23 | .data ((ADDR(.start) - datasize) & ~0xFFF) : { 24 | FILL(0xffffffff); 25 | *(.data*) 26 | edata = ALIGN(4096); 27 | } :data 28 | 29 | .rodata ((ADDR(.data) - rodatasize) & ~0xFFF) : { 30 | FILL(0xffffffff); 31 | *(.rodata*) 32 | erodata = ALIGN(4096); 33 | } :rodata 34 | 35 | .text ((ADDR(.rodata) - textsize) & ~0xFFF) : { 36 | __sloader = .; 37 | FILL(0xffffffff); 38 | *(.text*) 39 | etext = ALIGN(4096); 40 | } :text 41 | 42 | /* Empty section to work around an LLD bug sizing .text. */ 43 | .empty ADDR(.text) : { } :empty 44 | 45 | .bss ((ADDR(.text) - bsssize) & ~0x1FFFFF) (NOLOAD) : { 46 | sbss = .; 47 | *(.bss* COMMON) 48 | ebss = ALIGN(4096); 49 | } 50 | 51 | textsize = SIZEOF(.text); 52 | rodatasize = SIZEOF(.rodata); 53 | datasize = SIZEOF(.data); 54 | bsssize = SIZEOF(.bss); 55 | 56 | _BL_SPACE = __eloader - __sloader; 57 | 58 | /DISCARD/ : { 59 | *(.got* .comment* .note* .eh_frame*) 60 | } 61 | } 62 | 63 | PHDRS { 64 | text PT_LOAD; 65 | rodata PT_LOAD; 66 | data PT_LOAD; 67 | srodata PT_LOAD; 68 | empty PT_NULL; 69 | } 70 | -------------------------------------------------------------------------------- /src/phbl.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | //! Initialize the loader environment. 6 | //! 7 | //! When this code is called, we are in the minimal state 8 | //! established by the assembler code invoked from the reset 9 | //! vector. We know that: 10 | //! 11 | //! 1. We are in 64-bit long mode. 12 | //! 2. The entire loader is covered by some virtual identity 13 | //! mapping, and is rwx and cached. 14 | //! 3. UART MMIO space is mapped rw- and uncached. 15 | //! 4. The BSS is zeroed. 16 | //! 5. A minimal GDT is loaded. 17 | //! 6. No IDT is loaded. 18 | //! 19 | //! The rest of the machine is in its reset state. 20 | //! 21 | //! In particular, we know very little about the virtual memory 22 | //! mapping that we entered Rust with. For example, we do not 23 | //! presume that the page tables we are using are themselves 24 | //! even in this mapping, writeable, etc. 25 | //! 26 | //! This code is responsible for: 27 | //! 28 | //! 1. Remapping the address space to properly place the loader 29 | //! and MMIO space with minimized virtual address mappings. 30 | //! 2. Initializing the UART so that we can log errors. 31 | //! 3. Setting up the IDT. 32 | //! 4. Initializing a static data structure that describes the 33 | //! machine environment and returning it to the caller. In 34 | //! particular, the bounds of the loader and MMIO regions are 35 | //! discovered here so that subsequent mappings do not 36 | //! overwrite the loader itself, its page tables, stack, or 37 | //! the MMIO regions. 38 | 39 | extern crate alloc; 40 | 41 | use crate::idt; 42 | use crate::iomux; 43 | use crate::mem; 44 | use crate::mmu; 45 | use crate::uart::{self, Uart}; 46 | #[cfg(not(any(test, clippy)))] 47 | use alloc::boxed::Box; 48 | use core::fmt; 49 | use core::ops::Range; 50 | use core::sync::atomic::{AtomicBool, Ordering}; 51 | 52 | #[cfg(not(test))] 53 | core::arch::global_asm!(include_str!("start.S"), options(att_syntax)); 54 | 55 | /// The loader configuration, consumed by the rest of PHBL. 56 | pub(crate) struct Config { 57 | pub(crate) cons: Uart, 58 | pub(crate) loader_region: Range, 59 | pub(crate) page_table: mmu::LoaderPageTable, 60 | } 61 | 62 | impl fmt::Debug for Config { 63 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 64 | writeln!(f, "Config {{")?; 65 | writeln!(f, " cons: Uart({:x}),", self.cons.addr())?; 66 | let vstart = self.loader_region.start.addr(); 67 | let vend = self.loader_region.end.addr(); 68 | writeln!(f, " loader: {:#x?}", vstart..vend)?; 69 | writeln!(f, " pageroot: P4KA({:#x}),", self.page_table.phys_addr())?; 70 | write!(f, "}}") 71 | } 72 | } 73 | 74 | /// Initializes the loader environment, creating the system 75 | /// Config. This remaps the kernel and MMIO region, respecting 76 | /// segment permissions, etc. Initializes the UART, and returns 77 | /// a LoaderPageTable that we can use to create new mappings for 78 | /// e.g. read and loading the host kernel. This is called 79 | /// directly from assembler code. 80 | #[unsafe(no_mangle)] 81 | pub(crate) unsafe extern "C" fn init(bist: u32) -> &'static mut Config { 82 | static INITED: AtomicBool = AtomicBool::new(false); 83 | if INITED.swap(true, Ordering::AcqRel) { 84 | panic!("Init already called"); 85 | } 86 | unsafe { 87 | iomux::init(); 88 | uart::init(); 89 | } 90 | idt::init(); 91 | if bist != 0 { 92 | panic!("bist failed: {bist:#x}"); 93 | } 94 | let cons = Uart::uart0(); 95 | let page_table = remap(mem::V4KA::new(cons.addr())); 96 | let cpio_region = cpio_addr()..saddr(); 97 | let loader_region = saddr()..eaddr(); 98 | let mmio_region = mmio_addr()..mmio_end(); 99 | let reserved_regions = [loader_region.clone(), cpio_region, mmio_region]; 100 | let config = Box::new(Config { 101 | cons, 102 | loader_region, 103 | page_table: mmu::LoaderPageTable::new(page_table, &reserved_regions), 104 | }); 105 | Box::leak(config) 106 | } 107 | 108 | // Stubs for linker-provided symbols. 109 | unsafe extern "C" { 110 | static sbss: [u8; 0]; 111 | static ebss: [u8; 0]; 112 | static __sloader: [u8; 0]; 113 | static etext: [u8; 0]; 114 | static erodata: [u8; 0]; 115 | static edata: [u8; 0]; 116 | static __eloader: [u8; 0]; 117 | static bootblock: [u8; 0]; 118 | 119 | pub fn dnr() -> !; 120 | } 121 | 122 | /// Returns the address of the start of the cpio archive region. 123 | fn cpio_addr() -> mem::V4KA { 124 | const CPIO_LEN: usize = 128 * mem::MIB; 125 | mem::V4KA::new(saddr().addr() - CPIO_LEN) 126 | } 127 | 128 | /// Returns the address of the start of the loader text segment. 129 | fn text_addr() -> mem::V4KA { 130 | mem::V4KA::new(unsafe { __sloader.as_ptr().addr() }) 131 | } 132 | 133 | /// Returns the address of the start of the loader read-only 134 | /// data segment. 135 | fn rodata_addr() -> mem::V4KA { 136 | mem::V4KA::new(unsafe { etext.as_ptr().addr() }) 137 | } 138 | 139 | /// Returns the address of the start of the loader read/write 140 | /// data segment. 141 | fn data_addr() -> mem::V4KA { 142 | mem::V4KA::new(unsafe { erodata.as_ptr().addr() }) 143 | } 144 | 145 | /// Returns the address of the end of the loader read/write 146 | /// data segment. 147 | fn edata_addr() -> mem::V4KA { 148 | mem::V4KA::new(unsafe { edata.as_ptr().addr() }) 149 | } 150 | 151 | /// Returns the address of the start of the loader BSS segment. 152 | fn bss_addr() -> mem::V4KA { 153 | mem::V4KA::new(unsafe { sbss.as_ptr().addr() }) 154 | } 155 | 156 | /// Returns the address of the end of the loader BSS. 157 | fn ebss_addr() -> mem::V4KA { 158 | mem::V4KA::new(unsafe { ebss.as_ptr().addr() }) 159 | } 160 | 161 | /// Returns the start of the loader, including all segments. 162 | fn saddr() -> mem::V4KA { 163 | bss_addr() 164 | } 165 | 166 | /// Returns the address of end of the loader memory image, 167 | /// including the boot block and reset vector. 168 | fn eaddr() -> mem::V4KA { 169 | mem::V4KA::new(unsafe { __eloader.as_ptr().addr() }) 170 | } 171 | 172 | /// Returns the address of the boot block. 173 | fn bootblock_addr() -> mem::V4KA { 174 | mem::V4KA::new(unsafe { bootblock.as_ptr().addr() }) 175 | } 176 | 177 | /// Returns the address of the start of the MMIO region 178 | /// containing the UART. 179 | fn mmio_addr() -> mem::V4KA { 180 | mem::V4KA::new(0x8000_0000) 181 | } 182 | 183 | /// Returns the address of the end of the MMIO region containing 184 | /// the UART. 185 | fn mmio_end() -> mem::V4KA { 186 | mem::V4KA::new(0x1_0000_0000) 187 | } 188 | 189 | /// Returns a mutable slice over the cpio archive region. 190 | /// Clears the region. 191 | pub(crate) fn ramdisk_region_init_mut() -> &'static mut [u8] { 192 | let cpio = cpio_addr().addr(); 193 | let ecpio = saddr().addr(); 194 | const PHBL_MIN: usize = 2 * mem::GIB - 256 * mem::MIB; 195 | assert!(PHBL_MIN < cpio && cpio < ecpio); 196 | const PHBL_BASE: *mut u8 = core::ptr::without_provenance_mut(PHBL_MIN); 197 | let len = ecpio - cpio; 198 | let cpio = PHBL_BASE.with_addr(cpio); 199 | unsafe { 200 | core::ptr::write_bytes(cpio, 0, len); 201 | core::slice::from_raw_parts_mut(cpio, len) 202 | } 203 | } 204 | 205 | /// When the loader enters Rust code, we know that we have a 206 | /// minimal virtual memory environment where the loader itself 207 | /// is mapped rwx, and the UART registers region is mapped 208 | /// rw- and uncached. This remaps the loader and MMIO space 209 | /// properly, enforcing appropriate protections for sections 210 | /// and so on. 211 | fn remap(cons_addr: mem::V4KA) -> &'static mut mmu::PageTable { 212 | let cpio = cpio_addr()..saddr(); 213 | let text = text_addr()..rodata_addr(); 214 | let rodata = rodata_addr()..data_addr(); 215 | let data = data_addr()..edata_addr(); 216 | let bss = bss_addr()..ebss_addr(); 217 | let boot = bootblock_addr()..eaddr(); 218 | 219 | let cons_end = mem::V4KA::new(cons_addr.addr() + mem::V4KA::SIZE); 220 | let cons = cons_addr..cons_end; 221 | 222 | let regions = &[ 223 | mem::Region::new(cpio, mem::Attrs::new_data()), 224 | mem::Region::new(text, mem::Attrs::new_text()), 225 | mem::Region::new(rodata, mem::Attrs::new_rodata()), 226 | mem::Region::new(data, mem::Attrs::new_data()), 227 | mem::Region::new(bss, mem::Attrs::new_bss()), 228 | mem::Region::new(boot, mem::Attrs::new_rodata()), 229 | mem::Region::new(cons, mem::Attrs::new_mmio()), 230 | ]; 231 | let page_table = mmu::PageTable::new(); 232 | unsafe { 233 | page_table.identity_map(regions); 234 | page_table.activate() 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/start.S: -------------------------------------------------------------------------------- 1 | // The reset vector and early boot start for the pico host boot 2 | // loader. 3 | // 4 | // This code is responsible for setting up an execution 5 | // environment for Rust code, and nothing more. We do the bare 6 | // minimum reqired in assembler and leave the heavy lifting to 7 | // Rust. 8 | 9 | // Definitions for bits in %cr0. 10 | CR0_PE = 1 << 0 11 | CR0_ET = 1 << 4 12 | CR0_WP = 1 << 16 13 | CR0_PG = 1 << 31 14 | CR0_MB1 = CR0_ET 15 | 16 | // Definitions for bits in %cr4. 17 | CR4_PAE = 1 << 5 18 | 19 | // Constants for the EFER MSR. 20 | IA32_EFER_MSR = 0xc0000080 21 | EFER_LME = 1 << 8 22 | EFER_NX = 1 << 11 23 | 24 | // Memory type range register related constants. 25 | IA32_MTRR_DEF_TYPE_MSR = 0x2FF 26 | MTRR_ENABLE = 1 << 11 27 | MTRR_WB = 0x06 28 | 29 | // Paging constants. 30 | PAGE_SIZE = 4096 31 | PG_R = 1 << 0 32 | PG_W = 1 << 1 33 | PG_WT = 1 << 3 34 | PG_NC = 1 << 4 35 | PG_HUGE = 1 << 7 36 | PG_X = 0 << 63 37 | PG_NX = 1 << 63 38 | 39 | // Segmentation constants for 16, 32 and 64 bit. 40 | GDT_NULL = 0 << 3 41 | GDT_CODE64 = 1 << 3 42 | GDT_CODE32 = 2 << 3 43 | GDT_DATA32 = 3 << 3 44 | .globl GDT_CODE64 45 | 46 | SEG_CODE_RO = 1 << 41 // Only for code, not data segments. 47 | SEG_DATA_RW = 1 << 41 // Only for data, not code segments. 48 | SEG_DATA = 0 << 43 49 | SEG_CODE = 1 << 43 // Code segments are read-only. 50 | SEG_PRESENT = 1 << 47 51 | SEG_LONG = 1 << 53 52 | SEG_MUSTBE1 = 1 << 44 53 | 54 | SEG32_DEFAULT = 1 << 54 55 | SEG32_GRANULARITY = 1 << 55 56 | SEG32_BASE = 0 << 16 57 | SEG32_LIMIT = (0xF << 48) + 0xFFFF // 4GiB 58 | SEG32_BOUNDS = (SEG32_BASE + SEG32_LIMIT) // [0..4GiB) 59 | SEG32 = (SEG32_DEFAULT + SEG32_GRANULARITY + SEG32_BOUNDS) 60 | 61 | SEG16_MASK = 0xFFFF 62 | 63 | // Stack configuration. 64 | STACK_SIZE = 8 * PAGE_SIZE 65 | .globl STACK_SIZE 66 | 67 | // This is mapped to the reset vector and provides the 68 | // first x86 instructions executed when the CPU starts. 69 | // Architecturally, both IF and DF are defined to be 70 | // clear after RESET, but it never hurts to be explicit, 71 | // so we clear both and then simply jump to the 16-bit 72 | // startup code. 73 | .section ".reset", "a", @progbits 74 | .globl reset 75 | reset: 76 | cli 77 | cld 78 | jmp start 79 | ud2 80 | .balign 16, 0xff 81 | 82 | // Real execution begins here. Load a GDT and jump to 83 | // 32-bit protected mode. 84 | // 85 | // Note that since there is no firmware to set the A20 86 | // latch, we do not have to deal with it. Similarly, 87 | // we do not mask out the PIC, as there is no PIC on 88 | // Oxide machines. 89 | .section ".start", "a", @progbits 90 | .balign PAGE_SIZE 91 | .code16 92 | start: 93 | // Save the BIST data. 94 | movl %eax, %ebp 95 | 96 | // Coming out of reset, caching and write-back are 97 | // disabled. Clear the cache-inhibiting bits in %cr0 98 | // by setting the register to have only the reserved 99 | // bits set: in this case, only CR0_ET. 100 | movl $CR0_MB1, %eax 101 | movl %eax, %cr0 102 | 103 | // Set up a GDT. Since we are in 16-bit real mode, the 104 | // GDT descriptor must be within the current segment, 105 | // and we know one is present because the linker put one 106 | // there. We calculate its offset and load it into the 107 | // GDTR. 108 | // 109 | // Note that the GDT remains at the same linear address, 110 | // which is within the first 4GiB of address space, for 111 | // the lifetime of the program. Thus, we do not bother 112 | // rewriting the GDTR as we move to different modes. 113 | movl $gdtdesc, %ebx 114 | andl $SEG16_MASK, %ebx 115 | lgdtl %cs:(%bx) 116 | 117 | // Enable protected mode. 118 | movl %cr0, %eax 119 | orl $CR0_PE, %eax 120 | movl %eax, %cr0 121 | 122 | // Jump to 32-bit code. 123 | ljmpl $GDT_CODE32, $1f 124 | 125 | .balign 64 126 | .code32 127 | 1: 128 | // Set up data segmentation so that we have access to 129 | // the full 32-bit protected mode address space. We 130 | // don't use FS or GS in the bootstrap code, so leave 131 | // those at their reset values (0). 132 | movw $GDT_DATA32, %ax 133 | movw %ax, %ds 134 | movw %ax, %es 135 | movw %ax, %ss 136 | 137 | // Enable MTRRs and set the default memory access type 138 | // to writeback. Coming out of reset, all of physical 139 | // memory is considered UC. Enabling MTRRs and setting 140 | // this to writeback enables cache control via 141 | // attributes in page table entries. 142 | // See Intel SDM vol 3A sec 11.11.2.1 for details. 143 | movl $IA32_MTRR_DEF_TYPE_MSR, %ecx 144 | movl $(MTRR_ENABLE | MTRR_WB), %eax 145 | xorl %edx, %edx 146 | wrmsr 147 | 148 | // Enable the physical address extension in %cr4. 149 | movl %cr4, %eax 150 | orl $CR4_PAE, %eax 151 | movl %eax, %cr4 152 | 153 | // Load the page table root pointer into the MMU. 154 | movl $pml4, %eax 155 | movl %eax, %cr3 156 | 157 | // Enable long mode and support for the the NX bit in 158 | // PTEs. 159 | movl $IA32_EFER_MSR, %ecx 160 | movl $(EFER_LME | EFER_NX), %eax 161 | xorl %edx, %edx 162 | wrmsr 163 | 164 | // Enable paging and write-protect enforcement for the 165 | // kernel. Since PAE is enabled in %cr4 and long mode 166 | // is enabled in the EFER MSR, the MMU uses 4 level 167 | // paging. 168 | movl %cr0, %eax 169 | orl $(CR0_PG | CR0_WP), %eax 170 | movl %eax, %cr0 171 | 172 | // Jump to 64-bit code. 173 | ljmpl $GDT_CODE64, $start64 174 | 175 | // Define a GDT for the loader. We provide a 64-bit code 176 | // segment and 32-bit code and data segments. 177 | .section ".start.rodata", "a", @progbits 178 | .balign 64 179 | gdt: 180 | // 0x0: Null segment. 181 | .quad 0 182 | // 0x8: 64-bit code segment. 183 | .quad (SEG_PRESENT + SEG_CODE_RO + SEG_CODE + SEG_LONG + SEG_MUSTBE1) 184 | // 0x10: 32-bit code segment. 185 | .quad (SEG_PRESENT + SEG_CODE_RO + SEG_CODE + SEG32 + SEG_MUSTBE1) 186 | // 0x18: 32-bit data segment. 187 | .quad (SEG_PRESENT + SEG_DATA_RW + SEG_DATA + SEG32 + SEG_MUSTBE1) 188 | egdt: 189 | 190 | .skip 6 191 | gdtdesc: 192 | .word egdt - gdt - 1 193 | .quad gdt 194 | 195 | .text 196 | .balign 64 197 | .code64 198 | start64: 199 | // Clear the segmentation registers. 200 | // %fs and %gs were cleared on reset, so no 201 | // need to clear them again. 202 | xorl %eax, %eax 203 | movw %ax, %ds 204 | movw %ax, %es 205 | movw %ax, %ss 206 | 207 | // Zero out the BSS. 208 | movq $ebss, %rcx 209 | movq $sbss, %rdi 210 | xorl %eax, %eax 211 | subq %rdi, %rcx 212 | rep; stosb 213 | 214 | // Set up the stack. 215 | movq $stack, %rsp 216 | addq $STACK_SIZE, %rsp 217 | 218 | // Call `init`. This remaps the kernel, initializes the 219 | // UART, and sets up the IDT. It also validates the 220 | // BIST data. If init completes successfully, we call 221 | // `entry` with its return value, a mutable reference 222 | // to the system `Config`. 223 | movl %ebp, %edi 224 | xorl %ebp, %ebp 225 | call init 226 | movq %rax, %rdi 227 | call entry 228 | 229 | // Do not resuscitate. If main ever returns, we fall 230 | // through to this code; we also call it from panic. 231 | .balign 64 232 | .globl dnr 233 | dnr: 234 | cli 235 | hlt 236 | jmp dnr 237 | ud2 238 | 239 | // The rodata section contains space for the early page tables. 240 | // We leave assembler with an identity mapping for the second 241 | // and fourth GiB of address space, which contains the loader 242 | // and MMIO areas, respectively. Rust code remaps everything 243 | // almost immediately, but this way, the UART is usable in 244 | // early boot. 245 | .rodata 246 | .balign PAGE_SIZE 247 | pml4: 248 | .quad pml3 + (PG_R | PG_W | PG_X) 249 | .space PAGE_SIZE - 8 250 | 251 | pml3: 252 | .quad 0 253 | .quad (1 << 30) + (PG_HUGE | PG_R | PG_W | PG_X) 254 | .quad 0 255 | .quad (3 << 30) + (PG_HUGE | PG_R | PG_W | PG_NX | PG_NC | PG_WT) 256 | .space PAGE_SIZE - 4 * 8 257 | 258 | // The only data we define in the BSS in assembler is 259 | // the Rust stack. 260 | .bss 261 | .balign PAGE_SIZE 262 | .globl stack 263 | stack: 264 | .space STACK_SIZE 265 | -------------------------------------------------------------------------------- /src/uart.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | //! Synopsis DesignWare Advanced Peripheral Bus UART driver 6 | //! 7 | //! AMD EPYC processors have an AMBA UART built around the 8 | //! Synopsis DesignWare APB UART part. This is largely NS16550 9 | //! compatible, but accessed via MMIO; registers are aligned on 10 | //! 32-bit boundaries. 11 | //! 12 | //! Milan supports 4 UARTs; the first two support flow control 13 | //! if the last two are disabled. 14 | 15 | use bitstruct::bitstruct; 16 | use core::fmt; 17 | use core::ptr; 18 | use core::sync::atomic::{AtomicBool, Ordering}; 19 | use static_assertions::const_assert_eq; 20 | 21 | bitstruct! { 22 | /// Receive buffer register 23 | #[derive(Clone, Copy)] 24 | pub struct Rbr(u32) { 25 | data: u8 = 0..8; 26 | } 27 | } 28 | 29 | bitstruct! { 30 | /// Transmit hold register. 31 | pub struct Thr(u32) { 32 | data: u8 = 0..8; 33 | } 34 | } 35 | 36 | bitstruct! { 37 | /// Divisor latch low 38 | pub struct Dll(u32) { 39 | lo: u8 = 0..8; 40 | } 41 | } 42 | 43 | bitstruct! { 44 | /// Divisor latch hi 45 | pub struct Dlh(u32) { 46 | hi: u8 = 0..8; 47 | } 48 | } 49 | 50 | enum RcvrTrigger { 51 | One = 0b00, 52 | Quarter = 0b01, 53 | Half = 0b10, 54 | Less2 = 0b11, 55 | } 56 | 57 | enum TxEmptyTrigger { 58 | Empty = 0b00, 59 | Two = 0b01, 60 | Quarter = 0b10, 61 | Half = 0b11, 62 | } 63 | 64 | bitstruct! { 65 | /// FIFO control register 66 | pub struct Fcr(u32) { 67 | enable: bool = 0; 68 | rcvr_fifo_reset: bool = 1; 69 | xmtr_fifo_reset: bool = 2; 70 | tx_empty_trigger: TxEmptyTrigger = 4..=5; 71 | rcvr_trigger: RcvrTrigger = 6..=7; 72 | } 73 | } 74 | 75 | impl bitstruct::FromRaw for Fcr { 76 | fn from_raw(raw: u8) -> TxEmptyTrigger { 77 | match raw { 78 | 0b00 => TxEmptyTrigger::Empty, 79 | 0b01 => TxEmptyTrigger::Two, 80 | 0b10 => TxEmptyTrigger::Quarter, 81 | 0b11 => TxEmptyTrigger::Half, 82 | _ => panic!("impossible data bits value"), 83 | } 84 | } 85 | } 86 | 87 | impl bitstruct::IntoRaw for Fcr { 88 | fn into_raw(bits: TxEmptyTrigger) -> u8 { 89 | bits as u8 90 | } 91 | } 92 | 93 | impl bitstruct::FromRaw for Fcr { 94 | fn from_raw(raw: u8) -> RcvrTrigger { 95 | match raw { 96 | 0b00 => RcvrTrigger::One, 97 | 0b01 => RcvrTrigger::Quarter, 98 | 0b10 => RcvrTrigger::Half, 99 | 0b11 => RcvrTrigger::Less2, 100 | _ => panic!("impossible data bits value"), 101 | } 102 | } 103 | } 104 | 105 | impl bitstruct::IntoRaw for Fcr { 106 | fn into_raw(bits: RcvrTrigger) -> u8 { 107 | bits as u8 108 | } 109 | } 110 | 111 | enum Datas { 112 | Bits5 = 0b00, 113 | Bits6 = 0b01, 114 | Bits7 = 0b10, 115 | Bits8 = 0b11, 116 | } 117 | 118 | enum Parity { 119 | No, 120 | DisabledEven, 121 | Odd, 122 | Even, 123 | } 124 | 125 | enum Stops { 126 | Stop1, 127 | Stop2, 128 | } 129 | 130 | #[repr(u32)] 131 | enum Rate { 132 | B3M = 3_000_000u32, 133 | } 134 | 135 | bitstruct! { 136 | /// Line control register. 137 | #[derive(Clone, Copy)] 138 | pub struct Lcr(u32) { 139 | data_bits: Datas = 0..=1; 140 | stop: Stops = 2; 141 | parity: Parity = 3..=4; 142 | dlab: bool = 7; 143 | } 144 | } 145 | 146 | impl bitstruct::FromRaw for Lcr { 147 | fn from_raw(raw: u8) -> Datas { 148 | match raw { 149 | 0b00 => Datas::Bits5, 150 | 0b01 => Datas::Bits6, 151 | 0b10 => Datas::Bits7, 152 | 0b11 => Datas::Bits8, 153 | _ => panic!("impossible data bits value"), 154 | } 155 | } 156 | } 157 | 158 | impl bitstruct::IntoRaw for Lcr { 159 | fn into_raw(bits: Datas) -> u8 { 160 | bits as u8 161 | } 162 | } 163 | 164 | impl bitstruct::FromRaw for Lcr { 165 | fn from_raw(raw: bool) -> Stops { 166 | match raw { 167 | false => Stops::Stop1, 168 | true => Stops::Stop2, 169 | } 170 | } 171 | } 172 | 173 | impl bitstruct::IntoRaw for Lcr { 174 | fn into_raw(bits: Stops) -> bool { 175 | match bits { 176 | Stops::Stop1 => false, 177 | Stops::Stop2 => true, 178 | } 179 | } 180 | } 181 | 182 | impl bitstruct::FromRaw for Lcr { 183 | fn from_raw(raw: u8) -> Parity { 184 | match raw { 185 | 0b00 => Parity::No, 186 | 0b01 => Parity::DisabledEven, 187 | 0b10 => Parity::Odd, 188 | 0b11 => Parity::Even, 189 | _ => panic!("impossible data bits value"), 190 | } 191 | } 192 | } 193 | 194 | impl bitstruct::IntoRaw for Lcr { 195 | fn into_raw(parity: Parity) -> u8 { 196 | match parity { 197 | Parity::No => 0b00, 198 | Parity::DisabledEven => 0b01, 199 | Parity::Odd => 0b10, 200 | Parity::Even => 0b11, 201 | } 202 | } 203 | } 204 | 205 | bitstruct! { 206 | /// Ill-named Modem Control Register 207 | struct Mcr(u32) { 208 | dtr: bool = 0; 209 | rts: bool = 1; 210 | // out1: bool = 2; 211 | out2: bool = 3; 212 | // loopback: bool = 4; 213 | auto_flow: bool = 5; 214 | } 215 | } 216 | 217 | bitstruct! { 218 | /// Line Status Register 219 | struct Lsr(u32) { 220 | data_ready: bool = 0; 221 | overrun_err: bool = 1; 222 | parity_err: bool = 2; 223 | framing_err: bool = 3; 224 | break_intr: bool = 4; 225 | thr_empty: bool = 5; 226 | xmit_empty: bool = 6; 227 | rcv_fifo_err: bool = 7; 228 | } 229 | } 230 | 231 | bitstruct! { 232 | /// Software Reset Register 233 | struct Srr(u32) { 234 | uart_reset: bool = 0; 235 | rcvr_fifo_reset: bool = 1; 236 | xmtr_fifo_reset: bool = 2; 237 | } 238 | } 239 | 240 | bitstruct! { 241 | /// UART Status Register 242 | struct Usr(u32) { 243 | busy: bool = 0; 244 | tx_fifo_not_full: bool = 1; 245 | tx_fifo_empty: bool = 2; 246 | rx_fifo_not_empty: bool = 3; 247 | rx_fifo_full: bool = 4; 248 | } 249 | } 250 | 251 | /// The base virtual address of all UARTs. 252 | const UART_MMIO_BASE_ADDR: usize = 0xFEDC_9000; 253 | 254 | /// Describes the UART registers when the divisor latch is set 255 | /// in the line control register. This is the state in that 256 | /// that the UART is in after calling Device::reset. 257 | #[repr(C)] 258 | struct ConfigMmio { 259 | dll: Dll, 260 | dlh: Dlh, 261 | fcr: Fcr, 262 | lcr: Lcr, 263 | mcr: Mcr, 264 | _res: [u32; 29], 265 | srr: Srr, 266 | _rest: [u32; 29], 267 | } 268 | const_assert_eq!(core::mem::size_of::(), 256); 269 | 270 | impl ConfigMmio { 271 | fn lcr(&self) -> Lcr { 272 | unsafe { ptr::read_volatile(&self.lcr) } 273 | } 274 | 275 | /// Sets the line rate on the device. 276 | fn set_rate(&mut self, rate: Rate) { 277 | const SCLK: u32 = 48_000_000; 278 | let divisor = SCLK / (16 * rate as u32); 279 | let dll = Dll(divisor & 0xFF); 280 | let dlh = Dlh(divisor >> 8); 281 | unsafe { 282 | let lcr = self.lcr().with_dlab(true); 283 | ptr::write_volatile(&mut self.lcr, lcr); 284 | ptr::write_volatile(&mut self.dll, dll); 285 | ptr::write_volatile(&mut self.dlh, dlh); 286 | let lcr = self.lcr().with_dlab(false); 287 | ptr::write_volatile(&mut self.lcr, lcr); 288 | } 289 | } 290 | 291 | fn set_data_bits(&mut self, data: Datas) { 292 | unsafe { 293 | let lcr = self.lcr().with_data_bits(data); 294 | ptr::write_volatile(&mut self.lcr, lcr); 295 | } 296 | } 297 | 298 | fn set_stop_bits(&mut self, stop: Stops) { 299 | unsafe { 300 | let lcr = self.lcr().with_stop(stop); 301 | ptr::write_volatile(&mut self.lcr, lcr); 302 | } 303 | } 304 | 305 | fn set_parity(&mut self, parity: Parity) { 306 | unsafe { 307 | let lcr = self.lcr().with_parity(parity); 308 | ptr::write_volatile(&mut self.lcr, lcr); 309 | } 310 | } 311 | 312 | fn config_flow_control(&mut self) { 313 | let mcr = Mcr(0) 314 | .with_auto_flow(true) 315 | .with_out2(true) 316 | .with_rts(true) 317 | .with_dtr(true); 318 | unsafe { 319 | ptr::write_volatile(&mut self.mcr, mcr); 320 | } 321 | } 322 | 323 | fn config_fifos(&mut self) { 324 | let fcr = Fcr(0) 325 | .with_enable(true) 326 | .with_rcvr_fifo_reset(true) 327 | .with_xmtr_fifo_reset(true) 328 | .with_tx_empty_trigger(TxEmptyTrigger::Quarter) 329 | .with_rcvr_trigger(RcvrTrigger::Half); 330 | unsafe { 331 | ptr::write_volatile(&mut self.fcr, fcr); 332 | } 333 | } 334 | } 335 | 336 | /// Describes the UART registers for a read 337 | #[repr(C)] 338 | struct MmioRead { 339 | rbr: Rbr, // 0x00 340 | _ier: u32, // 0x04 341 | _iir: u32, // 0x08 342 | _lcr: u32, // 0x0C 343 | _mcr: u32, // 0x10 344 | _lsr: Lsr, // 0x14 345 | _msr: u32, // 0x18 346 | _scr: u32, // 0x1C 347 | _lpdll: u32, // 0x20 348 | _lpdlh: u32, // 0x24 349 | _res0: u32, // 0x28 350 | _res1: u32, // 0x2C 351 | _sdat: [u32; 16], // 0x30 - 0x6C // srbr and sthr 352 | _far: u32, // 0x70 353 | _tfr: u32, // 0x74 354 | _rfw: u32, // 0x78 355 | usr: Usr, // 0x7C 356 | _tfl: u32, // 0x80 357 | _rfl: u32, // 0x84 358 | _srr: u32, // 0x88 359 | _srts: u32, // 0x8C, 360 | _sbcr: u32, // 0x90 361 | _sdmam: u32, // 0x94 362 | _sfe: u32, // 0x98 363 | _srt: u32, // 0x9C 364 | _stet: u32, // 0xA0 365 | _htx: u32, // 0xA4 366 | _dmasa: u32, // 0xA8 367 | _res2: [u32; 18], // 0xAC - 0xF0 368 | _cpr: u32, // 0xF4 369 | _ucv: u32, // 0xF8 370 | _ctr: u32, // 0xFC 371 | } 372 | const_assert_eq!(core::mem::size_of::(), 256); 373 | 374 | /// Describes the UART registers for a write 375 | #[repr(C)] 376 | struct MmioWrite { 377 | thr: Thr, // 0x00 378 | _ier: u32, // 0x04 379 | _iir: u32, // 0x08 380 | _lcr: u32, // 0x0C 381 | _mcr: u32, // 0x10 382 | _lsr: Lsr, // 0x14 383 | _msr: u32, // 0x18 384 | _scr: u32, // 0x1C 385 | _lpdll: u32, // 0x20 386 | _lpdlh: u32, // 0x24 387 | _res0: u32, // 0x28 388 | _res1: u32, // 0x2C 389 | _sdat: [u32; 16], // 0x30 - 0x6C // srbr and sthr 390 | _far: u32, // 0x70 391 | _tfr: u32, // 0x74 392 | _rfw: u32, // 0x78 393 | usr: Usr, // 0x7C 394 | _tfl: u32, // 0x80 395 | _rfl: u32, // 0x84 396 | _srr: u32, // 0x88 397 | _srts: u32, // 0x8C, 398 | _sbcr: u32, // 0x90 399 | _sdmam: u32, // 0x94 400 | _sfe: u32, // 0x98 401 | _srt: u32, // 0x9C 402 | _stet: u32, // 0xA0 403 | _htx: u32, // 0xA4 404 | _dmasa: u32, // 0xA8 405 | _res2: [u32; 18], // 0xAC - 0xF0 406 | _cpr: u32, // 0xF4 407 | _ucv: u32, // 0xF8 408 | _ctr: u32, // 0xFC 409 | } 410 | const_assert_eq!(core::mem::size_of::(), 256); 411 | 412 | /// Represents a specific UART and its base MMIO address. 413 | #[derive(Clone, Copy, Debug)] 414 | #[repr(usize)] 415 | pub enum Device { 416 | Uart0 = UART_MMIO_BASE_ADDR, 417 | _Uart1 = UART_MMIO_BASE_ADDR + 0x1000, 418 | _Uart2 = UART_MMIO_BASE_ADDR + 0x5000, 419 | _Uart3 = UART_MMIO_BASE_ADDR + 0x6000, 420 | } 421 | 422 | static UART0_INITED: AtomicBool = AtomicBool::new(false); 423 | static UART1_INITED: AtomicBool = AtomicBool::new(false); 424 | static UART2_INITED: AtomicBool = AtomicBool::new(false); 425 | static UART3_INITED: AtomicBool = AtomicBool::new(false); 426 | 427 | impl Device { 428 | /// Returns the base virtual address of the device's 429 | /// MMIO region. 430 | fn addr(self) -> usize { 431 | self as usize 432 | } 433 | 434 | fn init(self, rate: Rate, data: Datas, stop: Stops, par: Parity) -> bool { 435 | let uart = self.reset(); 436 | uart.set_rate(rate); 437 | uart.set_data_bits(data); 438 | uart.set_stop_bits(stop); 439 | uart.set_parity(par); 440 | uart.config_flow_control(); 441 | uart.config_fifos(); 442 | true 443 | } 444 | 445 | fn reset<'a>(self) -> &'a mut ConfigMmio { 446 | let regs = ptr::with_exposed_provenance_mut::(self.addr()); 447 | let uart = unsafe { &mut *regs }; 448 | unsafe { 449 | ptr::write_volatile( 450 | &mut uart.srr, 451 | Srr(0) 452 | .with_uart_reset(true) 453 | .with_rcvr_fifo_reset(true) 454 | .with_xmtr_fifo_reset(true), 455 | ); 456 | } 457 | uart 458 | } 459 | } 460 | 461 | /// The UART itself. 462 | pub struct Uart(Device); 463 | 464 | impl Uart { 465 | pub fn uart0() -> Uart { 466 | assert!(UART0_INITED.load(Ordering::Acquire)); 467 | Uart(Device::Uart0) 468 | } 469 | 470 | pub(crate) fn addr(&self) -> usize { 471 | self.0.addr() 472 | } 473 | 474 | fn write_mmio_mut(&mut self) -> &mut MmioWrite { 475 | let regs = ptr::with_exposed_provenance_mut::(self.0.addr()); 476 | unsafe { &mut *regs } 477 | } 478 | 479 | // Note that reading from the device alters its state. We 480 | // model that by returning a mut ref. This also means that 481 | // it is mutually exclusive with a write MMIO structure, 482 | // as the two share the same register space. 483 | fn read_mmio_mut(&mut self) -> &mut MmioRead { 484 | let regs = ptr::with_exposed_provenance_mut::(self.0.addr()); 485 | unsafe { &mut *regs } 486 | } 487 | 488 | #[allow(dead_code)] 489 | fn getb(&mut self) -> u8 { 490 | while { 491 | let usr = unsafe { ptr::read_volatile(&self.read_mmio_mut().usr) }; 492 | !usr.rx_fifo_not_empty() 493 | } { 494 | core::hint::spin_loop(); 495 | } 496 | let data = unsafe { ptr::read_volatile(&self.read_mmio_mut().rbr) }; 497 | data.data() 498 | } 499 | 500 | fn putb(&mut self, b: u8) { 501 | while { 502 | let usr = unsafe { ptr::read_volatile(&self.write_mmio_mut().usr) }; 503 | !usr.tx_fifo_not_full() 504 | } { 505 | // We're not racing against anyone, but, this doesn't hurt. 506 | core::hint::spin_loop(); 507 | } 508 | let data = Thr(0).with_data(b); 509 | unsafe { 510 | ptr::write_volatile(&mut self.write_mmio_mut().thr, data); 511 | } 512 | } 513 | } 514 | 515 | /// Returns the (initialized) UART device used for the logging 516 | /// console. 517 | pub fn cons() -> Uart { 518 | Uart::uart0() 519 | } 520 | 521 | /// Initializes the console UART. 522 | /// 523 | /// # Safety 524 | /// The caller must ensure that MMIO space for the UARTs is 525 | /// properly mapped before calling this. 526 | pub unsafe fn init() { 527 | if !UART0_INITED.swap(true, Ordering::AcqRel) { 528 | Device::Uart0.init(Rate::B3M, Datas::Bits8, Stops::Stop1, Parity::No); 529 | } 530 | UART1_INITED.store(false, Ordering::Release); 531 | UART2_INITED.store(false, Ordering::Release); 532 | UART3_INITED.store(false, Ordering::Release); 533 | } 534 | 535 | /// By implementing `Write` on the UART, we can implement the 536 | /// formatted output functions. 537 | impl fmt::Write for Uart { 538 | fn write_str(&mut self, s: &str) -> fmt::Result { 539 | for b in s.bytes() { 540 | if b == b'\n' { 541 | self.putb(b'\r'); 542 | } 543 | self.putb(b); 544 | } 545 | Ok(()) 546 | } 547 | } 548 | 549 | /// A simple println!(). 550 | #[macro_export] 551 | macro_rules! println { 552 | () => ($crate::print!("\n")); 553 | ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); 554 | } 555 | 556 | #[macro_export] 557 | macro_rules! print { 558 | ($($args:tt)*) => ({ 559 | use core::fmt::Write; 560 | let mut cons = $crate::uart::cons(); 561 | cons.write_fmt(format_args!($($args)*)).unwrap(); 562 | }) 563 | } 564 | -------------------------------------------------------------------------------- /x86_64-oxide-none-elf.json: -------------------------------------------------------------------------------- 1 | { 2 | "llvm-target": "x86_64-unknown-none", 3 | "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", 4 | "target-endian": "little", 5 | "target-pointer-width": "64", 6 | "target-c-int-width": "32", 7 | "panic-strategy": "abort", 8 | "arch": "x86_64", 9 | "os": "none", 10 | "vendor": "oxide", 11 | "executables": true, 12 | "relocation-model": "static", 13 | "code-model": "small", 14 | "frame-pointer": "always", 15 | "disable-redzone": true, 16 | "features": "-avx,-avx2,-avx512bf16,-f16c,-fxsr,-mmx,-sse,-sse2,-sse3,-sse4.1,-sse4.2,-sse4a,-ssse3,-x87,+soft-float", 17 | "rustc-abi": "x86-softfloat", 18 | "linker-flavor": "ld", 19 | "linker": "gld", 20 | "no-default-libraries": "true", 21 | "pre-link-args": { 22 | "ld": [ 23 | "-nostdlib", 24 | "-Tsrc/phbl.ld", 25 | "-zmax-page-size=4096" 26 | ], 27 | "ld.lld": [ 28 | "-nostdlib", 29 | "-Tsrc/phbl.ld" 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtask" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | clap = { version = "4.4", features = ["derive"] } 10 | duct = "1.0" 11 | -------------------------------------------------------------------------------- /xtask/src/main.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | //! 6 | //! Build driver for pico host boot loader. 7 | //! 8 | use clap::Parser; 9 | use duct::cmd; 10 | use std::env; 11 | use std::path::{Path, PathBuf}; 12 | 13 | #[derive(Parser)] 14 | #[command( 15 | name = "phbl", 16 | author = "Oxide Computer Company", 17 | version = "0.1.0", 18 | about = "xtask build tool for pico host boot loader" 19 | )] 20 | struct Xtask { 21 | #[clap(subcommand)] 22 | cmd: Command, 23 | } 24 | 25 | #[derive(Parser)] 26 | enum Command { 27 | /// Builds phbl 28 | Build { 29 | #[clap(flatten)] 30 | profile: BuildProfile, 31 | #[clap(flatten)] 32 | locked: Locked, 33 | #[clap(long)] 34 | target_dir: Option, 35 | 36 | /// Path to compressed CPIO archive 37 | #[clap(long)] 38 | cpioz: PathBuf, 39 | }, 40 | /// cargo clean 41 | Clean, 42 | /// Run cargo clippy linter 43 | Clippy { 44 | #[clap(flatten)] 45 | locked: Locked, 46 | }, 47 | /// disassemble phbl 48 | Disasm { 49 | #[clap(flatten)] 50 | profile: BuildProfile, 51 | #[clap(flatten)] 52 | locked: Locked, 53 | 54 | /// Interleave source and assembler output 55 | #[clap(long)] 56 | source: bool, 57 | 58 | /// Path to compressed CPIO archive 59 | #[clap(long, default_value = "/dev/null")] 60 | cpioz: PathBuf, 61 | }, 62 | /// Expand macros 63 | Expand, 64 | /// Run unit tests 65 | Test { 66 | #[clap(flatten)] 67 | profile: BuildProfile, 68 | #[clap(flatten)] 69 | locked: Locked, 70 | }, 71 | } 72 | 73 | /// Mutually exclusive debug/release flags, used by all commands 74 | /// that run builds. 75 | #[derive(Clone, Parser)] 76 | struct BuildProfile { 77 | /// Build debug version (default) 78 | #[clap(long, conflicts_with_all = &["release"])] 79 | debug: bool, 80 | 81 | /// Build optimized version 82 | #[clap(long)] 83 | release: bool, 84 | } 85 | 86 | impl BuildProfile { 87 | // Returns the cargo argument corresponding to the given 88 | // profile. 89 | fn to_str(&self) -> &'static str { 90 | self.release.then_some("--release").unwrap_or("") 91 | } 92 | 93 | // Returns the output subdirectory component corresponding 94 | // to the profile. 95 | fn dir(&self) -> &'static Path { 96 | Path::new(if self.release { "release" } else { "debug" }) 97 | } 98 | } 99 | 100 | /// Cargo `--locked` setting; separate from BuildProfile because 101 | /// `clippy` uses it but doesn't care about debug/release. 102 | #[derive(Parser)] 103 | struct Locked { 104 | /// Build locked to Cargo.lock 105 | #[clap(long)] 106 | locked: bool, 107 | } 108 | 109 | impl Locked { 110 | fn to_str(&self) -> &str { 111 | self.locked.then_some("--locked").unwrap_or("") 112 | } 113 | } 114 | 115 | fn main() { 116 | let xtask = Xtask::parse(); 117 | match xtask.cmd { 118 | Command::Build { profile, target_dir, locked, cpioz } => { 119 | build(profile, target_dir, locked, cpioz) 120 | } 121 | Command::Test { profile, locked } => test(profile, locked), 122 | Command::Disasm { profile, locked, source, cpioz } => { 123 | disasm(profile, locked, source, cpioz) 124 | } 125 | Command::Expand => expand(), 126 | Command::Clippy { locked } => clippy(locked), 127 | Command::Clean => clean(), 128 | } 129 | } 130 | 131 | /// Runs a cross-compiled build. 132 | fn build( 133 | profile: BuildProfile, 134 | target_dir: Option, 135 | locked: Locked, 136 | cpioz: PathBuf, 137 | ) { 138 | std::env::set_var("PHBL_PHASE1_COMPRESSED_CPIO_ARCHIVE_PATH", cpioz); 139 | let profile = profile.to_str(); 140 | let locked = locked.to_str(); 141 | let target_dir = target_dir.unwrap_or("target".into()); 142 | let target_dir = target_dir.display(); 143 | let target = target(); 144 | let args = format!( 145 | "build {profile} {locked} \ 146 | -Z build-std=core,alloc \ 147 | -Z build-std-features=compiler-builtins-mem \ 148 | --target {target}.json \ 149 | --target-dir {target_dir}" 150 | ); 151 | cmd(cargo(), args.split_whitespace()).run().expect("build successful"); 152 | } 153 | 154 | /// Runs tests. 155 | fn test(profile: BuildProfile, locked: Locked) { 156 | let profile = profile.to_str(); 157 | let locked = locked.to_str(); 158 | let args = format!("test {profile} {locked}"); 159 | cmd(cargo(), args.split_whitespace()).run().expect("test successful"); 160 | } 161 | 162 | /// Build and disassemble the phbl binary. 163 | fn disasm(profile: BuildProfile, locked: Locked, source: bool, cpioz: PathBuf) { 164 | let target_dir = None; 165 | build(profile.clone(), target_dir, locked, cpioz); 166 | let triple = target(); 167 | let profile_dir = profile.dir().to_str().unwrap(); 168 | let flags = source.then_some("-S").unwrap_or(""); 169 | let args = format!("-Cd {flags} target/{triple}/{profile_dir}/phbl"); 170 | println!("args = {args}"); 171 | cmd(objdump(), args.split_whitespace()) 172 | .run() 173 | .expect("disassembly successful"); 174 | } 175 | 176 | /// Expands macros. 177 | fn expand() { 178 | cmd!(cargo(), "rustc", "--", "-Zunpretty=expanded") 179 | .run() 180 | .expect("expand successful"); 181 | } 182 | 183 | /// Runs the Clippy linter. 184 | fn clippy(locked: Locked) { 185 | let locked = locked.to_str(); 186 | let args = format!("clippy {locked}"); 187 | cmd(cargo(), args.split_whitespace()).run().expect("clippy successful"); 188 | } 189 | 190 | /// Runs clean on the project. 191 | fn clean() { 192 | cmd!(cargo(), "clean").run().expect("clean successful"); 193 | } 194 | 195 | /// Returns the value of the given environment variable, 196 | /// or the default if unspecified. 197 | fn env_or(var: &str, default: &str) -> String { 198 | env::var(var).unwrap_or(default.into()) 199 | } 200 | 201 | /// Returns the name of the cargo binary. 202 | fn cargo() -> String { 203 | env_or("CARGO", "cargo") 204 | } 205 | 206 | /// Returns the target triple we are building for. 207 | fn target() -> String { 208 | env_or("TARGET", "x86_64-oxide-none-elf") 209 | } 210 | 211 | /// Locates the LLVM objdump binary. 212 | fn objdump() -> String { 213 | env_or("OBJDUMP", "llvm-objdump".into()) 214 | } 215 | --------------------------------------------------------------------------------