Compare commits
39 Commits
explore/mm
...
develop
Author | SHA1 | Date |
---|---|---|
|
da9f2940ba | |
|
30db2405ef | |
|
a9a97d132d | |
|
f4418c3164 | |
|
637304bdb3 | |
|
a95d4e3fb0 | |
|
162592beaa | |
|
cfe4a230de | |
|
3c57c6e2df | |
|
2d9da8c5db | |
|
0234f2136e | |
|
e5a3ea6998 | |
|
7f4e9de6d5 | |
|
8f26c6fa44 | |
|
f76fab3fff | |
|
9cbc6ce80f | |
|
8b6a585250 | |
|
ea97d29c3c | |
|
61eb2f9538 | |
|
16ec45b97c | |
|
33dbf79041 | |
|
1e17e03a8d | |
|
721af870bf | |
|
40782ea7cc | |
|
4cdeeb8556 | |
|
b26d61cb67 | |
|
cc4170200f | |
|
e3f199f89f | |
|
af3dc82c76 | |
|
7d03ea85a2 | |
|
ca263b33a1 | |
|
90389705a7 | |
|
5e1bbf9758 | |
|
503f43d983 | |
|
5a304557a7 | |
|
f6da27062f | |
|
e6ddbb76e7 | |
|
d706b2edac | |
|
115c93e3f8 |
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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.
|
||||
|
|
96
Justfile
96
Justfile
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
138
Makefile.toml
138
Makefile.toml
|
@ -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"]
|
||||
|
|
16
README.md
16
README.md
|
@ -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
|
|||
|
||||

|
||||
|
||||

|
||||
|
||||
[](https://deps.rs/repo/github/metta-systems/vesper)
|
||||
|
||||
[](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.
|
||||
|
|
|
@ -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"
|
|
@ -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"]
|
|
@ -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);
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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"
|
|
@ -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
|
|
@ -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(())
|
||||
}
|
|
@ -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
|
||||
project’s 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. Don’t cheat. Be careful to account for and balance the interests of all groups (developers, artists and designers, end users and the general public).
|
||||
|
|
Binary file not shown.
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
@ -0,0 +1 @@
|
|||
zellij-config.sh
|
|
@ -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. #
|
||||
#########################################################
|
|
@ -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"]
|
|
@ -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}
|
|
@ -0,0 +1,5 @@
|
|||
QEMU=
|
||||
QEMU_OPTS=
|
||||
QEMU_RUNNER_OPTS=
|
||||
CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY=
|
||||
KERNEL_BIN=
|
|
@ -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 }
|
|
@ -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",
|
|
@ -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();
|
||||
}
|
|
@ -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.
|
|
@ -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::*};
|
|
@ -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 {
|
|
@ -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 {
|
|
@ -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(())
|
||||
}
|
|
@ -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),
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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)]
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
*/
|
||||
pub mod console;
|
||||
pub mod serial;
|
||||
|
||||
pub use {
|
||||
console::{Console, ConsoleOps},
|
||||
serial::SerialOps,
|
||||
};
|
|
@ -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);
|
||||
}
|
|
@ -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",
|
||||
));
|
|
@ -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),
|
|
@ -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)
|
|
@ -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);
|
|
@ -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 _) }
|
||||
}
|
||||
}
|
|
@ -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) }
|
||||
}
|
||||
}
|
|
@ -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();
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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);
|
|
@ -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
|
|
@ -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 {
|
|
@ -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 }
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
|
@ -1,6 +0,0 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
*/
|
||||
pub mod console;
|
||||
|
||||
pub use console::{Console, ConsoleOps};
|
|
@ -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() {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
imports_granularity = "One"
|
|
@ -11,7 +11,6 @@
|
|||
"linker": "rust-lld",
|
||||
"pre-link-args": {
|
||||
"ld.lld": [
|
||||
"--script=linker/aarch64.ld",
|
||||
"--print-gc-sections"
|
||||
]
|
||||
},
|
||||
|
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue