Compare commits

..

39 Commits

Author SHA1 Message Date
Berkus Decker da9f2940ba Merge pull-request #92 from metta-systems:fix/update-deps to develop
build: Bump dependencies versions
Upgrade clap, crossterm, tokio.

[close #92]
2022-02-12 01:13:13 +02:00
Berkus Decker 30db2405ef build: Bump dependencies versions
Upgrade clap, crossterm, tokio.
2022-02-12 00:44:36 +02:00
Berkus Decker a9a97d132d Merge pull-request #85 from metta-systems:feature/chainboot to develop
Add chain boot loader
Closes #14

[close #85]
2022-02-10 02:39:46 +02:00
Berkus Decker f4418c3164 feat: Add `just boot` command
Due to just or cargo-make taking over the
controlling PTY we cannot launch an interactive
command from a cargo-make file, so just print
the command line to launch.
2022-02-10 02:16:28 +02:00
Berkus Decker 637304bdb3 feat: Add better nm output
Demangle and sort by symbol address.
2022-02-10 02:16:28 +02:00
Berkus Decker a95d4e3fb0 fix: 🐛 Use inline(always) to optimize code
This removes additional dummy jumps.
2022-02-10 02:16:28 +02:00
Berkus Decker 162592beaa feat: Add chainofcommand
Works fine with a caveat that you need
to press ENTER after Ctrl+C, investigate.

Multiplex QEMU output for serial access.
2022-02-10 02:16:28 +02:00
Berkus Decker cfe4a230de feat: Add chainboot boot loader 2022-02-10 02:15:57 +02:00
Berkus Decker 3c57c6e2df fix: 🐛 Calculate PL011 divisors, fix PullUp/Down init
Rename registers in an attempt to make them more
readable.
2022-02-10 02:15:18 +02:00
Berkus Decker 2d9da8c5db fix: 🐛 Fix MiniUart PullUp/Down init
Use STAT reg, init GPIO before AUX_MU.
2022-02-10 02:15:18 +02:00
Berkus Decker 0234f2136e feat: Add rpi4 support to GPIO 2022-02-10 02:15:18 +02:00
Berkus Decker e5a3ea6998 refactor: 📦 Update Mailbox code 2022-02-10 02:15:18 +02:00
Berkus Decker 7f4e9de6d5 feat: Update BcmHost for rpi4 2022-02-10 02:15:18 +02:00
Berkus Decker 8f26c6fa44 refactor: 📦 Clean up code
Debugging with jtag.
Explicitly default to no features.
Allow unused allocator code.
Add board selection.
Add RPi4 datasheet.
Add explanation on bss alignment type.
2022-02-10 02:15:18 +02:00
Berkus Decker f76fab3fff chore: ♻️ Use different DTB files for rpi3 and rpi4 2022-02-10 02:14:07 +02:00
Berkus Decker 9cbc6ce80f build: 🛠 Extract QEMU_DISASM_OPTS 2022-02-10 02:14:07 +02:00
Berkus Decker 8b6a585250 build: 🛠 Set machine ID for QEMU versions after 6.2.0
Work around ancient QEMU versions on CI boxes.
2022-02-10 02:14:07 +02:00
Berkus Decker ea97d29c3c build: 🛠 Generate binary files as part of build step 2022-02-10 02:14:07 +02:00
Berkus Decker 61eb2f9538 feat!: Update ConsoleOps
Split ConsoleOps vs SerialOps, rename console
methods, make them saner. Add docs.
Add clear_rx() fn.

Drop default trait impls for safety.
2022-02-10 02:14:07 +02:00
Berkus Decker 16ec45b97c feat: Add loop_while() fn 2022-02-10 02:14:07 +02:00
Berkus Decker 33dbf79041 feat!: Add shared MMIODerefWrapper instead of per-module ones 2022-02-10 02:14:07 +02:00
Berkus Decker 1e17e03a8d build: 🛠 Support per-binary linker scripts 2022-02-10 02:14:07 +02:00
Berkus Decker 721af870bf build: 🛠 Make image prepare steps more verbose 2022-02-10 02:14:07 +02:00
Berkus Decker 40782ea7cc chore(debug): ♻️ Upgrade gdb version and update run scripts 2022-02-10 02:14:07 +02:00
Berkus Decker 4cdeeb8556 refactor: 📦 Split kernel into machine and nucleus
Move openocd task to upper level and remove kernel
build dependency - the qemu runner doesn't really
depend on it.
2022-02-10 02:14:07 +02:00
Berkus Decker b26d61cb67 build: 🛠 Improve Justfile help output
Enter `just` to see a list of available commands
with explanations.

Added `just ci` step.
Added `just fmt-check`.
Removed unnecessary kernel file removals.
Moved sdeject task and added `just device-eject`.
2022-01-23 16:03:03 +02:00
Berkus Decker cc4170200f chore(rustfmt): ♻️ Enable merging imports in rustfmt 2022-01-23 16:03:03 +02:00
Berkus Decker e3f199f89f docs(emulation): 📚 Fix typos in the banner 2022-01-23 16:03:03 +02:00
Berkus Decker af3dc82c76 docs(readme): 📚 Fix kernel type terminology
While Vesper strives to be as thin as possible
it's not exactly an exokernel, but it's a very
thin microkernel, dubbed nanokernel here.
2022-01-23 16:03:03 +02:00
Berkus Decker 7d03ea85a2 Merge pull-request #80 from metta-systems:feature/update-openocd to develop
Update openocd version and scripts
Allow board selection via env var. Bump rust version to 1.59.0 and stabilise asm!/global_asm! directives plus adopt more strict clippy checks.

[close #80]
2021-12-20 22:16:31 +02:00
Berkus Decker ca263b33a1 fix: Add missing #[must_use] attributes
Clippy was very unhappy.
2021-12-20 21:46:09 +02:00
Berkus Decker 90389705a7 feat: Stabilise asm/global_asm
Requires rust 1.59.0 or later.
2021-12-20 21:46:09 +02:00
Berkus Decker 5e1bbf9758 build: Bump Rust edition to 2021 2021-12-20 21:03:55 +02:00
Berkus Decker 503f43d983 build(tools): Enable target board selection 2021-12-20 17:52:14 +02:00
Berkus Decker 5a304557a7 build(tools): Add RasPi4 target configuration for OpenOCD
Update and move rpi3 jtag configs.
Add rpi bringup doc - lists romtables for configuring.
2021-12-20 17:51:20 +02:00
Berkus Decker f6da27062f build(tools): Update OpenOCD version
RTT patch has been merged, so use the latest 0.11.0-rc2+dev-01576-g0d9e8bd52-dirty
2021-12-20 17:51:08 +02:00
Berkus Decker e6ddbb76e7 feat: Add zellij qemu runner with serial emulation 2021-11-17 17:19:39 +02:00
Berkus Decker d706b2edac chore: Bump dependencies 2021-11-17 17:16:58 +02:00
Berkus Decker 115c93e3f8
Update code of conduct
Extend it with code of ethics, based on Spotify version.
2021-09-26 23:00:46 +03:00
91 changed files with 4443 additions and 3751 deletions

View File

@ -1,16 +1,12 @@
[unstable]
build-std = ["core", "compiler_builtins", "alloc"]
build-std-features = ["compiler-builtins-mem"]
[build]
# https://internals.rust-lang.org/t/evaluating-pipelined-rustc-compilation/10199/12
pipelining = true
[target.aarch64-vesper-metta]
rustflags = [
"-C", "target-feature=-fp-armv8",
"-C", "target-cpu=cortex-a53",
"-C", "embed-bitcode=yes",
"-Z", "macro-backtrace",
"-C", "target-feature=-fp-armv8",
"-C", "target-cpu=cortex-a53",
"-C", "embed-bitcode=yes",
"-Z", "macro-backtrace",
]
runner = "cargo make test-runner"

View File

@ -45,6 +45,17 @@ jobs:
- name: "Install build tools"
run: cargo install cargo-make cargo-binutils
- name: "Prepare packages (Linux)"
run: |
sudo apt install software-properties-common
sudo add-apt-repository ppa:jacob/virtualisation
sudo apt update
if: runner.os == 'Linux'
- name: "Install dev libraries (Linux)"
run: sudo apt install libudev-dev
if: runner.os == 'Linux'
- name: "Validate rust-lld"
run: |
which rust-lld || echo "Not found"
@ -63,9 +74,6 @@ jobs:
- name: Install QEMU (Linux)
run: |
sudo apt install software-properties-common
sudo add-apt-repository ppa:jacob/virtualisation
sudo apt update
sudo apt install qemu-system-aarch64
if: runner.os == 'Linux'
@ -101,8 +109,13 @@ jobs:
- name: 'Build kernel'
run: cargo make build
- name: 'Run tests'
- name: 'Run tests (macOS)'
run: cargo make test
if: runner.os == 'macOS'
- name: 'Run tests (other OSes)'
run: env QEMU_MACHINE=raspi3 cargo make test
if: runner.os != 'macOS'
check_formatting:
name: "Check Formatting"
@ -130,6 +143,8 @@ jobs:
timeout-minutes: 10
steps:
- uses: actions/checkout@v1
- run: sudo apt update
- run: sudo apt install libudev-dev
- run: rustup toolchain install nightly
- run: cargo install cargo-make
- run: env CLIPPY_FEATURES=${{ matrix.features }} cargo make clippy

843
Cargo.lock generated
View File

@ -2,6 +2,59 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "CoreFoundation-sys"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0e9889e6db118d49d88d84728d0e964d973a5680befb5f85f55141beea5c20b"
dependencies = [
"libc",
"mach 0.1.2",
]
[[package]]
name = "IOKit-sys"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99696c398cbaf669d2368076bdb3d627fb0ce51a26899d7c61228c5c0af3bf4a"
dependencies = [
"CoreFoundation-sys",
"libc",
"mach 0.1.2",
]
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "anyhow"
version = "1.0.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0"
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bit_field"
version = "0.10.1"
@ -10,9 +63,21 @@ checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4"
[[package]]
name = "bitflags"
version = "1.2.1"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bytes"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
[[package]]
name = "cc"
version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
[[package]]
name = "cfg-if"
@ -21,14 +86,95 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cortex-a"
version = "6.0.0"
name = "chainboot"
version = "0.0.1"
dependencies = [
"bit_field",
"bitflags",
"cfg-if",
"cortex-a",
"machine",
"r0",
"seahash",
"snafu",
"tock-registers",
"usize_conversions",
"ux",
]
[[package]]
name = "chainofcommand"
version = "0.0.1"
dependencies = [
"anyhow",
"bytes",
"clap",
"crossterm",
"defer",
"fehler",
"futures",
"seahash",
"tokio",
"tokio-serial",
"tokio-util",
]
[[package]]
name = "clap"
version = "3.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d25a057b35117d030453cd322f7adbdd331e886bc5bde46cdae656952c4feba6"
checksum = "b63edc3f163b3c71ec8aa23f9bd6070f77edbf3d1d198b164afa90ff00e4ec62"
dependencies = [
"atty",
"bitflags",
"indexmap",
"os_str_bytes",
"strsim",
"termcolor",
"textwrap",
]
[[package]]
name = "cortex-a"
version = "7.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bd95fd055d118f77d4e4d527201b6ceccd13586b19b4dac1270f7081fef0f98"
dependencies = [
"tock-registers",
]
[[package]]
name = "crossterm"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77b75a27dc8d220f1f8521ea69cd55a34d720a200ebb3a624d9aa19193d3b432"
dependencies = [
"bitflags",
"crossterm_winapi",
"futures-core",
"libc",
"mio",
"parking_lot 0.12.0",
"signal-hook",
"signal-hook-mio",
"winapi",
]
[[package]]
name = "crossterm_winapi"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
dependencies = [
"winapi",
]
[[package]]
name = "defer"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "647605a6345d5e89c3950a36a638c56478af9b414c55c6f2477c73b115f9acde"
[[package]]
name = "doc-comment"
version = "0.3.3"
@ -36,25 +182,421 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "proc-macro2"
version = "1.0.27"
name = "fehler"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038"
checksum = "d5729fe49ba028cd550747b6e62cd3d841beccab5390aa398538c31a2d983635"
dependencies = [
"fehler-macros",
]
[[package]]
name = "fehler-macros"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccb5acb1045ebbfa222e2c50679e392a71dd77030b78fb0189f2d9c5974400f9"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
[[package]]
name = "futures-executor"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
[[package]]
name = "futures-macro"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-sink"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868"
[[package]]
name = "futures-task"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a"
[[package]]
name = "futures-util"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "indexmap"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "libc"
version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c"
[[package]]
name = "lock_api"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]]
name = "mach"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd13ee2dd61cc82833ba05ade5a30bb3d63f7ced605ef827063c63078302de9"
dependencies = [
"libc",
]
[[package]]
name = "mach"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86dd2487cdfea56def77b88438a2c915fb45113c5319bfe7e14306ca4cd0b0e1"
dependencies = [
"libc",
]
[[package]]
name = "machine"
version = "0.0.1"
dependencies = [
"bit_field",
"bitflags",
"cfg-if",
"cortex-a",
"qemu-exit",
"r0",
"snafu",
"tock-registers",
"usize_conversions",
"ux",
]
[[package]]
name = "memchr"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg",
]
[[package]]
name = "mio"
version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc"
dependencies = [
"libc",
"log",
"miow",
"ntapi",
"winapi",
]
[[package]]
name = "mio-serial"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6f47dcdec193b77e2166faa0e3be4ee04f324ce6780c54efe7af6c3ee7360e4"
dependencies = [
"log",
"mio",
"nix 0.22.3",
"serialport",
"winapi",
]
[[package]]
name = "miow"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
dependencies = [
"winapi",
]
[[package]]
name = "nix"
version = "0.22.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf"
dependencies = [
"bitflags",
"cc",
"cfg-if",
"libc",
"memoffset",
]
[[package]]
name = "nix"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6"
dependencies = [
"bitflags",
"cc",
"cfg-if",
"libc",
"memoffset",
]
[[package]]
name = "ntapi"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f"
dependencies = [
"winapi",
]
[[package]]
name = "nucleus"
version = "0.0.1"
dependencies = [
"bit_field",
"bitflags",
"cfg-if",
"cortex-a",
"machine",
"r0",
"snafu",
"tock-registers",
"usize_conversions",
"ux",
]
[[package]]
name = "num_cpus"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "once_cell"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
[[package]]
name = "os_str_bytes"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
dependencies = [
"memchr",
]
[[package]]
name = "parking_lot"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
"parking_lot_core 0.8.5",
]
[[package]]
name = "parking_lot"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58"
dependencies = [
"lock_api",
"parking_lot_core 0.9.1",
]
[[package]]
name = "parking_lot_core"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
dependencies = [
"cfg-if",
"instant",
"libc",
"redox_syscall",
"smallvec",
"winapi",
]
[[package]]
name = "parking_lot_core"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-sys",
]
[[package]]
name = "pin-project-lite"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "proc-macro2"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [
"unicode-xid",
]
[[package]]
name = "qemu-exit"
version = "2.0.1"
version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "220eb94f40665452ab6114bf8a8d86aa1fd41c6dbfaa4ab71b5912c8adb80389"
checksum = "9ff023245bfcc73fb890e1f8d5383825b3131cc920020a5c487d6f113dfc428a"
[[package]]
name = "quote"
version = "1.0.9"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
dependencies = [
"proc-macro2",
]
@ -66,10 +608,105 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd7a31eed1591dcbc95d92ad7161908e72f4677f8fabf2a32ca49b4237cbf211"
[[package]]
name = "snafu"
version = "0.7.0-beta.0"
name = "redox_syscall"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15f918bbe88782fcafbae7cc73706f69334ed796485350caa067f07c9aeabdc3"
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "seahash"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "serialport"
version = "4.0.1"
source = "git+https://github.com/metta-systems/serialport-rs?branch=macos-ENOTTY-fix#91ee1f740ee7d7c9506df8af082860a478656e18"
dependencies = [
"CoreFoundation-sys",
"IOKit-sys",
"bitflags",
"cfg-if",
"mach 0.2.3",
"nix 0.23.1",
"regex",
"winapi",
]
[[package]]
name = "signal-hook"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "647c97df271007dcea485bb74ffdb57f2e683f1306c854f468a0c244badabf2d"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-mio"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29fd5867f1c4f2c5be079aee7a2adf1152ebb04a4bc4d341f504b7dece607ed4"
dependencies = [
"libc",
"mio",
"signal-hook",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
dependencies = [
"libc",
]
[[package]]
name = "slab"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
[[package]]
name = "smallvec"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
[[package]]
name = "snafu"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eba135d2c579aa65364522eb78590cdf703176ef71ad4c32b00f58f7afb2df5"
dependencies = [
"doc-comment",
"snafu-derive",
@ -77,38 +714,117 @@ dependencies = [
[[package]]
name = "snafu-derive"
version = "0.7.0-beta.0"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dc5ac2fdc63fa51eee6a8dd048589f899d265dbd465b2a6176e3903ce453bfc"
checksum = "7a7fe9b0669ef117c5cabc5549638528f36771f058ff977d7689deb517833a75"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.73"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7"
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "termcolor"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
[[package]]
name = "tock-registers"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ee8fba06c1f4d0b396ef61a54530bb6b28f0dc61c38bc8bc5a5a48161e6282e"
[[package]]
name = "tokio"
version = "1.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a"
dependencies = [
"bytes",
"libc",
"memchr",
"mio",
"num_cpus",
"once_cell",
"parking_lot 0.11.2",
"pin-project-lite",
"signal-hook-registry",
"tokio-macros",
"winapi",
]
[[package]]
name = "tokio-macros"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tokio-serial"
version = "5.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c75b04fbb029a3f77272b3356453c97c5dbdebcf28aa248a34fd80a442f2dda1"
dependencies = [
"cfg-if",
"futures",
"log",
"mio-serial",
"tokio",
]
[[package]]
name = "tokio-util"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64910e1b9c1901aaf5375561e35b9c057d95ff41a44ede043a03e09279eabaf1"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"log",
"pin-project-lite",
"tokio",
]
[[package]]
name = "unicode-segmentation"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
[[package]]
name = "unicode-xid"
version = "0.2.2"
@ -128,18 +844,75 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88dfeb711b61ce620c0cb6fd9f8e3e678622f0c971da2a63c4b3e25e88ed012f"
[[package]]
name = "vesper"
version = "0.0.1"
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"bit_field",
"bitflags",
"cfg-if",
"cortex-a",
"qemu-exit",
"r0",
"snafu",
"static_assertions",
"tock-registers",
"usize_conversions",
"ux",
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6"
dependencies = [
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_msvc"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5"
[[package]]
name = "windows_i686_gnu"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615"
[[package]]
name = "windows_i686_msvc"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172"
[[package]]
name = "windows_x86_64_gnu"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc"
[[package]]
name = "windows_x86_64_msvc"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316"

View File

@ -1,8 +1,13 @@
[workspace]
members = [
"nucleus"
"nucleus",
"bin/chainboot",
"bin/chainofcommand"
]
[patch.crates-io]
serialport = { git = "https://github.com/metta-systems/serialport-rs", branch = "macos-ENOTTY-fix" }
[profile.dev]
# See https://github.com/rust-lang/cargo/issues/7359 about why panic=abort is not working here.
# It is still defined in the target JSON so not stricly necessary to specify it here anyway.

View File

@ -1,57 +1,111 @@
qemu:
# Build and run kernel in QEMU
cargo make qemu
_default:
@just --list
qemu-gdb:
# Build and run kernel in QEMU with GDB port enabled
cargo make qemu-gdb
# Build default hw kernel and run chainofcommand to boot this kernel onto the board
boot: chainofcommand
cargo make chainboot
device:
# Build and write kernel to an SD Card
cargo make sdcard
# Build and run kernel in QEMU with serial port emulation
zellij:
cargo make zellij-nucleus
zellij --layout-path emulation/layout.zellij
build:
# Build default hw kernel
# Build and run chainboot in QEMU with serial port emulation
zellij-cb:
# Connect to it via chainofcommand to load an actual kernel
# TODO: actually run chainofcommand in a zellij session too
cargo make zellij-cb
zellij --layout-path emulation/layout.zellij
# Build chainofcommand serial loader
chainofcommand:
cd bin/chainofcommand
cargo make build
# Build and run kernel in QEMU
qemu:
cargo make qemu
# Build and run kernel in QEMU with GDB port enabled
qemu-gdb:
cargo make qemu-gdb
# Build and run chainboot in QEMU
qemu-cb:
# Connect to it via chainofcommand to load an actual kernel
cargo make qemu-cb
# Build and write kernel to an SD Card
device:
cargo make sdcard
# Build and write kernel to an SD Card, then eject the SD Card volume
device-eject:
cargo make sdeject
# Build and write chainboot to an SD Card, then eject the SD Card volume
cb-eject:
cd bin/chainboot
cargo make cb-eject
# Build default hw kernel
build:
cargo make build
cargo make kernel-binary
# Clean project
clean:
# Clean project
cargo make clean
rm -f kernel8 kernel8.img
# Run clippy checks
clippy:
# Run clippy checks
# TODO: use cargo-hack
cargo make clippy
env CLIPPY_FEATURES=noserial cargo make clippy
env CLIPPY_FEATURES=qemu cargo make clippy
env CLIPPY_FEATURES=noserial,qemu cargo make clippy
env CLIPPY_FEATURES=jtag cargo make clippy
env CLIPPY_FEATURES=noserial,jtag cargo make clippy
# Run tests in QEMU
test:
# Run tests in QEMU
cargo make test
alias disasm := hopper
# Build and disassemble kernel
hopper:
# Build and disassemble kernel
cargo make hopper
alias ocd := openocd
# Start openocd (by default connected via JTAG to a target device)
openocd:
# Start openocd (by default connected via JTAG to a target device)
cargo make openocd
# Build and run kernel in GDB using openocd or QEMU as target (gdb port 5555)
gdb:
# Build and run kernel in GDB using openocd or QEMU as target (gdb port 5555)
cargo make gdb
# Build and run chainboot in GDB using openocd or QEMU as target (gdb port 5555)
gdb-cb:
cargo make gdb-cb
# Build and print all symbols in the kernel
nm:
# Build and print all symbols in the kernel
cargo make nm
# Check formatting
fmt-check:
cargo fmt -- --check
# Run `cargo expand` on nucleus
expand:
# Run `cargo expand` on modules
cargo make expand -- nucleus
# Generate and open documentation
doc:
# Generate and open documentation
cargo make docs-flow
# Run CI tasks
ci: clean build test clippy fmt-check

View File

@ -53,34 +53,3 @@ No contributor can revoke this license.
without any warranty or condition, and no contributor
will be liable to anyone for any damages related to this
software or this license, under any kind of legal claim.***
---
[Addtional restrictions](https://blog.yossarian.net/2020/06/03/You-may-not-use-my-projects-in-a-military-or-law-enforcement-context):
The following terms additionally apply and override any above terms for
applicable parties:
You may not use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software in a military or law enforcement context,
defined as follows:
1. A military context is a professional context where the intended application
of the Software is integration or use with or by military software, tools
(software or hardware), or personnel. This includes contractors and
subcontractors as well as research affiliates of any military organization.
2. A law enforcement context is a professional context where the intended
application of the Software is integration or use with or by law enforcement
software, tools (software or hardware), or personnel. This includes
contractors and subcontractors as well as research affiliates of any law
enforcement organization.
Entities that sell or license to military or law enforcement organizations
may use the Software under the original terms, but only in contexts that do
not assist or supplement the sold or licensed product.
Students and academics who are affiliated with research institutions may use
the Software under the original terms, but only in contexts that do not assist
or supplement collaboration or affiliation with any military or law
enforcement organization.

View File

@ -18,13 +18,17 @@ DEFAULT_TARGET = "aarch64-vesper-metta"
TARGET = { value = "${DEFAULT_TARGET}", condition = { env_not_set = ["TARGET"] } }
# Name of the target board "rpi3" or "rpi4"
TARGET_BOARD = { value = "rpi3", condition = { env_not_set = ["TARGET_BOARD"] } }
TARGET_BOARD = { value = "rpi4", condition = { env_not_set = ["TARGET_BOARD"] } }
# Name of the DTB file for target board configuration, use bcm2710-rpi-3-b-plus.dtb for RasPi3B+
TARGET_DTB = { value = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/targets/bcm2711-rpi-4-b.dtb", condition = { env_not_set = ["TARGET_DTB"] } }
# AArch64 QEMU binary
QEMU = { value = "/usr/local/opt/qemu/HEAD-51db2d7cf2-mmudebug/bin/qemu-system-aarch64", condition = { env_not_set = ["QEMU"] } }
QEMU = { value = "qemu-system-aarch64", condition = { env_not_set = ["QEMU"] } }
# QEMU machine type, defaults to raspi3b but CI runners override it due to ancient QEMU versions they use.
QEMU_MACHINE = { value = "raspi3b", condition = { env_not_set = ["QEMU_MACHINE"] } }
# An aarch64-enabled GDB
GDB = { value = "/usr/local/opt/gdb/10.1-aarch64/bin/aarch64-linux-elf-gdb", condition = { env_not_set = ["GDB"] } }
GDB = { value = "/usr/local/opt/gdb/HEAD-a2c58332-aarch64/bin/aarch64-unknown-elf-gdb", condition = { env_not_set = ["GDB"] } }
# OpenOCD with JLink support
# (RTT patch from http://openocd.zylin.com/#/c/4055/11 has already been merged into main line)
@ -38,10 +42,12 @@ VOLUME = { value = "/Volumes/BOOT", condition = { env_not_set = ["VOLUME"] } }
#
CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
RUST_LIBS = "-Z build-std=compiler_builtins,core,alloc -Z build-std-features=compiler-builtins-mem"
TARGET_JSON = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/targets/${TARGET}.json"
PLATFORM_TARGET="--target=${TARGET_JSON} --features=${TARGET_FEATURES} ${RUST_LIBS}"
DEVICE_FEATURES = "noserial"
QEMU_FEATURES = "qemu"
QEMU_FEATURES = "qemu,rpi3"
OBJCOPY = "rust-objcopy" # Part of `cargo objcopy` in cargo-binutils
OBJCOPY_PARAMS = "--strip-all -O binary"
@ -54,9 +60,10 @@ QEMU_CONTAINER_CMD = "qemu-system-aarch64"
#
# Could additionally use -nographic to disable GUI -- this shall be useful for automated tests.
#
# -d in_asm,unimp,int
QEMU_OPTS = "-M raspi3 -d int -semihosting"
QEMU_SERIAL_OPTS = "-serial null -serial stdio"
# QEMU has renamed the RasPi machines since version 6.2.0, use just `raspi3` for previous versions.
QEMU_OPTS = "-M ${QEMU_MACHINE} -d int -semihosting"
QEMU_DISASM_OPTS = "-d in_asm,unimp,int"
QEMU_SERIAL_OPTS = "-serial pty -serial stdio"
QEMU_TESTS_OPTS = "-nographic"
# For gdb connection:
# - if this is set, MUST have gdb attached for SYS_WRITE0 to work, otherwise QEMU will crash.
@ -65,8 +72,11 @@ QEMU_GDB_OPTS = "-gdb tcp::5555 -S"
GDB_CONNECT_FILE = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/${TARGET}/gdb-connect"
KERNEL_ELF = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/kernel8"
KERNEL_BIN = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/kernel8.img"
KERNEL_ELF = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/${TARGET}/release/nucleus"
KERNEL_BIN = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/nucleus.bin"
CHAINBOOT_SERIAL = "/dev/tty.SLAB_USBtoUART"
CHAINBOOT_BAUD = 115200
[tasks.default]
alias = "all"
@ -77,3 +87,113 @@ dependencies = ["kernel-binary"]
[tasks.modules]
command = "cargo"
args = ["modules", "tree"]
[tasks.build]
env = { "TARGET_FEATURES" = "${TARGET_BOARD}" }
command = "cargo"
args = ["build", "@@split(PLATFORM_TARGET, )", "--release"]
[tasks.build-qemu]
env = { "TARGET_FEATURES" = "${QEMU_FEATURES}" }
command = "cargo"
args = ["build", "@@split(PLATFORM_TARGET, )", "--release"]
[tasks.qemu-runner]
dependencies = ["build-qemu", "kernel-binary"]
env = { "TARGET_FEATURES" = "${QEMU_FEATURES}" }
script = [
"echo Run QEMU ${QEMU_OPTS} ${QEMU_RUNNER_OPTS} with ${KERNEL_BIN}",
"${QEMU} ${QEMU_OPTS} ${QEMU_RUNNER_OPTS} -dtb ${TARGET_DTB} -kernel ${KERNEL_BIN}"
]
[tasks.expand]
env = { "TARGET_FEATURES" = "" }
command = "cargo"
args = ["expand", "@@split(PLATFORM_TARGET, )", "--release"]
[tasks.test]
env = { "TARGET_FEATURES" = "${QEMU_FEATURES}" }
command = "cargo"
args = ["test", "@@split(PLATFORM_TARGET, )"]
[tasks.docs]
env = { "TARGET_FEATURES" = "" }
command = "cargo"
args = ["doc", "--open", "--no-deps", "@@split(PLATFORM_TARGET, )"]
[tasks.clippy]
env = { "TARGET_FEATURES" = "rpi3", "CLIPPY_FEATURES" = { value = "--features=${CLIPPY_FEATURES}", condition = { env_set = ["CLIPPY_FEATURES"] } } }
command = "cargo"
args = ["clippy", "@@split(PLATFORM_TARGET, )", "@@remove-empty(CLIPPY_FEATURES)", "--", "-D", "warnings"]
# These tasks are written in cargo-make's own script to make it portable across platforms (no `basename` on Windows)
[tasks.custom-binary]
env = { "BINARY_FILE" = "${BINARY_FILE}" }
script_runner = "@duckscript"
script = [
'''
binaryFile = basename ${BINARY_FILE}
outElf = set ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/${binaryFile}.elf
outBin = set ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/${binaryFile}.bin
cp ${BINARY_FILE} ${outElf}
exec --fail-on-error ${OBJCOPY} %{OBJCOPY_PARAMS} ${BINARY_FILE} ${outBin}
echo Copied ${binaryFile} to ${outElf}
echo Converted ${binaryFile} to ${outBin}
'''
]
install_crate = { crate_name = "cargo-binutils", binary = "rust-objcopy", test_arg = ["--help"] }
[tasks.test-binary]
env = { "BINARY_FILE" = "${CARGO_MAKE_TASK_ARGS}" }
run_task = "custom-binary"
[tasks.test-runner]
dependencies = ["test-binary"]
script_runner = "@duckscript"
script = [
'''
binaryFile = basename ${CARGO_MAKE_TASK_ARGS}
exec --fail-on-error ${QEMU} %{QEMU_OPTS} %{QEMU_TESTS_OPTS} -dtb ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/targets/bcm2710-rpi-3-b-plus.dtb -kernel ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/${binaryFile}.bin
'''
]
[tasks.gdb-config]
script_runner = "@duckscript"
script = [
'''
writefile ${GDB_CONNECT_FILE} "target extended-remote :5555\n"
appendfile ${GDB_CONNECT_FILE} "break 0x80000\n"
'''
]
[tasks.zellij-config]
dependencies = ["build-qemu", "kernel-binary"]
script_runner = "@duckscript"
env = { "ZELLIJ_CONFIG_FILE" = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/emulation/zellij-config.sh" }
script = [
'''
writefile ${ZELLIJ_CONFIG_FILE} "QEMU=${QEMU}\n"
appendfile ${ZELLIJ_CONFIG_FILE} "QEMU_OPTS=\"${QEMU_OPTS}\"\n"
appendfile ${ZELLIJ_CONFIG_FILE} "QEMU_RUNNER_OPTS=${QEMU_RUNNER_OPTS}\n"
appendfile ${ZELLIJ_CONFIG_FILE} "CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY=${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}\n"
appendfile ${ZELLIJ_CONFIG_FILE} "TARGET_DTB=${TARGET_DTB}\n"
appendfile ${ZELLIJ_CONFIG_FILE} "KERNEL_BIN=${KERNEL_BIN}\n"
'''
]
install_crate = { crate_name = "zellij", binary = "zellij", test_arg = ["--help"] }
[tasks.openocd]
script = [
"${OPENOCD} -f interface/jlink.cfg -f ../ocd/${TARGET_BOARD}_target.cfg"
]
[tasks.sdeject]
dependencies = ["sdcard"]
script = [
"diskutil unmount ${VOLUME}"
]
[tasks.chainboot]
dependencies = ["build", "kernel-binary"]
command = "echo"
args = ["\n***===***\n", "Run the following command in your terminal:\n", " ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/debug/chainofcommand ${CHAINBOOT_SERIAL} ${CHAINBOOT_BAUD} --kernel ${KERNEL_BIN}\n", "***===***\n\n"]

View File

@ -2,7 +2,7 @@
## About kernel
Vesper is a capability-based single-address-space exokernel, it tries to remain small and secure. To achieve this, kernel functionality is extremely limited - it provides only address space isolation and IPC, after bootup kernel does not allocate any memory itself.
Vesper is a capability-based single-address-space nanokernel, it tries to remain small and secure. To achieve this, kernel functionality is extremely limited - it provides only address space isolation and IPC, after bootup kernel does not allocate any memory itself.
Exokernel's distinctive trait is that it provides mechanisms but not policies. Vesper tries to move as many policy decisions as possible to the library OS.
@ -94,6 +94,14 @@ You need to have [Hopper](https://hopperapp.com) and hopperv4 cli helper install
just disasm
```
### To see other available commands
```
just
```
It will list all just commands with their short descriptions.
## Development flow
`mainline`, `develop` and `released` branches:
@ -117,8 +125,6 @@ Various references from [OSDev Wiki](https://wiki.osdev.org/Raspberry_Pi_Bare_Bo
![Build](https://github.com/metta-systems/vesper/workflows/Build/badge.svg)
![License](https://raster.shields.io/badge/license-BlueOak%20with%20restrictions-blue.png)
[![Dependency Status](https://deps.rs/repo/github/metta-systems/vesper/status.svg)](https://deps.rs/repo/github/metta-systems/vesper)
[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/metta-systems/vesper)
@ -134,10 +140,6 @@ Individual files contain the following tag instead of the full license text.
This enables machine processing of license information based on the SPDX
License Identifiers that are here available: http://spdx.org/licenses/
@todo http://copyfree.org/support/community
-- copyfree
-- no CoC approach of that community
----
For more information please re-read.

39
bin/chainboot/Cargo.toml Normal file
View File

@ -0,0 +1,39 @@
[package]
name = "chainboot"
version = "0.0.1"
authors = ["Berkus Decker <berkus+vesper@metta.systems>"]
description = "Chain boot loader"
license = "BlueOak-1.0.0"
categories = ["no-std", "embedded", "os"]
publish = false
edition = "2021"
[badges]
maintenance = { status = "experimental" }
[features]
default = ["asm"]
# Build for running under QEMU with semihosting, so various halt/reboot options would for example quit QEMU instead.
qemu = ["machine/qemu"]
# Build for debugging it over JTAG/SWD connection - halts on first non-startup function start.
jtag = ["machine/jtag"]
# Dummy feature, ignored in this crate.
noserial = []
# Startup relocation code is implemented in assembly
asm = []
# Mutually exclusive features to choose a target board
rpi3 = ["machine/rpi3"]
rpi4 = ["machine/rpi4"]
[dependencies]
machine = { path = "../../machine" }
r0 = "1.0"
cortex-a = "7.0"
tock-registers = "0.7"
ux = { version = "0.1", default-features = false }
usize_conversions = "0.2"
bit_field = "0.10"
bitflags = "1.3"
cfg-if = "1.0"
snafu = { version = "0.7", default-features = false }
seahash = "4.1"

View File

@ -0,0 +1,52 @@
[env]
CHAINBOOT_ELF = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/${TARGET}/release/chainboot"
CHAINBOOT_BIN = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/chainboot.bin"
[tasks.kernel-binary]
env = { "BINARY_FILE" = "${CHAINBOOT_ELF}" }
run_task = "custom-binary"
[tasks.hopper]
disabled = true
[tasks.zellij-nucleus]
disabled = true
[tasks.zellij-cb]
env = { "KERNEL_BIN" = "${CHAINBOOT_BIN}", "QEMU_OPTS" = "${QEMU_OPTS} ${QEMU_DISASM_OPTS}" }
run_task = "zellij-config"
[tasks.zellij-cb-gdb]
env = { "KERNEL_BIN" = "${CHAINBOOT_BIN}", "QEMU_OPTS" = "${QEMU_OPTS} ${QEMU_DISASM_OPTS} ${QEMU_GDB_OPTS}", "TARGET_BOARD" = "rpi3", "TARGET_DTB" = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/targets/bcm2710-rpi-3-b-plus.dtb" }
run_task = "zellij-config"
[tasks.qemu]
disabled = true
[tasks.qemu-cb]
env = { "QEMU_RUNNER_OPTS" = "${QEMU_DISASM_OPTS} -serial pty", "KERNEL_BIN" = "${CHAINBOOT_BIN}", "TARGET_DTB" = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/targets/bcm2710-rpi-3-b-plus.dtb" }
extend = "qemu-runner"
[tasks.gdb]
disabled = true
[tasks.gdb-cb]
dependencies = ["build", "kernel-binary", "gdb-config"]
env = { "RUST_GDB" = "${GDB}" }
script = [
"rust-gdb -x ${GDB_CONNECT_FILE} ${CHAINBOOT_ELF}"
]
[tasks.sdcard]
dependencies = ["build", "kernel-binary"]
script_runner = "@duckscript"
script = [
'''
kernelImage = set "chain_boot_rpi4.img"
cp ${CHAINBOOT_BIN} ${VOLUME}/${kernelImage}
echo "Copied chainboot to ${VOLUME}/${kernelImage}"
'''
]
[tasks.cb-eject]
dependencies = ["sdeject"]

6
bin/chainboot/build.rs Normal file
View File

@ -0,0 +1,6 @@
const LINKER_SCRIPT: &str = "bin/chainboot/src/link.ld";
fn main() {
println!("cargo:rerun-if-changed={}", LINKER_SCRIPT);
println!("cargo:rustc-link-arg=--script={}", LINKER_SCRIPT);
}

68
bin/chainboot/src/boot.rs Normal file
View File

@ -0,0 +1,68 @@
// Assembly counterpart to this file.
#[cfg(feature = "asm")]
core::arch::global_asm!(include_str!("boot.s"));
// This is quite impossible - the linker constants are resolved to fully constant offsets in asm
// version, but are image-relative symbols in rust, and I see no way to force it otherwise.
#[no_mangle]
#[link_section = ".text._start"]
#[cfg(not(feature = "asm"))]
pub unsafe extern "C" fn _start() -> ! {
use {
cortex_a::registers::{MPIDR_EL1, SP},
machine::endless_sleep,
tock_registers::interfaces::{Readable, Writeable},
};
const CORE_0: u64 = 0;
const CORE_MASK: u64 = 0x3;
if CORE_0 == MPIDR_EL1.get() & CORE_MASK {
// if not core0, infinitely wait for events
endless_sleep()
}
// These are a problem, because they are not interpreted as constants here.
// Subsequently, this code tries to read values from not-yet-existing data locations.
extern "C" {
// Boundaries of the .bss section, provided by the linker script
static mut __bss_start: u64;
static mut __bss_end_exclusive: u64;
// Load address of the kernel binary
static mut __binary_nonzero_lma: u64;
// Address to relocate to and image size
static mut __binary_nonzero_vma: u64;
static mut __binary_nonzero_vma_end_exclusive: u64;
// Stack top
static mut __boot_core_stack_end_exclusive: u64;
}
// Set stack pointer.
SP.set(&mut __boot_core_stack_end_exclusive as *mut u64 as u64);
// Zeroes the .bss section
r0::zero_bss(&mut __bss_start, &mut __bss_end_exclusive);
// Relocate the code
core::ptr::copy_nonoverlapping(
&mut __binary_nonzero_lma as *const u64,
&mut __binary_nonzero_vma as *mut u64,
(&mut __binary_nonzero_vma_end_exclusive as *mut u64 as u64
- &mut __binary_nonzero_vma as *mut u64 as u64) as usize,
);
_start_rust();
}
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
/// The Rust entry of the `kernel` binary.
///
/// The function is called from the assembly `_start` function, keep it to support "asm" feature.
#[no_mangle]
#[inline(always)]
pub unsafe fn _start_rust(max_kernel_size: u64) -> ! {
crate::kernel_init(max_kernel_size)
}

93
bin/chainboot/src/boot.s Normal file
View File

@ -0,0 +1,93 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2021 Andre Richter <andre.o.richter@gmail.com>
// Modifications
// Copyright (c) 2021- Berkus <berkus+github@metta.systems>
//--------------------------------------------------------------------------------------------------
// Definitions
//--------------------------------------------------------------------------------------------------
// Load the address of a symbol into a register, PC-relative.
//
// The symbol must lie within +/- 4 GiB of the Program Counter.
//
// # Resources
//
// - https://sourceware.org/binutils/docs-2.36/as/AArch64_002dRelocations.html
.macro ADR_REL register, symbol
adrp \register, \symbol
add \register, \register, #:lo12:\symbol
.endm
// Load the address of a symbol into a register, absolute.
//
// # Resources
//
// - https://sourceware.org/binutils/docs-2.36/as/AArch64_002dRelocations.html
.macro ADR_ABS register, symbol
movz \register, #:abs_g2:\symbol
movk \register, #:abs_g1_nc:\symbol
movk \register, #:abs_g0_nc:\symbol
.endm
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
.section .text._start
//------------------------------------------------------------------------------
// fn _start()
//------------------------------------------------------------------------------
_start:
// Only proceed on the boot core. Park it otherwise.
mrs x1, MPIDR_EL1
and x1, x1, 0b11 // core id mask
cmp x1, 0 // boot core id
b.ne .L_parking_loop
// If execution reaches here, it is the boot core.
// Initialize bss.
ADR_ABS x0, __bss_start
ADR_ABS x1, __bss_end_exclusive
.L_bss_init_loop:
cmp x0, x1
b.eq .L_relocate_binary
stp xzr, xzr, [x0], #16
b .L_bss_init_loop
// Next, relocate the binary.
.L_relocate_binary:
ADR_REL x0, __binary_nonzero_lma // The address the binary got loaded to.
ADR_ABS x1, __binary_nonzero_vma // The address the binary was linked to.
ADR_ABS x2, __binary_nonzero_vma_end_exclusive
sub x4, x1, x0 // Get difference between vma and lma as max size
.L_copy_loop:
ldr x3, [x0], #8
str x3, [x1], #8
cmp x1, x2
b.lo .L_copy_loop
// Prepare the jump to Rust code.
// Set the stack pointer.
ADR_ABS x0, __rpi_phys_binary_load_addr
mov sp, x0
// Pass maximum kernel size as an argument to Rust init function.
mov x0, x4
// Jump to the relocated Rust code.
ADR_ABS x1, _start_rust
br x1
// Infinitely wait for events (aka "park the core").
.L_parking_loop:
wfe
b .L_parking_loop
.size _start, . - _start
.type _start, function
.global _start

98
bin/chainboot/src/link.ld Normal file
View File

@ -0,0 +1,98 @@
/* SPDX-License-Identifier: MIT OR Apache-2.0
*
* Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com>
* Copyright (c) 2021- Berkus <berkus+github@metta.systems>
*/
/*
* Information from:
* [Output Section Address](https://sourceware.org/binutils/docs/ld/Output-Section-Address.html)
* [Output Section LMA](https://sourceware.org/binutils/docs/ld/Output-Section-LMA.html)
* [Output Section Attributes](https://sourceware.org/binutils/docs/ld/Output-Section-Attributes.html#Output-Section-Attributes)
*/
/* The physical address at which the the kernel binary will be loaded by the Raspberry's firmware */
__rpi_phys_binary_load_addr = 0x80000;
ENTRY(__rpi_phys_binary_load_addr)
/* Flags:
* 4 == R
* 5 == RX
* 6 == RW
*
* Segments are marked PT_LOAD below so that the ELF file provides virtual and physical addresses.
* It doesn't mean all of them need actually be loaded.
*/
PHDRS
{
segment_boot_core_stack PT_LOAD FLAGS(6);
segment_start_code PT_LOAD FLAGS(5);
segment_code PT_LOAD FLAGS(5);
segment_data PT_LOAD FLAGS(6);
}
SECTIONS
{
/***********************************************************************************************
* Boot Core Stack
***********************************************************************************************/
.boot_core_stack (NOLOAD) :
{
/* ^ */
/* | stack */
. += __rpi_phys_binary_load_addr; /* | growth */
/* | direction */
__boot_core_stack_end_exclusive = .; /* | */
} :segment_boot_core_stack
. = __rpi_phys_binary_load_addr;
.text :
{
KEEP(*(.text._start))
/* *(text.memcpy) -- only relevant for Rust relocator impl which is currently impossible */
} :segment_start_code
/* Align to 8 bytes, b/c relocating the binary is done in u64 chunks */
. = ALIGN(8);
__binary_nonzero_lma = .;
/* Set the link address to 32 MiB */
/* This dictates the max size of the loadable kernel. */
. += 0x2000000;
/***********************************************************************************************
* Code + RO Data + Global Offset Table
***********************************************************************************************/
__binary_nonzero_vma = .;
.text : AT (ADDR(.text) + SIZEOF(.text))
{
*(.text._start_rust) /* The Rust entry point */
/* *(text.memcpy) -- only relevant for Rust relocator impl which is currently impossible */
*(.text*) /* Everything else */
} :segment_code
.rodata : ALIGN(8) { *(.rodata*) } :segment_code
.got : ALIGN(8) { *(.got) } :segment_code
/***********************************************************************************************
* Data + BSS
***********************************************************************************************/
.data : { *(.data*) } :segment_data
/* Fill up to 8 bytes, b/c relocating the binary is done in u64 chunks */
. = ALIGN(8);
__binary_nonzero_vma_end_exclusive = .;
/* Section is zeroed in pairs of u64. Align start and end to 16 bytes */
.bss (NOLOAD) : ALIGN(16)
{
__bss_start = .;
*(.bss*);
. = ALIGN(16);
__bss_end_exclusive = .;
} :segment_data
}

155
bin/chainboot/src/main.rs Normal file
View File

@ -0,0 +1,155 @@
// Based on miniload by @andre-richter
#![feature(format_args_nl)]
#![feature(custom_test_frameworks)]
#![test_runner(machine::tests::test_runner)]
#![reexport_test_harness_main = "test_main"]
#![no_main]
#![no_std]
use {
core::{hash::Hasher, panic::PanicInfo},
cortex_a::asm::barrier,
machine::{
devices::SerialOps,
platform::rpi3::{gpio::GPIO, pl011_uart::PL011Uart, BcmHost},
print, println, CONSOLE,
},
seahash::SeaHasher,
};
mod boot;
/// Early init code.
///
/// # Safety
///
/// - Only a single core must be active and running this function.
/// - The init calls in this function must appear in the correct order.
#[inline(always)]
unsafe fn kernel_init(max_kernel_size: u64) -> ! {
#[cfg(feature = "jtag")]
machine::arch::jtag::wait_debugger();
let gpio = GPIO::default();
let uart = PL011Uart::default();
let uart = uart.prepare(&gpio).expect("What could go wrong?");
CONSOLE.lock(|c| {
// Move uart into the global CONSOLE.
c.replace_with(uart.into());
});
// println! is usable from here on.
// Transition from unsafe to safe.
kernel_main(max_kernel_size)
}
// https://onlineasciitools.com/convert-text-to-ascii-art (FIGlet) with `cricket` font
const LOGO: &str = r#"
__ __ __ __
.----| |--.---.-|__.-----| |--.-----.-----| |_
| __| | _ | | | _ | _ | _ | _|
|____|__|__|___._|__|__|__|_____|_____|_____|____|
"#;
fn read_u64() -> u64 {
CONSOLE.lock(|c| {
let mut val: u64 = u64::from(c.read_byte());
val |= u64::from(c.read_byte()) << 8;
val |= u64::from(c.read_byte()) << 16;
val |= u64::from(c.read_byte()) << 24;
val |= u64::from(c.read_byte()) << 32;
val |= u64::from(c.read_byte()) << 40;
val |= u64::from(c.read_byte()) << 48;
val |= u64::from(c.read_byte()) << 56;
val
})
}
/// The main function running after the early init.
#[inline(always)]
fn kernel_main(max_kernel_size: u64) -> ! {
#[cfg(test)]
test_main();
print!("{}", LOGO);
println!("{:>51}\n", BcmHost::board_name());
println!("[<<] Requesting kernel image...");
let kernel_addr: *mut u8 = BcmHost::kernel_load_address() as *mut u8;
loop {
CONSOLE.lock(|c| c.flush());
// Discard any spurious received characters before starting with the loader protocol.
CONSOLE.lock(|c| c.clear_rx());
// Notify `chainofcommand` to send the binary.
for _ in 0..3 {
CONSOLE.lock(|c| c.write_byte(3u8));
}
// Read the binary's size.
let size = read_u64();
// Check the size to fit RAM
if size > max_kernel_size {
println!("ERR Kernel image too big (over {} bytes)", max_kernel_size);
continue;
}
print!("OK");
// We use seahash, simple and with no_std implementation.
let mut hasher = SeaHasher::new();
// Read the kernel byte by byte.
for i in 0..size {
let val = CONSOLE.lock(|c| c.read_byte());
unsafe {
core::ptr::write_volatile(kernel_addr.offset(i as isize), val);
}
let written = unsafe { core::ptr::read_volatile(kernel_addr.offset(i as isize)) };
hasher.write_u8(written);
}
// Read the binary's checksum.
let checksum = read_u64();
let valid = hasher.finish() == checksum;
if !valid {
println!("ERR Kernel image checksum mismatch");
continue;
}
print!("OK");
break;
}
println!(
"[<<] Loaded! Executing the payload now from {:p}\n",
kernel_addr
);
CONSOLE.lock(|c| c.flush());
// Use black magic to create a function pointer.
let kernel: fn() -> ! = unsafe { core::mem::transmute(kernel_addr) };
// Force everything to complete before we jump.
unsafe { barrier::isb(barrier::SY) };
// Jump to loaded kernel!
kernel()
}
#[cfg(not(test))]
#[panic_handler]
fn panicked(info: &PanicInfo) -> ! {
machine::panic::handler(info)
}
#[cfg(test)]
#[panic_handler]
fn panicked(info: &PanicInfo) -> ! {
machine::panic::handler_for_tests(info)
}

View File

@ -0,0 +1,25 @@
[package]
name = "chainofcommand"
version = "0.0.1"
authors = ["Berkus Decker <berkus+vesper@metta.systems>"]
description = "Host server for chainboot"
license = "BlueOak-1.0.0"
categories = ["no-std", "embedded", "os"]
publish = false
edition = "2021"
[badges]
maintenance = { status = "experimental" }
[dependencies]
clap = "3.0"
seahash = "4.1"
anyhow = "1.0"
fehler = "1.0"
crossterm = { version = "0.23", features = ["event-stream"] }
tokio-serial = "5.4"
tokio = { version = "1.16", features = ["full"] }
futures = "0.3"
defer = "0.1"
tokio-util = { version = "0.7", features = ["codec"] }
bytes = "1.1"

View File

@ -0,0 +1,44 @@
[tasks.build]
command = "cargo"
args = ["build"]
[tasks.test]
command = "cargo"
args = ["test"]
[tasks.clippy]
command = "cargo"
args = ["clippy", "--", "-D", "warnings"]
[tasks.hopper]
disabled = true
[tasks.kernel-binary]
disabled = true
[tasks.zellij-nucleus]
disabled = true
[tasks.zellij-cb]
disabled = true
[tasks.zellij-cb-gdb]
disabled = true
[tasks.qemu]
disabled = true
[tasks.qemu-cb]
disabled = true
[tasks.sdcard]
disabled = true
[tasks.cb-eject]
disabled = true
[tasks.gdb]
disabled = true
[tasks.gdb-cb]
disabled = true

View File

@ -0,0 +1,436 @@
#![feature(trait_alias)]
use {
anyhow::{anyhow, Result},
bytes::Bytes,
clap::{App, AppSettings, Arg},
crossterm::{
cursor,
event::{Event, EventStream, KeyCode, KeyEvent, KeyModifiers},
execute, style, terminal,
tty::IsTty,
},
defer::defer,
futures::{future::FutureExt, StreamExt},
seahash::SeaHasher,
std::{
fs::File,
hash::Hasher,
io::{BufRead, BufReader},
path::Path,
time::Duration,
},
tokio::{io::AsyncReadExt, sync::mpsc},
tokio_serial::{SerialPortBuilderExt, SerialStream},
};
trait Writable = std::io::Write + Send;
trait ThePath = AsRef<Path> + std::fmt::Display + Clone + Sync + Send + 'static;
async fn expect(
to_console2: &mpsc::Sender<Vec<u8>>,
from_serial: &mut mpsc::Receiver<Vec<u8>>,
m: &str,
) -> Result<()> {
if let Some(buf) = from_serial.recv().await {
if buf.len() == m.len() && String::from_utf8_lossy(buf.as_ref()) == m {
return Ok(());
}
to_console2.send(buf).await?;
return Err(anyhow!("Failed to receive expected value"));
}
Err(anyhow!("Failed to receive expected value"))
}
async fn load_kernel<P>(to_console2: &mpsc::Sender<Vec<u8>>, kernel: P) -> Result<(File, u64)>
where
P: ThePath,
{
to_console2
.send("[>>] Loading kernel image\n".into())
.await?;
let kernel_file = match std::fs::File::open(kernel.clone()) {
Ok(file) => file,
Err(_) => return Err(anyhow!("Couldn't open kernel file {}", kernel)),
};
let kernel_size: u64 = kernel_file.metadata()?.len();
to_console2
.send(format!("[>>] .. {} ({} bytes)\n", kernel, kernel_size).into())
.await?;
Ok((kernel_file, kernel_size))
}
async fn send_kernel<P>(
to_console2: &mpsc::Sender<Vec<u8>>,
to_serial: &mpsc::Sender<Vec<u8>>,
from_serial: &mut mpsc::Receiver<Vec<u8>>,
kernel: P,
) -> Result<()>
where
P: ThePath,
{
let (kernel_file, kernel_size) = load_kernel(to_console2, kernel).await?;
to_console2.send("[>>] Sending image size\n".into()).await?;
to_serial.send(kernel_size.to_le_bytes().into()).await?;
// Wait for OK response
expect(to_console2, from_serial, "OK").await?;
to_console2
.send("[>>] Sending kernel image\n".into())
.await?;
let mut hasher = SeaHasher::new();
let mut reader = BufReader::with_capacity(1, kernel_file);
loop {
let length = {
let buf = reader.fill_buf()?;
to_serial.send(buf.into()).await?;
hasher.write(buf);
buf.len()
};
if length == 0 {
break;
}
reader.consume(length);
}
let hashed_value: u64 = hasher.finish();
to_console2
.send(format!("[>>] Sending image checksum {:x}\n", hashed_value).into())
.await?;
to_serial.send(hashed_value.to_le_bytes().into()).await?;
expect(to_console2, from_serial, "OK").await?;
Ok(())
}
// Async reading using Tokio: https://fasterthanli.me/articles/a-terminal-case-of-linux
async fn serial_loop(
mut port: tokio_serial::SerialStream,
to_console: mpsc::Sender<Vec<u8>>,
mut from_console: mpsc::Receiver<Vec<u8>>,
) -> Result<()> {
let mut buf = [0; 256];
loop {
tokio::select! {
// _ = poll_send => {},
Some(msg) = from_console.recv() => {
// debug!("serial write {} bytes", msg.len());
tokio::io::AsyncWriteExt::write_all(&mut port, msg.as_ref()).await?;
}
res = port.read(&mut buf) => {
match res {
Ok(0) => {
// info!("Serial <EOF>");
return Ok(());
}
Ok(n) => {
// debug!("Serial read {n} bytes.");
to_console.send(buf[0..n].to_owned()).await?;
}
Err(e) => {
// if e.kind() == ErrorKind::TimedOut {
// execute!(w, style::Print("\r\nTimeout: the serial device has been unplugged!"))?;
// } else {
// execute!(w, style::Print(format!("\r\nSerial Error: {:?}\r", e)))?;
// }
// break;
return Err(anyhow!(e));
}
}
}
}
}
}
async fn console_loop<P>(
to_console2: mpsc::Sender<Vec<u8>>,
mut from_internal: mpsc::Receiver<Vec<u8>>,
to_serial: mpsc::Sender<Vec<u8>>,
mut from_serial: mpsc::Receiver<Vec<u8>>,
kernel: P,
) -> Result<()>
where
P: ThePath,
{
let mut w = std::io::stdout();
let mut breaks = 0;
let mut event_reader = EventStream::new();
loop {
tokio::select! {
biased;
Some(received) = from_internal.recv() => {
for &x in &received[..] {
execute!(w, style::Print(format!("{}", x as char)))?;
}
w.flush()?;
}
Some(received) = from_serial.recv() => {
// execute!(w, cursor::MoveToNextLine(1), style::Print(format!("[>>] Received {} bytes from serial", from_serial.len())), cursor::MoveToNextLine(1))?;
for &x in &received[..] {
if x == 0x3 {
// execute!(w, cursor::MoveToNextLine(1), style::Print("[>>] Received a BREAK"), cursor::MoveToNextLine(1))?;
breaks += 1;
// Await for 3 consecutive \3 to start downloading
if breaks == 3 {
// execute!(w, cursor::MoveToNextLine(1), style::Print("[>>] Received 3 BREAKs"), cursor::MoveToNextLine(1))?;
breaks = 0;
send_kernel(&to_console2, &to_serial, &mut from_serial, kernel.clone()).await?;
to_console2.send("[>>] Send successful, pass-through\n".into()).await?;
}
} else {
while breaks > 0 {
execute!(w, style::Print(format!("{}", 3 as char)))?;
breaks -= 1;
}
execute!(w, style::Print(format!("{}", x as char)))?;
w.flush()?;
}
}
}
maybe_event = event_reader.next().fuse() => {
match maybe_event {
Some(Ok(Event::Key(key_event))) => {
if key_event.code == KeyCode::Char('c') && key_event.modifiers == KeyModifiers::CONTROL {
return Ok(());
}
if let Some(key) = handle_key_event(key_event) {
to_serial.send(key.to_vec()).await?;
// Local echo
execute!(w, style::Print(format!("{:?}", key)))?;
w.flush()?;
}
}
Some(Ok(_)) => {},
Some(Err(e)) => {
execute!(w, style::Print(format!("Console read error: {:?}\r", e)))?;
w.flush()?;
},
None => return Err(anyhow!("woops")),
}
}
}
}
}
async fn main_loop<P>(port: SerialStream, kernel: P) -> Result<()>
where
P: ThePath,
{
// read from serial -> to_console==>from_serial -> output to console
let (to_console, from_serial) = mpsc::channel(256);
let (to_console2, from_internal) = mpsc::channel(256);
// read from console -> to_serial==>from_console -> output to serial
let (to_serial, from_console) = mpsc::channel(256);
tokio::spawn(serial_loop(port, to_console.clone(), from_console));
console_loop(to_console2, from_internal, to_serial, from_serial, kernel).await
// TODO: framed
// rx_device -> serial_reader -> app
// app -> serial_writer -> serial_consumer -> (poll_send to drive) -> serial_sink -> tx_device
// let (rx_device, tx_device) = split(port);
// let mut serial_reader = FramedRead::new(rx_device, BytesCodec::new());
// let serial_sink = FramedWrite::new(tx_device, BytesCodec::new());
//
// let (serial_writer, serial_consumer) = mpsc::unbounded::<Bytes>();
// let mut poll_send = serial_consumer.map(Ok).forward(serial_sink);
}
// From remote_serial -- https://github.com/zhp-rs/remote_serial/ (Licensed under MIT License)
fn handle_key_event(key_event: KeyEvent) -> Option<Bytes> {
let mut buf = [0; 4];
let key_str: Option<&[u8]> = match key_event.code {
KeyCode::Backspace => Some(b"\x08"),
KeyCode::Enter => Some(b"\x0D"),
KeyCode::Left => Some(b"\x1b[D"),
KeyCode::Right => Some(b"\x1b[C"),
KeyCode::Home => Some(b"\x1b[H"),
KeyCode::End => Some(b"\x1b[F"),
KeyCode::Up => Some(b"\x1b[A"),
KeyCode::Down => Some(b"\x1b[B"),
KeyCode::Tab => Some(b"\x09"),
KeyCode::Delete => Some(b"\x1b[3~"),
KeyCode::Insert => Some(b"\x1b[2~"),
KeyCode::Esc => Some(b"\x1b"),
KeyCode::Char(ch) => {
if key_event.modifiers & KeyModifiers::CONTROL == KeyModifiers::CONTROL {
buf[0] = ch as u8;
if ('a'..='z').contains(&ch) || (ch == ' ') {
buf[0] &= 0x1f;
Some(&buf[0..1])
} else if ('4'..='7').contains(&ch) {
// crossterm returns Control-4 thru 7 for \x1c thru \x1f
buf[0] = (buf[0] + 8) & 0x1f;
Some(&buf[0..1])
} else {
Some(ch.encode_utf8(&mut buf).as_bytes())
}
} else {
Some(ch.encode_utf8(&mut buf).as_bytes())
}
}
_ => None,
};
key_str.map(Bytes::copy_from_slice)
}
// 1. connect to given serial port, e.g. /dev/ttyUSB23234
// 2. Await for \3\3\3 start signal, meanwhile pass-through all traffic to console
// 3. send selected kernel binary with checksum to the target
// 4. go to 2
#[tokio::main]
async fn main() -> Result<()> {
let matches = App::new("ChainOfCommand - command chainboot protocol")
.about("Use to send freshly built kernel to chainboot-compatible boot loader")
.setting(AppSettings::DisableVersionFlag)
.arg(
Arg::new("port")
.help("The device path to a serial port, e.g. /dev/ttyUSB0")
.required(true),
)
.arg(
Arg::new("baud")
.help("The baud rate to connect at")
.use_delimiter(false)
.required(true), // .validator(valid_baud),
)
.arg(
Arg::new("kernel")
.long("kernel")
.help("Path of the binary kernel image to send")
.takes_value(true)
.default_value("kernel8.img"),
)
.get_matches();
let port_name = matches.value_of("port").unwrap();
let baud_rate = matches.value_of("baud").unwrap().parse::<u32>().unwrap();
let kernel = matches.value_of("kernel").unwrap().to_owned();
// Check that STDIN is a proper tty
if !std::io::stdin().is_tty() {
panic!("Must have a TTY for stdin");
}
// Disable line buffering, local echo, etc.
terminal::enable_raw_mode()?;
defer(|| terminal::disable_raw_mode().unwrap_or(()));
let mut serial_toggle = false;
let mut stdout = std::io::stdout();
execute!(stdout, cursor::SavePosition)?;
loop {
execute!(
stdout,
cursor::RestorePosition,
style::Print("[>>] Opening serial port ")
)?;
// tokio_serial::new() creates a builder with 8N1 setup without flow control by default.
let port = tokio_serial::new(port_name, baud_rate).open_native_async();
if let Err(e) = port {
let cont = match e.kind {
tokio_serial::ErrorKind::NoDevice => true,
tokio_serial::ErrorKind::Io(e)
if e == std::io::ErrorKind::NotFound
|| e == std::io::ErrorKind::PermissionDenied =>
{
true
}
_ => false,
};
if cont {
execute!(
stdout,
cursor::RestorePosition,
style::Print(format!(
"[>>] Waiting for serial port {}\r",
if serial_toggle { "# " } else { " #" }
))
)?;
stdout.flush()?;
serial_toggle = !serial_toggle;
if crossterm::event::poll(Duration::from_millis(1000))? {
if let Event::Key(KeyEvent { code, modifiers }) = crossterm::event::read()? {
if code == KeyCode::Char('c') && modifiers == KeyModifiers::CONTROL {
return Ok(());
}
}
}
continue;
}
return Err(e.into());
}
execute!(
stdout,
style::Print("\n[>>] Waiting for handshake, pass-through"),
)?;
stdout.flush()?;
// Run in pass-through mode by default.
// Once we receive BREAK (0x3) three times, switch to kernel send mode and upload kernel,
// then switch back to pass-through mode.
// Input from STDIN should pass through to serial
// Input from serial should pass through to STDOUT
let port = port?;
if let Err(e) = main_loop(port, kernel.clone()).await {
execute!(stdout, style::Print(format!("\nError: {:?}\n", e)))?;
stdout.flush()?;
let cont = match e.downcast_ref::<std::io::Error>() {
Some(e)
if e.kind() == std::io::ErrorKind::NotFound
|| e.kind() == std::io::ErrorKind::PermissionDenied =>
{
true
}
_ => false,
} || matches!(e.downcast_ref::<tokio_serial::Error>(), Some(e) if e.kind == tokio_serial::ErrorKind::NoDevice)
|| matches!(
e.downcast_ref::<tokio::sync::mpsc::error::SendError<Vec<u8>>>(),
Some(_)
);
if !cont {
break;
}
} else {
// main_loop() returned Ok() we're good to finish
break;
}
execute!(stdout, cursor::SavePosition)?;
}
Ok(())
}

View File

@ -1,65 +1,7 @@
# Contributor Covenant Code of Conduct
This is our Code of Conduct and Ethics (the “Code”)
## Our Pledge
It is based on our values and aspirations (be good, innovate, work together, be part of the solution). It describes what we expect from the members of the community in three main rules:
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality,
personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Maintainers are responsible for clarifying the standards of acceptable behavior
and are expected to take appropriate and fair corrective action in response to
any instances of unacceptable behavior.
Maintainers have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, or to ban temporarily or permanently any
contributor for other behaviors that they deem inappropriate, threatening,
offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the Technical Advisory Board (TAB) at
<tab@metta.systems>. All complaints will be reviewed and
investigated and will result in a response that is deemed necessary and
appropriate to the circumstances. The TAB is obligated to maintain
confidentiality with regard to the reporter of an incident. Further details of
specific enforcement policies may be posted separately.
Maintainers who do not follow or enforce the Code of Conduct in good faith may
face temporary or permanent repercussions as determined by other members of the
projects leadership.
## Attribution
This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
1. Do the right thing. Always act with honesty, integrity, and reliability. Keep moral and ethical standards high.
2. Be nice. Treat people with dignity and respect, regardless of who they are and where they came from. Stay decent and courteous in all relationships.
3. Play fair. Dont cheat. Be careful to account for and balance the interests of all groups (developers, artists and designers, end users and the general public).

BIN
doc/bcm2711-peripherals.pdf Normal file

Binary file not shown.

View File

@ -53,12 +53,15 @@ Connecting TDI to pin 7 (GPIO4) did not work!
In config.txt:
```
# Set GPIO pins for JTAG debugger connection on all rpi models
enable_jtag_gpio=1
# Set GPIO pins for JTAG debugger connection on rpi3
gpio=22-27=a4
```
Quote from [official doc](https://www.raspberrypi.org/documentation/configuration/config-txt/gpio.md):
> Setting enable_jtag_gpio=1 selects Alt4 mode for GPIO pins 22-27, and sets up some internal SoC connections, thus enabling the JTAG interface for the ARM CPU. It works on all models of Raspberry Pi.
Alternatively, just specify this: (@todo verify this works with all alt4 pins)
```
enable_jtag_gpio=1
```
### Wire Connection between boards

View File

@ -1,4 +0,0 @@
# Using RPi4 as SWD bitbanging host
[Use SPI for better reliability](https://lupyuen.github.io/articles/openocd-on-raspberry-pi-better-with-swd-on-spi)

1
emulation/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
zellij-config.sh

View File

@ -0,0 +1,12 @@
#########################################################
# Welcome to the RasPi3 Emulation with #
# MiniUart and PL011UART #
# #
# #
# 1. Use the mouse to select different panes #
# and interact with the UARTs. #
# #
# 2. To quit the emulation: #
# 2.1 Select the pane in which QEMU runs (this one). #
# 2.2 CTRL-C to close qemu. #
#########################################################

39
emulation/layout.zellij Normal file
View File

@ -0,0 +1,39 @@
---
template:
direction: Horizontal
parts:
- direction: Vertical
borderless: true
split_size:
Fixed: 1
run:
plugin:
location: "zellij:tab-bar"
- direction: Vertical
body: true
tabs:
- direction: Vertical
parts:
- direction: Horizontal
borderless: true
run:
command:
cmd: "bash"
args: ["-c", "bash emulation/qemu_multi_uart.sh"]
- direction: Horizontal
parts:
- direction: Vertical
split_size:
Percent: 30
run:
command:
cmd: "bash"
args: ["-c", "clear; echo -e \"\\033]0;MiniUart\\007\"; bash /dev/ptmx FIRST=1"]
- direction: Vertical
split_size:
Percent: 70
run:
command:
cmd: "bash"
args: ["-c", "clear; echo -e \"\\033]0;PL011 Uart\\007\"; bash /dev/ptmx SECOND=1"]

35
emulation/qemu_multi_uart.sh Executable file
View File

@ -0,0 +1,35 @@
#!/bin/bash
#
# MIT License
#
# Copyright (c) 2018-2019 Berkus Decker <berkus+vesper@metta.systems>
# Based on QEMU runner example by Andre Richter <andre.o.richter@gmail.com>
echo -e "\033]0;QEMU\007"
clear
cat emulation/instructions.txt
# Generated by `cargo make zellij-config`
source emulation/zellij-config.sh
# Let the two other serials boot up
sleep 1
FIRST_TTY=$(ps a | grep -e [F]IRST | awk '{ print $2 }')
SECOND_TTY=$(ps a | grep -e [S]ECOND | awk '{ print $2 }')
FIRST_PID=$(ps a | grep -e [F]IRST | awk '{ print $1 }')
SECOND_PID=$(ps a | grep -e [S]ECOND | awk '{ print $1 }')
function cleanup() {
# Drop ptmx shells
kill $FIRST_PID
kill $SECOND_PID
}
trap cleanup EXIT
echo "=== Running QEMU with MiniUart ${FIRST_TTY} and PL011 Uart ${SECOND_TTY} ==="
${QEMU} ${QEMU_OPTS} ${QEMU_RUNNER_OPTS} -dtb ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/targets/bcm2710-rpi-3-b-plus.dtb -kernel ${KERNEL_BIN} -serial /dev/tty${SECOND_TTY} -serial /dev/tty${FIRST_TTY}

View File

@ -0,0 +1,5 @@
QEMU=
QEMU_OPTS=
QEMU_RUNNER_OPTS=
CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY=
KERNEL_BIN=

40
machine/Cargo.toml Normal file
View File

@ -0,0 +1,40 @@
[package]
name = "machine"
version = "0.0.1"
authors = ["Berkus Decker <berkus+vesper@metta.systems>"]
description = "Vesper nanokernel shared code library, useful also for the chainboot loader."
documentation = "https://docs.metta.systems/vesper"
homepage = "https://github.com/metta-systems/vesper"
repository = "https://github.com/metta-systems/vesper"
readme = "README.md"
license = "BlueOak-1.0.0"
categories = ["no-std", "embedded", "os"]
publish = false
edition = "2021"
[badges]
maintenance = { status = "experimental" }
[features]
default = []
noserial = []
# Enable JTAG debugging of kernel - enable jtag helpers and
# block waiting for JTAG probe attach at the start of kernel main.
jtag = []
# Build for running under QEMU with semihosting, so various halt/reboot options would for example quit QEMU instead.
qemu = ["rpi3"]
# Mutually exclusive features to choose a target board
rpi3 = []
rpi4 = []
[dependencies]
r0 = "1.0"
qemu-exit = "3.0"
cortex-a = "7.0"
tock-registers = "0.7"
ux = { version = "0.1", default-features = false }
usize_conversions = "0.2"
bit_field = "0.10"
bitflags = "1.3"
cfg-if = "1.0"
snafu = { version = "0.7", default-features = false }

View File

@ -24,6 +24,7 @@ macro_rules! entry {
/// # Safety
/// Only type-checks!
#[export_name = "main"]
#[inline(always)]
pub unsafe fn __main() -> ! {
// type check the given path
let f: fn() -> ! = $path;
@ -44,6 +45,7 @@ macro_rules! entry {
unsafe fn reset() -> ! {
extern "C" {
// Boundaries of the .bss section, provided by the linker script
// The type, `u64`, indicates that the memory is 8-byte aligned
static mut __BSS_START: u64;
static mut __BSS_END: u64;
}
@ -66,7 +68,7 @@ unsafe fn reset() -> ! {
// #[inline]
// fn enable_armv6_unaligned_access() {
// unsafe {
// asm!(
// core::arch::asm!(
// "mrc p15, 0, {u}, c1, c0, 0",
// "or {u}, {u}, {CR_U}",
// "mcr p15, 0, {u}, c1, c0, 0",

View File

@ -1,16 +1,17 @@
//! JTAG helper functions.
use cortex_a::asm;
use {
core::ptr::{read_volatile, write_volatile},
cortex_a::asm,
};
#[no_mangle]
static mut WAIT_FLAG: bool = true;
/// Wait for debugger to attach.
/// Then in gdb issue `> set var *(&WAIT_FLAG) = 0`
/// from inside this function's frame to contiue running.
/// from inside this function's frame to continue running.
pub fn wait_debugger() {
use core::ptr::{read_volatile, write_volatile};
while unsafe { read_volatile(&WAIT_FLAG) } {
asm::nop();
}

View File

@ -0,0 +1,11 @@
# Memory Configuration
The types VirtAddr and PhysAddr are representing the addresses before and after the mapping in the MMU.
Page table types must represent pages of differing sizes.
For every entry in the MMU page table we should be able to receive a proper page type - e.g. Invalid, further page table, or
a specific-size page.
----
For more information please re-read.

View File

@ -2,4 +2,9 @@
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
pub mod rpi3;
mod asid;
mod phys_addr;
mod virt_addr;
pub use {asid::*, phys_addr::*, virt_addr::*};

View File

@ -4,10 +4,7 @@
*/
use {
crate::{
memory::VirtAddr,
mm::{align_down, align_up},
},
crate::mm::{align_down, align_up},
bit_field::BitField,
core::{
convert::{From, Into},
@ -76,9 +73,10 @@ impl PhysAddr {
/// Aligns the physical address upwards to the given alignment.
///
/// See the `align_up` function for more information.
#[must_use]
pub fn aligned_up<U>(self, align: U) -> Self
where
U: Into<usize>,
U: Into<u64>,
{
PhysAddr(align_up(self.0, align.into()))
}
@ -86,9 +84,10 @@ impl PhysAddr {
/// Aligns the physical address downwards to the given alignment.
///
/// See the `align_down` function for more information.
#[must_use]
pub fn aligned_down<U>(self, align: U) -> Self
where
U: Into<usize>,
U: Into<u64>,
{
PhysAddr(align_down(self.0, align.into()))
}
@ -96,17 +95,10 @@ impl PhysAddr {
/// Checks whether the physical address has the demanded alignment.
pub fn is_aligned<U>(self, align: U) -> bool
where
U: Into<usize>,
U: Into<u64>,
{
self.aligned_down(align) == self
}
/// Convert physical memory address into a kernel virtual address.
pub fn user_to_kernel(&self) -> VirtAddr {
use super::PHYSICAL_MEMORY_OFFSET;
assert!(self.0 < !PHYSICAL_MEMORY_OFFSET); // Can't have phys address over 1GiB then
VirtAddr::new(self.0 + PHYSICAL_MEMORY_OFFSET)
}
}
impl fmt::Debug for PhysAddr {

View File

@ -4,10 +4,7 @@
*/
use {
crate::{
memory::PhysAddr,
mm::{align_down, align_up},
},
crate::mm::{align_down, align_up},
bit_field::BitField,
core::{
convert::{From, Into, TryInto},
@ -37,7 +34,7 @@ pub struct VirtAddr(u64);
/// a valid sign extension and are not null either. So automatic sign extension would have
/// overwritten possibly meaningful bits. This likely indicates a bug, for example an invalid
/// address calculation.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Debug)]
pub struct VirtAddrNotValid(u64);
impl VirtAddr {
@ -111,9 +108,10 @@ impl VirtAddr {
/// Aligns the virtual address upwards to the given alignment.
///
/// See the `align_up` free function for more information.
#[must_use]
pub fn aligned_up<U>(self, align: U) -> Self
where
U: Into<usize>,
U: Into<u64>,
{
VirtAddr(align_up(self.0, align.into()))
}
@ -121,9 +119,10 @@ impl VirtAddr {
/// Aligns the virtual address downwards to the given alignment.
///
/// See the `align_down` free function for more information.
#[must_use]
pub fn aligned_down<U>(self, align: U) -> Self
where
U: Into<usize>,
U: Into<u64>,
{
VirtAddr(align_down(self.0, align.into()))
}
@ -131,7 +130,7 @@ impl VirtAddr {
/// Checks whether the virtual address has the demanded alignment.
pub fn is_aligned<U>(self, align: U) -> bool
where
U: Into<usize>,
U: Into<u64>,
{
self.aligned_down(align) == self
}
@ -161,13 +160,6 @@ impl VirtAddr {
pub fn l0_index(&self) -> u9 {
u9::new(((self.0 >> 12 >> 9 >> 9 >> 9) & 0o777).try_into().unwrap())
}
/// Convert kernel-space virtual address into a physical memory address.
pub fn kernel_to_user(&self) -> PhysAddr {
use super::PHYSICAL_MEMORY_OFFSET;
assert!(self.0 > PHYSICAL_MEMORY_OFFSET);
PhysAddr::new(self.0 - PHYSICAL_MEMORY_OFFSET)
}
}
impl fmt::Debug for VirtAddr {

View File

@ -0,0 +1,713 @@
/*
* SPDX-License-Identifier: MIT OR BlueOak-1.0.0
* Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
* Original code distributed under MIT, additional changes are under BlueOak-1.0.0
*/
//! MMU initialisation.
//!
//! Paging is mostly based on [previous version](https://os.phil-opp.com/page-tables/) of
//! Phil Opp's [paging guide](https://os.phil-opp.com/paging-implementation/) and
//! [ARMv8 ARM memory addressing](https://static.docs.arm.com/100940/0100/armv8_a_address%20translation_100940_0100_en.pdf).
use {
crate::{
arch::aarch64::memory::{get_virt_addr_properties, AttributeFields},
println,
},
core::{
marker::PhantomData,
ops::{Index, IndexMut},
},
cortex_a::{
asm::barrier,
registers::{ID_AA64MMFR0_EL1, SCTLR_EL1, TCR_EL1, TTBR0_EL1},
},
tock_registers::{
fields::FieldValue,
interfaces::{ReadWriteable, Readable, Writeable},
register_bitfields,
},
};
mod mair {
use {cortex_a::registers::MAIR_EL1, tock_registers::interfaces::Writeable};
/// Setup function for the MAIR_EL1 register.
pub fn set_up() {
// Define the three memory types that we will map. Normal DRAM, Uncached and device.
MAIR_EL1.write(
// Attribute 2 -- Device Memory
MAIR_EL1::Attr2_Device::nonGathering_nonReordering_EarlyWriteAck
// Attribute 1 -- Non Cacheable DRAM
+ MAIR_EL1::Attr1_Normal_Outer::NonCacheable
+ MAIR_EL1::Attr1_Normal_Inner::NonCacheable
// Attribute 0 -- Regular Cacheable
+ MAIR_EL1::Attr0_Normal_Outer::WriteBack_NonTransient_ReadWriteAlloc
+ MAIR_EL1::Attr0_Normal_Inner::WriteBack_NonTransient_ReadWriteAlloc,
);
}
// Three descriptive consts for indexing into the correct MAIR_EL1 attributes.
pub mod attr {
pub const NORMAL: u64 = 0;
pub const NORMAL_NON_CACHEABLE: u64 = 1;
pub const DEVICE_NGNRE: u64 = 2;
// DEVICE_GRE
// DEVICE_NGNRNE
}
}
/// Parse the ID_AA64MMFR0_EL1 register for runtime information about supported MMU features.
/// Print the current state of TCR register.
pub fn print_features() {
// use crate::cortex_a::regs::RegisterReadWrite;
let sctlr = SCTLR_EL1.extract();
if let Some(SCTLR_EL1::M::Value::Enable) = sctlr.read_as_enum(SCTLR_EL1::M) {
println!("[i] MMU currently enabled");
}
if let Some(SCTLR_EL1::I::Value::Cacheable) = sctlr.read_as_enum(SCTLR_EL1::I) {
println!("[i] MMU I-cache enabled");
}
if let Some(SCTLR_EL1::C::Value::Cacheable) = sctlr.read_as_enum(SCTLR_EL1::C) {
println!("[i] MMU D-cache enabled");
}
let mmfr = ID_AA64MMFR0_EL1.extract();
if let Some(ID_AA64MMFR0_EL1::TGran4::Value::Supported) =
mmfr.read_as_enum(ID_AA64MMFR0_EL1::TGran4)
{
println!("[i] MMU: 4 KiB granule supported!");
}
if let Some(ID_AA64MMFR0_EL1::TGran16::Value::Supported) =
mmfr.read_as_enum(ID_AA64MMFR0_EL1::TGran16)
{
println!("[i] MMU: 16 KiB granule supported!");
}
if let Some(ID_AA64MMFR0_EL1::TGran64::Value::Supported) =
mmfr.read_as_enum(ID_AA64MMFR0_EL1::TGran64)
{
println!("[i] MMU: 64 KiB granule supported!");
}
match mmfr.read_as_enum(ID_AA64MMFR0_EL1::ASIDBits) {
Some(ID_AA64MMFR0_EL1::ASIDBits::Value::Bits_16) => {
println!("[i] MMU: 16 bit ASIDs supported!")
}
Some(ID_AA64MMFR0_EL1::ASIDBits::Value::Bits_8) => {
println!("[i] MMU: 8 bit ASIDs supported!")
}
_ => println!("[i] MMU: Invalid ASID bits specified!"),
}
match mmfr.read_as_enum(ID_AA64MMFR0_EL1::PARange) {
Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_32) => {
println!("[i] MMU: Up to 32 Bit physical address range supported!")
}
Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_36) => {
println!("[i] MMU: Up to 36 Bit physical address range supported!")
}
Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_40) => {
println!("[i] MMU: Up to 40 Bit physical address range supported!")
}
Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_42) => {
println!("[i] MMU: Up to 42 Bit physical address range supported!")
}
Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_44) => {
println!("[i] MMU: Up to 44 Bit physical address range supported!")
}
Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_48) => {
println!("[i] MMU: Up to 48 Bit physical address range supported!")
}
Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_52) => {
println!("[i] MMU: Up to 52 Bit physical address range supported!")
}
_ => println!("[i] MMU: Invalid PARange specified!"),
}
let tcr = TCR_EL1.extract();
match tcr.read_as_enum(TCR_EL1::IPS) {
Some(TCR_EL1::IPS::Value::Bits_32) => {
println!("[i] MMU: 32 Bit intermediate physical address size supported!")
}
Some(TCR_EL1::IPS::Value::Bits_36) => {
println!("[i] MMU: 36 Bit intermediate physical address size supported!")
}
Some(TCR_EL1::IPS::Value::Bits_40) => {
println!("[i] MMU: 40 Bit intermediate physical address size supported!")
}
Some(TCR_EL1::IPS::Value::Bits_42) => {
println!("[i] MMU: 42 Bit intermediate physical address size supported!")
}
Some(TCR_EL1::IPS::Value::Bits_44) => {
println!("[i] MMU: 44 Bit intermediate physical address size supported!")
}
Some(TCR_EL1::IPS::Value::Bits_48) => {
println!("[i] MMU: 48 Bit intermediate physical address size supported!")
}
Some(TCR_EL1::IPS::Value::Bits_52) => {
println!("[i] MMU: 52 Bit intermediate physical address size supported!")
}
_ => println!("[i] MMU: Invalid IPS specified!"),
}
match tcr.read_as_enum(TCR_EL1::TG0) {
Some(TCR_EL1::TG0::Value::KiB_4) => println!("[i] MMU: TTBR0 4 KiB granule active!"),
Some(TCR_EL1::TG0::Value::KiB_16) => println!("[i] MMU: TTBR0 16 KiB granule active!"),
Some(TCR_EL1::TG0::Value::KiB_64) => println!("[i] MMU: TTBR0 64 KiB granule active!"),
_ => println!("[i] MMU: Invalid TTBR0 granule size specified!"),
}
let t0sz = tcr.read(TCR_EL1::T0SZ);
println!("[i] MMU: T0sz = 64-{} = {} bits", t0sz, 64 - t0sz);
match tcr.read_as_enum(TCR_EL1::TG1) {
Some(TCR_EL1::TG1::Value::KiB_4) => println!("[i] MMU: TTBR1 4 KiB granule active!"),
Some(TCR_EL1::TG1::Value::KiB_16) => println!("[i] MMU: TTBR1 16 KiB granule active!"),
Some(TCR_EL1::TG1::Value::KiB_64) => println!("[i] MMU: TTBR1 64 KiB granule active!"),
_ => println!("[i] MMU: Invalid TTBR1 granule size specified!"),
}
let t1sz = tcr.read(TCR_EL1::T1SZ);
println!("[i] MMU: T1sz = 64-{} = {} bits", t1sz, 64 - t1sz);
}
register_bitfields! {
u64,
// AArch64 Reference Manual page 2150, D5-2445
STAGE1_DESCRIPTOR [
// In table descriptors
NSTable_EL3 OFFSET(63) NUMBITS(1) [],
/// Access Permissions for subsequent tables
APTable OFFSET(61) NUMBITS(2) [
RW_EL1 = 0b00,
RW_EL1_EL0 = 0b01,
RO_EL1 = 0b10,
RO_EL1_EL0 = 0b11
],
// User execute-never for subsequent tables
UXNTable OFFSET(60) NUMBITS(1) [
Execute = 0,
NeverExecute = 1
],
/// Privileged execute-never for subsequent tables
PXNTable OFFSET(59) NUMBITS(1) [
Execute = 0,
NeverExecute = 1
],
// In block descriptors
// OS-specific data
OSData OFFSET(55) NUMBITS(4) [],
// User execute-never
UXN OFFSET(54) NUMBITS(1) [
Execute = 0,
NeverExecute = 1
],
/// Privileged execute-never
PXN OFFSET(53) NUMBITS(1) [
Execute = 0,
NeverExecute = 1
],
// @fixme ?? where is this described
CONTIGUOUS OFFSET(52) NUMBITS(1) [
False = 0,
True = 1
],
// @fixme ?? where is this described
DIRTY OFFSET(51) NUMBITS(1) [
False = 0,
True = 1
],
/// Various address fields, depending on use case
LVL2_OUTPUT_ADDR_4KiB OFFSET(21) NUMBITS(27) [], // [47:21]
NEXT_LVL_TABLE_ADDR_4KiB OFFSET(12) NUMBITS(36) [], // [47:12]
// @fixme ?? where is this described
NON_GLOBAL OFFSET(11) NUMBITS(1) [
False = 0,
True = 1
],
/// Access flag
AF OFFSET(10) NUMBITS(1) [
False = 0,
True = 1
],
/// Shareability field
SH OFFSET(8) NUMBITS(2) [
OuterShareable = 0b10,
InnerShareable = 0b11
],
/// Access Permissions
AP OFFSET(6) NUMBITS(2) [
RW_EL1 = 0b00,
RW_EL1_EL0 = 0b01,
RO_EL1 = 0b10,
RO_EL1_EL0 = 0b11
],
NS_EL3 OFFSET(5) NUMBITS(1) [],
/// Memory attributes index into the MAIR_EL1 register
AttrIndx OFFSET(2) NUMBITS(3) [],
TYPE OFFSET(1) NUMBITS(1) [
Block = 0,
Table = 1
],
VALID OFFSET(0) NUMBITS(1) [
False = 0,
True = 1
]
]
}
/// A function that maps the generic memory range attributes to HW-specific
/// attributes of the MMU.
fn into_mmu_attributes(
attribute_fields: AttributeFields,
) -> FieldValue<u64, STAGE1_DESCRIPTOR::Register> {
use super::{AccessPermissions, MemAttributes};
// Memory attributes
let mut desc = match attribute_fields.mem_attributes {
MemAttributes::CacheableDRAM => {
STAGE1_DESCRIPTOR::SH::InnerShareable
+ STAGE1_DESCRIPTOR::AttrIndx.val(mair::attr::NORMAL)
}
MemAttributes::NonCacheableDRAM => {
STAGE1_DESCRIPTOR::SH::InnerShareable
+ STAGE1_DESCRIPTOR::AttrIndx.val(mair::attr::NORMAL_NON_CACHEABLE)
}
MemAttributes::Device => {
STAGE1_DESCRIPTOR::SH::OuterShareable
+ STAGE1_DESCRIPTOR::AttrIndx.val(mair::attr::DEVICE_NGNRE)
}
};
// Access Permissions
desc += match attribute_fields.acc_perms {
AccessPermissions::ReadOnly => STAGE1_DESCRIPTOR::AP::RO_EL1,
AccessPermissions::ReadWrite => STAGE1_DESCRIPTOR::AP::RW_EL1,
};
// Execute Never
desc += if attribute_fields.execute_never {
STAGE1_DESCRIPTOR::PXN::NeverExecute
} else {
STAGE1_DESCRIPTOR::PXN::Execute
};
desc
}
/*
* With 4k page granule, a virtual address is split into 4 lookup parts
* spanning 9 bits each:
*
* _______________________________________________
* | | | | | | |
* | signx | Lv0 | Lv1 | Lv2 | Lv3 | off |
* |_______|_______|_______|_______|_______|_______|
* 63-48 47-39 38-30 29-21 20-12 11-00
*
* mask page size
*
* Lv0: FF8000000000 --
* Lv1: 7FC0000000 1G
* Lv2: 3FE00000 2M
* Lv3: 1FF000 4K
* off: FFF
*
* RPi3 supports 64K and 4K granules, also 40-bit physical addresses.
* It also can address only 1G physical memory, so these 40-bit phys addresses are a fake.
*
* 48-bit virtual address space; different mappings in VBAR0 (EL0) and VBAR1 (EL1+).
*/
/// Number of entries in a 4KiB mmu table.
pub const NUM_ENTRIES_4KIB: u64 = 512;
/// Trait for abstracting over the possible page sizes, 4KiB, 16KiB, 2MiB, 1GiB.
pub trait PageSize: Copy + Eq + PartialOrd + Ord {
/// The page size in bytes.
const SIZE: u64;
/// A string representation of the page size for debug output.
const SIZE_AS_DEBUG_STR: &'static str;
/// The page shift in bits.
const SHIFT: usize;
/// The page mask in bits.
const MASK: u64;
}
/// This trait is implemented for 4KiB, 16KiB, and 2MiB pages, but not for 1GiB pages.
pub trait NotGiantPageSize: PageSize {} // @todo doesn't have to be pub??
/// A standard 4KiB page.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Size4KiB {}
impl PageSize for Size4KiB {
const SIZE: u64 = 4096;
const SIZE_AS_DEBUG_STR: &'static str = "4KiB";
const SHIFT: usize = 12;
const MASK: u64 = 0xfff;
}
impl NotGiantPageSize for Size4KiB {}
/// A “huge” 2MiB page.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Size2MiB {}
impl PageSize for Size2MiB {
const SIZE: u64 = Size4KiB::SIZE * NUM_ENTRIES_4KIB;
const SIZE_AS_DEBUG_STR: &'static str = "2MiB";
const SHIFT: usize = 21;
const MASK: u64 = 0x1fffff;
}
impl NotGiantPageSize for Size2MiB {}
type EntryFlags = tock_registers::fields::FieldValue<u64, STAGE1_DESCRIPTOR::Register>;
// type EntryRegister = register::LocalRegisterCopy<u64, STAGE1_DESCRIPTOR::Register>;
/// L0 table -- only pointers to L1 tables
pub enum PageGlobalDirectory {}
/// L1 tables -- pointers to L2 tables or giant 1GiB pages
pub enum PageUpperDirectory {}
/// L2 tables -- pointers to L3 tables or huge 2MiB pages
pub enum PageDirectory {}
/// L3 tables -- only pointers to 4/16KiB pages
pub enum PageTable {}
/// Shared trait for specific table levels.
pub trait TableLevel {}
/// Shared trait for hierarchical table levels.
///
/// Specifies what is the next level of page table hierarchy.
pub trait HierarchicalLevel: TableLevel {
/// Level of the next translation table below this one.
type NextLevel: TableLevel;
}
impl TableLevel for PageGlobalDirectory {}
impl TableLevel for PageUpperDirectory {}
impl TableLevel for PageDirectory {}
impl TableLevel for PageTable {}
impl HierarchicalLevel for PageGlobalDirectory {
type NextLevel = PageUpperDirectory;
}
impl HierarchicalLevel for PageUpperDirectory {
type NextLevel = PageDirectory;
}
impl HierarchicalLevel for PageDirectory {
type NextLevel = PageTable;
}
// PageTables do not have next level, therefore they are not HierarchicalLevel
/// MMU address translation table.
/// Contains just u64 internally, provides enum interface on top
#[repr(C)]
#[repr(align(4096))]
pub struct Table<L: TableLevel> {
entries: [u64; NUM_ENTRIES_4KIB as usize],
level: PhantomData<L>,
}
// Implementation code shared for all levels of page tables
impl<L> Table<L>
where
L: TableLevel,
{
/// Zero out entire table.
pub fn zero(&mut self) {
for entry in self.entries.iter_mut() {
*entry = 0;
}
}
}
impl<L> Index<usize> for Table<L>
where
L: TableLevel,
{
type Output = u64;
fn index(&self, index: usize) -> &u64 {
&self.entries[index]
}
}
impl<L> IndexMut<usize> for Table<L>
where
L: TableLevel,
{
fn index_mut(&mut self, index: usize) -> &mut u64 {
&mut self.entries[index]
}
}
/// Type-safe enum wrapper covering Table<L>'s 64-bit entries.
#[derive(Clone)]
// #[repr(transparent)]
enum PageTableEntry {
/// Empty page table entry.
Invalid,
/// Table descriptor is a L0, L1 or L2 table pointing to another table.
/// L0 tables can only point to L1 tables.
/// A descriptor pointing to the next page table.
TableDescriptor(EntryFlags),
/// A Level2 block descriptor with 2 MiB aperture.
///
/// The output points to physical memory.
Lvl2BlockDescriptor(EntryFlags),
/// A page PageTableEntry::descriptor with 4 KiB aperture.
///
/// The output points to physical memory.
PageDescriptor(EntryFlags),
}
/// A descriptor pointing to the next page table. (within PageTableEntry enum)
// struct TableDescriptor(register::FieldValue<u64, STAGE1_DESCRIPTOR::Register>);
impl PageTableEntry {
fn new_table_descriptor(next_lvl_table_addr: usize) -> Result<PageTableEntry, &'static str> {
if next_lvl_table_addr % Size4KiB::SIZE as usize != 0 {
// @todo SIZE must be usize
return Err("TableDescriptor: Address is not 4 KiB aligned.");
}
let shifted = next_lvl_table_addr >> Size4KiB::SHIFT;
Ok(PageTableEntry::TableDescriptor(
STAGE1_DESCRIPTOR::VALID::True
+ STAGE1_DESCRIPTOR::TYPE::Table
+ STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(shifted as u64),
))
}
}
/// A Level2 block descriptor with 2 MiB aperture.
///
/// The output points to physical memory.
// struct Lvl2BlockDescriptor(register::FieldValue<u64, STAGE1_DESCRIPTOR::Register>);
impl PageTableEntry {
fn new_lvl2_block_descriptor(
output_addr: usize,
attribute_fields: AttributeFields,
) -> Result<PageTableEntry, &'static str> {
if output_addr % Size2MiB::SIZE as usize != 0 {
return Err("BlockDescriptor: Address is not 2 MiB aligned.");
}
let shifted = output_addr >> Size2MiB::SHIFT;
Ok(PageTableEntry::Lvl2BlockDescriptor(
STAGE1_DESCRIPTOR::VALID::True
+ STAGE1_DESCRIPTOR::AF::True
+ into_mmu_attributes(attribute_fields)
+ STAGE1_DESCRIPTOR::TYPE::Block
+ STAGE1_DESCRIPTOR::LVL2_OUTPUT_ADDR_4KiB.val(shifted as u64),
))
}
}
/// A page descriptor with 4 KiB aperture.
///
/// The output points to physical memory.
impl PageTableEntry {
fn new_page_descriptor(
output_addr: usize,
attribute_fields: AttributeFields,
) -> Result<PageTableEntry, &'static str> {
if output_addr % Size4KiB::SIZE as usize != 0 {
return Err("PageDescriptor: Address is not 4 KiB aligned.");
}
let shifted = output_addr >> Size4KiB::SHIFT;
Ok(PageTableEntry::PageDescriptor(
STAGE1_DESCRIPTOR::VALID::True
+ STAGE1_DESCRIPTOR::AF::True
+ into_mmu_attributes(attribute_fields)
+ STAGE1_DESCRIPTOR::TYPE::Table
+ STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(shifted as u64),
))
}
}
impl From<u64> for PageTableEntry {
fn from(_val: u64) -> PageTableEntry {
// xxx0 -> Invalid
// xx11 -> TableDescriptor on L0, L1 and L2
// xx10 -> Block Entry L1 and L2
// xx11 -> PageDescriptor L3
PageTableEntry::Invalid
}
}
impl From<PageTableEntry> for u64 {
fn from(val: PageTableEntry) -> u64 {
match val {
PageTableEntry::Invalid => 0,
PageTableEntry::TableDescriptor(x)
| PageTableEntry::Lvl2BlockDescriptor(x)
| PageTableEntry::PageDescriptor(x) => x.value,
}
}
}
static mut LVL2_TABLE: Table<PageDirectory> = Table::<PageDirectory> {
entries: [0; NUM_ENTRIES_4KIB as usize],
level: PhantomData,
};
static mut LVL3_TABLE: Table<PageTable> = Table::<PageTable> {
entries: [0; NUM_ENTRIES_4KIB as usize],
level: PhantomData,
};
trait BaseAddr {
fn base_addr_u64(&self) -> u64;
fn base_addr_usize(&self) -> usize;
}
impl BaseAddr for [u64; 512] {
fn base_addr_u64(&self) -> u64 {
self as *const u64 as u64
}
fn base_addr_usize(&self) -> usize {
self as *const u64 as usize
}
}
/// Set up identity mapped page tables for the first 1 gigabyte of address space.
/// default: 880 MB ARM ram, 128MB VC
///
/// # Safety
///
/// Completely unsafe, we're in the hardware land! Incorrectly initialised tables will just
/// restart the CPU.
pub unsafe fn init() -> Result<(), &'static str> {
// Prepare the memory attribute indirection register.
mair::set_up();
// Point the first 2 MiB of virtual addresses to the follow-up LVL3
// page-table.
LVL2_TABLE.entries[0] =
PageTableEntry::new_table_descriptor(LVL3_TABLE.entries.base_addr_usize())?.into();
// Fill the rest of the LVL2 (2 MiB) entries as block descriptors.
//
// Notice the skip(1) which makes the iteration start at the second 2 MiB
// block (0x20_0000).
for (block_descriptor_nr, entry) in LVL2_TABLE.entries.iter_mut().enumerate().skip(1) {
let virt_addr = block_descriptor_nr << Size2MiB::SHIFT;
let (output_addr, attribute_fields) = match get_virt_addr_properties(virt_addr) {
Err(s) => return Err(s),
Ok((a, b)) => (a, b),
};
let block_desc =
match PageTableEntry::new_lvl2_block_descriptor(output_addr, attribute_fields) {
Err(s) => return Err(s),
Ok(desc) => desc,
};
*entry = block_desc.into();
}
// Finally, fill the single LVL3 table (4 KiB granule).
for (page_descriptor_nr, entry) in LVL3_TABLE.entries.iter_mut().enumerate() {
let virt_addr = page_descriptor_nr << Size4KiB::SHIFT;
let (output_addr, attribute_fields) = match get_virt_addr_properties(virt_addr) {
Err(s) => return Err(s),
Ok((a, b)) => (a, b),
};
let page_desc = match PageTableEntry::new_page_descriptor(output_addr, attribute_fields) {
Err(s) => return Err(s),
Ok(desc) => desc,
};
*entry = page_desc.into();
}
// Point to the LVL2 table base address in TTBR0.
TTBR0_EL1.set_baddr(LVL2_TABLE.entries.base_addr_u64()); // User (lo-)space addresses
// TTBR1_EL1.set_baddr(LVL2_TABLE.entries.base_addr_u64()); // Kernel (hi-)space addresses
// Configure various settings of stage 1 of the EL1 translation regime.
let ips = ID_AA64MMFR0_EL1.read(ID_AA64MMFR0_EL1::PARange);
TCR_EL1.write(
TCR_EL1::TBI0::Ignored // @todo TBI1 also set to Ignored??
+ TCR_EL1::IPS.val(ips) // Intermediate Physical Address Size
// ttbr0 user memory addresses
+ TCR_EL1::TG0::KiB_4 // 4 KiB granule
+ TCR_EL1::SH0::Inner
+ TCR_EL1::ORGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable
+ TCR_EL1::IRGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable
+ TCR_EL1::EPD0::EnableTTBR0Walks
+ TCR_EL1::T0SZ.val(34) // ARMv8ARM Table D5-11 minimum TxSZ for starting table level 2
// ttbr1 kernel memory addresses
+ TCR_EL1::TG1::KiB_4 // 4 KiB granule
+ TCR_EL1::SH1::Inner
+ TCR_EL1::ORGN1::WriteBack_ReadAlloc_WriteAlloc_Cacheable
+ TCR_EL1::IRGN1::WriteBack_ReadAlloc_WriteAlloc_Cacheable
+ TCR_EL1::EPD1::EnableTTBR1Walks
+ TCR_EL1::T1SZ.val(34), // ARMv8ARM Table D5-11 minimum TxSZ for starting table level 2
);
// Switch the MMU on.
//
// First, force all previous changes to be seen before the MMU is enabled.
barrier::isb(barrier::SY);
// use cortex_a::regs::RegisterReadWrite;
// Enable the MMU and turn on data and instruction caching.
SCTLR_EL1.modify(SCTLR_EL1::M::Enable + SCTLR_EL1::C::Cacheable + SCTLR_EL1::I::Cacheable);
// Force MMU init to complete before next instruction
/*
* Invalidate the local I-cache so that any instructions fetched
* speculatively from the PoC are discarded, since they may have
* been dynamically patched at the PoU.
*/
barrier::isb(barrier::SY);
Ok(())
}

View File

@ -11,34 +11,10 @@ use {
};
mod addr;
pub mod features; // @todo make only pub re-export?
pub mod mmu;
mod page_size;
mod phys_frame;
mod virt_page;
pub use mmu::*;
pub use addr::{PhysAddr, VirtAddr};
// mod area_frame_allocator;
// pub use self::area_frame_allocator::AreaFrameAllocator;
// mod boot_allocator; // Hands out physical memory obtained from devtree
// use self::paging::PAGE_SIZE;
pub use addr::PhysAddr;
pub use addr::VirtAddr;
pub use page_size::PageSize;
pub use phys_frame::PhysFrame;
/// @todo ??
pub trait FrameAllocator {
/// Allocate a physical memory frame.
fn allocate_frame(&mut self) -> Option<PhysFrame>; // @todo Result<>
/// Deallocate a physical frame.
fn deallocate_frame(&mut self, frame: PhysFrame);
}
// Identity-map things for now.
//
// aarch64 granules and page sizes howto:
// https://stackoverflow.com/questions/34269185/simultaneous-existence-of-different-sized-pages-on-aarch64
@ -165,8 +141,6 @@ pub use kernel_mem_range::*;
/// Contains only special ranges, aka anything that is _not_ normal cacheable
/// DRAM.
static KERNEL_VIRTUAL_LAYOUT: [Descriptor; 6] = [
// These are part of a static linked image and used for proper kernel-space initialization.
// i.e. these data are subtracted from the dtb-provided memory map.
Descriptor {
name: "Kernel stack",
virtual_range: || {
@ -267,7 +241,6 @@ static KERNEL_VIRTUAL_LAYOUT: [Descriptor; 6] = [
execute_never: true,
},
},
// @todo these should come from DTB and mem-map?
Descriptor {
name: "DMA heap pool",
virtual_range: || RangeInclusive::new(map::virt::DMA_HEAP_START, map::virt::DMA_HEAP_END),
@ -278,7 +251,6 @@ static KERNEL_VIRTUAL_LAYOUT: [Descriptor; 6] = [
execute_never: true,
},
},
// @todo these should come from DTB and mem-map?
Descriptor {
name: "Device MMIO",
virtual_range: || RangeInclusive::new(map::phys::VIDEOMEM_BASE, map::phys::MMIO_END),

View File

@ -39,3 +39,14 @@ pub fn loop_until<F: Fn() -> bool>(f: F) {
asm::nop();
}
}
/// Loop while a passed function returns `true`.
#[inline]
pub fn loop_while<F: Fn() -> bool>(f: F) {
loop {
if !f() {
break;
}
asm::nop();
}
}

View File

@ -62,7 +62,7 @@ use {
},
};
global_asm!(include_str!("vectors.S"));
core::arch::global_asm!(include_str!("vectors.S"));
/// Errors possibly returned from the traps module.
#[derive(Debug, Snafu)]

View File

@ -4,27 +4,51 @@
#![allow(dead_code)]
use crate::platform;
use core::fmt;
use {
crate::{devices::SerialOps, platform},
core::fmt,
};
/// A trait that must be implemented by devices that are candidates for the
/// global console.
#[allow(unused_variables)]
pub trait ConsoleOps {
fn putc(&self, c: char) {}
fn puts(&self, string: &str) {}
fn getc(&self) -> char {
' '
}
fn flush(&self) {}
pub trait ConsoleOps: SerialOps {
/// Send a character
fn write_char(&self, c: char);
/// Display a string
fn write_string(&self, string: &str);
/// Receive a character
fn read_char(&self) -> char;
}
/// A dummy console that just ignores its inputs.
pub struct NullConsole;
impl Drop for NullConsole {
fn drop(&mut self) {}
}
impl ConsoleOps for NullConsole {}
impl ConsoleOps for NullConsole {
fn write_char(&self, _c: char) {}
fn write_string(&self, _string: &str) {}
fn read_char(&self) -> char {
' '
}
}
impl SerialOps for NullConsole {
fn read_byte(&self) -> u8 {
0
}
fn write_byte(&self, _byte: u8) {}
fn flush(&self) {}
fn clear_rx(&self) {}
}
/// Possible outputs which the console can store.
pub enum Output {
@ -65,7 +89,6 @@ impl Console {
}
}
#[inline(always)]
fn current_ptr(&self) -> &dyn ConsoleOps {
match &self.output {
Output::None(i) => i,
@ -84,15 +107,15 @@ impl Console {
/// A command prompt.
pub fn command_prompt<'a>(&self, buf: &'a mut [u8]) -> &'a [u8] {
self.puts("\n$> ");
self.write_string("\n$> ");
let mut i = 0;
let mut input;
loop {
input = self.getc();
input = self.read_char();
if input == '\n' {
self.puts("\n"); // do \r\n output
self.write_char('\n'); // do \r\n output
return &buf[..i];
} else {
if i < buf.len() {
@ -102,7 +125,7 @@ impl Console {
return &buf[..i];
}
self.putc(input);
self.write_char(input);
}
}
}
@ -114,21 +137,32 @@ impl Drop for Console {
/// Dispatch the respective function to the currently stored output device.
impl ConsoleOps for Console {
fn putc(&self, c: char) {
self.current_ptr().putc(c);
fn write_char(&self, c: char) {
self.current_ptr().write_char(c);
}
fn puts(&self, string: &str) {
self.current_ptr().puts(string);
fn write_string(&self, string: &str) {
self.current_ptr().write_string(string);
}
fn getc(&self) -> char {
self.current_ptr().getc()
fn read_char(&self) -> char {
self.current_ptr().read_char()
}
}
impl SerialOps for Console {
fn read_byte(&self) -> u8 {
self.current_ptr().read_byte()
}
fn write_byte(&self, byte: u8) {
self.current_ptr().write_byte(byte)
}
fn flush(&self) {
self.current_ptr().flush()
}
fn clear_rx(&self) {
self.current_ptr().clear_rx()
}
}
/// Implementing this trait enables usage of the format_args! macros, which in
@ -137,8 +171,7 @@ impl ConsoleOps for Console {
/// See src/macros.rs.
impl fmt::Write for Console {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.current_ptr().puts(s);
self.current_ptr().write_string(s);
Ok(())
}
}

View File

@ -0,0 +1,10 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
*/
pub mod console;
pub mod serial;
pub use {
console::{Console, ConsoleOps},
serial::SerialOps,
};

View File

@ -0,0 +1,12 @@
pub trait SerialOps {
/// Read one byte from serial without translation.
fn read_byte(&self) -> u8;
/// Write one byte to serial without translation.
fn write_byte(&self, byte: u8);
/// Wait until the TX FIFO is empty, aka all characters have been put on the
/// line.
fn flush(&self);
/// Consume input until RX FIFO is empty, aka all pending characters have been
/// consumed.
fn clear_rx(&self);
}

57
machine/src/lib.rs Normal file
View File

@ -0,0 +1,57 @@
#![no_std]
#![no_main]
#![feature(decl_macro)]
#![feature(allocator_api)]
#![feature(format_args_nl)]
#![feature(const_fn_fn_ptr_basics)]
#![feature(nonnull_slice_from_raw_parts)]
#![feature(custom_test_frameworks)]
#![test_runner(crate::tests::test_runner)]
#![reexport_test_harness_main = "test_main"]
#![allow(clippy::upper_case_acronyms)]
#![allow(clippy::enum_variant_names)]
#![allow(clippy::nonstandard_macro_braces)] // https://github.com/shepmaster/snafu/issues/296
#![allow(missing_docs)] // Temp: switch to deny
#![deny(warnings)]
#[cfg(not(target_arch = "aarch64"))]
use architecture_not_supported_sorry;
/// Architecture-specific code.
#[macro_use]
pub mod arch;
pub use arch::*;
pub mod devices;
pub mod macros;
mod mm;
pub mod panic;
pub mod platform;
pub mod qemu;
mod sync;
pub mod tests;
pub mod write_to;
/// The global console. Output of the kernel print! and println! macros goes here.
pub static CONSOLE: sync::NullLock<devices::Console> = sync::NullLock::new(devices::Console::new());
/// The global allocator for DMA-able memory. That is, memory which is tagged
/// non-cacheable in the page tables.
#[allow(dead_code)]
static DMA_ALLOCATOR: sync::NullLock<mm::BumpAllocator> =
sync::NullLock::new(mm::BumpAllocator::new(
// @todo Init this after we loaded boot memory map
memory::map::virt::DMA_HEAP_START as usize,
memory::map::virt::DMA_HEAP_END as usize,
"Global DMA Allocator",
// Try the following arguments instead to see all mailbox operations
// fail. It will cause the allocator to use memory that are marked
// cacheable and therefore not DMA-safe. The answer from the VideoCore
// won't be received by the CPU because it reads an old cached value
// that resembles an error case instead.
// 0x00600000 as usize,
// 0x007FFFFF as usize,
// "Global Non-DMA Allocator",
));

View File

@ -55,6 +55,7 @@ unsafe impl Allocator for BumpAllocator {
impl BumpAllocator {
/// Create a named bump allocator between start and end addresses.
#[allow(dead_code)]
pub const fn new(pool_start: usize, pool_end: usize, name: &'static str) -> Self {
Self {
next: Cell::new(pool_start),

View File

@ -10,18 +10,18 @@ pub use bump_allocator::BumpAllocator;
///
/// Returns the greatest x with alignment `align` so that x <= addr.
/// The alignment must be a power of 2.
pub fn align_down(addr: u64, align: usize) -> u64 {
pub fn align_down(addr: u64, align: u64) -> u64 {
assert!(align.is_power_of_two(), "`align` must be a power of two");
addr & !(align as u64 - 1)
addr & !(align - 1)
}
/// Align address upwards.
///
/// Returns the smallest x with alignment `align` so that x >= addr.
/// The alignment must be a power of 2.
pub fn align_up(addr: u64, align: usize) -> u64 {
pub fn align_up(addr: u64, align: u64) -> u64 {
assert!(align.is_power_of_two(), "`align` must be a power of two");
let align_mask = align as u64 - 1;
let align_mask = align - 1;
if addr & align_mask == 0 {
addr // already aligned
} else {
@ -31,7 +31,6 @@ pub fn align_up(addr: u64, align: usize) -> u64 {
/// Calculate the next possible aligned address without sanity checking the
/// input parameters.
// u64 for return and addr?
#[inline]
fn aligned_addr_unchecked(addr: usize, alignment: usize) -> usize {
(addr + (alignment - 1)) & !(alignment - 1)

View File

@ -1,14 +1,10 @@
#[cfg(not(test))]
#[panic_handler]
fn panicked(info: &core::panic::PanicInfo) -> ! {
pub fn handler(info: &core::panic::PanicInfo) -> ! {
// @todo This may fail to print if the panic message is too long for local print buffer.
crate::println!("{}", info);
crate::endless_sleep()
}
#[cfg(test)]
#[panic_handler]
fn panicked(info: &core::panic::PanicInfo) -> ! {
pub fn handler_for_tests(info: &core::panic::PanicInfo) -> ! {
crate::println!("\n[failed]\n");
// @todo This may fail to print if the panic message is too long for local print buffer.
crate::println!("\nError: {}\n", info);

View File

@ -0,0 +1,52 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use core::{marker::PhantomData, ops};
//--------------------------------------------------------------------------------------------------
// Public Definitions
//--------------------------------------------------------------------------------------------------
pub mod rpi3;
pub struct MMIODerefWrapper<T> {
base_addr: usize,
phantom: PhantomData<fn() -> T>,
}
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
impl<T> MMIODerefWrapper<T> {
/// Create an instance.
///
/// # Safety
///
/// Unsafe, duh!
pub const unsafe fn new(start_addr: usize) -> Self {
Self {
base_addr: start_addr,
phantom: PhantomData,
}
}
}
/// Deref to RegisterBlock
///
/// Allows writing
/// ```
/// self.GPPUD.read()
/// ```
/// instead of something along the lines of
/// ```
/// unsafe { (*GPIO::ptr()).GPPUD.read() }
/// ```
impl<T> ops::Deref for MMIODerefWrapper<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { &*(self.base_addr as *const _) }
}
}

View File

@ -0,0 +1,70 @@
use super::mailbox::{self, LocalMailboxStorage, Mailbox, MailboxError, MailboxOps};
/// FrameBuffer channel supported structure - use with mailbox::channel::FrameBuffer
/// Must have the same alignment as the mailbox buffers.
type FrameBufferData = LocalMailboxStorage<10>;
mod index {
pub const WIDTH: usize = 0;
pub const HEIGHT: usize = 1;
pub const VIRTUAL_WIDTH: usize = 2;
pub const VIRTUAL_HEIGHT: usize = 3;
pub const PITCH: usize = 4;
pub const DEPTH: usize = 5;
pub const X_OFFSET: usize = 6;
pub const Y_OFFSET: usize = 7;
pub const POINTER: usize = 8; // FIXME: could be 4096 for the alignment restriction.
pub const SIZE: usize = 9;
}
pub struct FrameBuffer {
mailbox: Mailbox<10, FrameBufferData>,
}
impl core::fmt::Debug for FrameBufferData {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(
f,
"\n\n\n#### FrameBuffer({}x{}, {}x{}, d{}, --{}--, +{}x{}, {}@{:x})\n\n\n",
self.storage[index::WIDTH],
self.storage[index::HEIGHT],
self.storage[index::VIRTUAL_WIDTH],
self.storage[index::VIRTUAL_HEIGHT],
self.storage[index::HEIGHT],
self.storage[index::PITCH],
self.storage[index::X_OFFSET],
self.storage[index::Y_OFFSET],
self.storage[index::SIZE],
self.storage[index::POINTER],
)
}
}
impl FrameBuffer {
pub fn new(
base_addr: usize,
width: u32,
height: u32,
depth: u32,
) -> Result<FrameBuffer, MailboxError> {
let mut fb = FrameBuffer {
mailbox: unsafe { Mailbox::<10, FrameBufferData>::new(base_addr)? },
};
fb.mailbox.buffer.storage[index::WIDTH] = width;
fb.mailbox.buffer.storage[index::VIRTUAL_WIDTH] = width;
fb.mailbox.buffer.storage[index::HEIGHT] = height;
fb.mailbox.buffer.storage[index::VIRTUAL_HEIGHT] = height;
fb.mailbox.buffer.storage[index::DEPTH] = depth;
Ok(fb)
}
}
impl MailboxOps for FrameBuffer {
fn write(&self, _channel: u32) -> mailbox::Result<()> {
self.mailbox.do_write(mailbox::channel::FrameBuffer)
}
fn read(&self, _channel: u32) -> mailbox::Result<()> {
unsafe { self.mailbox.do_read(mailbox::channel::FrameBuffer, 0) }
}
}

View File

@ -7,36 +7,18 @@
use {
super::BcmHost,
crate::arch::loop_delay,
core::{marker::PhantomData, ops},
crate::platform::MMIODerefWrapper,
core::marker::PhantomData,
tock_registers::{
fields::FieldValue,
interfaces::{ReadWriteable, Readable, Writeable},
register_bitfields,
register_structs,
registers::{ReadOnly, ReadWrite, WriteOnly},
},
};
// Descriptions taken from
// https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf
register_bitfields! {
u32,
/// GPIO Pull-up/down Clock Register 0
PUDCLK0 [
/// Pin 15
PUDCLK15 OFFSET(15) NUMBITS(1) [
NoEffect = 0,
AssertClock = 1
],
/// Pin 14
PUDCLK14 OFFSET(14) NUMBITS(1) [
NoEffect = 0,
AssertClock = 1
]
]
}
/// Generates `pub enums` with no variants for each `ident` passed in.
macro states($($name:ident),*) {
@ -48,102 +30,105 @@ states! {
Uninitialized, Input, Output, Alt
}
/// A wrapper type that prevents reads or writes to its value.
///
/// This type implements no methods. It is meant to make the inner type
/// inaccessible to prevent accidental reads or writes.
#[repr(C)]
pub struct Reserved<T>(T);
/// The offsets for reach register.
/// From <https://wiki.osdev.org/Raspberry_Pi_Bare_Bones> and
/// <https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf>
#[allow(non_snake_case)]
#[repr(C)]
pub struct RegisterBlock {
pub FSEL: [ReadWrite<u32>; 6], // 0x00-0x14 function select
__reserved_0: Reserved<u32>, // 0x18
pub SET: [WriteOnly<u32>; 2], // 0x1c-0x20 set output pin
__reserved_1: Reserved<u32>, // 0x24
pub CLR: [WriteOnly<u32>; 2], // 0x28-0x2c clear output pin
__reserved_2: Reserved<u32>, // 0x30
pub LEV: [ReadOnly<u32>; 2], // 0x34-0x38 get input pin level
__reserved_3: Reserved<u32>, // 0x3C
pub EDS: [ReadWrite<u32>; 2], // 0x40-0x44
__reserved_4: Reserved<u32>, // 0x48
pub REN: [ReadWrite<u32>; 2], // 0x4c-0x50
__reserved_5: Reserved<u32>, // 0x54
pub FEN: [ReadWrite<u32>; 2], // 0x58-0x5c
__reserved_6: Reserved<u32>, // 0x60
pub HEN: [ReadWrite<u32>; 2], // 0x64-0x68
__reserved_7: Reserved<u32>, // 0x6c
pub LEN: [ReadWrite<u32>; 2], // 0x70-0x74
__reserved_8: Reserved<u32>, // 0x78
pub AREN: [ReadWrite<u32>; 2], // 0x7c-0x80
__reserved_9: Reserved<u32>, // 0x84
pub AFEN: [ReadWrite<u32>; 2], // 0x88-0x8c
__reserved_10: Reserved<u32>, // 0x90
pub PUD: ReadWrite<u32>, // 0x94 pull up down
pub PUDCLK: [ReadWrite<u32, PUDCLK0::Register>; 2], // 0x98-0x9C -- @todo remove this register
register_structs! {
/// The offsets for each register.
/// From <https://wiki.osdev.org/Raspberry_Pi_Bare_Bones> and
/// <https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf>
#[allow(non_snake_case)]
RegisterBlock {
(0x00 => pub FSEL: [ReadWrite<u32>; 6]), // function select
(0x18 => __reserved_1),
(0x1c => pub SET: [WriteOnly<u32>; 2]), // set output pin
(0x24 => __reserved_2),
(0x28 => pub CLR: [WriteOnly<u32>; 2]), // clear output pin
(0x30 => __reserved_3),
(0x34 => pub LEV: [ReadOnly<u32>; 2]), // get input pin level
(0x3c => __reserved_4),
(0x40 => pub EDS: [ReadWrite<u32>; 2]),
(0x48 => __reserved_5),
(0x4c => pub REN: [ReadWrite<u32>; 2]),
(0x54 => __reserved_6),
(0x58 => pub FEN: [ReadWrite<u32>; 2]),
(0x60 => __reserved_7),
(0x64 => pub HEN: [ReadWrite<u32>; 2]),
(0x6c => __reserved_8),
(0x70 => pub LEN: [ReadWrite<u32>; 2]),
(0x78 => __reserved_9),
(0x7c => pub AREN: [ReadWrite<u32>; 2]),
(0x84 => __reserved_10),
(0x88 => pub AFEN: [ReadWrite<u32>; 2]),
(0x90 => __reserved_11),
#[cfg(feature = "rpi3")]
(0x94 => pub PUD: ReadWrite<u32>), // pull up down
#[cfg(feature = "rpi3")]
(0x98 => pub PUDCLK: [ReadWrite<u32>; 2]),
#[cfg(feature = "rpi3")]
(0xa0 => __reserved_12),
#[cfg(feature = "rpi4")]
(0xe4 => PullUpDownControl: [ReadWrite<u32>; 4]),
(0xf4 => @END),
}
}
// Hide RegisterBlock from public api.
type Registers = MMIODerefWrapper<RegisterBlock>;
/// Public interface to the GPIO MMIO area
pub struct GPIO {
base_addr: usize,
registers: Registers,
}
/// Deref to RegisterBlock
///
/// Allows writing
/// ```
/// self.GPPUD.read()
/// ```
/// instead of something along the lines of
/// ```
/// unsafe { (*GPIO::ptr()).GPPUD.read() }
/// ```
impl ops::Deref for GPIO {
type Target = RegisterBlock;
fn deref(&self) -> &Self::Target {
unsafe { &*self.ptr() }
}
}
pub const GPIO_START: usize = 0x20_0000;
impl Default for GPIO {
fn default() -> GPIO {
// Default RPi3 GPIO base address
const GPIO_BASE: usize = BcmHost::get_peripheral_address() + 0x20_0000;
GPIO::new(GPIO_BASE)
const GPIO_BASE: usize = BcmHost::get_peripheral_address() + GPIO_START;
unsafe { GPIO::new(GPIO_BASE) }
}
}
impl GPIO {
pub fn new(base_addr: usize) -> GPIO {
GPIO { base_addr }
}
/// Returns a pointer to the register block
fn ptr(&self) -> *const RegisterBlock {
self.base_addr as *const _
/// # Safety
///
/// Unsafe, duh!
pub const unsafe fn new(base_addr: usize) -> GPIO {
GPIO {
registers: Registers::new(base_addr),
}
}
pub fn get_pin(&self, pin: usize) -> Pin<Uninitialized> {
Pin::new(pin, self.base_addr)
unsafe { Pin::new(pin, self.registers.base_addr) }
}
}
pub fn enable_uart_pins(gpio: &GPIO) {
gpio.PUD.set(0);
#[cfg(feature = "rpi3")]
pub fn power_off(&self) {
use crate::arch::loop_delay;
loop_delay(150);
// power off gpio pins (but not VCC pins)
for bank in 0..5 {
self.registers.FSEL[bank].set(0);
}
// enable pins 14 and 15
gpio.PUDCLK[0].write(PUDCLK0::PUDCLK14::AssertClock + PUDCLK0::PUDCLK15::AssertClock);
self.registers.PUD.set(0);
loop_delay(150);
loop_delay(2000);
gpio.PUDCLK[0].set(0);
self.registers.PUDCLK[0].set(0xffff_ffff);
self.registers.PUDCLK[1].set(0xffff_ffff);
loop_delay(2000);
// flush GPIO setup
self.registers.PUDCLK[0].set(0);
self.registers.PUDCLK[1].set(0);
}
#[cfg(feature = "rpi4")]
pub fn power_off(&self) {
todo!()
}
}
/// An alternative GPIO function.
@ -165,6 +150,21 @@ impl ::core::convert::From<Function> for u32 {
}
}
/// Pull up/down resistor setup.
#[repr(u8)]
#[derive(PartialEq)]
pub enum PullUpDown {
None = 0b00,
Up = 0b01,
Down = 0b10,
}
impl ::core::convert::From<PullUpDown> for u32 {
fn from(p: PullUpDown) -> Self {
p as u32
}
}
/// A GPIO pin in state `State`.
///
/// The `State` generic always corresponds to an un-instantiable type that is
@ -174,7 +174,7 @@ impl ::core::convert::From<Function> for u32 {
/// `into_alt` methods before it can be used.
pub struct Pin<State> {
pin: usize,
base_addr: usize,
registers: Registers,
_state: PhantomData<State>,
}
@ -186,33 +186,43 @@ impl<State> Pin<State> {
fn transition<NewState>(self) -> Pin<NewState> {
Pin {
pin: self.pin,
base_addr: self.base_addr,
registers: self.registers,
_state: PhantomData,
}
}
/// Returns a pointer to the register block
#[inline(always)]
fn ptr(&self) -> *const RegisterBlock {
self.base_addr as *const _
#[cfg(feature = "rpi3")]
pub fn set_pull_up_down(&self, pull: PullUpDown) {
use crate::arch::loop_delay;
let bank = self.pin / 32;
let off = self.pin % 32;
self.registers.PUD.set(0);
loop_delay(2000);
self.registers.PUDCLK[bank].modify(FieldValue::<u32, ()>::new(
0b1,
off,
if pull == PullUpDown::Up { 1 } else { 0 },
));
loop_delay(2000);
self.registers.PUD.set(0);
self.registers.PUDCLK[bank].set(0);
}
}
/// Deref to Pin's Registers
///
/// Allows writing
/// ```
/// self.PUD.read()
/// ```
/// instead of something along the lines of
/// ```
/// unsafe { (*Pin::ptr()).PUD.read() }
/// ```
impl<State> ops::Deref for Pin<State> {
type Target = RegisterBlock;
fn deref(&self) -> &Self::Target {
unsafe { &*self.ptr() }
#[cfg(feature = "rpi4")]
pub fn set_pull_up_down(&self, pull: PullUpDown) {
let bank = self.pin / 16;
let off = self.pin % 16;
self.registers.PullUpDownControl[bank].modify(FieldValue::<u32, ()>::new(
0b11,
off * 2,
pull.into(),
));
}
}
@ -222,13 +232,13 @@ impl Pin<Uninitialized> {
/// # Panics
///
/// Panics if `pin` > `53`.
fn new(pin: usize, base_addr: usize) -> Pin<Uninitialized> {
unsafe fn new(pin: usize, base_addr: usize) -> Pin<Uninitialized> {
if pin > 53 {
panic!("gpio::Pin::new(): pin {} exceeds maximum of 53", pin);
}
Pin {
base_addr,
registers: Registers::new(base_addr),
pin,
_state: PhantomData,
}
@ -239,7 +249,11 @@ impl Pin<Uninitialized> {
pub fn into_alt(self, function: Function) -> Pin<Alt> {
let bank = self.pin / 10;
let off = self.pin % 10;
self.FSEL[bank].modify(FieldValue::<u32, ()>::new(0b111, off * 3, function.into()));
self.registers.FSEL[bank].modify(FieldValue::<u32, ()>::new(
0b111,
off * 3,
function.into(),
));
self.transition()
}
@ -262,7 +276,7 @@ impl Pin<Output> {
// Guarantees: pin number is between [0; 53] by construction.
let bank = self.pin / 32;
let shift = self.pin % 32;
self.SET[bank].set(1 << shift);
self.registers.SET[bank].set(1 << shift);
}
/// Clears (turns off) this pin.
@ -270,7 +284,7 @@ impl Pin<Output> {
// Guarantees: pin number is between [0; 53] by construction.
let bank = self.pin / 32;
let shift = self.pin % 32;
self.CLR[bank].set(1 << shift);
self.registers.CLR[bank].set(1 << shift);
}
}
@ -283,7 +297,7 @@ impl Pin<Input> {
// Guarantees: pin number is between [0; 53] by construction.
let bank = self.pin / 32;
let off = self.pin % 32;
self.LEV[bank].matches_all(FieldValue::<u32, ()>::new(1, off, 1))
self.registers.LEV[bank].matches_all(FieldValue::<u32, ()>::new(1, off, 1))
}
}
@ -294,7 +308,7 @@ mod tests {
#[test_case]
fn test_pin_transitions() {
let mut reg = [0u32; 40];
let gpio = GPIO::new(&mut reg as *mut _ as usize);
let gpio = unsafe { GPIO::new(&mut reg as *mut _ as usize) };
let _out = gpio.get_pin(1).into_output();
assert_eq!(reg[0], 0b001_000);
@ -307,7 +321,7 @@ mod tests {
#[test_case]
fn test_pin_outputs() {
let mut reg = [0u32; 40];
let gpio = GPIO::new(&mut reg as *mut _ as usize);
let gpio = unsafe { GPIO::new(&mut reg as *mut _ as usize) };
let pin = gpio.get_pin(1);
let mut out = pin.into_output();
@ -327,7 +341,7 @@ mod tests {
#[test_case]
fn test_pin_inputs() {
let mut reg = [0u32; 40];
let gpio = GPIO::new(&mut reg as *mut _ as usize);
let gpio = unsafe { GPIO::new(&mut reg as *mut _ as usize) };
let pin = gpio.get_pin(1);
let inp = pin.into_input();

View File

@ -13,16 +13,16 @@
use {
super::BcmHost,
crate::println,
crate::{platform::MMIODerefWrapper, println},
core::{
ops::Deref,
ptr::NonNull,
result::Result as CoreResult,
sync::atomic::{compiler_fence, Ordering},
},
cortex_a::asm::barrier,
snafu::Snafu,
tock_registers::{
interfaces::{Readable, Writeable},
register_bitfields,
register_bitfields, register_structs,
registers::{ReadOnly, WriteOnly},
},
};
@ -31,15 +31,16 @@ use {
/// The address for the buffer needs to be 16-byte aligned
/// so that the VideoCore can handle it properly.
/// The reason is that lowest 4 bits of the address will contain the channel number.
pub struct Mailbox {
// pub buffer: &'a mut [u32],
base_addr: usize,
buffer: NonNull<[u32]>,
pub struct Mailbox<const N_SLOTS: usize, Storage = LocalMailboxStorage<N_SLOTS>> {
registers: Registers,
pub buffer: Storage,
}
/// Mailbox that is ready to be called.
/// This prevents invalid use of the mailbox until it is fully prepared.
pub struct PreparedMailbox(Mailbox);
pub struct PreparedMailbox<const N_SLOTS: usize, Storage = LocalMailboxStorage<N_SLOTS>>(
Mailbox<N_SLOTS, Storage>,
);
const MAILBOX_ALIGNMENT: usize = 16;
const MAILBOX_ITEMS_COUNT: usize = 36;
@ -57,6 +58,9 @@ const CHANNEL_MASK: u32 = 0xf;
// always for communication from VC to ARM and Mailbox 1 is for ARM to VC.
//
// The ARM should never write Mailbox 0 or read Mailbox 1.
//
// There are 32 mailboxes on the ARM, which could be used for in-processor or inter-processor comms,
// TODO: allow using all of them.
register_bitfields! {
u32,
@ -69,42 +73,35 @@ register_bitfields! {
]
}
#[allow(non_snake_case)]
#[repr(C)]
pub struct RegisterBlock {
READ: ReadOnly<u32>, // 0x00 This is Mailbox0 read for ARM, can't write
__reserved_0: [u32; 5], // 0x04
STATUS: ReadOnly<u32, STATUS::Register>, // 0x18
__reserved_1: u32, // 0x1C
WRITE: WriteOnly<u32>, // 0x20 This is Mailbox1 write for ARM, can't read
}
pub enum MailboxError {
Response,
Unknown,
Timeout,
}
impl core::fmt::Display for MailboxError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(
f,
"{}",
match self {
MailboxError::Response => "ResponseError",
MailboxError::Unknown => "UnknownError",
MailboxError::Timeout => "Timeout",
}
)
register_structs! {
#[allow(non_snake_case)]
pub RegisterBlock {
(0x00 => READ: ReadOnly<u32>), // This is Mailbox0 read for ARM, can't write
(0x04 => __reserved_1),
(0x18 => STATUS: ReadOnly<u32, STATUS::Register>),
(0x1c => __reserved_2),
(0x20 => WRITE: WriteOnly<u32>), // This is Mailbox1 write for ARM, can't read
(0x24 => @END),
}
}
pub type Result<T> = ::core::result::Result<T, MailboxError>;
// Hide RegisterBlock from public api.
type Registers = MMIODerefWrapper<RegisterBlock>;
#[derive(Snafu, Debug)]
pub enum MailboxError {
#[snafu(display("ResponseError"))]
Response,
#[snafu(display("UnknownError"))]
Unknown,
#[snafu(display("Timeout"))]
Timeout,
}
pub type Result<T> = CoreResult<T, MailboxError>;
/// Typical operations with a mailbox.
pub trait MailboxOps {
/// Deref from self to a mailbox RegisterBlock. Used by Deref implementations.
fn ptr(&self) -> *const RegisterBlock;
fn write(&self, channel: u32) -> Result<()>;
fn read(&self, channel: u32) -> Result<()>;
fn call(&self, channel: u32) -> Result<()> {
@ -113,6 +110,50 @@ pub trait MailboxOps {
}
}
pub trait MailboxStorage {
fn new() -> Self;
}
pub trait MailboxStorageRef {
fn as_ref(&self) -> &[u32];
fn as_mut(&mut self) -> &mut [u32];
fn as_ptr(&self) -> *const u32;
fn value_at(&self, index: usize) -> u32;
}
// TODO: allow from 2 to 36 slots (2 because you need at least an End tag)
#[repr(align(16))] // MAILBOX_ALIGNMENT
pub struct LocalMailboxStorage<const N_SLOTS: usize> {
pub storage: [u32; N_SLOTS],
}
impl<const N_SLOTS: usize> MailboxStorage for LocalMailboxStorage<N_SLOTS> {
fn new() -> Self {
Self {
storage: [0u32; N_SLOTS],
}
}
}
impl<const N_SLOTS: usize> MailboxStorageRef for LocalMailboxStorage<N_SLOTS> {
fn as_ref(&self) -> &[u32] {
&self.storage
}
fn as_mut(&mut self) -> &mut [u32] {
&mut self.storage
}
fn as_ptr(&self) -> *const u32 {
self.storage.as_ptr()
}
// @todo Probably need a ResultMailbox for accessing data after call()?
fn value_at(&self, index: usize) -> u32 {
self.storage[index]
}
}
/*
* Source https://elinux.org/RPi_Framebuffer
* Source for channels 8 and 9: https://github.com/raspberrypi/firmware/wiki/Mailboxes
@ -243,143 +284,39 @@ pub mod alpha_mode {
pub const IGNORED: u32 = 2;
}
pub fn write(regs: &RegisterBlock, buf: *const u32, channel: u32) -> Result<()> {
let mut count: u32 = 0;
let buf_ptr: u32 = buf as u32;
// This address adjustment will be performed from the outside when necessary
// (see FrameBuffer for example).
// let buf_ptr = BcmHost::phys2bus(buf_ptr); not used for PropertyTags channel
println!("Mailbox::write {:#08x}/{:#x}", buf_ptr, channel);
// Insert a compiler fence that ensures that all stores to the
// mailbox buffer are finished before the GPU is signaled (which is
// done by a store operation as well).
compiler_fence(Ordering::Release);
while regs.STATUS.is_set(STATUS::FULL) {
count += 1;
if count > (1 << 25) {
return Err(MailboxError::Timeout);
}
}
unsafe {
barrier::dmb(barrier::SY);
}
regs.WRITE
.set((buf_ptr & !CHANNEL_MASK) | (channel & CHANNEL_MASK));
Ok(())
}
pub fn read(regs: &RegisterBlock, expected: u32, channel: u32) -> Result<()> {
loop {
let mut count: u32 = 0;
while regs.STATUS.is_set(STATUS::EMPTY) {
count += 1;
if count > (1 << 25) {
println!("Timed out waiting for mailbox response");
return Err(MailboxError::Timeout);
}
}
/* Read the data
* Data memory barriers as we've switched peripheral
*/
unsafe {
barrier::dmb(barrier::SY);
}
let data: u32 = regs.READ.get();
unsafe {
barrier::dmb(barrier::SY);
}
println!(
"Received mailbox response {:#08x}, expecting {:#08x}",
data, expected
);
// is it a response to our message?
if ((data & CHANNEL_MASK) == channel) && ((data & !CHANNEL_MASK) == expected) {
// is it a valid successful response?
return Ok(());
} else {
// ignore invalid responses and loop again.
// will return Timeout above if no matching response is received.
}
}
}
/// Deref to RegisterBlock
///
/// Allows writing
/// ```
/// self.STATUS.read()
/// ```
/// instead of something along the lines of
/// ```
/// unsafe { (*Mailbox::ptr()).STATUS.read() }
/// ```
impl Deref for PreparedMailbox {
type Target = RegisterBlock;
fn deref(&self) -> &Self::Target {
unsafe { &*self.ptr() }
}
}
impl core::fmt::Debug for Mailbox {
impl<const N_SLOTS: usize> core::fmt::Debug for Mailbox<N_SLOTS> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let count = unsafe { self.buffer.as_ref()[0] } / 4;
assert_eq!(unsafe { self.buffer.as_ref()[0] }, count * 4);
let count = self.buffer.as_ref()[0] / 4;
assert_eq!(self.buffer.as_ref()[0], count * 4);
assert!(count <= 36);
for i in 0usize..count as usize {
writeln!(f, "[{:02}] {:08x}", i, unsafe { self.buffer.as_ref()[i] })?;
writeln!(f, "[{:02}] {:08x}", i, self.buffer.value_at(i))?;
}
Ok(())
}
}
impl core::fmt::Debug for PreparedMailbox {
impl<const N_SLOTS: usize> core::fmt::Debug for PreparedMailbox<N_SLOTS> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
self.0.fmt(f)
}
}
impl Default for Mailbox {
impl<const N_SLOTS: usize> Default for Mailbox<N_SLOTS> {
fn default() -> Self {
Self::new(MAILBOX_BASE).expect("Couldn't allocate a default mailbox")
unsafe { Self::new(MAILBOX_BASE) }.expect("Couldn't allocate a default mailbox")
}
}
// @todo Probably need a ResultMailbox for accessing data after call()?
impl PreparedMailbox {
pub fn value_at(&self, index: usize) -> u32 {
unsafe { self.0.buffer.as_ref()[index] }
}
}
impl Mailbox {
/// Create a new mailbox in the DMA-able memory area.
pub fn new(base_addr: usize) -> ::core::result::Result<Mailbox, ()> {
use core::alloc::Allocator;
crate::DMA_ALLOCATOR
.lock(|dma| {
dma.allocate_zeroed(
core::alloc::Layout::from_size_align(
MAILBOX_ITEMS_COUNT * core::mem::size_of::<u32>(),
MAILBOX_ALIGNMENT,
)
.unwrap(), // .map_err(|_| ())?,
)
})
.map(|ret| {
Ok(Mailbox {
base_addr,
buffer: ret.cast::<[u32; MAILBOX_ITEMS_COUNT]>(),
})
})
.map_err(|_| ())?
impl<const N_SLOTS: usize, Storage: MailboxStorage + MailboxStorageRef> Mailbox<N_SLOTS, Storage> {
/// Create a new mailbox locally in an aligned stack space.
/// # Safety
/// Caller is responsible for picking the correct MMIO register base address.
pub unsafe fn new(base_addr: usize) -> Result<Mailbox<N_SLOTS, Storage>> {
Ok(Mailbox {
registers: Registers::new(base_addr),
buffer: Storage::new(),
})
}
// Specific mailbox functions
@ -389,19 +326,17 @@ impl Mailbox {
/// @returns index of the next available slot.
#[inline]
pub fn request(&mut self) -> usize {
unsafe { self.buffer.as_mut()[1] = REQUEST };
self.buffer.as_mut()[1] = REQUEST;
2
}
/// Mark mailbox payload as completed.
/// Consumes the Mailbox and returns a Preparedmailbox that can be called.
#[inline]
pub fn end(mut self, index: usize) -> PreparedMailbox {
pub fn end(mut self, index: usize) -> PreparedMailbox<N_SLOTS, Storage> {
// @todo return Result
unsafe {
self.buffer.as_mut()[index] = tag::End;
self.buffer.as_mut()[0] = (index as u32 + 1) * 4;
}
self.buffer.as_mut()[index] = tag::End;
self.buffer.as_mut()[0] = (index as u32 + 1) * 4;
PreparedMailbox(self)
}
@ -409,7 +344,7 @@ impl Mailbox {
/// @returns index of the next available slot.
#[inline]
pub fn set_physical_wh(&mut self, index: usize, width: u32, height: u32) -> usize {
let buf = unsafe { self.buffer.as_mut() };
let buf = self.buffer.as_mut();
buf[index] = tag::SetPhysicalWH;
buf[index + 1] = 8; // Buffer size // val buf size
buf[index + 2] = 8; // Request size // val size
@ -422,7 +357,7 @@ impl Mailbox {
/// @returns index of the next available slot.
#[inline]
pub fn set_virtual_wh(&mut self, index: usize, width: u32, height: u32) -> usize {
let buf = unsafe { self.buffer.as_mut() };
let buf = self.buffer.as_mut();
buf[index] = tag::SetVirtualWH;
buf[index + 1] = 8; // Buffer size // val buf size
buf[index + 2] = 8; // Request size // val size
@ -435,7 +370,7 @@ impl Mailbox {
/// @returns index of the next available slot.
#[inline]
pub fn set_depth(&mut self, index: usize, depth: u32) -> usize {
let buf = unsafe { self.buffer.as_mut() };
let buf = self.buffer.as_mut();
buf[index] = tag::SetDepth;
buf[index + 1] = 4; // Buffer size // val buf size
buf[index + 2] = 4; // Request size // val size
@ -447,7 +382,7 @@ impl Mailbox {
/// @returns index of the next available slot.
#[inline]
pub fn allocate_buffer_aligned(&mut self, index: usize, alignment: u32) -> usize {
let buf = unsafe { self.buffer.as_mut() };
let buf = self.buffer.as_mut();
buf[index] = tag::AllocateBuffer;
buf[index + 1] = 8; // Buffer size // val buf size
buf[index + 2] = 4; // Request size // val size
@ -460,7 +395,7 @@ impl Mailbox {
/// @returns index of the next available slot.
#[inline]
pub fn set_led_on(&mut self, index: usize, enable: bool) -> usize {
let buf = unsafe { self.buffer.as_mut() };
let buf = self.buffer.as_mut();
buf[index] = tag::SetGpioState;
buf[index + 1] = 8; // Buffer size // val buf size
buf[index + 2] = 0; // Response size // val size
@ -471,7 +406,7 @@ impl Mailbox {
#[inline]
pub fn set_clock_rate(&mut self, index: usize, channel: u32, rate: u32) -> usize {
let buf = unsafe { self.buffer.as_mut() };
let buf = self.buffer.as_mut();
buf[index] = tag::SetClockRate;
buf[index + 1] = 12; // Buffer size // val buf size
buf[index + 2] = 8; // Response size // val size
@ -487,7 +422,7 @@ impl Mailbox {
/// and no tags will be returned.
#[inline]
pub fn set_pixel_order(&mut self, index: usize, order: u32) -> usize {
let buf = unsafe { self.buffer.as_mut() };
let buf = self.buffer.as_mut();
buf[index] = tag::SetPixelOrder;
buf[index + 1] = 4; // Buffer size // val buf size
buf[index + 2] = 4; // Response size // val size
@ -501,7 +436,7 @@ impl Mailbox {
/// and no tags will be returned.
#[inline]
pub fn test_pixel_order(&mut self, index: usize, order: u32) -> usize {
let buf = unsafe { self.buffer.as_mut() };
let buf = self.buffer.as_mut();
buf[index] = tag::TestPixelOrder;
buf[index + 1] = 4; // Buffer size // val buf size
buf[index + 2] = 4; // Response size // val size
@ -511,7 +446,7 @@ impl Mailbox {
#[inline]
pub fn set_alpha_mode(&mut self, index: usize, mode: u32) -> usize {
let buf = unsafe { self.buffer.as_mut() };
let buf = self.buffer.as_mut();
buf[index] = tag::SetAlphaMode;
buf[index + 1] = 4; // Buffer size // val buf size
buf[index + 2] = 4; // Response size // val size
@ -521,7 +456,7 @@ impl Mailbox {
#[inline]
pub fn get_pitch(&mut self, index: usize) -> usize {
let buf = unsafe { self.buffer.as_mut() };
let buf = self.buffer.as_mut();
buf[index] = tag::GetPitch;
buf[index + 1] = 4; // Buffer size // val buf size
buf[index + 2] = 4; // Response size // val size
@ -531,7 +466,7 @@ impl Mailbox {
#[inline]
pub fn set_device_power(&mut self, index: usize, device_id: u32, power_flags: u32) -> usize {
let buf = unsafe { self.buffer.as_mut() };
let buf = self.buffer.as_mut();
buf[index] = tag::SetPowerState;
buf[index + 1] = 8; // Buffer size // val buf size
buf[index + 2] = 8; // Response size // val size
@ -539,42 +474,130 @@ impl Mailbox {
buf[index + 4] = power_flags; // bit 0: off, bit 1: no wait
index + 5
}
}
impl MailboxOps for PreparedMailbox {
/// Returns a pointer to the register block
fn ptr(&self) -> *const RegisterBlock {
self.0.base_addr as *const _
}
// Actual work functions
fn write(&self, channel: u32) -> Result<()> {
write(self, self.0.buffer.as_ptr() as *const _, channel)
}
/// <https://github.com/raspberrypi/firmware/wiki/Accessing-mailboxes> says:
/// **With the exception of the property tags mailbox channel,**
/// when passing memory addresses as the data part of a mailbox message,
/// the addresses should be **bus addresses as seen from the VC.**
pub fn do_write(&self, channel: u32) -> Result<()> {
let buf_ptr = self.buffer.as_ptr() as *const u32 as u32;
let buf_ptr = if channel != channel::PropertyTagsArmToVc {
BcmHost::phys2bus(buf_ptr as usize) as u32
} else {
buf_ptr
};
// @todo read() should probably consume PreparedMailbox completely ?
fn read(&self, channel: u32) -> Result<()> {
// SAFETY: buffer is HW-mutable in the read call below!
read(
self,
self.0.buffer.as_ptr() as *const [u32] as *const u32 as u32,
channel,
)?;
let mut count: u32 = 0;
match unsafe { self.0.buffer.as_ref()[1] } {
response::SUCCESS => {
println!("\n######\nMailbox::returning SUCCESS");
Ok(())
}
response::ERROR => {
println!("\n######\nMailbox::returning ResponseError");
Err(MailboxError::Response)
}
_ => {
println!("\n######\nMailbox::returning UnknownError");
println!("{:x}\n######", unsafe { self.0.buffer.as_ref()[1] });
Err(MailboxError::Unknown)
println!("Mailbox::write {:#08x}/{:#x}", buf_ptr, channel);
// Insert a compiler fence that ensures that all stores to the
// mailbox buffer are finished before the GPU is signaled (which is
// done by a store operation as well).
compiler_fence(Ordering::Release);
while self.registers.STATUS.is_set(STATUS::FULL) {
count += 1;
if count > (1 << 25) {
return Err(MailboxError::Timeout);
}
}
unsafe {
barrier::dmb(barrier::SY);
}
self.registers
.WRITE
.set((buf_ptr & !CHANNEL_MASK) | (channel & CHANNEL_MASK));
Ok(())
}
/// Perform the mailbox read.
///
/// # Safety
///
/// Buffer will be mutated by the hardware before read operation is completed.
pub unsafe fn do_read(&self, channel: u32, expected: u32) -> Result<()> {
loop {
let mut count: u32 = 0;
while self.registers.STATUS.is_set(STATUS::EMPTY) {
count += 1;
if count > (1 << 25) {
println!("Timed out waiting for mailbox response");
return Err(MailboxError::Timeout);
}
}
/* Read the data
* Data memory barriers as we've switched peripheral
*/
barrier::dmb(barrier::SY);
let data: u32 = self.registers.READ.get();
barrier::dmb(barrier::SY);
println!(
"Received mailbox response {:#08x}, expecting {:#08x}",
data, expected
);
// is it a response to our message?
if ((data & CHANNEL_MASK) == channel) && ((data & !CHANNEL_MASK) == expected) {
// is it a valid successful response?
return match self.buffer.value_at(1) {
response::SUCCESS => {
println!("\n######\nMailbox::returning SUCCESS");
Ok(())
}
response::ERROR => {
println!("\n######\nMailbox::returning ResponseError");
Err(MailboxError::Response)
}
_ => {
println!("\n######\nMailbox::returning UnknownError");
println!("{:x}\n######", self.buffer.value_at(1));
Err(MailboxError::Unknown)
}
};
} else {
// ignore invalid responses and loop again.
// will return Timeout above if no matching response is received.
}
}
}
}
impl<const N_SLOTS: usize, Storage: MailboxStorage + MailboxStorageRef> MailboxOps
for PreparedMailbox<N_SLOTS, Storage>
{
fn write(&self, channel: u32) -> Result<()> {
self.0.do_write(channel)
}
// @todo read() should probably consume PreparedMailbox completely - because request is overwritten with response
fn read(&self, channel: u32) -> Result<()> {
unsafe { self.0.do_read(channel, self.0.buffer.as_ptr() as u32) }
}
}
impl<const N_SLOTS: usize, Storage: MailboxStorage + MailboxStorageRef> MailboxStorageRef
for PreparedMailbox<N_SLOTS, Storage>
{
fn as_ref(&self) -> &[u32] {
self.0.buffer.as_ref()
}
fn as_mut(&mut self) -> &mut [u32] {
self.0.buffer.as_mut()
}
fn as_ptr(&self) -> *const u32 {
self.0.buffer.as_ptr()
}
// @todo Probably need a ResultMailbox for accessing data after call()?
fn value_at(&self, index: usize) -> u32 {
self.0.buffer.value_at(index)
}
}

View File

@ -9,12 +9,15 @@
use tock_registers::interfaces::{Readable, Writeable};
use {
super::{gpio, BcmHost},
crate::devices::ConsoleOps,
crate::{
devices::{ConsoleOps, SerialOps},
platform::MMIODerefWrapper,
},
cfg_if::cfg_if,
core::{convert::From, fmt, ops},
core::{convert::From, fmt},
tock_registers::{
interfaces::ReadWriteable,
register_bitfields,
register_bitfields, register_structs,
registers::{ReadOnly, ReadWrite, WriteOnly},
},
};
@ -88,33 +91,62 @@ register_bitfields! {
]
],
/// Mini Uart Baudrate
/// Mini Uart Status
AUX_MU_STAT [
TX_DONE OFFSET(9) NUMBITS(1) [
No = 0,
Yes = 1
],
/// This bit is set if the transmit FIFO can accept at least
/// one byte.
SPACE_AVAILABLE OFFSET(1) NUMBITS(1) [
No = 0,
Yes = 1
],
/// This bit is set if the receive FIFO holds at least 1
/// symbol.
SYMBOL_AVAILABLE OFFSET(0) NUMBITS(1) [
No = 0,
Yes = 1
]
],
/// Mini Uart Baud rate
AUX_MU_BAUD [
/// Mini UART baudrate counter
/// Mini UART baud rate counter
RATE OFFSET(0) NUMBITS(16) []
]
}
#[allow(non_snake_case)]
#[repr(C)]
pub struct RegisterBlock {
__reserved_0: u32, // 0x00 - AUX_IRQ?
AUX_ENABLES: ReadWrite<u32, AUX_ENABLES::Register>, // 0x04
__reserved_1: [u32; 14], // 0x08
AUX_MU_IO: ReadWrite<u32>, // 0x40 - Mini Uart I/O Data
AUX_MU_IER: WriteOnly<u32>, // 0x44 - Mini Uart Interrupt Enable
AUX_MU_IIR: WriteOnly<u32, AUX_MU_IIR::Register>, // 0x48
AUX_MU_LCR: WriteOnly<u32, AUX_MU_LCR::Register>, // 0x4C
AUX_MU_MCR: WriteOnly<u32>, // 0x50
AUX_MU_LSR: ReadOnly<u32, AUX_MU_LSR::Register>, // 0x54
__reserved_2: [u32; 2], // 0x58 - AUX_MU_MSR, AUX_MU_SCRATCH
AUX_MU_CNTL: WriteOnly<u32, AUX_MU_CNTL::Register>, // 0x60
__reserved_3: u32, // 0x64 - AUX_MU_STAT
AUX_MU_BAUD: WriteOnly<u32, AUX_MU_BAUD::Register>, // 0x68
register_structs! {
#[allow(non_snake_case)]
RegisterBlock {
// 0x00 - AUX_IRQ?
(0x00 => __reserved_1),
(0x04 => AUX_ENABLES: ReadWrite<u32, AUX_ENABLES::Register>),
(0x08 => __reserved_2),
(0x40 => AUX_MU_IO: ReadWrite<u32>),//Mini Uart I/O Data
(0x44 => AUX_MU_IER: WriteOnly<u32>),//Mini Uart Interrupt Enable
(0x48 => AUX_MU_IIR: WriteOnly<u32, AUX_MU_IIR::Register>),
(0x4c => AUX_MU_LCR: WriteOnly<u32, AUX_MU_LCR::Register>),
(0x50 => AUX_MU_MCR: WriteOnly<u32>),
(0x54 => AUX_MU_LSR: ReadOnly<u32, AUX_MU_LSR::Register>),
// 0x58 - AUX_MU_MSR
// 0x5c - AUX_MU_SCRATCH
(0x58 => __reserved_3),
(0x60 => AUX_MU_CNTL: WriteOnly<u32, AUX_MU_CNTL::Register>),
(0x64 => AUX_MU_STAT: ReadOnly<u32, AUX_MU_STAT::Register>),
(0x68 => AUX_MU_BAUD: WriteOnly<u32, AUX_MU_BAUD::Register>),
(0x6c => @END),
}
}
type Registers = MMIODerefWrapper<RegisterBlock>;
pub struct MiniUart {
base_addr: usize,
registers: Registers,
}
pub struct PreparedMiniUart(MiniUart);
@ -130,41 +162,24 @@ impl From<Rate> for u32 {
}
}
/// Deref to RegisterBlock
///
/// Allows writing
/// ```
/// self.MU_IER.read()
/// ```
/// instead of something along the lines of
/// ```
/// unsafe { (*MiniUart::ptr()).MU_IER.read() }
/// ```
impl ops::Deref for MiniUart {
type Target = RegisterBlock;
fn deref(&self) -> &Self::Target {
unsafe { &*self.ptr() }
}
}
// [temporary] Used in mmu.rs to set up local paging
pub const UART1_BASE: usize = BcmHost::get_peripheral_address() + 0x21_5000;
pub const UART1_START: usize = 0x21_5000;
impl Default for MiniUart {
fn default() -> MiniUart {
MiniUart::new(UART1_BASE)
fn default() -> Self {
const UART1_BASE: usize = BcmHost::get_peripheral_address() + UART1_START;
unsafe { MiniUart::new(UART1_BASE) }
}
}
impl MiniUart {
pub fn new(base_addr: usize) -> MiniUart {
MiniUart { base_addr }
}
/// Returns a pointer to the register block
fn ptr(&self) -> *const RegisterBlock {
self.base_addr as *const _
/// # Safety
///
/// Unsafe, duh!
pub const unsafe fn new(base_addr: usize) -> MiniUart {
MiniUart {
registers: Registers::new(base_addr),
}
}
}
@ -173,16 +188,7 @@ impl MiniUart {
if #[cfg(not(feature = "noserial"))] {
/// Set baud rate and characteristics (115200 8N1) and map to GPIO
pub fn prepare(self, gpio: &gpio::GPIO) -> PreparedMiniUart {
// initialize UART
self.AUX_ENABLES.modify(AUX_ENABLES::MINI_UART_ENABLE::SET);
self.AUX_MU_IER.set(0);
self.AUX_MU_CNTL.set(0);
self.AUX_MU_LCR.write(AUX_MU_LCR::DATA_SIZE::EightBit);
self.AUX_MU_MCR.set(0);
self.AUX_MU_IER.set(0);
self.AUX_MU_IIR.write(AUX_MU_IIR::FIFO_CLEAR::All);
self.AUX_MU_BAUD
.write(AUX_MU_BAUD::RATE.val(Rate::Baud115200.into()));
// GPIO pins should be set up first before enabling the UART
// Pin 14
const MINI_UART_TXD: gpio::Function = gpio::Function::Alt5;
@ -190,16 +196,24 @@ impl MiniUart {
const MINI_UART_RXD: gpio::Function = gpio::Function::Alt5;
// map UART1 to GPIO pins
gpio.get_pin(14).into_alt(MINI_UART_TXD);
gpio.get_pin(15).into_alt(MINI_UART_RXD);
gpio.get_pin(14).into_alt(MINI_UART_TXD).set_pull_up_down(gpio::PullUpDown::Up);
gpio.get_pin(15).into_alt(MINI_UART_RXD).set_pull_up_down(gpio::PullUpDown::Up);
gpio::enable_uart_pins(gpio);
self.AUX_MU_CNTL
.write(AUX_MU_CNTL::RX_EN::Enabled + AUX_MU_CNTL::TX_EN::Enabled);
// initialize UART
self.registers.AUX_ENABLES.modify(AUX_ENABLES::MINI_UART_ENABLE::SET);
self.registers.AUX_MU_IER.set(0);
self.registers.AUX_MU_CNTL.set(0);
self.registers.AUX_MU_LCR.write(AUX_MU_LCR::DATA_SIZE::EightBit);
self.registers.AUX_MU_MCR.set(0);
self.registers.AUX_MU_IER.set(0);
self.registers.AUX_MU_BAUD
.write(AUX_MU_BAUD::RATE.val(Rate::Baud115200.into()));
// Clear FIFOs before using the device
self.AUX_MU_IIR.write(AUX_MU_IIR::FIFO_CLEAR::All);
self.registers.AUX_MU_IIR.write(AUX_MU_IIR::FIFO_CLEAR::All);
self.registers.AUX_MU_CNTL
.write(AUX_MU_CNTL::RX_EN::Enabled + AUX_MU_CNTL::TX_EN::Enabled);
PreparedMiniUart(self)
}
@ -214,71 +228,101 @@ impl MiniUart {
impl Drop for PreparedMiniUart {
fn drop(&mut self) {
self.0
.registers
.AUX_ENABLES
.modify(AUX_ENABLES::MINI_UART_ENABLE::CLEAR);
// @todo disable gpio.PUD ?
}
}
impl SerialOps for PreparedMiniUart {
cfg_if! {
if #[cfg(not(feature = "noserial"))] {
/// Receive a byte without console translation
fn read_byte(&self) -> u8 {
// wait until something is in the buffer
crate::arch::loop_until(|| self.0.registers.AUX_MU_STAT.is_set(AUX_MU_STAT::SYMBOL_AVAILABLE));
// read it and return
self.0.registers.AUX_MU_IO.get() as u8
}
fn write_byte(&self, b: u8) {
// wait until we can send
crate::arch::loop_until(|| self.0.registers.AUX_MU_STAT.is_set(AUX_MU_STAT::SPACE_AVAILABLE));
// write the character to the buffer
self.0.registers.AUX_MU_IO.set(b as u32);
}
/// Wait until the TX FIFO is empty, aka all characters have been put on the
/// line.
fn flush(&self) {
crate::arch::loop_until(|| self.0.registers.AUX_MU_STAT.is_set(AUX_MU_STAT::TX_DONE));
}
/// Consume input until RX FIFO is empty, aka all pending characters have been
/// consumed.
fn clear_rx(&self) {
crate::arch::loop_while(|| {
let pending = self.0.registers.AUX_MU_STAT.is_set(AUX_MU_STAT::SYMBOL_AVAILABLE);
if pending { self.read_byte(); }
pending
});
}
} else {
fn read_byte(&self) -> u8 { 0 }
fn write_byte(&self, _byte: u8) {}
fn flush(&self) {}
fn clear_rx(&self) {}
}
}
}
impl ConsoleOps for PreparedMiniUart {
cfg_if! {
if #[cfg(not(feature = "noserial"))] {
/// Send a character
fn putc(&self, c: char) {
// wait until we can send
crate::arch::loop_until(|| self.0.AUX_MU_LSR.is_set(AUX_MU_LSR::TX_EMPTY));
// write the character to the buffer
self.0.AUX_MU_IO.set(c as u32);
fn write_char(&self, c: char) {
self.write_byte(c as u8);
}
/// Display a string
fn puts(&self, string: &str) {
fn write_string(&self, string: &str) {
for c in string.chars() {
// convert newline to carriage return + newline
if c == '\n' {
self.putc('\r')
self.write_char('\r')
}
self.putc(c);
self.write_char(c);
}
}
/// Receive a character
fn getc(&self) -> char {
// wait until something is in the buffer
crate::arch::loop_until(|| self.0.AUX_MU_LSR.is_set(AUX_MU_LSR::DATA_READY));
fn read_char(&self) -> char {
let mut ret = self.read_byte() as char;
// read it and return
let mut ret = self.0.AUX_MU_IO.get() as u8 as char;
// convert carriage return to newline
// convert carriage return to newline -- this doesn't work well for reading binaries...
if ret == '\r' {
ret = '\n'
}
ret
}
/// Wait until the TX FIFO is empty, aka all characters have been put on the
/// line.
fn flush(&self) {
crate::arch::loop_until(|| self.0.AUX_MU_LSR.is_set(AUX_MU_LSR::TX_IDLE));
}
} else {
fn putc(&self, _c: char) {}
fn puts(&self, _string: &str) {}
fn getc(&self) -> char {
fn write_char(&self, _c: char) {}
fn write_string(&self, _string: &str) {}
fn read_char(&self) -> char {
'\n'
}
fn flush(&self) {}
}
}
}
impl fmt::Write for PreparedMiniUart {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.puts(s);
self.write_string(s);
Ok(())
}
}

View File

@ -0,0 +1,109 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
#![allow(dead_code)]
pub mod display;
pub mod fb;
pub mod gpio;
pub mod mailbox;
pub mod mini_uart;
pub mod pl011_uart;
pub mod power;
pub mod vc;
/// See BCM2835-ARM-Peripherals.pdf
/// See <https://www.raspberrypi.org/forums/viewtopic.php?t=186090> for more details.
pub struct BcmHost;
// Per <https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#peripheral-addresses>:
//
// SoC Peripheral Address Peripheral Size SDRAM Address Source
// BCM2835 0x20000000 0x01000000 0x40000000 <https://github.com/raspberrypi/linux/blob/7f465f823c2ecbade5877b8bbcb2093a8060cb0e/arch/arm/boot/dts/bcm2835.dtsi#L21>
// BCM2836 0x3f000000 0x01000000 0xc0000000 <https://github.com/raspberrypi/linux/blob/7f465f823c2ecbade5877b8bbcb2093a8060cb0e/arch/arm/boot/dts/bcm2836.dtsi#L10>
// BCM2837 0x3f000000 0x01000000 0xc0000000 <https://github.com/raspberrypi/linux/blob/7f465f823c2ecbade5877b8bbcb2093a8060cb0e/arch/arm/boot/dts/bcm2837.dtsi#L9>
// BCM2711 0xfe000000 0x01800000 0xc0000000 <https://github.com/raspberrypi/linux/blob/7f465f823c2ecbade5877b8bbcb2093a8060cb0e/arch/arm/boot/dts/bcm2711.dtsi#L41>
// <https://www.raspberrypi.com/documentation/computers/processors.html>
// The BCM2835 is the Broadcom chip used in the Raspberry Pi Model A, B, B+, the Compute Module, and the Raspberry Pi Zero.
// The BCM2836 is used in the Raspberry Pi 2 Model B.
// The BCM2837 is used in the Raspberry Pi 3, and in later models of the Raspberry Pi 2.
// The BCM2837B0 is used in the Raspberry Pi 3B+ and 3A+.
// The BCM2711 is used in the Raspberry Pi 4 Model B.
// RP3A0 (BCM2710A1 — which is the die packaged inside the BCM2837 chip - Raspberry Pi 3) used in Raspberry Pi Zero 2 W
// Machine Board Chip
// raspi1 raspi bcm2835
// raspi1 raspi bcm2835
// raspi3b+ raspi bcm2837
// raspi4 raspi bcm2711
impl BcmHost {
/// At which address to load the kernel binary.
pub const fn kernel_load_address() -> u64 {
0x8_0000
}
/// As per <https://www.raspberrypi.org/forums/viewtopic.php?p=1170522#p1170522>
///
pub fn bus2phys(bus: usize) -> usize {
bus & !0xc000_0000
}
pub fn phys2bus(phys: usize) -> usize {
phys | 0xc000_0000
}
}
// RasPi3B+
#[cfg(feature = "rpi3")]
impl BcmHost {
/// Name of the hardware device this BcmHost is compiled for.
pub const fn board_name() -> &'static str {
"Raspberry Pi 3+"
}
/// This returns the ARM-side physical address where peripherals are mapped.
///
pub const fn get_peripheral_address() -> usize {
0x3f00_0000
}
/// This returns the size of the peripherals' space.
pub const fn get_peripheral_size() -> usize {
0x0100_0000
}
/// This returns the bus address of the SDRAM.
pub const fn get_sdram_address() -> usize {
0xc000_0000 // uncached
}
}
// RasPi4
#[cfg(feature = "rpi4")]
impl BcmHost {
/// Name of the hardware device this BcmHost is compiled for.
pub const fn board_name() -> &'static str {
"Raspberry Pi 4+"
}
/// This returns the ARM-side physical address where peripherals are mapped.
///
pub const fn get_peripheral_address() -> usize {
0xfe00_0000
}
/// This returns the size of the peripherals' space.
pub const fn get_peripheral_size() -> usize {
0x0180_0000
}
/// This returns the bus address of the SDRAM.
pub const fn get_sdram_address() -> usize {
0xc000_0000 // uncached
}
}

View File

@ -0,0 +1,448 @@
/*
* SPDX-License-Identifier: MIT OR BlueOak-1.0.0
* Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
* Original code distributed under MIT, additional changes are under BlueOak-1.0.0
*
* http://infocenter.arm.com/help/topic/com.arm.doc.ddi0183g/DDI0183G_uart_pl011_r1p5_trm.pdf
* https://docs.rs/embedded-serial/0.5.0/embedded_serial/
*/
use {
super::{
gpio,
mailbox::{self, Mailbox, MailboxOps},
BcmHost,
},
crate::{
arch::loop_while,
devices::{ConsoleOps, SerialOps},
platform::MMIODerefWrapper,
},
snafu::Snafu,
tock_registers::{
interfaces::{ReadWriteable, Readable, Writeable},
register_bitfields, register_structs,
registers::{ReadOnly, ReadWrite, WriteOnly},
},
};
// PL011 UART registers.
//
// Descriptions taken from
// https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf
register_bitfields! {
u32,
/// Flag Register
FR [
/// Transmit FIFO empty. The meaning of this bit depends on the
/// state of the FEN bit in the Line Control Register, If the
/// FIFO is disabled, this bit is set when the transmit holding
/// register is empty. If the FIFO is enabled, the TXFE bit is
/// set when the transmit FIFO is empty. This bit does not indicate
/// if there is data in the transmit shift register.
TXFE OFFSET(7) NUMBITS(1) [],
/// Receive FIFO full. The meaning of this bit depends on the
/// state of the FEN bit in the LCRH Register. If the FIFO is
/// disabled, this bit is set when the receive holding register
/// is full. If the FIFO is enabled, the RXFF bit is set when
/// the receive FIFO is full.
RXFF OFFSET(6) NUMBITS(1) [],
/// Transmit FIFO full. The meaning of this bit depends on the
/// state of the FEN bit in the LCRH Register. If the
/// FIFO is disabled, this bit is set when the transmit
/// holding register is full. If the FIFO is enabled, the TXFF
/// bit is set when the transmit FIFO is full.
TXFF OFFSET(5) NUMBITS(1) [],
/// Receive FIFO empty. The meaning of this bit depends on the
/// state of the FEN bit in the LCRH Register. If the
/// FIFO is disabled, this bit is set when the receive holding
/// register is empty. If the FIFO is enabled, the RXFE bit is
/// set when the receive FIFO is empty.
RXFE OFFSET(4) NUMBITS(1) [],
/// UART busy. If this bit is set to 1, the UART is busy
/// transmitting data. This bit remains set until the complete
/// byte, including all the stop bits, has been sent from the
/// shift register. This bit is set as soon as the transmit FIFO
/// becomes non-empty, regardless of whether the UART is enabled or not.
BUSY OFFSET(3) NUMBITS(1) []
],
/// Integer Baud rate divisor
IBRD [
/// Integer Baud rate divisor
IBRD OFFSET(0) NUMBITS(16) []
],
/// Fractional Baud rate divisor
FBRD [
/// Fractional Baud rate divisor
FBRD OFFSET(0) NUMBITS(6) []
],
/// Line Control register
LCRH [
Parity OFFSET(1) NUMBITS(1) [
Disabled = 0,
Enabled = 1
],
/// Use 2 stop bits
Stop2 OFFSET(3) NUMBITS(1) [
Disabled = 0,
Enabled = 1
],
Fifo OFFSET(4) NUMBITS(1) [
Disabled = 0,
Enabled = 1
],
/// Word length. These bits indicate the number of data bits
/// transmitted or received in a frame.
WordLength OFFSET(5) NUMBITS(2) [
FiveBit = 0b00,
SixBit = 0b01,
SevenBit = 0b10,
EightBit = 0b11
]
],
/// Control Register
CR [
/// Receive enable. If this bit is set to 1, the receive
/// section of the UART is enabled. Data reception occurs for
/// UART signals. When the UART is disabled in the middle of
/// reception, it completes the current character before
/// stopping.
RXE OFFSET(9) NUMBITS(1) [
Disabled = 0,
Enabled = 1
],
/// Transmit enable. If this bit is set to 1, the transmit
/// section of the UART is enabled. Data transmission occurs
/// for UART signals. When the UART is disabled in the middle
/// of transmission, it completes the current character before
/// stopping.
TXE OFFSET(8) NUMBITS(1) [
Disabled = 0,
Enabled = 1
],
/// UART enable
UARTEN OFFSET(0) NUMBITS(1) [
/// If the UART is disabled in the middle of transmission
/// or reception, it completes the current character
/// before stopping.
Disabled = 0,
Enabled = 1
]
],
/// Interupt Clear Register
ICR [
/// Meta field for all pending interrupts
ALL OFFSET(0) NUMBITS(11) []
],
/// Interupt Mask Set/Clear Register
IMSC [
/// Meta field for all interrupts
ALL OFFSET(0) NUMBITS(11) []
],
/// DMA Control Register
DMACR [
// RX DMA enabled
RXDMAE OFFSET(0) NUMBITS(1) [
Disabled = 0,
Enabled = 1
],
// TX DMA enabled
TXDMAE OFFSET(0) NUMBITS(1) [
Disabled = 0,
Enabled = 1
],
]
}
// https://developer.arm.com/documentation/ddi0183/g/programmers-model/summary-of-registers?lang=en
register_structs! {
#[allow(non_snake_case)]
RegisterBlock {
(0x00 => Data: ReadWrite<u32>), // DR
(0x04 => Status: ReadWrite<u32>), // RSR/ECR
(0x08 => __reserved_1),
(0x18 => Flag: ReadOnly<u32, FR::Register>),
(0x1c => __reserved_2),
(0x24 => IntegerBaudRate: ReadWrite<u32, IBRD::Register>),
(0x28 => FractionalBaudRate: ReadWrite<u32, FBRD::Register>),
(0x2c => LineControl: ReadWrite<u32, LCRH::Register>),
(0x30 => Control: ReadWrite<u32, CR::Register>),
(0x34 => InterruptFifoLevelSelect: ReadWrite<u32>),
(0x38 => InterruptMaskSetClear: ReadWrite<u32, IMSC::Register>),
(0x3c => RawInterruptStatus: ReadOnly<u32>),
(0x40 => MaskedInterruptStatus: ReadOnly<u32>),
(0x44 => InterruptClear: WriteOnly<u32, ICR::Register>),
(0x48 => DmaControl: ReadWrite<u32, DMACR::Register>),
(0x4c => __reserved_3),
(0x1000 => @END),
}
}
#[derive(Debug, Snafu)]
pub enum PL011UartError {
#[snafu(display("PL011 UART setup failed in mailbox operation"))]
MailboxError,
#[snafu(display(
"PL011 UART setup failed due to integer baud rate divisor out of range ({})",
ibrd
))]
InvalidIntegerDivisor { ibrd: u32 },
#[snafu(display(
"PL011 UART setup failed due to fractional baud rate divisor out of range ({})",
fbrd
))]
InvalidFractionalDivisor { fbrd: u32 },
}
pub type Result<T> = ::core::result::Result<T, PL011UartError>;
type Registers = MMIODerefWrapper<RegisterBlock>;
pub struct PL011Uart {
registers: Registers,
}
pub struct PreparedPL011Uart(PL011Uart);
pub struct RateDivisors {
integer_baud_rate_divisor: u32,
fractional_baud_rate_divisor: u32,
}
impl RateDivisors {
// Set integer & fractional part of baud rate.
// Integer = clock/(16 * Baud)
// e.g. 3000000 / (16 * 115200) = 1.627 = ~1.
// Fraction = (Fractional part * 64) + 0.5
// e.g. (.627 * 64) + 0.5 = 40.6 = ~40.
//
// Use integer-only calculation based on [this page](https://krinkinmu.github.io/2020/11/29/PL011.html)
// Calculate 64 * clock / (16 * rate) = 4 * clock / rate, then extract 6 lowest bits for fractional part
// and the next 16 bits for integer part.
pub fn from_clock_and_rate(clock: u64, baud_rate: u32) -> Result<RateDivisors> {
let value = 4 * clock / baud_rate as u64;
let i = ((value >> 6) & 0xffff) as u32;
let f = (value & 0x3f) as u32;
// TODO: check for integer overflow, i.e. any bits set above the 0x3fffff mask.
// FIXME: can't happen due to calculation above
if i > 65535 {
return Err(PL011UartError::InvalidIntegerDivisor { ibrd: i });
}
// FIXME: can't happen due to calculation above
if f > 63 {
return Err(PL011UartError::InvalidFractionalDivisor { fbrd: f });
}
Ok(RateDivisors {
integer_baud_rate_divisor: i,
fractional_baud_rate_divisor: f,
})
}
}
pub const UART0_START: usize = 0x20_1000;
impl Default for PL011Uart {
fn default() -> Self {
const UART0_BASE: usize = BcmHost::get_peripheral_address() + UART0_START;
unsafe { PL011Uart::new(UART0_BASE) }
}
}
impl PL011Uart {
/// # Safety
///
/// Unsafe, duh!
pub const unsafe fn new(base_addr: usize) -> PL011Uart {
PL011Uart {
registers: Registers::new(base_addr),
}
}
/// Set baud rate and characteristics (115200 8N1) and map to GPIO
pub fn prepare(self, gpio: &gpio::GPIO) -> Result<PreparedPL011Uart> {
// Turn off UART
self.registers.Control.set(0);
// Wait for any ongoing transmissions to complete
self.flush_internal();
// Flush TX FIFO
self.registers.LineControl.modify(LCRH::Fifo::Disabled);
// set up clock for consistent divisor values
const CLOCK: u32 = 4_000_000; // 4Mhz
const BAUD_RATE: u32 = 115_200;
let mut mailbox = Mailbox::<9>::default();
let index = mailbox.request();
let index = mailbox.set_clock_rate(index, mailbox::clock::UART, CLOCK);
let mailbox = mailbox.end(index);
if mailbox.call(mailbox::channel::PropertyTagsArmToVc).is_err() {
return Err(PL011UartError::MailboxError); // Abort if UART clocks couldn't be set
};
// Pin 14
const UART_TXD: gpio::Function = gpio::Function::Alt0;
// Pin 15
const UART_RXD: gpio::Function = gpio::Function::Alt0;
// Map UART0 to GPIO pins and enable pull-ups
gpio.get_pin(14)
.into_alt(UART_TXD)
.set_pull_up_down(gpio::PullUpDown::Up);
gpio.get_pin(15)
.into_alt(UART_RXD)
.set_pull_up_down(gpio::PullUpDown::Up);
// Clear pending interrupts
self.registers.InterruptClear.write(ICR::ALL::SET);
// From the PL011 Technical Reference Manual:
//
// The LCR_H, IBRD, and FBRD registers form the single 30-bit wide LCR Register that is
// updated on a single write strobe generated by a LCR_H write. So, to internally update the
// contents of IBRD or FBRD, a LCR_H write must always be performed at the end.
//
// Set the baud rate divisors, 8N1 and FIFO enabled.
let divisors = RateDivisors::from_clock_and_rate(CLOCK.into(), BAUD_RATE)?;
self.registers
.IntegerBaudRate
.write(IBRD::IBRD.val(divisors.integer_baud_rate_divisor & 0xffff));
self.registers
.FractionalBaudRate
.write(FBRD::FBRD.val(divisors.fractional_baud_rate_divisor & 0b11_1111));
self.registers.LineControl.write(
LCRH::WordLength::EightBit
+ LCRH::Fifo::Enabled
+ LCRH::Parity::Disabled
+ LCRH::Stop2::Disabled,
);
// Mask all interrupts by setting corresponding bits to 1
self.registers.InterruptMaskSetClear.write(IMSC::ALL::SET);
// Disable DMA
self.registers
.DmaControl
.write(DMACR::RXDMAE::Disabled + DMACR::TXDMAE::Disabled);
// Turn on UART
self.registers
.Control
.write(CR::UARTEN::Enabled + CR::TXE::Enabled + CR::RXE::Enabled);
Ok(PreparedPL011Uart(self))
}
fn flush_internal(&self) {
loop_while(|| self.registers.Flag.is_set(FR::BUSY));
}
}
impl Drop for PreparedPL011Uart {
fn drop(&mut self) {
self.0.registers.Control.set(0);
}
}
impl SerialOps for PreparedPL011Uart {
fn read_byte(&self) -> u8 {
// wait until something is in the buffer
loop_while(|| self.0.registers.Flag.is_set(FR::RXFE));
// read it and return
self.0.registers.Data.get() as u8
}
fn write_byte(&self, b: u8) {
// wait until we can send
loop_while(|| self.0.registers.Flag.is_set(FR::TXFF));
// write the character to the buffer
self.0.registers.Data.set(b as u32);
}
/// Wait until the TX FIFO is empty, aka all characters have been put on the
/// line.
fn flush(&self) {
self.0.flush_internal();
}
/// Consume input until RX FIFO is empty, aka all pending characters have been
/// consumed.
fn clear_rx(&self) {
loop_while(|| {
let pending = !self.0.registers.Flag.is_set(FR::RXFE);
if pending {
self.read_byte();
}
pending
});
}
}
impl ConsoleOps for PreparedPL011Uart {
/// Send a character
fn write_char(&self, c: char) {
self.write_byte(c as u8)
}
/// Display a string
fn write_string(&self, string: &str) {
for c in string.chars() {
// convert newline to carriage return + newline
if c == '\n' {
self.write_char('\r')
}
self.write_char(c);
}
}
/// Receive a character
fn read_char(&self) -> char {
let mut ret = self.read_byte() as char;
// convert carriage return to newline
if ret == '\r' {
ret = '\n'
}
ret
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test_case]
fn test_divisors() {
const CLOCK: u64 = 3_000_000;
const BAUD_RATE: u32 = 115_200;
let divisors = RateDivisors::from_clock_and_rate(CLOCK, BAUD_RATE);
assert_eq!(divisors.integer_baud_rate_divisor, 1);
assert_eq!(divisors.fractional_baud_rate_divisor, 40);
}
}

View File

@ -11,23 +11,24 @@ use {
mailbox::{channel, Mailbox, MailboxOps},
BcmHost,
},
crate::arch::loop_delay,
core::ops,
crate::platform::MMIODerefWrapper,
snafu::Snafu,
tock_registers::{
interfaces::{Readable, Writeable},
register_structs,
registers::ReadWrite,
},
};
const POWER_BASE: usize = BcmHost::get_peripheral_address() + 0x0010_001C;
#[allow(non_snake_case)]
#[repr(C)]
pub struct RegisterBlock {
PM_RSTC: ReadWrite<u32>, // 0x1C
PM_RSTS: ReadWrite<u32>, // 0x20
PM_WDOG: ReadWrite<u32>, // 0x24
register_structs! {
#[allow(non_snake_case)]
RegisterBlock {
(0x00 => __reserved_1),
(0x1c => PM_RSTC: ReadWrite<u32>),
(0x20 => PM_RSTS: ReadWrite<u32>),
(0x24 => PM_WDOG: ReadWrite<u32>),
(0x28 => @END),
}
}
const PM_PASSWORD: u32 = 0x5a00_0000;
@ -50,34 +51,40 @@ pub enum PowerError {
#[snafu(display("Power setup failed in mailbox operation"))]
MailboxError,
}
pub type Result<T> = ::core::result::Result<T, PowerError>;
type Registers = MMIODerefWrapper<RegisterBlock>;
const POWER_START: usize = 0x0010_0000;
/// Public interface to the Power subsystem
pub struct Power;
pub struct Power {
registers: Registers,
}
impl ops::Deref for Power {
type Target = RegisterBlock;
fn deref(&self) -> &Self::Target {
unsafe { &*Self::ptr() }
impl Default for Power {
fn default() -> Power {
const POWER_BASE: usize = BcmHost::get_peripheral_address() + POWER_START;
unsafe { Power::new(POWER_BASE) }
}
}
impl Power {
pub fn new() -> Power {
Power
}
/// Returns a pointer to the register block
fn ptr() -> *const RegisterBlock {
POWER_BASE as *const _
/// # Safety
///
/// Unsafe, duh!
pub const unsafe fn new(base_addr: usize) -> Power {
Power {
registers: Registers::new(base_addr),
}
}
/// Shutdown the board
pub fn off(&self, gpio: &gpio::GPIO) -> Result<()> {
// power off devices one by one
for dev_id in 0..16 {
let mut mbox = Mailbox::default();
let mut mbox = Mailbox::<8>::default();
let index = mbox.request();
let index =
mbox.set_device_power(index, dev_id, POWER_STATE_OFF | POWER_STATE_DO_NOT_WAIT);
@ -87,30 +94,14 @@ impl Power {
.map_err(|_| PowerError::MailboxError)?;
}
// power off gpio pins (but not VCC pins)
for bank in 0..5 {
gpio.FSEL[bank].set(0);
}
gpio.PUD.set(0);
loop_delay(150);
gpio.PUDCLK[0].set(0xffff_ffff);
gpio.PUDCLK[1].set(0xffff_ffff);
loop_delay(150);
// flush GPIO setup
gpio.PUDCLK[0].set(0);
gpio.PUDCLK[1].set(0);
gpio.power_off();
// We set the watchdog hard reset bit here to distinguish this
// reset from the normal (full) reset. bootcode.bin will not
// reboot after a hard reset.
let mut val = self.PM_RSTS.get();
let mut val = self.registers.PM_RSTS.get();
val |= PM_PASSWORD | PM_RSTS_RASPBERRYPI_HALT;
self.PM_RSTS.set(val);
self.registers.PM_RSTS.set(val);
// Continue with normal reset mechanism
self.reset();
@ -119,11 +110,11 @@ impl Power {
/// Reboot
pub fn reset(&self) -> ! {
// use a timeout of 10 ticks (~150us)
self.PM_WDOG.set(PM_PASSWORD | 10);
let mut val = self.PM_RSTC.get();
self.registers.PM_WDOG.set(PM_PASSWORD | 10);
let mut val = self.registers.PM_RSTC.get();
val &= PM_RSTC_WRCFG_CLR;
val |= PM_PASSWORD | PM_RSTC_WRCFG_FULL_RESET;
self.PM_RSTC.set(val);
self.registers.PM_RSTC.set(val);
crate::endless_sleep()
}

View File

@ -8,7 +8,7 @@ use {
mailbox::{self, channel, response::VAL_LEN_FLAG, Mailbox, MailboxOps},
BcmHost,
},
crate::println,
crate::{platform::rpi3::mailbox::MailboxStorageRef, println},
core::convert::TryInto,
snafu::Snafu,
};
@ -42,7 +42,7 @@ impl VC {
* (if the base or size has changed) is implicitly freed.
*/
let mut mbox = Mailbox::default();
let mut mbox = Mailbox::<36>::default();
let index = mbox.request();
let index = mbox.set_physical_wh(index, w, h);
let index = mbox.set_virtual_wh(index, w, h);
@ -67,7 +67,7 @@ impl VC {
// SetPixelOrder doesn't work in QEMU, however TestPixelOrder does.
// Apparently, QEMU doesn't care about intermixing Get/Set and Test tags either.
let mut mbox = Mailbox::default();
let mut mbox = Mailbox::<36>::default();
let index = mbox.request();
#[cfg(qemu)]
let index = mbox.test_pixel_order(index, 1);

View File

@ -12,7 +12,6 @@ pub mod semihosting {
qemu_exit_handle.exit_success()
}
#[cfg(test)]
pub fn exit_failure() -> ! {
use qemu_exit::QEMUExit;
@ -22,12 +21,11 @@ pub mod semihosting {
qemu_exit_handle.exit_failure()
}
#[cfg(test)]
pub fn sys_write0_call(text: &str) {
// SAFETY: text must be \0-terminated!
let cmd = 0x04;
unsafe {
asm!(
core::arch::asm!(
"hlt #0xF000"
, in("w0") cmd
, in("x1") text.as_ptr() as u64

View File

@ -8,7 +8,7 @@
use crate::{print, println, qemu};
pub trait TestFn {
fn run(&self) -> ();
fn run(&self);
}
impl<T> TestFn for T
@ -22,7 +22,6 @@ where
}
}
#[cfg(test)]
pub fn test_runner(tests: &[&dyn TestFn]) {
println!("Running {} tests", tests.len());
for test in tests {

View File

@ -1,8 +1,8 @@
[package]
name = "vesper"
name = "nucleus"
version = "0.0.1"
authors = ["Berkus Decker <berkus+vesper@metta.systems>"]
description = "Vesper exokernel"
description = "Vesper nanokernel binary"
documentation = "https://docs.metta.systems/vesper"
homepage = "https://github.com/metta-systems/vesper"
repository = "https://github.com/metta-systems/vesper"
@ -10,30 +10,30 @@ readme = "README.md"
license = "BlueOak-1.0.0"
categories = ["no-std", "embedded", "os"]
publish = false
edition = "2018"
edition = "2021"
[badges]
maintenance = { status = "experimental" }
[features]
noserial = []
noserial = ["machine/noserial"]
# Enable JTAG debugging of kernel - enable jtag helpers and
# block waiting for JTAG probe attach at the start of kernel main.
jtag = []
jtag = ["machine/jtag"]
# Build for running under QEMU with semihosting, so various halt/reboot options would for example quit QEMU instead.
qemu = ["qemu-exit"]
qemu = ["machine/qemu"]
# Mutually exclusive features to choose a target board
rpi3 = ["machine/rpi3"]
rpi4 = ["machine/rpi4"]
[dependencies]
machine = { path = "../machine" }
r0 = "1.0"
qemu-exit = { version = "2.0", optional = true }
cortex-a = "6.0"
cortex-a = "7.0"
tock-registers = "0.7"
ux = { version = "0.1.3", default-features = false }
usize_conversions = "0.2.0"
bit_field = "0.10.1"
bitflags = "1.2"
ux = { version = "0.1", default-features = false }
usize_conversions = "0.2"
bit_field = "0.10"
bitflags = "1.3"
cfg-if = "1.0"
snafu = { version = "0.7.0-beta.0", default-features = false }
static_assertions = "1.1.0"
#enum_dispatch = "0.3"
#tap = "1.0" -- check no_std?
snafu = { version = "0.7", default-features = false }

View File

@ -3,86 +3,42 @@
#
# Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
#
[tasks.build]
env = { "TARGET_FEATURES" = "" }
args = ["build", "--target=${TARGET_JSON}", "--release", "--features=${TARGET_FEATURES}"]
[tasks.expand]
env = { "TARGET_FEATURES" = "" }
args = ["expand", "--target=${TARGET_JSON}", "--release", "--features=${TARGET_FEATURES}"]
[tasks.test]
env = { "TARGET_FEATURES" = "${QEMU_FEATURES}" }
args = ["test", "--target=${TARGET_JSON}", "--features=${TARGET_FEATURES}"]
[tasks.docs]
env = { "TARGET_FEATURES" = "" }
args = ["doc", "--open", "--no-deps", "--target=${TARGET_JSON}", "--features=${TARGET_FEATURES}"]
# These tasks are written in cargo-make's own script to make it portable across platforms (no `basename` on Windows)
[tasks.kernel-binary]
script_runner = "@duckscript"
script = [
'''
cp ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/${TARGET}/release/vesper ${KERNEL_ELF}
exec --fail-on-error ${OBJCOPY} %{OBJCOPY_PARAMS} ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/${TARGET}/release/vesper ${KERNEL_BIN}
'''
]
[tasks.custom-binary]
script_runner = "@duckscript"
script = [
'''
binaryFile = basename ${CARGO_MAKE_TASK_ARGS}
cp ${CARGO_MAKE_TASK_ARGS} ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/${binaryFile}.elf
exec --fail-on-error ${OBJCOPY} %{OBJCOPY_PARAMS} ${CARGO_MAKE_TASK_ARGS} ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/${binaryFile}.bin
'''
]
[tasks.test-runner]
dependencies = ["custom-binary"]
script_runner = "@duckscript"
script = [
'''
binaryFile = basename ${CARGO_MAKE_TASK_ARGS}
exec --fail-on-error ${QEMU} %{QEMU_OPTS} %{QEMU_TESTS_OPTS} -dtb ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/targets/bcm2710-rpi-3-b-plus.dtb -kernel ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/${binaryFile}.bin
'''
]
[tasks.build-qemu]
env = { "TARGET_FEATURES" = "${QEMU_FEATURES}" }
command = "cargo"
args = ["build", "--target=${TARGET_JSON}", "--release", "--features=${TARGET_FEATURES}"]
[tasks.qemu-runner]
env = { "TARGET_FEATURES" = "${QEMU_FEATURES}" }
script = [
"${QEMU} ${QEMU_OPTS} ${QEMU_RUNNER_OPTS} -dtb ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/targets/bcm2710-rpi-3-b-plus.dtb -kernel ${KERNEL_BIN}"
]
dependencies = ["build-qemu", "kernel-binary"]
env = { "BINARY_FILE" = "${KERNEL_ELF}" }
run_task = "custom-binary"
[tasks.qemu]
env = { "QEMU_RUNNER_OPTS" = "${QEMU_SERIAL_OPTS}", "TARGET_DTB" = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/targets/bcm2710-rpi-3-b-plus.dtb" }
extend = "qemu-runner"
env = { "QEMU_RUNNER_OPTS" = "${QEMU_SERIAL_OPTS}" }
[tasks.qemu-cb]
disabled = true
[tasks.qemu-gdb]
env = { "QEMU_RUNNER_OPTS" = "${QEMU_SERIAL_OPTS} ${QEMU_GDB_OPTS}", "TARGET_DTB" = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/targets/bcm2710-rpi-3-b-plus.dtb" }
extend = "qemu-runner"
env = { "QEMU_RUNNER_OPTS" = "${QEMU_SERIAL_OPTS} ${QEMU_GDB_OPTS}" }
[tasks.openocd]
dependencies = ["build", "kernel-binary"]
script = [
"${OPENOCD} -f interface/jlink.cfg -f ../ocd/${TARGET_BOARD}_target.cfg"
]
[tasks.zellij-nucleus]
env = { "KERNEL_BIN" = "${KERNEL_BIN}" }
run_task = "zellij-config"
[tasks.zellij-cb]
disabled = true
[tasks.zellij-cb-gdb]
disabled = true
[tasks.gdb-config]
script_runner = "@duckscript"
script = [
'''
writefile ${GDB_CONNECT_FILE} "target extended-remote :5555\n"
appendfile ${GDB_CONNECT_FILE} "break 0x80000\n"
appendfile ${GDB_CONNECT_FILE} "continue\n"
'''
]
# Problem: this eats STDIN! need to run in subshell or sth
[tasks.gdb]
dependencies = ["build", "kernel-binary", "gdb-config"]
env = { "RUST_GDB" = "${GDB}" }
@ -90,33 +46,34 @@ script = [
"rust-gdb -x ${GDB_CONNECT_FILE} ${KERNEL_ELF}"
]
[tasks.gdb-cb]
disabled = true
[tasks.install-nm]
install_crate = { crate_name = "cargo-binutils", binary = "rust-nm", test_arg = ["--help"] }
[tasks.install-rustfilt]
install_crate = { crate_name = "rustfilt", binary = "rustfilt", test_arg = ["--help"] }
[tasks.nm]
dependencies = ["build", "kernel-binary"]
dependencies = ["build", "kernel-binary", "install-nm", "install-rustfilt"]
script = [
"${NM} -- ${KERNEL_ELF} | sort"
"${NM} -- ${KERNEL_ELF} | sort -k 1 | rustfilt"
]
#install_crate = "cargo-binutils"
[tasks.sdcard]
dependencies = ["build", "kernel-binary"]
script_runner = "@duckscript"
script = [
'''
kernelImage = basename ${KERNEL_BIN}
kernelImage = set "kernel8.img"
cp ${KERNEL_BIN} ${VOLUME}/${kernelImage}
echo "Copied nucleus to ${VOLUME}/${kernelImage}"
'''
]
[tasks.sdeject]
dependencies = ["sdcard"]
script = [
"diskutil unmount ${VOLUME}"
]
[tasks.clippy]
env = { "TARGET_FEATURES" = { value = "--features=${CLIPPY_FEATURES}", condition = { env_set = ["CLIPPY_FEATURES"] } } }
command = "cargo"
args = ["clippy", "--target=${TARGET_JSON}", "@@remove-empty(TARGET_FEATURES)", "--", "-D", "warnings"]
[tasks.cb-eject]
disabled = true
[tasks.hopper]
dependencies = ["build", "kernel-binary"]

6
nucleus/build.rs Normal file
View File

@ -0,0 +1,6 @@
const LINKER_SCRIPT: &str = "linker/aarch64.ld";
fn main() {
println!("cargo:rerun-if-changed={}", LINKER_SCRIPT);
println!("cargo:rustc-link-arg=--script={}", LINKER_SCRIPT);
}

View File

@ -1,151 +0,0 @@
# Memory Configuration
The types VirtAddr and PhysAddr are representing the addresses before and after the mapping in the MMU.
Page table types must represent pages of differing sizes.
For every entry in the MMU page table we should be able to receive a proper page type - e.g. Invalid, further page table, or
a specific-size page.
----
For more information please re-read.
----
## Plan
1. MMU tables - because we need separate memspaces for kernel and userspace
1a. Allocate initial page tables
1b. Map over available RAM sensibly
1c. Create kernel's own mapping (TTBR_EL1)
## What does the kernel MMU code support?
* mapping
* unmapping
* switching per-process mappings (virtspaces)
* virt2phys resolution
* direct phys access for kernel (TTBR_EL1 mapping to physmem)
* initial kernel memory allocation: for mapping tables and capnodes, for initial thread TCB and stacks
## public api
ARMMU invocations:
on page directory cap
cache maintenance (clean/invalidate/unify)
on page table cap
map
unmap
on small frame/frame caps
map
remap
unmap
cache maintenance (clean/invalidate/unify)
get address
on asid control cap
on asid pool cap
## Minimum Required Functionality (build from this)
* resolve VA to PA - resolving lets kernel access mapped process memory.
(start from the process' virtspace root - Page Directory)
* flush page, pd, pt, virtspace - will be important for thread switching
* map a page table to appropriate location
* unmap entire mapped page table
* map a phys frame to virt location
* unmap a mapped frame
## Requirements
GIVEN
A PageGlobalDirectory of process
FIND
A kernel-physical address of where it contains a certain leaf node.
## sel4
> seL4 does not provide virtual memory management, beyond kernel primitives for manipulating hardware paging structures. User-level must provide services for creating intermediate paging structures, mapping and unmapping pages.
> Users are free to define their own address space layout with one restriction: the seL4 kernel claims the high part of the virtual memory range. On most 32-bit platforms, this is 0xe0000000 and above. This variable is set per platform, and can be found by finding the kernelBase variable in the seL4 source.
(from https://docs.sel4.systems/Tutorials/mapping.html)
> Note that to map a frame multiple times, one must make copies of the frame capability: each frame capability can only track one mapping.
## howto steps
initial mapping:
* for kernel space -
1. obtain memory map
2. build a list of regions available as RAM
3. find largest covering page sizes
4. allocate several memory pages and fill them with table info
(need page table creation functions here)
5. now kernel is able to address physical memory through it's (negative) kernel mapping.
6. prepare init thread VSpace
- this is more complicated wrt mapping
// The region of the initial thread is the user image + ipcbuf and boot info.
/* setup virtual memory for the kernel */
map_kernel_window();
/* Construct an initial address space with enough virtual addresses
* to cover the user image + ipc buffer and bootinfo frames */
it_pd_cap = create_it_address_space(root_cnode_cap, it_v_reg);
/* Create and map bootinfo frame cap */
create_bi_frame_cap(
root_cnode_cap,
it_pd_cap,
bi_frame_pptr,
bi_frame_vptr
);
/* create the initial thread's IPC buffer */
ipcbuf_cap = create_ipcbuf_frame(root_cnode_cap, it_pd_cap, ipcbuf_vptr);
/* create all userland image frames */
create_frames_ret =
create_frames_of_region(
root_cnode_cap,
it_pd_cap,
ui_reg,
true,
pv_offset
);
ndks_boot.bi_frame->userImageFrames = create_frames_ret.region;
... later ...
/* create the initial thread */
if (!create_initial_thread(
root_cnode_cap,
it_pd_cap,
v_entry,
bi_frame_vptr,
ipcbuf_vptr,
ipcbuf_cap
)) {
/* create all of the untypeds. Both devices and kernel window memory */
if (!create_untypeds(
root_cnode_cap,
(region_t) {
0xf0000000, (pptr_t)ki_boot_end
} /* reusable boot code/data */
)) {
return false;
}

View File

@ -1,17 +0,0 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
mod asid;
mod phys_addr;
mod virt_addr;
pub use asid::*;
pub use phys_addr::*;
pub use virt_addr::*;
// @todo Check largest VA supported, calculate physical_memory_offset
// @todo Keep in mind amount of physical memory present, the following
// @todo will only work for 1Gb board:
pub const PHYSICAL_MEMORY_OFFSET: u64 = 0xffff_8000_0000_0000; // Last 1GiB of VA space

View File

@ -1,106 +0,0 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
*/
use super::{Frame, FrameAllocator};
use multiboot2::{MemoryArea, MemoryAreaIter}; // replace with DTB?
pub struct AreaFrameAllocator {
next_free_frame: Frame,
current_area: Option<&'static MemoryArea>,
areas: MemoryAreaIter,
kernel_start: Frame,
kernel_end: Frame,
multiboot_start: Frame,
multiboot_end: Frame,
}
impl FrameAllocator for AreaFrameAllocator {
fn allocate_frame(&mut self) -> Option<Frame> {
if let Some(_area) = self.current_area {
// "Clone" the frame to return it if it's free. Frame doesn't
// implement Clone, but we can construct an identical frame.
let frame = Frame {
number: self.next_free_frame.number,
};
// the last frame of the current area
let current_area_last_frame = Frame::containing_address(0x3f00_0000);
// {
// let address = area.base_addr + area.length - 1;
// Frame::containing_address(address as usize)
// };
if frame > current_area_last_frame {
// all frames of current area are used, switch to next area
// self.choose_next_area();
unimplemented!();
} else if frame >= self.kernel_start && frame <= self.kernel_end {
// `frame` is used by the kernel
self.next_free_frame = Frame {
number: self.kernel_end.number + 1,
};
} else if frame >= self.multiboot_start && frame <= self.multiboot_end {
// `frame` is used by the multiboot information structure
self.next_free_frame = Frame {
number: self.multiboot_end.number + 1,
};
} else {
// frame is unused, increment `next_free_frame` and return it
self.next_free_frame.number += 1;
return Some(frame);
}
// `frame` was not valid, try it again with the updated `next_free_frame`
self.allocate_frame()
} else {
None // no free frames left
}
}
fn deallocate_frame(&mut self, _frame: Frame) {
unimplemented!()
}
}
// Fixme: no multiboot, but dtb instead with avail memory regions
// Need dtb parser here!
impl AreaFrameAllocator {
pub fn new(
kernel_start: usize,
kernel_end: usize,
multiboot_start: usize,
multiboot_end: usize,
memory_areas: MemoryAreaIter,
) -> AreaFrameAllocator {
let mut allocator = AreaFrameAllocator {
next_free_frame: Frame::containing_address(0),
current_area: None,
areas: memory_areas,
kernel_start: Frame::containing_address(kernel_start),
kernel_end: Frame::containing_address(kernel_end),
multiboot_start: Frame::containing_address(multiboot_start),
multiboot_end: Frame::containing_address(multiboot_end),
};
// allocator.choose_next_area();
allocator.next_free_frame = Frame::containing_address(0x100000); // start from 1Mb
allocator
}
fn choose_next_area(&mut self) {
self.current_area = self
.areas
.clone()
.filter(|area| {
let address = area.base_addr + area.length - 1;
Frame::containing_address(address as usize) >= self.next_free_frame
})
.min_by_key(|area| area.base_addr);
if let Some(area) = self.current_area {
let start_frame = Frame::containing_address(area.base_addr as usize);
if self.next_free_frame < start_frame {
self.next_free_frame = start_frame;
}
}
}
}

View File

@ -1,15 +0,0 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
*/
// Allocate regions from boot memory list obtained from devtree
pub struct BootRegionAllocator {}
impl BootRegionAllocator {
pub fn new(&boot_info: BootInfo) -> Self {
Self {}
}
pub fn alloc_region(&mut self) {}
pub fn alloc_zeroed(&mut self) {}
}

View File

@ -1,126 +0,0 @@
//! Print MMU suported features for debugging.
use {
crate::println,
cortex_a::regs::{RegisterReadOnly, RegisterReadWrite, ID_AA64MMFR0_EL1, SCTLR_EL1, TCR_EL1},
};
/// Parse the ID_AA64MMFR0_EL1 register for runtime information about supported MMU features.
/// Print the current state of TCR register.
pub fn print_features() {
let sctlr = SCTLR_EL1.extract();
if let Some(SCTLR_EL1::M::Value::Enable) = sctlr.read_as_enum(SCTLR_EL1::M) {
println!("[i] MMU currently enabled");
}
if let Some(SCTLR_EL1::I::Value::Cacheable) = sctlr.read_as_enum(SCTLR_EL1::I) {
println!("[i] MMU I-cache enabled");
}
if let Some(SCTLR_EL1::C::Value::Cacheable) = sctlr.read_as_enum(SCTLR_EL1::C) {
println!("[i] MMU D-cache enabled");
}
let mmfr = ID_AA64MMFR0_EL1.extract();
if let Some(ID_AA64MMFR0_EL1::TGran4::Value::Supported) =
mmfr.read_as_enum(ID_AA64MMFR0_EL1::TGran4)
{
println!("[i] MMU: 4 KiB granule supported!");
}
if let Some(ID_AA64MMFR0_EL1::TGran16::Value::Supported) =
mmfr.read_as_enum(ID_AA64MMFR0_EL1::TGran16)
{
println!("[i] MMU: 16 KiB granule supported!");
}
if let Some(ID_AA64MMFR0_EL1::TGran64::Value::Supported) =
mmfr.read_as_enum(ID_AA64MMFR0_EL1::TGran64)
{
println!("[i] MMU: 64 KiB granule supported!");
}
match mmfr.read_as_enum(ID_AA64MMFR0_EL1::ASIDBits) {
Some(ID_AA64MMFR0_EL1::ASIDBits::Value::Bits_16) => {
println!("[i] MMU: 16 bit ASIDs supported!")
}
Some(ID_AA64MMFR0_EL1::ASIDBits::Value::Bits_8) => {
println!("[i] MMU: 8 bit ASIDs supported!")
}
_ => println!("[i] MMU: Invalid ASID bits specified!"),
}
match mmfr.read_as_enum(ID_AA64MMFR0_EL1::PARange) {
Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_32) => {
println!("[i] MMU: Up to 32 Bit physical address range supported!")
}
Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_36) => {
println!("[i] MMU: Up to 36 Bit physical address range supported!")
}
Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_40) => {
println!("[i] MMU: Up to 40 Bit physical address range supported!")
}
Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_42) => {
println!("[i] MMU: Up to 42 Bit physical address range supported!")
}
Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_44) => {
println!("[i] MMU: Up to 44 Bit physical address range supported!")
}
Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_48) => {
println!("[i] MMU: Up to 48 Bit physical address range supported!")
}
Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_52) => {
println!("[i] MMU: Up to 52 Bit physical address range supported!")
}
_ => println!("[i] MMU: Invalid PARange specified!"),
}
let tcr = TCR_EL1.extract();
match tcr.read_as_enum(TCR_EL1::IPS) {
Some(TCR_EL1::IPS::Value::Bits_32) => {
println!("[i] MMU: 32 Bit intermediate physical address size supported!")
}
Some(TCR_EL1::IPS::Value::Bits_36) => {
println!("[i] MMU: 36 Bit intermediate physical address size supported!")
}
Some(TCR_EL1::IPS::Value::Bits_40) => {
println!("[i] MMU: 40 Bit intermediate physical address size supported!")
}
Some(TCR_EL1::IPS::Value::Bits_42) => {
println!("[i] MMU: 42 Bit intermediate physical address size supported!")
}
Some(TCR_EL1::IPS::Value::Bits_44) => {
println!("[i] MMU: 44 Bit intermediate physical address size supported!")
}
Some(TCR_EL1::IPS::Value::Bits_48) => {
println!("[i] MMU: 48 Bit intermediate physical address size supported!")
}
Some(TCR_EL1::IPS::Value::Bits_52) => {
println!("[i] MMU: 52 Bit intermediate physical address size supported!")
}
_ => println!("[i] MMU: Invalid IPS specified!"),
}
match tcr.read_as_enum(TCR_EL1::TG0) {
Some(TCR_EL1::TG0::Value::KiB_4) => println!("[i] MMU: TTBR0 4 KiB granule active!"),
Some(TCR_EL1::TG0::Value::KiB_16) => println!("[i] MMU: TTBR0 16 KiB granule active!"),
Some(TCR_EL1::TG0::Value::KiB_64) => println!("[i] MMU: TTBR0 64 KiB granule active!"),
_ => println!("[i] MMU: Invalid TTBR0 granule size specified!"),
}
let t0sz = tcr.read(TCR_EL1::T0SZ);
println!("[i] MMU: T0sz = 64-{} = {} bits", t0sz, 64 - t0sz);
match tcr.read_as_enum(TCR_EL1::TG1) {
Some(TCR_EL1::TG1::Value::KiB_4) => println!("[i] MMU: TTBR1 4 KiB granule active!"),
Some(TCR_EL1::TG1::Value::KiB_16) => println!("[i] MMU: TTBR1 16 KiB granule active!"),
Some(TCR_EL1::TG1::Value::KiB_64) => println!("[i] MMU: TTBR1 64 KiB granule active!"),
_ => println!("[i] MMU: Invalid TTBR1 granule size specified!"),
}
let t1sz = tcr.read(TCR_EL1::T1SZ);
println!("[i] MMU: T1sz = 64-{} = {} bits", t1sz, 64 - t1sz);
}

View File

@ -1,586 +0,0 @@
use {
crate::{
arch::aarch64::memory::{
get_virt_addr_properties, AttributeFields, /*FrameAllocator, PhysAddr, VirtAddr,*/
},
println,
},
// bitflags::bitflags,
core::{
// convert::TryInto,
// fmt,
marker::PhantomData,
ops::{Index, IndexMut},
// ptr::Unique,
},
cortex_a::{
barrier,
regs::{ID_AA64MMFR0_EL1, SCTLR_EL1, TCR_EL1, TTBR0_EL1},
},
register::{
cpu::{RegisterReadOnly, RegisterReadWrite},
register_bitfields,
},
// ux::*,
};
mod mair {
use cortex_a::regs::MAIR_EL1;
/// Setup function for the MAIR_EL1 register.
pub fn set_up() {
use cortex_a::regs::RegisterReadWrite;
// Define the three memory types that we will map. Normal DRAM, Uncached and device.
MAIR_EL1.write(
// Attribute 2 -- Device Memory
MAIR_EL1::Attr2_Device::nonGathering_nonReordering_EarlyWriteAck
// Attribute 1 -- Non Cacheable DRAM
+ MAIR_EL1::Attr1_Normal_Outer::NonCacheable
+ MAIR_EL1::Attr1_Normal_Inner::NonCacheable
// Attribute 0 -- Regular Cacheable
+ MAIR_EL1::Attr0_Normal_Outer::WriteBack_NonTransient_ReadWriteAlloc
+ MAIR_EL1::Attr0_Normal_Inner::WriteBack_NonTransient_ReadWriteAlloc,
);
}
// Three descriptive consts for indexing into the correct MAIR_EL1 attributes.
pub mod attr {
pub const NORMAL: u64 = 0;
pub const NORMAL_NON_CACHEABLE: u64 = 1;
pub const DEVICE_NGNRE: u64 = 2;
// DEVICE_GRE
// DEVICE_NGNRNE
}
}
/// A function that maps the generic memory range attributes to HW-specific
/// attributes of the MMU.
fn into_mmu_attributes(
attribute_fields: AttributeFields,
) -> register::FieldValue<u64, STAGE1_DESCRIPTOR::Register> {
use super::{AccessPermissions, MemAttributes};
// Memory attributes
let mut desc = match attribute_fields.mem_attributes {
MemAttributes::CacheableDRAM => {
STAGE1_DESCRIPTOR::SH::InnerShareable
+ STAGE1_DESCRIPTOR::AttrIndx.val(mair::attr::NORMAL)
}
MemAttributes::NonCacheableDRAM => {
STAGE1_DESCRIPTOR::SH::InnerShareable
+ STAGE1_DESCRIPTOR::AttrIndx.val(mair::attr::NORMAL_NON_CACHEABLE)
}
MemAttributes::Device => {
STAGE1_DESCRIPTOR::SH::OuterShareable
+ STAGE1_DESCRIPTOR::AttrIndx.val(mair::attr::DEVICE_NGNRE)
}
};
// Access Permissions
desc += match attribute_fields.acc_perms {
AccessPermissions::ReadOnly => STAGE1_DESCRIPTOR::AP::RO_EL1,
AccessPermissions::ReadWrite => STAGE1_DESCRIPTOR::AP::RW_EL1,
};
// Execute Never
desc += if attribute_fields.execute_never {
STAGE1_DESCRIPTOR::PXN::NeverExecute
} else {
STAGE1_DESCRIPTOR::PXN::Execute
};
desc
}
/// Type-safe enum wrapper covering Table<L>'s 64-bit entries.
#[derive(Clone)]
// #[repr(transparent)]
enum PageTableEntry {
/// Empty page table entry.
Invalid,
/// Table descriptor is a L0, L1 or L2 table pointing to another table.
/// L0 tables can only point to L1 tables.
/// A descriptor pointing to the next page table.
TableDescriptor(EntryFlags),
/// A Level2 block descriptor with 2 MiB aperture.
///
/// The output points to physical memory.
Lvl2BlockDescriptor(EntryFlags),
/// A page PageTableEntry::descriptor with 4 KiB aperture.
///
/// The output points to physical memory.
PageDescriptor(EntryFlags),
}
/// A descriptor pointing to the next page table. (within PageTableEntry enum)
// struct TableDescriptor(register::FieldValue<u64, STAGE1_DESCRIPTOR::Register>);
impl PageTableEntry {
fn new_table_descriptor(next_lvl_table_addr: usize) -> Result<PageTableEntry, &'static str> {
if next_lvl_table_addr % Size4KiB::SIZE as usize != 0 {
// @todo SIZE must be usize
return Err("TableDescriptor: Address is not 4 KiB aligned.");
}
let shifted = next_lvl_table_addr >> Size4KiB::SHIFT;
Ok(PageTableEntry::TableDescriptor(
STAGE1_DESCRIPTOR::VALID::True
+ STAGE1_DESCRIPTOR::TYPE::Table
+ STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(shifted as u64),
))
}
}
#[derive(Snafu, Debug)]
enum PageTableError {
#[snafu(display("BlockDescriptor: Address is not 2 MiB aligned."))]
//"PageDescriptor: Address is not 4 KiB aligned."
NotAligned(&'static str),
}
/// A Level2 block descriptor with 2 MiB aperture.
///
/// The output points to physical memory.
// struct Lvl2BlockDescriptor(register::FieldValue<u64, STAGE1_DESCRIPTOR::Register>);
impl PageTableEntry {
fn new_lvl2_block_descriptor(
output_addr: usize,
attribute_fields: AttributeFields,
) -> Result<PageTableEntry, PageTableError> {
if output_addr % Size2MiB::SIZE as usize != 0 {
return Err(PageTableError::NotAligned(Size2MiB::SIZE_AS_DEBUG_STR));
}
let shifted = output_addr >> Size2MiB::SHIFT;
Ok(PageTableEntry::Lvl2BlockDescriptor(
STAGE1_DESCRIPTOR::VALID::True
+ STAGE1_DESCRIPTOR::AF::True
+ into_mmu_attributes(attribute_fields)
+ STAGE1_DESCRIPTOR::TYPE::Block
+ STAGE1_DESCRIPTOR::LVL2_OUTPUT_ADDR_4KiB.val(shifted as u64),
))
}
}
/// A page descriptor with 4 KiB aperture.
///
/// The output points to physical memory.
impl PageTableEntry {
fn new_page_descriptor(
output_addr: usize,
attribute_fields: AttributeFields,
) -> Result<PageTableEntry, PageTableError> {
if output_addr % Size4KiB::SIZE as usize != 0 {
return Err(PageTableError::NotAligned(Size4KiB::SIZE_AS_DEBUG_STR));
}
let shifted = output_addr >> Size4KiB::SHIFT;
Ok(PageTableEntry::PageDescriptor(
STAGE1_DESCRIPTOR::VALID::True
+ STAGE1_DESCRIPTOR::AF::True
+ into_mmu_attributes(attribute_fields)
+ STAGE1_DESCRIPTOR::TYPE::Table
+ STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(shifted as u64),
))
}
}
impl From<u64> for PageTableEntry {
fn from(_val: u64) -> PageTableEntry {
// xxx0 -> Invalid
// xx11 -> TableDescriptor on L0, L1 and L2
// xx10 -> Block Entry L1 and L2
// xx11 -> PageDescriptor L3
PageTableEntry::Invalid
}
}
impl From<PageTableEntry> for u64 {
fn from(val: PageTableEntry) -> u64 {
match val {
PageTableEntry::Invalid => 0,
PageTableEntry::TableDescriptor(x)
| PageTableEntry::Lvl2BlockDescriptor(x)
| PageTableEntry::PageDescriptor(x) => x.value,
}
}
}
// to get L0 we must allocate a few frames from boot region allocator.
// So, first we init the dtb, parse mem-regions from there, then init boot_info page and start mmu,
// this part will be inited in mmu::init():
// // @todo do NOT keep these statically, always allocate from available bump memory
// static mut LVL2_TABLE: Table<PageDirectory> = Table::<PageDirectory> {
// entries: [0; NUM_ENTRIES_4KIB as usize],
// level: PhantomData,
// };
//
// // @todo do NOT keep these statically, always allocate from available bump memory
// static mut LVL3_TABLE: Table<PageTable> = Table::<PageTable> {
// entries: [0; NUM_ENTRIES_4KIB as usize],
// level: PhantomData,
// };
trait BaseAddr {
fn base_addr_u64(&self) -> u64;
fn base_addr_usize(&self) -> usize;
}
impl BaseAddr for [u64; 512] {
fn base_addr_u64(&self) -> u64 {
self as *const u64 as u64
}
fn base_addr_usize(&self) -> usize {
self as *const u64 as usize
}
}
/// Set up identity mapped page tables for the first 1 gigabyte of address space.
/// default: 880 MB ARM ram, 128MB VC
///
/// # Safety
///
/// Completely unsafe, we're in the hardware land! Incorrectly initialised tables will just
/// restart the CPU.
pub unsafe fn init() -> Result<(), &'static str> {
// Prepare the memory attribute indirection register.
mair::set_up();
// should receive in args an obtained memory map from DT
let memory_map = Regions {
start: 0x1000,
size: 0x10000,
};
// bump-allocate page tables for entire memory
// also allocate phys memory to kernel space!
//
// separate regions - regular memory, device mmaps,
// initial thread maps ALL the memory??
// instead
// init thread may map only necessary mem
// boot time only map kernel physmem space, and currently loaded kernel data
// PROBABLY only kernel mapping TTBR1 is needed, the rest is not very useful?
// take over protected memory space though anyway.
// Point the first 2 MiB of virtual addresses to the follow-up LVL3
// page-table.
// LVL2_TABLE.entries[0] =
// PageTableEntry::new_table_descriptor(LVL3_TABLE.entries.base_addr_usize())?.into();
// Fill the rest of the LVL2 (2 MiB) entries as block descriptors.
//
// Notice the skip(1) which makes the iteration start at the second 2 MiB
// block (0x20_0000).
for (block_descriptor_nr, entry) in LVL2_TABLE.entries.iter_mut().enumerate().skip(1) {
let virt_addr = block_descriptor_nr << Size2MiB::SHIFT;
let (output_addr, attribute_fields) = match get_virt_addr_properties(virt_addr) {
Err(s) => return Err(s),
Ok((a, b)) => (a, b),
};
let block_desc =
match PageTableEntry::new_lvl2_block_descriptor(output_addr, attribute_fields) {
Err(s) => return Err(s),
Ok(desc) => desc,
};
*entry = block_desc.into();
}
// Finally, fill the single LVL3 table (4 KiB granule).
for (page_descriptor_nr, entry) in LVL3_TABLE.entries.iter_mut().enumerate() {
let virt_addr = page_descriptor_nr << Size4KiB::SHIFT;
let (output_addr, attribute_fields) = match get_virt_addr_properties(virt_addr) {
Err(s) => return Err(s),
Ok((a, b)) => (a, b),
};
let page_desc = match PageTableEntry::new_page_descriptor(output_addr, attribute_fields) {
Err(s) => return Err(s),
Ok(desc) => desc,
};
*entry = page_desc.into();
}
}
// AArch64:
// Table D4-8-2021: check supported granule sizes, select alloc policy based on results.
// TTBR_ELx is the pdbr for specific page tables
// Page 2068 actual page descriptor formats
// Pointer to currently active page table
// Could be either user space (TTBR0) or kernel space (TTBR1) -- ??
pub struct ActivePageTable {
l0: Unique<Table<PageGlobalDirectory>>,
}
impl ActivePageTable {
pub unsafe fn new() -> ActivePageTable {
ActivePageTable {
l0: Unique::new_unchecked(0 as *mut _),
}
}
fn l0(&self) -> &Table<PageGlobalDirectory> {
unsafe { self.l0.as_ref() }
}
fn l0_mut(&mut self) -> &mut Table<PageGlobalDirectory> {
unsafe { self.l0.as_mut() }
}
// pub fn translate(&self, virtual_address: VirtAddr) -> Result<PhysAddr, TranslationError> {
// let offset = virtual_address % Size4KiB::SIZE as usize; // @todo use the size of the last page of course
// self.translate_page(Page::containing_address(virtual_address))?
// .map(|frame| frame.start_address() + offset)
// }
fn translate_page(&self, page: Page) -> Result<PhysFrame, TranslationError> {
// @todo translate only one level of hierarchy per impl function...
let l1 = self.l0().next_table(u64::from(page.l0_index()) as usize);
/*
let huge_page = || {
l1.and_then(|l1| {
let l1_entry = &l1[page.l1_index() as usize];
// 1GiB page?
if let Some(start_frame) = l1_entry.pointed_frame() {
if l1_entry.flags().read(STAGE1_DESCRIPTOR::TYPE)
!= STAGE1_DESCRIPTOR::TYPE::Table.value
{
// address must be 1GiB aligned
//start_frame.is_aligned()
assert!(start_frame.number % (NUM_ENTRIES_4KIB * NUM_ENTRIES_4KIB) == 0);
return Ok(PhysFrame::from_start_address(
start_frame.number
+ page.l2_index() * NUM_ENTRIES_4KIB
+ page.l3_index(),
));
}
}
if let Some(l2) = l1.next_table(page.l1_index()) {
let l2_entry = &l2[page.l2_index()];
// 2MiB page?
if let Some(start_frame) = l2_entry.pointed_frame() {
if l2_entry.flags().read(STAGE1_DESCRIPTOR::TYPE)
!= STAGE1_DESCRIPTOR::TYPE::Table
{
// address must be 2MiB aligned
assert!(start_frame.number % NUM_ENTRIES_4KIB == 0);
return Ok(PhysFrame::from_start_address(
start_frame.number + page.l3_index(),
));
}
}
}
Err(TranslationError::NoPage)
})
};
*/
let v = l1
.and_then(|l1| l1.next_table(u64::from(page.l1_index()) as usize))
.and_then(|l2| l2.next_table(u64::from(page.l2_index()) as usize))
.and_then(|l3| Some(l3[u64::from(page.l3_index()) as usize])); //.pointed_frame())
// .ok_or(TranslationError::NoPage)
// .or_else(huge_page)
Ok(v.unwrap().into())
}
pub fn map_to<A>(&mut self, page: Page, frame: PhysFrame, flags: EntryFlags, allocator: &mut A)
where
A: FrameAllocator,
{
let l0 = self.l0_mut();
let l1 = l0.next_table_create(u64::from(page.l0_index()) as usize, allocator);
let l2 = l1.next_table_create(u64::from(page.l1_index()) as usize, allocator);
let l3 = l2.next_table_create(u64::from(page.l2_index()) as usize, allocator);
assert_eq!(
l3[u64::from(page.l3_index()) as usize],
0 /*.is_unused()*/
);
l3[u64::from(page.l3_index()) as usize] = PageTableEntry::PageDescriptor(
STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(u64::from(frame))
+ flags // @todo properly extract flags
+ STAGE1_DESCRIPTOR::VALID::True,
)
.into();
}
pub fn map<A>(&mut self, page: Page, flags: EntryFlags, allocator: &mut A)
where
A: FrameAllocator,
{
// @todo fail mapping if table is not allocated, causing client to allocate and restart
// @todo problems described in preso - chicken&egg problem of allocating first allocations
let frame = allocator.allocate_frame().expect("out of memory");
self.map_to(page, frame, flags, allocator)
}
pub fn identity_map<A>(&mut self, frame: PhysFrame, flags: EntryFlags, allocator: &mut A)
where
A: FrameAllocator,
{
let page = Page::containing_address(VirtAddr::new(frame.start_address().as_u64()));
self.map_to(page, frame, flags, allocator)
}
fn unmap<A>(&mut self, page: Page, _allocator: &mut A)
where
A: FrameAllocator,
{
// use aarch64::instructions::tlb;
// use x86_64::VirtAddr;
assert!(self.translate(page.start_address()).is_ok());
let l3 = self
.l0_mut()
.next_table_mut(u64::from(page.l0_index()) as usize)
.and_then(|l1| l1.next_table_mut(u64::from(page.l1_index()) as usize))
.and_then(|l2| l2.next_table_mut(u64::from(page.l2_index()) as usize))
.expect("mapping code does not support huge pages");
let _frame = l3[u64::from(page.l3_index()) as usize];
// .pointed_frame()
// .unwrap();
l3[u64::from(page.l3_index()) as usize] = 0; /*.set_unused(); */
// tlb::flush(VirtAddr(page.start_address()));
// TODO free p(1,2,3) table if empty
//allocator.deallocate_frame(frame);
// @todo do NOT deallocate frames either, but need to signal client that it's unused
}
}
// Abstractions for page table entries.
/// The error returned by the `PageTableEntry::frame` method.
#[derive(Snafu, Debug, Clone, Copy, PartialEq)]
pub enum FrameError {
/// The entry does not have the `PRESENT` flag set, so it isn't currently mapped to a frame.
FrameNotPresent,
/// The entry has the `HUGE_PAGE` flag set. The `frame` method has a standard 4KiB frame
/// as return type, so a huge frame can't be returned. @todo
HugeFrame,
}
/// A 64-bit page table entry.
// pub struct PageTableEntry {
// entry: u64,
// }
const ADDR_MASK: u64 = 0x0000_ffff_ffff_f000;
/*
impl PageTableEntry {
/// Creates an unused page table entry.
pub fn new() -> Self {
PageTableEntry::Invalid
}
/// Returns whether this entry is zero.
pub fn is_unused(&self) -> bool {
self.entry == 0
}
/// Sets this entry to zero.
pub fn set_unused(&mut self) {
self.entry = 0;
}
/// Returns the flags of this entry.
pub fn flags(&self) -> EntryFlags {
EntryFlags::new(self.entry)
}
/// Returns the physical address mapped by this entry, might be zero.
pub fn addr(&self) -> PhysAddr {
PhysAddr::new(self.entry & ADDR_MASK)
}
/// Returns the physical frame mapped by this entry.
///
/// Returns the following errors:
///
/// - `FrameError::FrameNotPresent` if the entry doesn't have the `PRESENT` flag set.
/// - `FrameError::HugeFrame` if the entry has the `HUGE_PAGE` flag set (for huge pages the
/// `addr` function must be used)
pub fn frame(&self) -> Result<PhysFrame, FrameError> {
if !self.flags().read(STAGE1_DESCRIPTOR::VALID) {
Err(FrameError::FrameNotPresent)
// } else if self.flags().contains(EntryFlags::HUGE_PAGE) {
// Err(FrameError::HugeFrame)
} else {
Ok(PhysFrame::containing_address(self.addr()))
}
}
/// Map the entry to the specified physical address with the specified flags.
pub fn set_addr(&mut self, addr: PhysAddr, flags: EntryFlags) {
assert!(addr.is_aligned(Size4KiB::SIZE));
self.entry = addr.as_u64() | flags.bits();
}
/// Map the entry to the specified physical frame with the specified flags.
pub fn set_frame(&mut self, frame: PhysFrame, flags: EntryFlags) {
// assert!(!flags.contains(EntryFlags::HUGE_PAGE));
self.set_addr(frame.start_address(), flags)
}
/// Sets the flags of this entry.
pub fn set_flags(&mut self, flags: EntryFlags) {
// Todo: extract ADDR from self and replace all flags completely (?)
self.entry = self.addr().as_u64() | flags.bits();
}
}
impl fmt::Debug for PageTableEntry {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut f = f.debug_struct("PageTableEntry");
f.field("addr", &self.addr());
f.field("flags", &self.flags());
f.finish()
}
}*/
impl<Level> Table<Level>
where
Level: HierarchicalLevel,
{
pub fn next_table_create<Alloc>(
&mut self,
index: usize,
allocator: &mut Alloc,
) -> &mut Table<Level::NextLevel>
where
Alloc: FrameAllocator,
{
if self.next_table(index).is_none() {
assert!(
EntryRegister::new(self.entries[index]).read(STAGE1_DESCRIPTOR::TYPE)
== STAGE1_DESCRIPTOR::TYPE::Table.value,
"mapping code does not support huge pages"
);
let frame = allocator.allocate_frame().expect("no frames available");
self.entries[index] = PageTableEntry::TableDescriptor(
STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(u64::from(frame))
+ STAGE1_DESCRIPTOR::VALID::True,
)
.into();
// self.entries[index]
// .set_frame(frame, STAGE1_DESCRIPTOR::VALID::True /*| WRITABLE*/);
self.next_table_mut(index).unwrap().zero();
}
self.next_table_mut(index).unwrap()
}
}

View File

@ -1,607 +0,0 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
//! MMU initialisation.
//!
//! Paging is mostly based on [previous version](https://os.phil-opp.com/page-tables/) of
//! Phil Opp's [paging guide](https://os.phil-opp.com/paging-implementation/) and
//! [ARMv8 ARM memory addressing](https://static.docs.arm.com/100940/0100/armv8_a_address%20translation_100940_0100_en.pdf).
//! It includes ideas from Sergio Benitez' cs140e OSdev course material on type-safe access.
#![allow(dead_code)]
use {
crate::memory::{
page_size::{Size1GiB, Size2MiB, Size4KiB},
PageSize,
//virt_page::Page,
PhysAddr,
PhysFrame,
VirtAddr,
},
core::{
marker::PhantomData,
ops::{Index, IndexMut},
ptr::Unique,
},
cortex_a::barrier,
register::register_bitfields,
snafu::Snafu,
};
#[derive(Debug, Snafu)]
enum MmuError {}
pub fn init() -> Result<(), MmuError> {
// Prepare the memory attribute indirection register.
mair::set_up();
// Point to the LVL2 table base address in TTBR0.
TTBR0_EL1.set_baddr(LVL2_TABLE.entries.base_addr_u64()); // User (lo-)space addresses
// TTBR1_EL1.set_baddr(LVL2_TABLE.entries.base_addr_u64()); // Kernel (hi-)space addresses
// Configure various settings of stage 1 of the EL1 translation regime.
let ips = ID_AA64MMFR0_EL1.read(ID_AA64MMFR0_EL1::PARange);
TCR_EL1.write(
TCR_EL1::TBI0::Ignored // @todo TBI1 also set to Ignored??
+ TCR_EL1::IPS.val(ips) // Intermediate Physical Address Size
// ttbr0 user memory addresses
+ TCR_EL1::TG0::KiB_4 // 4 KiB granule
+ TCR_EL1::SH0::Inner
+ TCR_EL1::ORGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable
+ TCR_EL1::IRGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable
+ TCR_EL1::EPD0::EnableTTBR0Walks
+ TCR_EL1::T0SZ.val(34) // ARMv8ARM Table D5-11 minimum TxSZ for starting table level 2
// ttbr1 kernel memory addresses
+ TCR_EL1::TG1::KiB_4 // 4 KiB granule
+ TCR_EL1::SH1::Inner
+ TCR_EL1::ORGN1::WriteBack_ReadAlloc_WriteAlloc_Cacheable
+ TCR_EL1::IRGN1::WriteBack_ReadAlloc_WriteAlloc_Cacheable
+ TCR_EL1::EPD1::EnableTTBR1Walks
+ TCR_EL1::T1SZ.val(34), // ARMv8ARM Table D5-11 minimum TxSZ for starting table level 2
);
// Switch the MMU on.
//
// First, force all previous changes to be seen before the MMU is enabled.
unsafe {
barrier::isb(barrier::SY);
}
// use cortex_a::regs::RegisterReadWrite;
// Enable the MMU and turn on data and instruction caching.
SCTLR_EL1.modify(SCTLR_EL1::M::Enable + SCTLR_EL1::C::Cacheable + SCTLR_EL1::I::Cacheable);
// Force MMU init to complete before next instruction
/*
* Invalidate the local I-cache so that any instructions fetched
* speculatively from the PoC are discarded, since they may have
* been dynamically patched at the PoU.
*/
unsafe {
barrier::isb(barrier::SY);
}
Ok(())
}
/*
* With 4k page granule, a virtual address is split into 4 lookup parts
* spanning 9 bits each:
*
* _______________________________________________
* | | | | | | |
* | signx | Lv0 | Lv1 | Lv2 | Lv3 | off |
* |_______|_______|_______|_______|_______|_______|
* 63-48 47-39 38-30 29-21 20-12 11-00
*
* mask page size
*
* Lv0: FF8000000000 --
* Lv1: 7FC0000000
* off: 3FFFFFFF 1G
* Lv2: 3FE00000
* off: 1FFFFF 2M
* Lv3: 1FF000
* off: FFF 4K
*
* RPi3 supports 64K and 4K granules, also 40-bit physical addresses.
* It also can address only 1G physical memory, so these 40-bit phys addresses are a fake.
*
* 48-bit virtual address space; different mappings in VBAR0 (EL0) and VBAR1 (EL1+).
*/
register_bitfields! {
u64,
VA_INDEX [
LEVEL0 OFFSET(39) NUMBITS(9) [],
LEVEL1 OFFSET(30) NUMBITS(9) [],
LEVEL2 OFFSET(21) NUMBITS(9) [],
LEVEL3 OFFSET(12) NUMBITS(9) [],
OFFSET OFFSET(0) NUMBITS(12) []
]
}
register_bitfields! {
u64,
// AArch64 Reference Manual page 2150, D5-2445
TABLE_DESCRIPTOR [
// In table descriptors
NSTable_EL3 OFFSET(63) NUMBITS(1) [],
/// Access Permissions for subsequent tables
APTable OFFSET(61) NUMBITS(2) [
RW_EL1 = 0b00,
RW_EL1_EL0 = 0b01,
RO_EL1 = 0b10,
RO_EL1_EL0 = 0b11
],
// User execute-never for subsequent tables
UXNTable OFFSET(60) NUMBITS(1) [
Execute = 0,
NeverExecute = 1
],
/// Privileged execute-never for subsequent tables
PXNTable OFFSET(59) NUMBITS(1) [
Execute = 0,
NeverExecute = 1
],
// In block descriptors
// OS-specific data
OSData OFFSET(55) NUMBITS(4) [],
// User execute-never
UXN OFFSET(54) NUMBITS(1) [
Execute = 0,
NeverExecute = 1
],
/// Privileged execute-never
PXN OFFSET(53) NUMBITS(1) [
Execute = 0,
NeverExecute = 1
],
// @fixme ?? where is this described
CONTIGUOUS OFFSET(52) NUMBITS(1) [
False = 0,
True = 1
],
// @fixme ?? where is this described
DIRTY OFFSET(51) NUMBITS(1) [
False = 0,
True = 1
],
/// Various address fields, depending on use case
LVL2_OUTPUT_ADDR_4KiB OFFSET(21) NUMBITS(27) [], // [47:21]
NEXT_LVL_TABLE_ADDR_4KiB OFFSET(12) NUMBITS(36) [], // [47:12]
// @fixme ?? where is this described
NON_GLOBAL OFFSET(11) NUMBITS(1) [
False = 0,
True = 1
],
/// Access flag
AF OFFSET(10) NUMBITS(1) [
False = 0,
True = 1
],
/// Share-ability field
SH OFFSET(8) NUMBITS(2) [
OuterShareable = 0b10,
InnerShareable = 0b11
],
/// Access Permissions
AP OFFSET(6) NUMBITS(2) [
RW_EL1 = 0b00,
RW_EL1_EL0 = 0b01,
RO_EL1 = 0b10,
RO_EL1_EL0 = 0b11
],
NS_EL3 OFFSET(5) NUMBITS(1) [],
/// Memory attributes index into the MAIR_EL1 register
AttrIndx OFFSET(2) NUMBITS(3) [],
TYPE OFFSET(1) NUMBITS(1) [
Block = 0,
Table = 1
],
VALID OFFSET(0) NUMBITS(1) [
False = 0,
True = 1
]
]
}
// type VaIndex = register::FieldValue<u64, VA_INDEX::Register>;
type VaType = register::LocalRegisterCopy<u64, VA_INDEX::Register>;
type EntryFlags = register::FieldValue<u64, TABLE_DESCRIPTOR::Register>;
type EntryRegister = register::LocalRegisterCopy<u64, TABLE_DESCRIPTOR::Register>;
// Possible mappings:
// * TTBR0 pointing to user page global directory
// * TTBR0 pointing to user page upper directory (only if mmu is set up differently)
// * TTBR1 pointing to kernel page global directory with full physmem access
// * Paging system uses a separate address space in top kernel region (TTBR1) to access
// * entire physical memory contents.
// * This mapping is not available to user space (user space uses TTBR0).
// *
// * Use the largest possible granule size to map physical memory since we want to use
// * the least amount of memory for these mappings.
// TTBR0 Page Global Directory
// Level 0 descriptors can only output the address of a Level 1 table.
// Level 3 descriptors cannot point to another table and can only output block addresses.
// The format of the table is therefore slightly different for Level 3.
//
// this means:
// - in level 0 page table can be only TableDescriptors
// - in level 1,2 page table can be TableDescriptors, Lvl2BlockDescriptors (PageDescriptors)
// - in level 3 page table can be only PageDescriptors
// Level / Types | Table Descriptor | Lvl2BlockDescriptor (PageDescriptor)
// --------------+------------------+--------------------------------------
// 0 | X | (with 4KiB granule)
// 1 | X | X (1GiB range)
// 2 | X | X (2MiB range)
// 3 | | X (4KiB range) -- called PageDescriptor
// encoding actually the same as in Table Descriptor
// Translation granule affects the size of the block addressed.
// Lets use 4KiB granule on RPi3 for simplicity.
// 1, set 4KiB granule size to use the PGD - we could use 16KiB granule instead?
// - need to measure waste level
// - but lets stick with 4KiB for now
//
// If I have, for example, Table<Level0> I can get from it N `Table<Level1>` (via impl HierarchicalTable)
// From Table<Level1> I can get either `Table<Level2>` (via impl HierarchicalTable) or `BlockDescriptor<Size1GiB>`
// From Table<Level2> I can get either `Table<Level3>` (via impl HierarchicalTable) or `BlockDescriptor<Size2MiB>`
// From Table<Level3> I can only get `PageDescriptor<Size4KiB>` (because no impl HierarchicalTable exists)
/// GlobalDirectory [ UpperDirectory entries ]
/// UpperDirectory [ PageDirectory | GiantPage ]
/// PageDirectory [ PageTable | LargePage ]
/// PageTable [ PageFrames ]
// do those as separate types, then in accessors allow only certain combinations
// e.g.
// struct UpperDirectoryEntry; // DirectoryEntry<L0>
// struct PageDirectoryEntry; // DirectoryEntry<L1>
// struct GiantPageFrame; // PageFrame<Size1GiB>
// struct PageTableEntry; // DirectoryEntry<L2>
// struct LargePageFrame; // PageFrame<Size2MiB>
// struct PageFrame; // PageFrame<Size4KiB>
// enum PageTableEntry { Page(&mut PageDescriptor), Block(&mut BlockDescriptor), Etc(&mut u64), Invalid(&mut u64) }
// impl PageTabelEntry { fn new_from_entry_addr(&u64) }
// return enum PageTableEntry constructed from table bits in u64
enum L0Entries {
UpperDirectoryEntry(VirtAddr),
}
enum L1Entries {
PageDirectoryEntry(VirtAddr),
GiantPageFrame(PhysFrame<Size1GiB>),
}
enum L2Entries {
PageTableEntry(VirtAddr),
LargePageFrame(PhysFrame<Size2MiB>),
}
enum L3Entries {
PageFrame(PhysFrame<Size4KiB>),
}
enum Frames {
GiantPageFrame,
LargePageFrame,
PageFrame,
}
// ----
// ----
// ---- Table levels
// ----
// ----
/// L0 table -- only pointers to L1 tables
pub enum L0PageGlobalDirectory {}
/// L1 tables -- pointers to L2 tables or giant 1GiB pages
pub enum L1PageUpperDirectory {}
/// L2 tables -- pointers to L3 tables or huge 2MiB pages
pub enum L2PageDirectory {}
/// L3 tables -- only pointers to 4/16KiB pages
pub enum L3PageTable {}
/// Shared trait for specific table levels.
pub trait TableLevel {}
/// Shared trait for hierarchical table levels.
///
/// Specifies what is the next level of page table hierarchy.
pub trait HierarchicalLevel: TableLevel {
/// Level of the next translation table below this one.
type NextLevel: TableLevel;
// fn translate() -> Directory<NextLevel>;
}
/// Specify allowed page size for each level.
pub trait HierarchicalPageLevel: TableLevel {
/// Size of the page that can be contained in this table level.
type PageLevel: PageSize;
}
impl TableLevel for L0PageGlobalDirectory {}
impl TableLevel for L1PageUpperDirectory {}
impl TableLevel for L2PageDirectory {}
impl TableLevel for L3PageTable {}
impl HierarchicalLevel for L0PageGlobalDirectory {
type NextLevel = L1PageUpperDirectory;
}
impl HierarchicalLevel for L1PageUpperDirectory {
type NextLevel = L2PageDirectory;
}
impl HierarchicalLevel for L2PageDirectory {
type NextLevel = L3PageTable;
}
// L3PageTables do not have next level, therefore they are not HierarchicalLevel
// L0PageGlobalDirectory does not contain pages, so they are not HierarchicalPageLevel
impl HierarchicalPageLevel for L1PageUpperDirectory {
type PageLevel = Size1GiB;
}
impl HierarchicalPageLevel for L2PageDirectory {
type PageLevel = Size2MiB;
}
impl HierarchicalPageLevel for L3PageTable {
type PageLevel = Size4KiB;
}
// ----
// ----
// ---- Directory
// ----
// ----
// Maximum OA is 48 bits.
//
// Level 0 table descriptor has Output Address in [47:12] --> level 1 table
// Level 0 descriptor cannot be block descriptor.
//
// Level 1 table descriptor has Output Address in [47:12] --> level 2 table
// Level 1 block descriptor has Output Address in [47:30]
//
// Level 2 table descriptor has Output Address in [47:12] --> level 3 table
// Level 2 block descriptor has Output Address in [47:21]
//
// Level 3 block descriptor has Output Address in [47:12]
// Upper Attributes [63:51]
// Res0 [50:48]
// Lower Attributes [11:2]
// 11b [1:0]
// Each table consists of 2**9 entries
const TABLE_BITS: usize = 9;
const INDEX_MASK: usize = (1 << TABLE_BITS) - 1;
static_assertions::const_assert!(INDEX_MASK == 0x1ff);
// @todo Table in mmu.rs
/// MMU address translation table.
/// Contains just u64 internally, provides enum interface on top
#[repr(C)]
#[repr(align(4096))]
struct Directory<Level: TableLevel> {
entries: [u64; 1 << TABLE_BITS],
level: PhantomData<Level>,
}
impl Directory<L0PageGlobalDirectory> {
fn next(&self, address: VirtAddr) -> Option<L0Entries> {
let va = VaType::new(address.as_u64());
let index = va.read(VA_INDEX::LEVEL0);
match self.next_table_address(index as usize) {
Some(phys_addr) => Some(L0Entries::UpperDirectoryEntry(phys_addr.user_to_kernel())),
None => None,
}
}
}
impl Directory<L1PageUpperDirectory> {
fn next(&self, address: VirtAddr) -> Option<L1Entries> {
let va = VaType::new(address.as_u64());
let index = va.read(VA_INDEX::LEVEL1);
match self.next_table_address(index as usize) {
Some(phys_addr) => Some(L1Entries::PageDirectoryEntry(phys_addr.user_to_kernel())),
None => None, // @todo could be 1GiB frame
}
}
}
impl Directory<L2PageDirectory> {
fn next(&self, address: VirtAddr) -> Option<L2Entries> {
let va = VaType::new(address.as_u64());
let index = va.read(VA_INDEX::LEVEL2);
match self.next_table_address(index as usize) {
Some(phys_addr) => Some(L2Entries::PageTableEntry(phys_addr.user_to_kernel())),
None => None, // @todo could be 2MiB frame
}
}
}
impl Directory<L3PageTable> {
fn next(&self, address: VirtAddr) -> Option<L3Entries> {
let va = VaType::new(address.as_u64());
let _index = va.read(VA_INDEX::LEVEL3);
// @fixme wrong function
// match self.next_table_address(index as usize) {
// Some(phys_addr) => Some(L3Entries::PageFrame(phys_addr.user_to_kernel())),
// None => None, // Nothing there
// }
None
}
}
// Implementation code shared for all levels of page tables
impl<Level> Directory<Level>
where
Level: TableLevel,
{
/// Construct a zeroed table at given physical location.
// unsafe fn at(location: PhysAddr) -> &Self {}
/// Construct and return zeroed table.
fn zeroed() -> Self {
Self {
entries: [0; 1 << TABLE_BITS],
level: PhantomData,
}
}
/// Zero out entire table.
pub fn zero(&mut self) {
for entry in self.entries.iter_mut() {
*entry = 0;
}
}
}
impl<Level> Index<usize> for Directory<Level>
where
Level: TableLevel,
{
type Output = u64;
fn index(&self, index: usize) -> &Self::Output {
&self.entries[index]
}
}
impl<Level> IndexMut<usize> for Directory<Level>
where
Level: TableLevel,
{
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.entries[index]
}
}
impl<Level> Directory<Level>
where
Level: HierarchicalLevel,
{
fn next_table_address(&self, index: usize) -> Option<PhysAddr> {
let entry_flags = EntryRegister::new(self[index]);
// If table entry has 0b11 mask set, it is a valid table entry.
// Address of the following table may be extracted from bits 47:12
if entry_flags.matches_all(TABLE_DESCRIPTOR::VALID::True + TABLE_DESCRIPTOR::TYPE::Table) {
Some(PhysAddr::new(
entry_flags.read(TABLE_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB) << Size4KiB::SHIFT,
))
} else {
None
}
}
pub fn next_table(&self, index: usize) -> Option<&Directory<Level::NextLevel>> {
self.next_table_address(index)
.map(|address| unsafe { &*(address.user_to_kernel().as_ptr()) })
}
pub fn next_table_mut(&mut self, index: usize) -> Option<&mut Directory<Level::NextLevel>> {
self.next_table_address(index)
.map(|address| unsafe { &mut *(address.user_to_kernel().as_mut_ptr()) })
}
pub fn translate_levels(&self, _address: VirtAddr) -> Option<Frames> {
None
}
}
// ----
// ----
// ---- VirtSpace
// ----
// ----
/// Errors from mapping layer
#[derive(Debug, Snafu)]
pub enum TranslationError {
/// No page found. @todo
NoPage,
}
/// Virtual address space. @todo
pub struct VirtSpace {
l0: Unique<Directory<L0PageGlobalDirectory>>,
}
// translation steps:
// l0: upper page directory or Err()
// l1: lower page directory or 1Gb aperture or Err()
// l2: page table or 2MiB aperture or Err()
// l3: 4KiB aperture or Err()
impl VirtSpace {
// Translate translates address all the way down to physical address or error.
// On each level there's next_table() fn that resolves to the next level table if possible.
// pub fn translate(&self, virtual_address: VirtAddr) -> Result<PhysAddr, TranslationError> {
// // let offset = virtual_address % Self::PageLevel::SIZE as usize; // use the size of the last page?
// self.translate_page(Page::<Self::PageLevel>::containing_address(virtual_address))?
// .map(|frame, offset| frame.start_address() + offset)
// }
}
// pageglobaldirectory.translate() {
// get page index <- generic over page level (xx << (10 + (3 - level) * 9))
// return page[index]?.translate(rest);
// }
#[cfg(test)]
mod tests {
use super::*;
#[test_case]
fn table_construction() {
let mut level0_table = Directory::<L0PageGlobalDirectory>::zeroed();
let level1_table = Directory::<L1PageUpperDirectory>::zeroed();
let level2_table = Directory::<L2PageDirectory>::zeroed();
let level3_table = Directory::<L3PageTable>::zeroed();
assert!(level0_table.next_table_address(0).is_none());
// Make entry map to a level1 table
level0_table[0] = EntryFlags::from(
TABLE_DESCRIPTOR::VALID::True
+ TABLE_DESCRIPTOR::TYPE::Table
+ TABLE_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(0x424242),
)
.into();
assert!(level0_table.next_table_address(0).is_some());
let addr = level0_table.next_table_address(0).unwrap();
assert_eq!(addr, (0x424242 << 12));
}
}

View File

@ -1,68 +0,0 @@
/// Trait for abstracting over the possible page sizes, 4KiB, 16KiB, 2MiB, 1GiB.
pub trait PageSize: Copy + PartialEq + Eq + PartialOrd + Ord {
/// The page size in bytes.
const SIZE: usize;
/// A string representation of the page size for debug output.
const SIZE_AS_DEBUG_STR: &'static str;
/// The page shift in bits.
const SHIFT: usize;
/// The page mask in bits.
const MASK: u64;
}
/// This trait is implemented for 4KiB, 16KiB, and 2MiB pages, but not for 1GiB pages.
pub trait NotGiantPageSize: PageSize {} // @todo doesn't have to be pub??
/// A standard 4KiB page.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Size4KiB {}
impl PageSize for Size4KiB {
const SIZE: usize = 4 * 1024;
const SIZE_AS_DEBUG_STR: &'static str = "4KiB";
const SHIFT: usize = 12;
const MASK: u64 = 0xfff;
}
impl NotGiantPageSize for Size4KiB {}
/// A standard 16KiB page.
/// Currently unused.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Size16KiB {}
impl PageSize for Size16KiB {
const SIZE: usize = 16 * 1024;
const SIZE_AS_DEBUG_STR: &'static str = "16KiB";
const SHIFT: usize = 14;
const MASK: u64 = 0x3fff;
}
impl NotGiantPageSize for Size16KiB {}
/// A “huge” 2MiB page.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Size2MiB {}
impl PageSize for Size2MiB {
const SIZE: usize = 2 * 1024 * 1024;
const SIZE_AS_DEBUG_STR: &'static str = "2MiB";
const SHIFT: usize = 21;
const MASK: u64 = 0x1f_ffff;
}
impl NotGiantPageSize for Size2MiB {}
/// A “giant” 1GiB page.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Size1GiB {}
impl PageSize for Size1GiB {
const SIZE: usize = 1024 * 1024 * 1024;
const SIZE_AS_DEBUG_STR: &'static str = "1GiB";
const SHIFT: usize = 30;
const MASK: u64 = 0x3fff_ffff;
}

View File

@ -1,40 +0,0 @@
@startuml
'https://plantuml.com/object-diagram
object GiantPage
object GiantPage_2
object LargePage
object Page
object Page_2
object Unmapped
map L1PageUpperDirectory_2 {
entry0 *--> GiantPage_2
entry1 *--> Unmapped
}
map L3PageTable {
entry0 *--> Page
entry1 *--> Page_2
}
map L2PageDirectory {
entry0 *-> L3PageTable
entry1 *--> LargePage
}
map L1PageUpperDirectory {
entry0 *-> L2PageDirectory
entry1 *--> GiantPage
}
map L0PageGlobalDirectory {
entry0 *-> L1PageUpperDirectory
entry1 *--> L1PageUpperDirectory_2
}
map VirtSpace {
root *-> L0PageGlobalDirectory
}
@enduml

View File

@ -1,202 +0,0 @@
// Verbatim from https://github.com/rust-osdev/x86_64/blob/aa9ae54657beb87c2a491f2ab2140b2332afa6ba/src/structures/paging/frame.rs
// Abstractions for default-sized and huge physical memory frames.
use {
crate::memory::{
page_size::{PageSize, Size4KiB},
PhysAddr,
},
core::{
fmt,
marker::PhantomData,
ops::{Add, AddAssign, Sub, SubAssign},
},
};
/// A physical memory frame.
/// Frame is an addressable unit of the physical address space.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(C)]
pub struct PhysFrame<S: PageSize = Size4KiB> {
start_address: PhysAddr,
size: PhantomData<S>,
}
impl<S: PageSize> From<u64> for PhysFrame<S> {
fn from(address: u64) -> PhysFrame<S> {
PhysFrame::containing_address(PhysAddr::new(address))
}
}
impl<S: PageSize> From<PhysFrame<S>> for u64 {
fn from(frame: PhysFrame<S>) -> u64 {
frame.start_address.as_u64()
}
}
impl<S: PageSize> PhysFrame<S> {
/// Returns the frame that starts at the given virtual address.
///
/// Returns an error if the address is not correctly aligned (i.e. is not a valid frame start).
pub fn from_start_address(address: PhysAddr) -> Result<Self, ()> {
if !address.is_aligned(S::SIZE) {
return Err(());
}
Ok(PhysFrame::containing_address(address))
}
/// Returns the frame that contains the given physical address.
pub fn containing_address(address: PhysAddr) -> Self {
PhysFrame {
start_address: address.aligned_down(S::SIZE),
size: PhantomData,
}
}
/// Returns the start address of the frame.
pub fn start_address(&self) -> PhysAddr {
self.start_address
}
/// Returns the size the frame (4KB, 2MB or 1GB).
pub fn size(&self) -> usize {
S::SIZE
}
/// Returns a range of frames, exclusive `end`.
pub fn range(start: PhysFrame<S>, end: PhysFrame<S>) -> PhysFrameRange<S> {
PhysFrameRange { start, end }
}
/// Returns a range of frames, inclusive `end`.
pub fn range_inclusive(start: PhysFrame<S>, end: PhysFrame<S>) -> PhysFrameRangeInclusive<S> {
PhysFrameRangeInclusive { start, end }
}
}
impl<S: PageSize> fmt::Debug for PhysFrame<S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_fmt(format_args!(
"PhysFrame[{}]({:#x})",
S::SIZE_AS_DEBUG_STR,
self.start_address().as_u64()
))
}
}
impl<S: PageSize> Add<u64> for PhysFrame<S> {
type Output = Self;
/// Adds `rhs` same-sized frames to the current address.
fn add(self, rhs: u64) -> Self::Output {
PhysFrame::containing_address(self.start_address() + rhs * S::SIZE as u64)
}
}
impl<S: PageSize> AddAssign<u64> for PhysFrame<S> {
fn add_assign(&mut self, rhs: u64) {
*self = self.clone() + rhs;
}
}
impl<S: PageSize> Sub<u64> for PhysFrame<S> {
type Output = Self;
/// Subtracts `rhs` same-sized frames from the current address.
// @todo should I sub pages or just bytes here?
fn sub(self, rhs: u64) -> Self::Output {
PhysFrame::containing_address(self.start_address() - rhs * S::SIZE as u64)
}
}
impl<S: PageSize> SubAssign<u64> for PhysFrame<S> {
fn sub_assign(&mut self, rhs: u64) {
*self = self.clone() - rhs;
}
}
impl<S: PageSize> Sub<PhysFrame<S>> for PhysFrame<S> {
type Output = usize;
/// Return number of frames between start and end addresses.
fn sub(self, rhs: PhysFrame<S>) -> Self::Output {
(self.start_address - rhs.start_address) as usize / S::SIZE
}
}
/// A range of physical memory frames, exclusive the upper bound.
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub struct PhysFrameRange<S: PageSize = Size4KiB> {
/// The start of the range, inclusive.
pub start: PhysFrame<S>,
/// The end of the range, exclusive.
pub end: PhysFrame<S>,
}
impl<S: PageSize> PhysFrameRange<S> {
/// Returns whether the range contains no frames.
pub fn is_empty(&self) -> bool {
!(self.start < self.end)
}
}
impl<S: PageSize> Iterator for PhysFrameRange<S> {
type Item = PhysFrame<S>;
fn next(&mut self) -> Option<Self::Item> {
if !self.is_empty() {
let frame = self.start.clone();
self.start += 1;
Some(frame)
} else {
None
}
}
}
impl<S: PageSize> fmt::Debug for PhysFrameRange<S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("PhysFrameRange")
.field("start", &self.start)
.field("end", &self.end)
.finish()
}
}
/// An range of physical memory frames, inclusive the upper bound.
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub struct PhysFrameRangeInclusive<S: PageSize = Size4KiB> {
/// The start of the range, inclusive.
pub start: PhysFrame<S>,
/// The start of the range, exclusive.
pub end: PhysFrame<S>,
}
impl<S: PageSize> PhysFrameRangeInclusive<S> {
/// Returns whether the range contains no frames.
pub fn is_empty(&self) -> bool {
!(self.start <= self.end)
}
}
impl<S: PageSize> Iterator for PhysFrameRangeInclusive<S> {
type Item = PhysFrame<S>;
fn next(&mut self) -> Option<Self::Item> {
if !self.is_empty() {
let frame = self.start.clone();
self.start += 1;
Some(frame)
} else {
None
}
}
}
impl<S: PageSize> fmt::Debug for PhysFrameRangeInclusive<S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("PhysFrameRangeInclusive")
.field("start", &self.start)
.field("end", &self.end)
.finish()
}
}

View File

@ -1,324 +0,0 @@
// Verbatim from https://github.com/rust-osdev/x86_64/blob/aa9ae54657beb87c2a491f2ab2140b2332afa6ba/src/structures/paging/page.rs
// Abstractions for default-sized and huge virtual memory pages.
// @fixme x86_64 page level numbering: P4 -> P3 -> P2 -> P1
// @fixme armv8a page level numbering: L0 -> L1 -> L2 -> L3
#![allow(dead_code)]
use {
crate::memory::{
page_size::{NotGiantPageSize, PageSize, Size1GiB, Size2MiB, Size4KiB},
VirtAddr,
},
core::{
fmt,
marker::PhantomData,
ops::{Add, AddAssign, Sub, SubAssign},
},
ux::u9,
};
/// A virtual memory page.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Page<S: PageSize = Size4KiB> {
start_address: VirtAddr,
size: PhantomData<S>,
}
pub enum Error {
NotAligned,
}
impl<S: PageSize> Page<S> {
/// The page size in bytes.
pub const SIZE: usize = S::SIZE;
/// Returns the page that starts at the given virtual address.
///
/// Returns an error if the address is not correctly aligned (i.e. is not a valid page start).
pub fn from_start_address(address: VirtAddr) -> Result<Self, Error> {
if !address.is_aligned(S::SIZE) {
Err(Error::NotAligned)
} else {
Ok(Page::containing_address(address))
}
}
/// Returns the page that contains the given virtual address.
pub fn containing_address(address: VirtAddr) -> Self {
Page {
start_address: address.aligned_down(S::SIZE),
size: PhantomData,
}
}
/// Returns the start address of the page.
pub fn start_address(&self) -> VirtAddr {
self.start_address
}
/// Returns the size the page (4KB, 2MB or 1GB).
pub const fn size(&self) -> usize {
S::SIZE
}
/// Returns the level 0 page table index of this page.
pub fn l0_index(&self) -> u9 {
self.start_address().l0_index()
}
/// Returns the level 1 page table index of this page.
pub fn l1_index(&self) -> u9 {
self.start_address().l1_index()
}
/// Returns a range of pages, exclusive `end`.
pub fn range(start: Self, end: Self) -> PageRange<S> {
PageRange { start, end }
}
/// Returns a range of pages, inclusive `end`.
pub fn range_inclusive(start: Self, end: Self) -> PageRangeInclusive<S> {
PageRangeInclusive { start, end }
}
}
impl<S: NotGiantPageSize> Page<S> {
/// Returns the level 2 page table index of this page.
pub fn l2_index(&self) -> u9 {
self.start_address().l2_index()
}
}
impl Page<Size1GiB> {
/// Returns the 1GiB memory page with the specified page table indices.
pub fn from_page_table_indices_1gib(l0_index: u9, l1_index: u9) -> Self {
use bit_field::BitField;
let mut addr = 0;
addr.set_bits(39..48, u64::from(l0_index));
addr.set_bits(30..39, u64::from(l1_index));
Page::containing_address(VirtAddr::new(addr))
}
}
impl Page<Size2MiB> {
/// Returns the 2MiB memory page with the specified page table indices.
pub fn from_page_table_indices_2mib(l0_index: u9, l1_index: u9, l2_index: u9) -> Self {
use bit_field::BitField;
let mut addr = 0;
addr.set_bits(39..48, u64::from(l0_index));
addr.set_bits(30..39, u64::from(l1_index));
addr.set_bits(21..30, u64::from(l2_index));
Page::containing_address(VirtAddr::new(addr))
}
}
impl Page<Size4KiB> {
/// Returns the 4KiB memory page with the specified page table indices.
pub fn from_page_table_indices(l0_index: u9, l1_index: u9, l2_index: u9, l3_index: u9) -> Self {
use bit_field::BitField;
let mut addr = 0;
addr.set_bits(39..48, u64::from(l0_index));
addr.set_bits(30..39, u64::from(l1_index));
addr.set_bits(21..30, u64::from(l2_index));
addr.set_bits(12..21, u64::from(l3_index));
Page::containing_address(VirtAddr::new(addr))
}
/// Returns the level 3 page table index of this page.
pub fn l3_index(&self) -> u9 {
self.start_address().l3_index()
}
}
impl<S: PageSize> fmt::Debug for Page<S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_fmt(format_args!(
"Page<{}>({:#x})",
S::SIZE_AS_DEBUG_STR,
self.start_address().as_u64()
))
}
}
impl<S: PageSize> Add<u64> for Page<S> {
type Output = Self;
// @todo should I add pages or just bytes here?
fn add(self, rhs: u64) -> Self::Output {
Page::containing_address(self.start_address() + rhs * S::SIZE as u64)
}
}
impl<S: PageSize> AddAssign<u64> for Page<S> {
fn add_assign(&mut self, rhs: u64) {
*self = self.clone() + rhs;
}
}
impl<S: PageSize> Sub<u64> for Page<S> {
type Output = Self;
/// Subtracts `rhs` same-sized pages from the current address.
// @todo should I sub pages or just bytes here?
fn sub(self, rhs: u64) -> Self::Output {
Page::containing_address(self.start_address() - rhs * S::SIZE as u64)
}
}
impl<S: PageSize> SubAssign<u64> for Page<S> {
fn sub_assign(&mut self, rhs: u64) {
*self = self.clone() - rhs;
}
}
impl<S: PageSize> Sub<Self> for Page<S> {
type Output = usize;
fn sub(self, rhs: Self) -> Self::Output {
(self.start_address - rhs.start_address) as usize / S::SIZE
}
}
/// A range of pages with exclusive upper bound.
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct PageRange<S: PageSize = Size4KiB> {
/// The start of the range, inclusive.
pub start: Page<S>,
/// The end of the range, exclusive.
pub end: Page<S>,
}
impl<S: PageSize> PageRange<S> {
/// Returns whether this range contains no pages.
pub fn is_empty(&self) -> bool {
self.start >= self.end
}
pub fn num_pages(&self) -> usize {
(self.end - self.start) as usize / S::SIZE
}
}
impl<S: PageSize> Iterator for PageRange<S> {
type Item = Page<S>;
fn next(&mut self) -> Option<Self::Item> {
if !self.is_empty() {
let page = self.start.clone();
self.start += 1;
Some(page)
} else {
None
}
}
}
impl PageRange<Size2MiB> {
/// Converts the range of 2MiB pages to a range of 4KiB pages.
// @todo what about range of 1GiB pages?
pub fn as_4kib_page_range(&self) -> PageRange<Size4KiB> {
PageRange {
start: Page::containing_address(self.start.start_address()),
// @fixme end is calculated incorrectly, add test
end: Page::containing_address(self.end.start_address()),
}
}
}
impl<S: PageSize> fmt::Debug for PageRange<S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("PageRange")
.field("start", &self.start)
.field("end", &self.end)
.finish()
}
}
/// A range of pages with inclusive upper bound.
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct PageRangeInclusive<S: PageSize = Size4KiB> {
/// The start of the range, inclusive.
pub start: Page<S>,
/// The end of the range, inclusive.
pub end: Page<S>,
}
impl<S: PageSize> PageRangeInclusive<S> {
/// Returns whether this range contains no pages.
pub fn is_empty(&self) -> bool {
self.start > self.end
}
}
impl<S: PageSize> Iterator for PageRangeInclusive<S> {
type Item = Page<S>;
fn next(&mut self) -> Option<Self::Item> {
if !self.is_empty() {
let page = self.start.clone();
self.start += 1;
Some(page)
} else {
None
}
}
}
impl<S: PageSize> fmt::Debug for PageRangeInclusive<S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("PageRangeInclusive")
.field("start", &self.start)
.field("end", &self.end)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test_case]
pub fn test_page_ranges() {
let page_size = Size4KiB::SIZE;
let number = 1000;
let start_addr = VirtAddr::new(0xdeadbeaf);
let start: Page = Page::containing_address(start_addr);
let end = start.clone() + number;
let mut range = Page::range(start.clone(), end.clone());
for i in 0..number {
assert_eq!(
range.next(),
Some(Page::containing_address(start_addr + page_size * i))
);
}
assert_eq!(range.next(), None);
let mut range_inclusive = Page::range_inclusive(start, end);
for i in 0..=number {
assert_eq!(
range_inclusive.next(),
Some(Page::containing_address(start_addr + page_size * i))
);
}
assert_eq!(range_inclusive.next(), None);
}
#[test_case]
fn test_page_range_conversion() {
let page_size = Size2MiB::SIZE;
let number = 10;
let start_addr = VirtAddr::new(0xdeadbeaf);
let start: Page = Page::containing_address(start_addr);
let end = start.clone() + number;
let range = Page::range(start.clone(), end.clone()).as_4kib_page_range();
// 10 2MiB pages is 512 4KiB pages
aseert_eq!(range.num_pages(), 512);
}
}

View File

@ -1,132 +0,0 @@
#![allow(dead_code)]
use crate::{memory::PhysAddr, println, sync};
#[derive(Default, Copy, Clone)]
struct BootInfoMemRegion {
pub start: PhysAddr,
pub end: PhysAddr,
}
impl BootInfoMemRegion {
pub const fn new() -> BootInfoMemRegion {
BootInfoMemRegion {
start: PhysAddr::zero(),
end: PhysAddr::zero(),
}
}
pub fn size(&self) -> u64 {
self.end - self.start
}
pub fn is_empty(&self) -> bool {
self.start == self.end
}
}
const NUM_MEM_REGIONS: usize = 16;
pub enum BootInfoError {
NoFreeMemRegions,
}
#[derive(Default)]
struct BootInfo {
pub regions: [BootInfoMemRegion; NUM_MEM_REGIONS],
pub max_slot_pos: usize,
}
impl BootInfo {
pub const fn new() -> BootInfo {
BootInfo {
regions: [BootInfoMemRegion::new(); NUM_MEM_REGIONS],
max_slot_pos: 0,
}
}
pub fn insert_region(&mut self, reg: BootInfoMemRegion) -> Result<(), BootInfoError> {
if reg.is_empty() {
return Ok(());
}
assert!(reg.start <= reg.end);
for region in self.regions.iter_mut() {
if region.is_empty() {
*region = reg;
return Ok(());
}
}
return Err(BootInfoError::NoFreeMemRegions);
}
pub fn alloc_region(&mut self, size_bits: usize) -> Result<PhysAddr, BootInfoError> {
let mut reg_index: usize = 0;
let mut reg: BootInfoMemRegion = BootInfoMemRegion::new();
let mut rem_small: BootInfoMemRegion = BootInfoMemRegion::new();
let mut rem_large: BootInfoMemRegion = BootInfoMemRegion::new();
/*
* Search for a free mem region that will be the best fit for an allocation. We favour allocations
* that are aligned to either end of the region. If an allocation must split a region we favour
* an unbalanced split. In both cases we attempt to use the smallest region possible. In general
* this means we aim to make the size of the smallest remaining region smaller (ideally zero)
* followed by making the size of the largest remaining region smaller.
*/
for (i, reg_iter) in self.regions.iter().enumerate() {
let mut new_reg: BootInfoMemRegion = BootInfoMemRegion::new();
/* Determine whether placing the region at the start or the end will create a bigger left over region */
if reg_iter.start.aligned_up(1usize << size_bits) - reg_iter.start
< reg_iter.end - reg_iter.end.aligned_down(1usize << size_bits)
{
new_reg.start = reg_iter.start.aligned_up(1usize << size_bits);
new_reg.end = new_reg.start + (1u64 << size_bits);
} else {
new_reg.end = reg_iter.end.aligned_down(1usize << size_bits);
new_reg.start = new_reg.end - (1u64 << size_bits);
}
if new_reg.end > new_reg.start
&& new_reg.start >= reg_iter.start
&& new_reg.end <= reg_iter.end
{
let mut new_rem_small: BootInfoMemRegion = BootInfoMemRegion::new();
let mut new_rem_large: BootInfoMemRegion = BootInfoMemRegion::new();
if new_reg.start - reg_iter.start < reg_iter.end - new_reg.end {
new_rem_small.start = reg_iter.start;
new_rem_small.end = new_reg.start;
new_rem_large.start = new_reg.end;
new_rem_large.end = reg_iter.end;
} else {
new_rem_large.start = reg_iter.start;
new_rem_large.end = new_reg.start;
new_rem_small.start = new_reg.end;
new_rem_small.end = reg_iter.end;
}
if reg.is_empty()
|| (new_rem_small.size() < rem_small.size())
|| (new_rem_small.size() == rem_small.size()
&& new_rem_large.size() < rem_large.size())
{
reg = new_reg;
rem_small = new_rem_small;
rem_large = new_rem_large;
reg_index = i;
}
}
}
if reg.is_empty() {
panic!("Kernel init failed: not enough memory\n");
}
/* Remove the region in question */
self.regions[reg_index] = BootInfoMemRegion::new();
/* Add the remaining regions in largest to smallest order */
self.insert_region(rem_large)?;
if self.insert_region(rem_small).is_err() {
println!("BootInfo::alloc_region(): wasted {} bytes due to alignment, try to increase NUM_MEM_REGIONS", rem_small.size());
}
Ok(reg.start)
}
}
#[link_section = ".data.boot"] // @todo put zero-initialized stuff to .bss.boot!
static BOOT_INFO: sync::NullLock<BootInfo> = sync::NullLock::new(BootInfo::new());

View File

@ -1,6 +0,0 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
*/
pub mod console;
pub use console::{Console, ConsoleOps};

View File

@ -3,90 +3,53 @@
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
//! Vesper single-address-space exokernel.
//! Vesper single-address-space nanokernel.
//!
//! This crate implements the kernel binary proper.
#![no_std]
#![no_main]
#![feature(asm)]
#![feature(global_asm)]
#![feature(decl_macro)]
#![feature(const_fn)]
#![feature(allocator_api)]
#![feature(ptr_internals)]
#![feature(format_args_nl)]
#![feature(nonnull_slice_from_raw_parts)]
#![feature(custom_test_frameworks)]
#![test_runner(crate::tests::test_runner)]
#![test_runner(machine::tests::test_runner)]
#![reexport_test_harness_main = "test_main"]
#![deny(missing_docs)]
#![deny(warnings)]
#![allow(clippy::nonstandard_macro_braces)] // https://github.com/shepmaster/snafu/issues/296
#![allow(clippy::upper_case_acronyms)]
#![allow(clippy::enum_variant_names)]
#[cfg(not(target_arch = "aarch64"))]
use architecture_not_supported_sorry;
/// Architecture-specific code.
#[macro_use]
pub mod arch;
pub use arch::*;
mod boot_info;
mod devices;
mod macros;
mod mm;
mod panic;
mod platform;
#[cfg(feature = "qemu")]
mod qemu;
mod sync;
#[cfg(test)]
mod tests;
mod write_to;
#[cfg(not(test))]
use core::panic::PanicInfo;
#[allow(unused_imports)]
use machine::devices::SerialOps;
use {
crate::platform::rpi3::{
display::{Color, DrawError},
mailbox::{channel, Mailbox, MailboxOps},
vc::VC,
},
cfg_if::cfg_if,
machine::{
arch, entry, memory,
platform::rpi3::{
display::{Color, DrawError},
mailbox::{channel, Mailbox, MailboxOps},
vc::VC,
},
println, CONSOLE,
},
};
entry!(kmain);
/// The global console. Output of the kernel print! and println! macros goes here.
static CONSOLE: sync::NullLock<devices::Console> = sync::NullLock::new(devices::Console::new());
/// The global allocator for DMA-able memory. That is, memory which is tagged
/// non-cacheable in the page tables.
static DMA_ALLOCATOR: sync::NullLock<mm::BumpAllocator> =
sync::NullLock::new(mm::BumpAllocator::new(
// @todo Init this after we loaded boot memory map
memory::map::virt::DMA_HEAP_START as usize,
memory::map::virt::DMA_HEAP_END as usize,
"Global DMA Allocator",
// Try the following arguments instead to see all mailbox operations
// fail. It will cause the allocator to use memory that are marked
// cacheable and therefore not DMA-safe. The answer from the VideoCore
// won't be received by the CPU because it reads an old cached value
// that resembles an error case instead.
// 0x00600000 as usize,
// 0x007FFFFF as usize,
// "Global Non-DMA Allocator",
));
#[cfg(not(test))]
#[panic_handler]
fn panicked(info: &PanicInfo) -> ! {
machine::panic::handler(info)
}
fn print_mmu_state_and_features() {
memory::features::print_features();
memory::mmu::print_features();
}
fn init_mmu() {
// unsafe {
// memory::mmu::init().unwrap();
// }
unsafe {
memory::mmu::init().unwrap();
}
println!("[!] MMU initialised");
print_mmu_state_and_features();
}
@ -107,7 +70,7 @@ fn init_exception_traps() {
#[cfg(not(feature = "noserial"))]
fn init_uart_serial() {
use crate::platform::rpi3::{gpio::GPIO, mini_uart::MiniUart, pl011_uart::PL011Uart};
use machine::platform::rpi3::{gpio::GPIO, mini_uart::MiniUart, pl011_uart::PL011Uart};
let gpio = GPIO::default();
let uart = MiniUart::default();
@ -122,7 +85,6 @@ fn init_uart_serial() {
// Then immediately switch to PL011 (just as an example)
let uart = PL011Uart::default();
let mbox = Mailbox::default();
// uart.init() will reconfigure the GPIO, which causes a race against
// the MiniUart that is still putting out characters on the physical
@ -135,10 +97,9 @@ fn init_uart_serial() {
// physical wires (e.g. the Framebuffer), you don't need to do this,
// because flush() is anyways called implicitly by replace_with(). This
// is just a special case.
use crate::devices::console::ConsoleOps;
CONSOLE.lock(|c| c.flush());
match uart.prepare(mbox, &gpio) {
match uart.prepare(&gpio) {
Ok(uart) => {
CONSOLE.lock(|c| {
// Move uart into the global CONSOLE.
@ -155,7 +116,7 @@ fn init_uart_serial() {
#[inline]
pub fn kmain() -> ! {
#[cfg(feature = "jtag")]
jtag::wait_debugger();
machine::arch::jtag::wait_debugger();
init_mmu();
init_exception_traps();
@ -185,7 +146,7 @@ fn command_prompt() {
b"uart" => init_uart_serial(),
b"disp" => check_display_init(),
b"trap" => check_data_abort_trap(),
b"map" => arch::memory::print_layout(),
b"map" => machine::arch::memory::print_layout(),
b"led on" => set_led(true),
b"led off" => set_led(false),
b"help" => print_help(),
@ -209,7 +170,7 @@ fn print_help() {
}
fn set_led(enable: bool) {
let mut mbox = Mailbox::default();
let mut mbox = Mailbox::<8>::default();
let index = mbox.request();
let index = mbox.set_led_on(index, enable);
let mbox = mbox.end(index);
@ -226,12 +187,12 @@ fn reboot() -> ! {
cfg_if! {
if #[cfg(feature = "qemu")] {
println!("Bye, shutting down QEMU");
qemu::semihosting::exit_success()
machine::qemu::semihosting::exit_success()
} else {
use crate::platform::rpi3::power::Power;
use machine::platform::rpi3::power::Power;
println!("Bye, going to reset now");
Power::new().reset()
Power::default().reset()
}
}
}
@ -255,7 +216,7 @@ fn display_graphics() -> Result<(), DrawError> {
display.draw_text(50, 50, "Hello there!", Color::rgb(128, 192, 255))?;
let mut buf = [0u8; 64];
let s = write_to::show(&mut buf, format_args!("Display width {}", display.width));
let s = machine::write_to::show(&mut buf, format_args!("Display width {}", display.width));
if s.is_err() {
display.draw_text(50, 150, "Error displaying", Color::red())?
@ -284,7 +245,12 @@ fn check_data_abort_trap() {
#[cfg(test)]
mod main_tests {
use super::*;
use {super::*, core::panic::PanicInfo};
#[panic_handler]
fn panicked(info: &PanicInfo) -> ! {
machine::panic::handler_for_tests(info)
}
#[test_case]
fn test_data_abort_trap() {

View File

@ -1,107 +0,0 @@
use {
super::{
mailbox::{channel, read, write, MailboxOps, RegisterBlock, Result},
BcmHost,
},
core::ops::Deref,
};
/// FrameBuffer channel supported structure - use with mailbox::channel::FrameBuffer
/// Must have the same alignment as the mailbox buffers.
#[repr(C)]
#[repr(align(16))]
pub struct FrameBuffer {
pub width: u32,
pub height: u32,
pub vwidth: u32,
pub vheight: u32,
pub pitch: u32,
pub depth: u32,
pub x_offset: u32,
pub y_offset: u32,
pub pointer: u32,
pub size: u32,
// Must be after HW-dictated fields to not break structure alignment.
base_addr: usize,
}
// @todo rewrite in terms of using the Mailbox
/// Deref to RegisterBlock
///
/// Allows writing
/// ```
/// self.STATUS.read()
/// ```
/// instead of something along the lines of
/// ```
/// unsafe { (*FrameBuffer::ptr()).STATUS.read() }
/// ```
impl Deref for FrameBuffer {
type Target = RegisterBlock; // mailbox RegisterBlock reused here
fn deref(&self) -> &Self::Target {
unsafe { &*self.ptr() }
}
}
impl core::fmt::Debug for FrameBuffer {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(
f,
"\n\n\n#### FrameBuffer({}x{}, {}x{}, d{}, --{}--, +{}x{}, {}@{:x})\n\n\n",
self.width,
self.height,
self.vwidth,
self.vheight,
self.depth,
self.pitch,
self.x_offset,
self.y_offset,
self.size,
self.pointer,
)
}
}
impl FrameBuffer {
pub fn new(reg_base: usize, width: u32, height: u32, depth: u32) -> FrameBuffer {
FrameBuffer {
width,
height,
vwidth: width,
vheight: height,
pitch: 0,
depth,
x_offset: 0,
y_offset: 0,
pointer: 0, // could be 4096 for the alignment?
size: 0,
base_addr: reg_base,
}
}
}
impl MailboxOps for FrameBuffer {
/// Returns a pointer to the register block
fn ptr(&self) -> *const RegisterBlock {
self.base_addr as *const _
}
/// <https://github.com/raspberrypi/firmware/wiki/Accessing-mailboxes> says:
/// **With the exception of the property tags mailbox channel,**
/// when passing memory addresses as the data part of a mailbox message,
/// the addresses should be **bus addresses as seen from the VC.**
fn write(&self, _channel: u32) -> Result<()> {
write(
self,
BcmHost::phys2bus(&self as *const _ as usize) as *const _,
channel::FrameBuffer,
)
}
fn read(&self, _channel: u32) -> Result<()> {
read(self, 0, channel::FrameBuffer)
}
}

View File

@ -1,50 +0,0 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
#![allow(dead_code)]
pub mod display;
pub mod fb;
pub mod gpio;
pub mod mailbox;
pub mod mini_uart;
pub mod pl011_uart;
pub mod power;
pub mod vc;
/// See BCM2835-ARM-Peripherals.pdf
/// See <https://www.raspberrypi.org/forums/viewtopic.php?t=186090> for more details.
pub struct BcmHost;
impl BcmHost {
/// This returns the ARM-side physical address where peripherals are mapped.
///
/// As per <https://www.raspberrypi.org/documentation/hardware/raspberrypi/peripheral_addresses.md>
/// BCM SOC could address only 1Gb of memory, so 0x4000_0000 is the high watermark.
pub const fn get_peripheral_address() -> usize {
0x3f00_0000
}
/// This returns the size of the peripherals' space.
pub const fn get_peripheral_size() -> usize {
0x0100_0000
}
/// This returns the bus address of the SDRAM.
pub const fn get_sdram_address() -> usize {
0xc000_0000 // uncached
}
/// As per <https://www.raspberrypi.org/forums/viewtopic.php?p=1170522#p1170522>
///
pub fn bus2phys(bus: usize) -> usize {
bus & !0xc000_0000
}
pub fn phys2bus(phys: usize) -> usize {
phys | 0xc000_0000
}
}

View File

@ -1,282 +0,0 @@
/*
* SPDX-License-Identifier: MIT OR BlueOak-1.0.0
* Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
* Original code distributed under MIT, additional changes are under BlueOak-1.0.0
*
* http://infocenter.arm.com/help/topic/com.arm.doc.ddi0183g/DDI0183G_uart_pl011_r1p5_trm.pdf
* https://docs.rs/embedded-serial/0.5.0/embedded_serial/
*/
use {
super::{
gpio,
mailbox::{self, MailboxOps},
BcmHost,
},
crate::{arch::loop_until, devices::ConsoleOps},
core::ops,
snafu::Snafu,
tock_registers::{
interfaces::{Readable, Writeable},
register_bitfields,
registers::{ReadOnly, ReadWrite, WriteOnly},
},
};
// PL011 UART registers.
//
// Descriptions taken from
// https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf
register_bitfields! {
u32,
/// Flag Register
FR [
/// Transmit FIFO full. The meaning of this bit depends on the
/// state of the FEN bit in the UARTLCR_ LCRH Register. If the
/// FIFO is disabled, this bit is set when the transmit
/// holding register is full. If the FIFO is enabled, the TXFF
/// bit is set when the transmit FIFO is full.
TXFF OFFSET(5) NUMBITS(1) [],
/// Receive FIFO empty. The meaning of this bit depends on the
/// state of the FEN bit in the UARTLCR_H Register. If the
/// FIFO is disabled, this bit is set when the receive holding
/// register is empty. If the FIFO is enabled, the RXFE bit is
/// set when the receive FIFO is empty.
RXFE OFFSET(4) NUMBITS(1) []
],
/// Integer Baud rate divisor
IBRD [
/// Integer Baud rate divisor
IBRD OFFSET(0) NUMBITS(16) []
],
/// Fractional Baud rate divisor
FBRD [
/// Fractional Baud rate divisor
FBRD OFFSET(0) NUMBITS(6) []
],
/// Line Control register
LCRH [
/// Word length. These bits indicate the number of data bits
/// transmitted or received in a frame.
WLEN OFFSET(5) NUMBITS(2) [
FiveBit = 0b00,
SixBit = 0b01,
SevenBit = 0b10,
EightBit = 0b11
]
],
/// Control Register
CR [
/// Receive enable. If this bit is set to 1, the receive
/// section of the UART is enabled. Data reception occurs for
/// UART signals. When the UART is disabled in the middle of
/// reception, it completes the current character before
/// stopping.
RXE OFFSET(9) NUMBITS(1) [
Disabled = 0,
Enabled = 1
],
/// Transmit enable. If this bit is set to 1, the transmit
/// section of the UART is enabled. Data transmission occurs
/// for UART signals. When the UART is disabled in the middle
/// of transmission, it completes the current character before
/// stopping.
TXE OFFSET(8) NUMBITS(1) [
Disabled = 0,
Enabled = 1
],
/// UART enable
UARTEN OFFSET(0) NUMBITS(1) [
/// If the UART is disabled in the middle of transmission
/// or reception, it completes the current character
/// before stopping.
Disabled = 0,
Enabled = 1
]
],
/// Interupt Clear Register
ICR [
/// Meta field for all pending interrupts
ALL OFFSET(0) NUMBITS(11) []
]
}
#[allow(non_snake_case)]
#[repr(C)]
pub struct RegisterBlock {
DR: ReadWrite<u32>, // 0x00
__reserved_0: [u32; 5], // 0x04 (UART0_RSRECR=0x04)
FR: ReadOnly<u32, FR::Register>, // 0x18
__reserved_1: [u32; 1], // 0x1c
ILPR: u32, // 0x20
IBRD: WriteOnly<u32, IBRD::Register>, // 0x24
FBRD: WriteOnly<u32, FBRD::Register>, // 0x28
LCRH: WriteOnly<u32, LCRH::Register>, // 0x2C
CR: WriteOnly<u32, CR::Register>, // 0x30
IFLS: u32, // 0x34
IMSC: u32, // 0x38
RIS: u32, // 0x3C
MIS: u32, // 0x40
ICR: WriteOnly<u32, ICR::Register>, // 0x44
DMACR: u32, // 0x48
__reserved_2: [u32; 14], // 0x4c-0x7c
ITCR: u32, // 0x80
ITIP: u32, // 0x84
ITOP: u32, // 0x88
TDR: u32, // 0x8C
}
#[derive(Debug, Snafu)]
pub enum PL011UartError {
#[snafu(display("PL011 UART setup failed in mailbox operation"))]
MailboxError,
}
pub type Result<T> = ::core::result::Result<T, PL011UartError>;
pub struct PL011Uart {
base_addr: usize,
}
pub struct PreparedPL011Uart(PL011Uart);
/// Divisor values for common baud rates
pub enum Rate {
Baud115200 = 2,
}
impl From<Rate> for u32 {
fn from(r: Rate) -> Self {
r as u32
}
}
impl ops::Deref for PL011Uart {
type Target = RegisterBlock;
fn deref(&self) -> &Self::Target {
unsafe { &*self.ptr() }
}
}
impl ops::Deref for PreparedPL011Uart {
type Target = RegisterBlock;
fn deref(&self) -> &Self::Target {
unsafe { &*self.0.ptr() }
}
}
impl Default for PL011Uart {
fn default() -> Self {
const UART0_BASE: usize = BcmHost::get_peripheral_address() + 0x20_1000;
PL011Uart::new(UART0_BASE)
}
}
impl PL011Uart {
pub fn new(base_addr: usize) -> PL011Uart {
PL011Uart { base_addr }
}
/// Returns a pointer to the register block
fn ptr(&self) -> *const RegisterBlock {
self.base_addr as *const _
}
/// Set baud rate and characteristics (115200 8N1) and map to GPIO
pub fn prepare(
self,
mut mbox: mailbox::Mailbox,
gpio: &gpio::GPIO,
) -> Result<PreparedPL011Uart> {
// turn off UART0
self.CR.set(0);
// set up clock for consistent divisor values
let index = mbox.request();
let index = mbox.set_clock_rate(index, mailbox::clock::UART, 4_000_000 /* 4Mhz */);
let mbox = mbox.end(index);
if mbox.call(mailbox::channel::PropertyTagsArmToVc).is_err() {
return Err(PL011UartError::MailboxError); // Abort if UART clocks couldn't be set
};
// Pin 14
const UART_TXD: gpio::Function = gpio::Function::Alt0;
// Pin 15
const UART_RXD: gpio::Function = gpio::Function::Alt0;
// map UART0 to GPIO pins
gpio.get_pin(14).into_alt(UART_TXD);
gpio.get_pin(15).into_alt(UART_RXD);
gpio::enable_uart_pins(gpio);
self.ICR.write(ICR::ALL::CLEAR);
// @todo Configure divisors more sanely
self.IBRD.write(IBRD::IBRD.val(Rate::Baud115200.into()));
self.FBRD.write(FBRD::FBRD.val(0xB)); // Results in 115200 baud
self.LCRH.write(LCRH::WLEN::EightBit); // 8N1
self.CR
.write(CR::UARTEN::Enabled + CR::TXE::Enabled + CR::RXE::Enabled);
Ok(PreparedPL011Uart(self))
}
}
impl Drop for PreparedPL011Uart {
fn drop(&mut self) {
self.CR
.write(CR::UARTEN::Disabled + CR::TXE::Disabled + CR::RXE::Disabled);
}
}
impl ConsoleOps for PreparedPL011Uart {
/// Send a character
fn putc(&self, c: char) {
// wait until we can send
loop_until(|| !self.FR.is_set(FR::TXFF));
// write the character to the buffer
self.DR.set(c as u32);
}
/// Display a string
fn puts(&self, string: &str) {
for c in string.chars() {
// convert newline to carriage return + newline
if c == '\n' {
self.putc('\r')
}
self.putc(c);
}
}
/// Receive a character
fn getc(&self) -> char {
// wait until something is in the buffer
loop_until(|| !self.FR.is_set(FR::RXFE));
// read it and return
let mut ret = self.DR.get() as u8 as char;
// convert carriage return to newline
if ret == '\r' {
ret = '\n'
}
ret
}
}

View File

@ -1,6 +1,8 @@
# Broadcom bcm2837 on Raspberry Pi 3 as JTAG target
# From https://www.suse.com/c/debugging-raspberry-pi-3-with-jtag/
echo "Booting JTAG for Raspberry Pi 3"
if { [info exists CHIPNAME] } {
set _CHIPNAME $CHIPNAME
} else {

View File

@ -2,6 +2,8 @@
# From https://gist.github.com/tnishinaga/46a3380e1f47f5e892bbb74e55b3cf3e
# See also https://xihan94.gitbook.io/raspberry-pi/raspberry-pi-4-bringup
echo "Booting JTAG for Raspberry Pi 4"
if { [info exists CHIPNAME] } {
set _CHIPNAME $CHIPNAME
} else {

1
rustfmt.toml Normal file
View File

@ -0,0 +1 @@
imports_granularity = "One"

View File

@ -11,7 +11,6 @@
"linker": "rust-lld",
"pre-link-args": {
"ld.lld": [
"--script=linker/aarch64.ld",
"--print-gc-sections"
]
},

BIN
targets/bcm2711-rpi-4-b.dtb Executable file

Binary file not shown.

BIN
targets/bcm2711-rpi-400.dtb Executable file

Binary file not shown.