Compare commits
84 Commits
develop
...
explore/ca
Author | SHA1 | Date |
---|---|---|
|
910ce22a7c | |
|
deabc2ae70 | |
|
f842a467e4 | |
|
94789e7979 | |
|
b594552f23 | |
|
0e1c0e45f7 | |
|
d3c02f0f5b | |
|
dd6f186623 | |
|
7e71ff1787 | |
|
f28c4a6440 | |
|
0dd7aeb44c | |
|
8d8a1c819a | |
|
abc84ec6aa | |
|
0fa4f3bc4e | |
|
dda2c2a4ca | |
|
e24c3aaca5 | |
|
f0f35ce1ed | |
|
bc31a43f82 | |
|
4cc40be9eb | |
|
e04ea59019 | |
|
3940352be1 | |
|
5f7ecca625 | |
|
fa91a108fd | |
|
91f9c2c215 | |
|
d8de631b44 | |
|
8f54e179c1 | |
|
99a0cac3db | |
|
5ba79d78fd | |
|
d04739312c | |
|
b0bbff20bd | |
|
74730117f2 | |
|
9638ddbb3e | |
|
8dc3586b55 | |
|
caa1929a0f | |
|
57ae94847d | |
|
b6852b46a5 | |
|
5b59eff974 | |
|
25f6fc0e98 | |
|
6e6e82f466 | |
|
d129db345f | |
|
6de2ef38a7 | |
|
d4b92b106a | |
|
42f0efcba9 | |
|
babd5caaab | |
|
4616dfee86 | |
|
a01445d0da | |
|
4979e82a12 | |
|
6cef160595 | |
|
aad25145bd | |
|
c94871fa46 | |
|
afe6510fae | |
|
8e7e652a76 | |
|
f796788ed5 | |
|
0aceb8fc27 | |
|
b66a64fe58 | |
|
d2982e56ff | |
|
eb0aed1730 | |
|
fc6fa7105a | |
|
591292c186 | |
|
e694c33c61 | |
|
145006b400 | |
|
eabc36845a | |
|
f4000966b3 | |
|
8b0468a995 | |
|
fe2cf95d4e | |
|
e6580a5465 | |
|
56aca62409 | |
|
ba8e8ae5ae | |
|
b14cc2ac22 | |
|
db810ade9a | |
|
e3c9926a43 | |
|
b3ddfc5665 | |
|
0cf8a88f12 | |
|
b2c99f52c7 | |
|
b025fb6dd3 | |
|
4c9703340e | |
|
ffde65fb81 | |
|
f25ace0a80 | |
|
ee1b5fc57a | |
|
15a38d5689 | |
|
be1873b678 | |
|
c75db1e2d6 | |
|
ec7172aded | |
|
333dece260 |
|
@ -1,12 +1,16 @@
|
|||
[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"
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: cargo
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 10
|
||||
ignore:
|
||||
- dependency-name: cortex-a
|
||||
versions:
|
||||
- 5.1.2
|
||||
- 5.1.3
|
||||
- 5.1.4
|
||||
- 5.1.5
|
||||
- 5.1.6
|
||||
- dependency-name: qemu-exit
|
||||
versions:
|
||||
- 1.0.2
|
||||
- dependency-name: register
|
||||
versions:
|
||||
- 1.0.2
|
|
@ -42,20 +42,12 @@ jobs:
|
|||
rustc -Vv
|
||||
cargo -Vv
|
||||
|
||||
- name: "Install Rustup Components"
|
||||
run: rustup component add rust-src llvm-tools-preview
|
||||
|
||||
- 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"
|
||||
|
@ -74,6 +66,9 @@ 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'
|
||||
|
||||
|
@ -109,13 +104,8 @@ jobs:
|
|||
- name: 'Build kernel'
|
||||
run: cargo make build
|
||||
|
||||
- name: 'Run tests (macOS)'
|
||||
- name: 'Run tests'
|
||||
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"
|
||||
|
@ -143,8 +133,7 @@ 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: rustup toolchain install nightly --profile minimal --component clippy
|
||||
- run: rustup component add rust-src llvm-tools-preview
|
||||
- run: cargo install cargo-make
|
||||
- run: env CLIPPY_FEATURES=${{ matrix.features }} cargo make clippy
|
||||
|
|
|
@ -1,60 +1,5 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# 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"
|
||||
|
@ -63,21 +8,9 @@ checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4"
|
|||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
|
@ -85,96 +18,15 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
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 = "b63edc3f163b3c71ec8aa23f9bd6070f77edbf3d1d198b164afa90ff00e4ec62"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
"indexmap",
|
||||
"os_str_bytes",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
"textwrap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cortex-a"
|
||||
version = "7.0.1"
|
||||
version = "5.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bd95fd055d118f77d4e4d527201b6ceccd13586b19b4dac1270f7081fef0f98"
|
||||
checksum = "dc37a4862a4f5b40df96f5e0f3bd4ce6a1bd8bb59a965cd476844673faf83896"
|
||||
dependencies = [
|
||||
"tock-registers",
|
||||
"register",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
@ -182,421 +34,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "fehler"
|
||||
version = "1.0.0"
|
||||
name = "paste"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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"
|
||||
checksum = "7151b083b0664ed58ed669fcdd92f01c3d2fdbf10af4931a301474950b52bfa9"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.36"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
|
||||
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "qemu-exit"
|
||||
version = "3.0.1"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ff023245bfcc73fb890e1f8d5383825b3131cc920020a5c487d6f113dfc428a"
|
||||
checksum = "0e64f0ef443037525a562c4fa6ad3460a351ec10c7ebb0b6c49a87a576d0d1b5"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.15"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
|
||||
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
@ -608,105 +70,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "bd7a31eed1591dcbc95d92ad7161908e72f4677f8fabf2a32ca49b4237cbf211"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.10"
|
||||
name = "register"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
|
||||
checksum = "faf386c0c48fea132b7fbbf71c32d1b1d62e7c59043bbabdd15c9ed9f254517e"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"tock-registers",
|
||||
]
|
||||
|
||||
[[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"
|
||||
version = "0.6.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2eba135d2c579aa65364522eb78590cdf703176ef71ad4c32b00f58f7afb2df5"
|
||||
checksum = "eab12d3c261b2308b0d80c26fffb58d17eba81a4be97890101f416b478c79ca7"
|
||||
dependencies = [
|
||||
"doc-comment",
|
||||
"snafu-derive",
|
||||
|
@ -714,122 +90,35 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "snafu-derive"
|
||||
version = "0.7.0"
|
||||
version = "0.6.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a7fe9b0669ef117c5cabc5549638528f36771f058ff977d7689deb517833a75"
|
||||
checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.86"
|
||||
version = "1.0.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
|
||||
checksum = "8833e20724c24de12bbaba5ad230ea61c3eafb05b881c7c9d3cfe8638b187e68"
|
||||
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"
|
||||
version = "0.6.0"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
||||
|
||||
[[package]]
|
||||
name = "usize_conversions"
|
||||
|
@ -844,75 +133,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "88dfeb711b61ce620c0cb6fd9f8e3e678622f0c971da2a63c4b3e25e88ed012f"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
name = "vesper"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
"bit_field",
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"cortex-a",
|
||||
"paste",
|
||||
"qemu-exit",
|
||||
"r0",
|
||||
"register",
|
||||
"snafu",
|
||||
"usize_conversions",
|
||||
"ux",
|
||||
"vesper-user",
|
||||
]
|
||||
|
||||
[[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"
|
||||
name = "vesper-user"
|
||||
version = "0.0.1"
|
||||
|
|
11
Cargo.toml
11
Cargo.toml
|
@ -1,13 +1,10 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"nucleus",
|
||||
"bin/chainboot",
|
||||
"bin/chainofcommand"
|
||||
"vesper-user",
|
||||
"crates/tock-registers"
|
||||
]
|
||||
|
||||
[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.
|
||||
|
@ -23,3 +20,7 @@ lto = true
|
|||
[profile.test]
|
||||
opt-level = 's'
|
||||
debug = true
|
||||
|
||||
[patch.crates-io]
|
||||
tock-registers = { path = 'crates/tock-registers' }
|
||||
# register = { git = 'https://github.com/metta-systems/register-rs', branch = 'update-deps-1' }
|
||||
|
|
82
Justfile
82
Justfile
|
@ -1,111 +1,57 @@
|
|||
_default:
|
||||
@just --list
|
||||
|
||||
# Build default hw kernel and run chainofcommand to boot this kernel onto the board
|
||||
boot: chainofcommand
|
||||
cargo make chainboot
|
||||
|
||||
# Build and run kernel in QEMU with serial port emulation
|
||||
zellij:
|
||||
cargo make zellij-nucleus
|
||||
zellij --layout-path emulation/layout.zellij
|
||||
|
||||
# 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:
|
||||
# Build and run kernel in QEMU
|
||||
cargo make qemu
|
||||
|
||||
# Build and run kernel in QEMU with GDB port enabled
|
||||
qemu-gdb:
|
||||
# Build and run kernel in QEMU with GDB port enabled
|
||||
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:
|
||||
# Build and write kernel to an SD Card
|
||||
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:
|
||||
# Build default hw kernel
|
||||
cargo make build
|
||||
cargo make kernel-binary
|
||||
|
||||
# Clean project
|
||||
clean:
|
||||
# Clean project
|
||||
cargo make clean
|
||||
rm -f kernel8 kernel8.img
|
||||
|
||||
# Run clippy checks
|
||||
clippy:
|
||||
# TODO: use cargo-hack
|
||||
# Run clippy checks
|
||||
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
|
||||
|
|
142
Makefile.toml
142
Makefile.toml
|
@ -17,22 +17,14 @@ DEFAULT_TARGET = "aarch64-vesper-metta"
|
|||
# Pass TARGET env var if it does not match the default target above.
|
||||
TARGET = { value = "${DEFAULT_TARGET}", condition = { env_not_set = ["TARGET"] } }
|
||||
|
||||
# Name of the target board "rpi3" or "rpi4"
|
||||
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 = "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/HEAD-a2c58332-aarch64/bin/aarch64-unknown-elf-gdb", condition = { env_not_set = ["GDB"] } }
|
||||
GDB = { value = "/usr/local/opt/gdb-8.2.1-aarhc64/bin/aarch64-linux-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)
|
||||
OPENOCD = { value = "/usr/local/opt/openocd/4d6519593-rtt/bin/openocd", condition = { env_not_set = ["OPENOCD"] } }
|
||||
# OpenOCD with JLink support and RTT patch from http://openocd.zylin.com/#/c/4055/11
|
||||
OPENOCD = { value = "/usr/local/openocd-aeb7b327-rtt/bin/openocd", condition = { env_not_set = ["OPENOCD"] } }
|
||||
|
||||
# Mounted sdcard partition path
|
||||
VOLUME = { value = "/Volumes/BOOT", condition = { env_not_set = ["VOLUME"] } }
|
||||
|
@ -42,12 +34,10 @@ 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,rpi3"
|
||||
QEMU_FEATURES = "qemu"
|
||||
|
||||
OBJCOPY = "rust-objcopy" # Part of `cargo objcopy` in cargo-binutils
|
||||
OBJCOPY_PARAMS = "--strip-all -O binary"
|
||||
|
@ -60,10 +50,9 @@ QEMU_CONTAINER_CMD = "qemu-system-aarch64"
|
|||
#
|
||||
# Could additionally use -nographic to disable GUI -- this shall be useful for automated tests.
|
||||
#
|
||||
# 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"
|
||||
# -d in_asm,unimp,int
|
||||
QEMU_OPTS = "-M raspi3 -d int -semihosting"
|
||||
QEMU_SERIAL_OPTS = "-serial null -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.
|
||||
|
@ -72,11 +61,8 @@ 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}/target/${TARGET}/release/nucleus"
|
||||
KERNEL_BIN = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/nucleus.bin"
|
||||
|
||||
CHAINBOOT_SERIAL = "/dev/tty.SLAB_USBtoUART"
|
||||
CHAINBOOT_BAUD = 115200
|
||||
KERNEL_ELF = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/kernel8"
|
||||
KERNEL_BIN = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/kernel8.img"
|
||||
|
||||
[tasks.default]
|
||||
alias = "all"
|
||||
|
@ -87,113 +73,3 @@ 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"]
|
||||
|
|
10
README.md
10
README.md
|
@ -2,7 +2,7 @@
|
|||
|
||||
## About kernel
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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,14 +94,6 @@ 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:
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
[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"
|
|
@ -1,52 +0,0 @@
|
|||
[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"]
|
|
@ -1,6 +0,0 @@
|
|||
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);
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
// 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)
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
// 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
|
|
@ -1,98 +0,0 @@
|
|||
/* 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
|
||||
}
|
|
@ -1,155 +0,0 @@
|
|||
// 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)
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
[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"
|
|
@ -1,44 +0,0 @@
|
|||
[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
|
|
@ -1,436 +0,0 @@
|
|||
#![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(())
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
; DO NOT EDIT (unless you know what you are doing)
|
||||
;
|
||||
; This subdirectory is a git "subrepo", and this file is maintained by the
|
||||
; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
|
||||
;
|
||||
[subrepo]
|
||||
remote = git@github.com:metta-systems/tock-registers.git
|
||||
branch = master
|
||||
commit = 4d8202452f5b7f1c056b524eac62445b74ceebce
|
||||
parent = d78ffd283311c6a257f3cc93df8bc4cfe42bb44a
|
||||
cmdver = 0.3.1
|
|
@ -0,0 +1,55 @@
|
|||
# Changelog
|
||||
|
||||
## master
|
||||
|
||||
## v0.6
|
||||
|
||||
- #2095: Fix syntax errors and inconsistencies in documentation
|
||||
- #2071: Clarify bit widths in documentation examples
|
||||
- #2015: Use UnsafeCell in registers (see issue #2005)
|
||||
- #1939: Make the Field::mask and FieldValue::mask fields private
|
||||
- #1823: Allow large unsigned values as bitmasks + add bitmask! helper macro
|
||||
- #1554: Allow lifetime parameters for `register_structs! { Foo<'a> { ..`
|
||||
- #1661: Add `Aliased` register type for MMIO with differing R/W behavior
|
||||
|
||||
## v0.5
|
||||
|
||||
- #1510
|
||||
- Register visibility granularity: don't automatically make everything
|
||||
`pub`, rather give creation macro callers visbility control.
|
||||
|
||||
- #1489
|
||||
- Make `register_structs!` unit test generation opt-out, so that
|
||||
`custom-test-frameworks` environments can disable them.
|
||||
|
||||
- #1481
|
||||
- Add `#[derive(Copy, Clone)]` to InMemoryRegister.
|
||||
|
||||
- #1428
|
||||
- Implement `mask()` for `FieldValue<u16>` which seems to have been
|
||||
skipped at some point.
|
||||
- Implement `read()` for `FieldValue` so that individual fields
|
||||
can be extracted from a register `FieldValue` representation.
|
||||
|
||||
- #1461: Update `register_structs` macro to support flexible visibility of each
|
||||
struct and each field. Also revert to private structs by default.
|
||||
|
||||
## v0.4.1
|
||||
|
||||
- #1458: Update struct macro to create `pub` structs
|
||||
|
||||
## v0.4
|
||||
|
||||
- #1368: Remove `new()` and add `InMemoryRegister`
|
||||
- #1410: Add new macro for generating structs
|
||||
|
||||
## v0.3
|
||||
|
||||
- #1243: Update to Rust 2018 (nightly)
|
||||
- #1250: Doc-only: Fix some rustdoc warnings
|
||||
|
||||
## v0.2
|
||||
|
||||
- #1161: Add `read_as_enum` to `LocalRegisterCopy`; thanks @andre-richter
|
||||
|
||||
## v0.1 - Initial Release
|
|
@ -0,0 +1,18 @@
|
|||
[package]
|
||||
name = "tock-registers"
|
||||
version = "0.6.0"
|
||||
authors = ["Tock Project Developers <tock-dev@googlegroups.com>"]
|
||||
description = "Memory-Mapped I/O and register interface developed for Tock."
|
||||
homepage = "https://www.tockos.org/"
|
||||
repository = "https://github.com/tock/tock/tree/master/libraries/tock-register-interface"
|
||||
readme = "README.md"
|
||||
keywords = ["tock", "embedded", "registers", "mmio", "bare-metal"]
|
||||
categories = ["data-structures", "embedded", "no-std"]
|
||||
license = "MIT/Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "tock/tock", branch = "master" }
|
||||
|
||||
[features]
|
||||
no_std_unit_tests = []
|
|
@ -0,0 +1,14 @@
|
|||
#
|
||||
# SPDX-License-Identifier: BlueOak-1.0.0
|
||||
#
|
||||
# Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
#
|
||||
[tasks.build]
|
||||
env = { "TARGET_FEATURES" = "" }
|
||||
args = ["build", "--target=${TARGET_JSON}", "--release", "--features=${TARGET_FEATURES}"]
|
||||
|
||||
[tasks.docs]
|
||||
clear = true
|
||||
|
||||
[tasks.test]
|
||||
clear = true
|
|
@ -0,0 +1,483 @@
|
|||
# Tock Register Interface
|
||||
|
||||
This crate provides an interface for defining and manipulating memory mapped
|
||||
registers and bitfields.
|
||||
|
||||
## Defining registers
|
||||
|
||||
The crate provides three types for working with memory mapped registers:
|
||||
`ReadWrite`, `ReadOnly`, and `WriteOnly`, providing read-write, read-only, and
|
||||
write-only functionality, respectively.
|
||||
|
||||
Defining the registers is done with the `register_structs` macro, which expects
|
||||
for each register an offset, a field name, and a type. Registers must be
|
||||
declared in increasing order of offsets and contiguously. Gaps when defining the
|
||||
registers must be explicitly annotated with an offset and gap identifier (by
|
||||
convention using a field named `_reservedN`), but without a type. The macro will
|
||||
then automatically take care of calculating the gap size and inserting a
|
||||
suitable filler struct. The end of the struct is marked with its size and the
|
||||
`@END` keyword, effectively pointing to the offset immediately past the list of
|
||||
registers.
|
||||
|
||||
```rust
|
||||
use tock_registers::registers::{ReadOnly, ReadWrite, WriteOnly};
|
||||
|
||||
register_structs! {
|
||||
Registers {
|
||||
// Control register: read-write
|
||||
// The 'Control' parameter constrains this register to only use fields from
|
||||
// a certain group (defined below in the bitfields section).
|
||||
(0x000 => cr: ReadWrite<u8, Control::Register>),
|
||||
|
||||
// Status register: read-only
|
||||
(0x001 => s: ReadOnly<u8, Status::Register>),
|
||||
|
||||
// Registers can be bytes, halfwords, or words:
|
||||
// Note that the second type parameter can be omitted, meaning that there
|
||||
// are no bitfields defined for these registers.
|
||||
(0x002 => byte0: ReadWrite<u8>),
|
||||
(0x003 => byte1: ReadWrite<u8>),
|
||||
(0x004 => short: ReadWrite<u16>),
|
||||
|
||||
// Empty space between registers must be marked with a padding field,
|
||||
// declared as follows. The length of this padding is automatically
|
||||
// computed by the macro.
|
||||
(0x006 => _reserved),
|
||||
(0x008 => word: ReadWrite<u32>),
|
||||
|
||||
// The type for a register can be anything. Conveniently, you can use an
|
||||
// array when there are a bunch of similar registers.
|
||||
(0x00C => array: [ReadWrite<u32>; 4]),
|
||||
(0x01C => ... ),
|
||||
|
||||
// Etc.
|
||||
|
||||
// The end of the struct is marked as follows.
|
||||
(0x100 => @END),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This generates a C-style struct of the following form.
|
||||
|
||||
```rust
|
||||
#[repr(C)]
|
||||
struct Registers {
|
||||
// Control register: read-write
|
||||
// The 'Control' parameter constrains this register to only use fields from
|
||||
// a certain group (defined below in the bitfields section).
|
||||
cr: ReadWrite<u8, Control::Register>,
|
||||
|
||||
// Status register: read-only
|
||||
s: ReadOnly<u8, Status::Register>
|
||||
|
||||
// Registers can be bytes, halfwords, or words:
|
||||
// Note that the second type parameter can be omitted, meaning that there
|
||||
// are no bitfields defined for these registers.
|
||||
byte0: ReadWrite<u8>,
|
||||
byte1: ReadWrite<u8>,
|
||||
short: ReadWrite<u16>,
|
||||
|
||||
// The padding length was automatically computed as 0x008 - 0x006.
|
||||
_reserved: [u8; 2],
|
||||
word: ReadWrite<u32>,
|
||||
|
||||
// Arrays are expanded as-is, like any other type.
|
||||
array: [ReadWrite<u32>; 4],
|
||||
|
||||
// Etc.
|
||||
}
|
||||
```
|
||||
|
||||
By default, `std` unit tests for the struct are generated as well (that is,
|
||||
tests attributed with `#[test]`). The unit tests make sure that the offsets and
|
||||
padding are consistent with the actual fields in the struct, and that alignment
|
||||
is correct.
|
||||
|
||||
Since those tests would break compilation in `custom-test-frameworks`
|
||||
environments, it is possible to opt out of the test generation. To do so, add
|
||||
the following cargo feature:
|
||||
|
||||
```toml
|
||||
[dependencies.tock-registers]
|
||||
version = "0.4.x"
|
||||
features = ["no_std_unit_tests"]
|
||||
```
|
||||
|
||||
WARNING: For now, the **unit tests checking offsets and alignments are not yet
|
||||
run** on `make ci-travis`. This means that leaving an unintentional gap between
|
||||
registers will **not** be caught. Instead, the `register_structs` macro will
|
||||
generate a struct with invalid offsets without warning. Please follow the
|
||||
discussion on https://github.com/tock/tock/pull/1393.
|
||||
|
||||
For example, the following call to the macro:
|
||||
|
||||
```rust
|
||||
register_structs! {
|
||||
Registers {
|
||||
(0x000 => foo: ReadOnly<u8>),
|
||||
(0x008 => bar: ReadOnly<u8>),
|
||||
(0x009 => @END),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
will generate the following struct, even though there is an unintentional gap of
|
||||
4 bytes between addresses `0x004` (the end of register `foo`) and `0x008` (the
|
||||
intended beginning of register `bar`).
|
||||
|
||||
```rust
|
||||
#[repr(C)]
|
||||
struct Registers {
|
||||
foo: ReadOnly<u32>,
|
||||
bar: ReadOnly<u32>,
|
||||
}
|
||||
```
|
||||
|
||||
By default, the visibility of the generated structs and fields is private. You
|
||||
can make them public using the `pub` keyword, just before the struct name or the
|
||||
field identifier.
|
||||
|
||||
For example, the following call to the macro:
|
||||
|
||||
```rust
|
||||
register_structs! {
|
||||
pub Registers {
|
||||
(0x000 => foo: ReadOnly<u32>),
|
||||
(0x004 => pub bar: ReadOnly<u32>),
|
||||
(0x008 => @END),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
will generate the following struct.
|
||||
|
||||
```rust
|
||||
#[repr(C)]
|
||||
pub struct Registers {
|
||||
foo: ReadOnly<u32>,
|
||||
pub bar: ReadOnly<u32>,
|
||||
}
|
||||
```
|
||||
|
||||
## Defining bitfields
|
||||
|
||||
Bitfields are defined through the `register_bitfields!` macro:
|
||||
|
||||
```rust
|
||||
register_bitfields! [
|
||||
// First parameter is the register width. Can be u8, u16, u32, or u64.
|
||||
u32,
|
||||
|
||||
// Each subsequent parameter is a register abbreviation, its descriptive
|
||||
// name, and its associated bitfields.
|
||||
// The descriptive name defines this 'group' of bitfields. Only registers
|
||||
// defined as ReadWrite<_, Control::Register> can use these bitfields.
|
||||
Control [
|
||||
// Bitfields are defined as:
|
||||
// name OFFSET(shift) NUMBITS(num) [ /* optional values */ ]
|
||||
|
||||
// This is a two-bit field which includes bits 4 and 5
|
||||
RANGE OFFSET(4) NUMBITS(2) [
|
||||
// Each of these defines a name for a value that the bitfield can be
|
||||
// written with or matched against. Note that this set is not exclusive--
|
||||
// the field can still be written with arbitrary constants.
|
||||
VeryHigh = 0,
|
||||
High = 1,
|
||||
Low = 2
|
||||
],
|
||||
|
||||
// A common case is single-bit bitfields, which usually just mean
|
||||
// 'enable' or 'disable' something.
|
||||
EN OFFSET(3) NUMBITS(1) [],
|
||||
INT OFFSET(2) NUMBITS(1) []
|
||||
],
|
||||
|
||||
// Another example:
|
||||
// Status register
|
||||
Status [
|
||||
TXCOMPLETE OFFSET(0) NUMBITS(1) [],
|
||||
TXINTERRUPT OFFSET(1) NUMBITS(1) [],
|
||||
RXCOMPLETE OFFSET(2) NUMBITS(1) [],
|
||||
RXINTERRUPT OFFSET(3) NUMBITS(1) [],
|
||||
MODE OFFSET(4) NUMBITS(3) [
|
||||
FullDuplex = 0,
|
||||
HalfDuplex = 1,
|
||||
Loopback = 2,
|
||||
Disabled = 3
|
||||
],
|
||||
ERRORCOUNT OFFSET(6) NUMBITS(3) []
|
||||
],
|
||||
|
||||
// In a simple case, offset can just be a number, and the number of bits
|
||||
// is set to 1:
|
||||
InterruptFlags [
|
||||
UNDES 10,
|
||||
TXEMPTY 9,
|
||||
NSSR 8,
|
||||
OVRES 3,
|
||||
MODF 2,
|
||||
TDRE 1,
|
||||
RDRF 0
|
||||
]
|
||||
]
|
||||
```
|
||||
|
||||
## Register Interface Summary
|
||||
|
||||
There are four types provided by the register interface: `ReadOnly`,
|
||||
`WriteOnly`, `ReadWrite`, and `Aliased`. They provide the following functions:
|
||||
|
||||
```rust
|
||||
ReadOnly<T: IntLike, R: RegisterLongName = ()>
|
||||
.get() -> T // Get the raw register value
|
||||
.read(field: Field<T, R>) -> T // Read the value of the given field
|
||||
.read_as_enum<E>(field: Field<T, R>) -> Option<E> // Read value of the given field as a enum member
|
||||
.is_set(field: Field<T, R>) -> bool // Check if one or more bits in a field are set
|
||||
.matches_any(value: FieldValue<T, R>) -> bool // Check if any specified parts of a field match
|
||||
.matches_all(value: FieldValue<T, R>) -> bool // Check if all specified parts of a field match
|
||||
.extract() -> LocalRegisterCopy<T, R> // Make local copy of register
|
||||
|
||||
WriteOnly<T: IntLike, R: RegisterLongName = ()>
|
||||
.set(value: T) // Set the raw register value
|
||||
.write(value: FieldValue<T, R>) // Write the value of one or more fields,
|
||||
// overwriting other fields to zero
|
||||
ReadWrite<T: IntLike, R: RegisterLongName = ()>
|
||||
.get() -> T // Get the raw register value
|
||||
.set(value: T) // Set the raw register value
|
||||
.read(field: Field<T, R>) -> T // Read the value of the given field
|
||||
.read_as_enum<E>(field: Field<T, R>) -> Option<E> // Read value of the given field as a enum member
|
||||
.write(value: FieldValue<T, R>) // Write the value of one or more fields,
|
||||
// overwriting other fields to zero
|
||||
.modify(value: FieldValue<T, R>) // Write the value of one or more fields,
|
||||
// leaving other fields unchanged
|
||||
.modify_no_read( // Write the value of one or more fields,
|
||||
original: LocalRegisterCopy<T, R>, // leaving other fields unchanged, but pass in
|
||||
value: FieldValue<T, R>) // the original value, instead of doing a register read
|
||||
.is_set(field: Field<T, R>) -> bool // Check if one or more bits in a field are set
|
||||
.matches_any(value: FieldValue<T, R>) -> bool // Check if any specified parts of a field match
|
||||
.matches_all(value: FieldValue<T, R>) -> bool // Check if all specified parts of a field match
|
||||
.extract() -> LocalRegisterCopy<T, R> // Make local copy of register
|
||||
|
||||
Aliased<T: IntLike, R: RegisterLongName = (), W: RegisterLongName = ()>
|
||||
.get() -> T // Get the raw register value
|
||||
.set(value: T) // Set the raw register value
|
||||
.read(field: Field<T, R>) -> T // Read the value of the given field
|
||||
.read_as_enum<E>(field: Field<T, R>) -> Option<E> // Read value of the given field as a enum member
|
||||
.write(value: FieldValue<T, W>) // Write the value of one or more fields,
|
||||
// overwriting other fields to zero
|
||||
.is_set(field: Field<T, R>) -> bool // Check if one or more bits in a field are set
|
||||
.matches_any(value: FieldValue<T, R>) -> bool // Check if any specified parts of a field match
|
||||
.matches_all(value: FieldValue<T, R>) -> bool // Check if all specified parts of a field match
|
||||
.extract() -> LocalRegisterCopy<T, R> // Make local copy of register
|
||||
```
|
||||
|
||||
The `Aliased` type represents cases where read-only and write-only registers,
|
||||
with different meanings, are aliased to the same memory location.
|
||||
|
||||
The first type parameter (the `IntLike` type) is `u8`, `u16`, `u32`, or `u64`.
|
||||
|
||||
## Example: Using registers and bitfields
|
||||
|
||||
Assuming we have defined a `Registers` struct and the corresponding bitfields as
|
||||
in the previous two sections. We also have an immutable reference to the
|
||||
`Registers` struct, named `registers`.
|
||||
|
||||
```rust
|
||||
// -----------------------------------------------------------------------------
|
||||
// RAW ACCESS
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Get or set the raw value of the register directly. Nothing fancy:
|
||||
registers.cr.set(registers.cr.get() + 1);
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// READ
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// `range` will contain the value of the RANGE field, e.g. 0, 1, 2, or 3.
|
||||
// The type annotation is not necessary, but provided for clarity here.
|
||||
let range: u8 = registers.cr.read(Control::RANGE);
|
||||
|
||||
// Or one can read `range` as a enum and `match` over it.
|
||||
let range = registers.cr.read_as_enum(Control::RANGE);
|
||||
match range {
|
||||
Some(Control::RANGE::Value::VeryHigh) => { /* ... */ }
|
||||
Some(Control::RANGE::Value::High) => { /* ... */ }
|
||||
Some(Control::RANGE::Value::Low) => { /* ... */ }
|
||||
|
||||
None => unreachable!("invalid value")
|
||||
}
|
||||
|
||||
// `en` will be 0 or 1
|
||||
let en: u8 = registers.cr.read(Control::EN);
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// MODIFY
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Write a value to a bitfield without altering the values in other fields:
|
||||
registers.cr.modify(Control::RANGE.val(2)); // Leaves EN, INT unchanged
|
||||
|
||||
// Named constants can be used instead of the raw values:
|
||||
registers.cr.modify(Control::RANGE::VeryHigh);
|
||||
|
||||
// Another example of writing a field with a raw value:
|
||||
registers.cr.modify(Control::EN.val(0)); // Leaves RANGE, INT unchanged
|
||||
|
||||
// For one-bit fields, the named values SET and CLEAR are automatically
|
||||
// defined:
|
||||
registers.cr.modify(Control::EN::SET);
|
||||
|
||||
// Write multiple values at once, without altering other fields:
|
||||
registers.cr.modify(Control::EN::CLEAR + Control::RANGE::Low); // INT unchanged
|
||||
|
||||
// Any number of non-overlapping fields can be combined:
|
||||
registers.cr.modify(Control::EN::CLEAR + Control::RANGE::High + CR::INT::SET);
|
||||
|
||||
// In some cases (such as a protected register) .modify() may not be appropriate.
|
||||
// To enable updating a register without coupling the read and write, use
|
||||
// modify_no_read():
|
||||
let original = registers.cr.extract();
|
||||
registers.cr.modify_no_read(original, Control::EN::CLEAR);
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// WRITE
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Same interface as modify, except that all unspecified fields are overwritten to zero.
|
||||
registers.cr.write(Control::RANGE.val(1)); // implictly sets all other bits to zero
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// BITFLAGS
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// For one-bit fields, easily check if they are set or clear:
|
||||
let txcomplete: bool = registers.s.is_set(Status::TXCOMPLETE);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// MATCHING
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// You can also query a specific register state easily with `matches_[any|all]`:
|
||||
|
||||
// Doesn't care about the state of any field except TXCOMPLETE and MODE:
|
||||
let ready: bool = registers.s.matches_all(Status::TXCOMPLETE:SET +
|
||||
Status::MODE::FullDuplex);
|
||||
|
||||
// This is very useful for awaiting for a specific condition:
|
||||
while !registers.s.matches_all(Status::TXCOMPLETE::SET +
|
||||
Status::RXCOMPLETE::SET +
|
||||
Status::TXINTERRUPT::CLEAR) {}
|
||||
|
||||
// Or for checking whether any interrupts are enabled:
|
||||
let any_ints = registers.s.matches_any(Status::TXINTERRUPT + Status::RXINTERRUPT);
|
||||
|
||||
// Also you can read a register with set of enumerated values as a enum and `match` over it:
|
||||
let mode = registers.cr.read_as_enum(Status::MODE);
|
||||
|
||||
match mode {
|
||||
Some(Status::MODE::FullDuplex) => { /* ... */ }
|
||||
Some(Status::MODE::HalfDuplex) => { /* ... */ }
|
||||
|
||||
None => unreachable!("invalid value")
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// LOCAL COPY
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// More complex code may want to read a register value once and then keep it in
|
||||
// a local variable before using the normal register interface functions on the
|
||||
// local copy.
|
||||
|
||||
// Create a copy of the register value as a local variable.
|
||||
let local = registers.cr.extract();
|
||||
|
||||
// Now all the functions for a ReadOnly register work.
|
||||
let txcomplete: bool = local.is_set(Status::TXCOMPLETE);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// In-Memory Registers
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// In some cases, code may want to edit a memory location with all of the
|
||||
// register features described above, but the actual memory location is not a
|
||||
// fixed MMIO register but instead an arbitrary location in memory. If this
|
||||
// location is then shared with the hardware (i.e. via DMA) then the code
|
||||
// must do volatile reads and writes since the value may change without the
|
||||
// software knowing. To support this, the library includes an `InMemoryRegister`
|
||||
// type.
|
||||
|
||||
let control: InMemoryRegister<u32, Control::Register> = InMemoryRegister::new(0)
|
||||
control.write(Contol::BYTE_COUNT.val(0) +
|
||||
Contol::ENABLE::Yes +
|
||||
Contol::LENGTH.val(10));
|
||||
```
|
||||
|
||||
Note that `modify` performs exactly one volatile load and one volatile store,
|
||||
`write` performs exactly one volatile store, and `read` performs exactly one
|
||||
volatile load. Thus, you are ensured that a single call will set or query all
|
||||
fields simultaneously.
|
||||
|
||||
## Performance
|
||||
|
||||
Examining the binaries while testing this interface, everything compiles
|
||||
down to the optimal inlined bit twiddling instructions--in other words, there is
|
||||
zero runtime cost, as far as an informal preliminary study has found.
|
||||
|
||||
## Nice type checking
|
||||
|
||||
This interface helps the compiler catch some common types of bugs via type checking.
|
||||
|
||||
If you define the bitfields for e.g. a control register, you can give them a
|
||||
descriptive group name like `Control`. This group of bitfields will only work
|
||||
with a register of the type `ReadWrite<_, Control>` (or `ReadOnly/WriteOnly`,
|
||||
etc). For instance, if we have the bitfields and registers as defined above,
|
||||
|
||||
```rust
|
||||
// This line compiles, because registers.cr is associated with the Control group
|
||||
// of bitfields.
|
||||
registers.cr.modify(Control::RANGE.val(1));
|
||||
|
||||
// This line will not compile, because registers.s is associated with the Status
|
||||
// group, not the Control group.
|
||||
let range = registers.s.read(Control::RANGE);
|
||||
```
|
||||
|
||||
## Naming conventions
|
||||
|
||||
There are several related names in the register definitions. Below is a
|
||||
description of the naming convention for each:
|
||||
|
||||
```rust
|
||||
use tock_registers::registers::ReadWrite;
|
||||
|
||||
#[repr(C)]
|
||||
struct Registers {
|
||||
// The register name in the struct should be a lowercase version of the
|
||||
// register abbreviation, as written in the datasheet:
|
||||
cr: ReadWrite<u8, Control::Register>,
|
||||
}
|
||||
|
||||
register_bitfields! [
|
||||
u8,
|
||||
|
||||
// The name should be the long descriptive register name,
|
||||
// camelcase, without the word 'register'.
|
||||
Control [
|
||||
// The field name should be the capitalized abbreviated
|
||||
// field name, as given in the datasheet.
|
||||
RANGE OFFSET(4) NUMBITS(3) [
|
||||
// Each of the field values should be camelcase,
|
||||
// as descriptive of their value as possible.
|
||||
VeryHigh = 0,
|
||||
High = 1,
|
||||
Low = 2
|
||||
]
|
||||
]
|
||||
]
|
||||
```
|
|
@ -0,0 +1,9 @@
|
|||
//! Tock Register Interface
|
||||
//!
|
||||
//!
|
||||
|
||||
#![feature(const_fn)]
|
||||
#![no_std]
|
||||
|
||||
pub mod macros;
|
||||
pub mod registers;
|
|
@ -0,0 +1,437 @@
|
|||
//! Macros for cleanly defining peripheral registers.
|
||||
|
||||
/// Helper macro for computing bitmask of variable number of bits
|
||||
#[macro_export]
|
||||
macro_rules! bitmask {
|
||||
($numbits:expr) => {
|
||||
(1 << ($numbits - 1)) + ((1 << ($numbits - 1)) - 1)
|
||||
};
|
||||
}
|
||||
|
||||
/// Helper macro for defining register fields.
|
||||
#[macro_export]
|
||||
macro_rules! register_bitmasks {
|
||||
{
|
||||
// BITFIELD_NAME OFFSET(x)
|
||||
$(#[$outer:meta])*
|
||||
$valtype:ident, $reg_desc:ident, [
|
||||
$( $(#[$inner:meta])* $field:ident OFFSET($offset:expr)),+ $(,)?
|
||||
]
|
||||
} => {
|
||||
$(#[$outer])*
|
||||
$( $crate::register_bitmasks!($valtype, $reg_desc, $(#[$inner])* $field, $offset, 1, []); )*
|
||||
};
|
||||
{
|
||||
// BITFIELD_NAME OFFSET
|
||||
// All fields are 1 bit
|
||||
$(#[$outer:meta])*
|
||||
$valtype:ident, $reg_desc:ident, [
|
||||
$( $(#[$inner:meta])* $field:ident $offset:expr ),+ $(,)?
|
||||
]
|
||||
} => {
|
||||
$(#[$outer])*
|
||||
$( $crate::register_bitmasks!($valtype, $reg_desc, $(#[$inner])* $field, $offset, 1, []); )*
|
||||
};
|
||||
|
||||
{
|
||||
// BITFIELD_NAME OFFSET(x) NUMBITS(y)
|
||||
$(#[$outer:meta])*
|
||||
$valtype:ident, $reg_desc:ident, [
|
||||
$( $(#[$inner:meta])* $field:ident OFFSET($offset:expr) NUMBITS($numbits:expr) ),+ $(,)?
|
||||
]
|
||||
} => {
|
||||
$(#[$outer])*
|
||||
$( $crate::register_bitmasks!($valtype, $reg_desc, $(#[$inner])* $field, $offset, $numbits, []); )*
|
||||
};
|
||||
|
||||
{
|
||||
// BITFIELD_NAME OFFSET(x) NUMBITS(y) []
|
||||
$(#[$outer:meta])*
|
||||
$valtype:ident, $reg_desc:ident, [
|
||||
$( $(#[$inner:meta])* $field:ident OFFSET($offset:expr) NUMBITS($numbits:expr)
|
||||
$values:tt ),+ $(,)?
|
||||
]
|
||||
} => {
|
||||
$(#[$outer])*
|
||||
$( $crate::register_bitmasks!($valtype, $reg_desc, $(#[$inner])* $field, $offset, $numbits,
|
||||
$values); )*
|
||||
};
|
||||
{
|
||||
$valtype:ident, $reg_desc:ident, $(#[$outer:meta])* $field:ident,
|
||||
$offset:expr, $numbits:expr,
|
||||
[$( $(#[$inner:meta])* $valname:ident = $value:expr ),+ $(,)?]
|
||||
} => { // this match arm is duplicated below with an allowance for 0 elements in the valname -> value array,
|
||||
// to seperately support the case of zero-variant enums not supporting non-default
|
||||
// representations.
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[allow(unused)]
|
||||
pub const $field: Field<$valtype, $reg_desc> =
|
||||
Field::<$valtype, $reg_desc>::new($crate::bitmask!($numbits), $offset);
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(unused)]
|
||||
$(#[$outer])*
|
||||
pub mod $field {
|
||||
#[allow(unused_imports)]
|
||||
use $crate::registers::{FieldValue, TryFromValue};
|
||||
use super::$reg_desc;
|
||||
|
||||
$(
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[allow(unused)]
|
||||
$(#[$inner])*
|
||||
pub const $valname: FieldValue<$valtype, $reg_desc> =
|
||||
FieldValue::<$valtype, $reg_desc>::new($crate::bitmask!($numbits),
|
||||
$offset, $value);
|
||||
)*
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[allow(unused)]
|
||||
pub const SET: FieldValue<$valtype, $reg_desc> =
|
||||
FieldValue::<$valtype, $reg_desc>::new($crate::bitmask!($numbits),
|
||||
$offset, $crate::bitmask!($numbits));
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[allow(unused)]
|
||||
pub const CLEAR: FieldValue<$valtype, $reg_desc> =
|
||||
FieldValue::<$valtype, $reg_desc>::new($crate::bitmask!($numbits),
|
||||
$offset, 0);
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[repr($valtype)] // so that values larger than isize::MAX can be stored
|
||||
$(#[$outer])*
|
||||
pub enum Value {
|
||||
$(
|
||||
$(#[$inner])*
|
||||
$valname = $value,
|
||||
)*
|
||||
}
|
||||
|
||||
impl TryFromValue<$valtype> for Value {
|
||||
type EnumType = Value;
|
||||
|
||||
fn try_from(v: $valtype) -> Option<Self::EnumType> {
|
||||
match v {
|
||||
$(
|
||||
$(#[$inner])*
|
||||
x if x == Value::$valname as $valtype => Some(Value::$valname),
|
||||
)*
|
||||
|
||||
_ => Option::None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
{
|
||||
$valtype:ident, $reg_desc:ident, $(#[$outer:meta])* $field:ident,
|
||||
$offset:expr, $numbits:expr,
|
||||
[]
|
||||
} => { //same pattern as previous match arm, for 0 elements in array. Removes code associated with array.
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[allow(unused)]
|
||||
pub const $field: Field<$valtype, $reg_desc> =
|
||||
Field::<$valtype, $reg_desc>::new($crate::bitmask!($numbits), $offset);
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(unused)]
|
||||
$(#[$outer])*
|
||||
pub mod $field {
|
||||
#[allow(unused_imports)]
|
||||
use $crate::registers::{FieldValue, TryFromValue};
|
||||
use super::$reg_desc;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[allow(unused)]
|
||||
pub const SET: FieldValue<$valtype, $reg_desc> =
|
||||
FieldValue::<$valtype, $reg_desc>::new($crate::bitmask!($numbits),
|
||||
$offset, $crate::bitmask!($numbits));
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[allow(unused)]
|
||||
pub const CLEAR: FieldValue<$valtype, $reg_desc> =
|
||||
FieldValue::<$valtype, $reg_desc>::new($crate::bitmask!($numbits),
|
||||
$offset, 0);
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[allow(non_camel_case_types)]
|
||||
$(#[$outer])*
|
||||
pub enum Value {}
|
||||
|
||||
impl TryFromValue<$valtype> for Value {
|
||||
type EnumType = Value;
|
||||
|
||||
fn try_from(_v: $valtype) -> Option<Self::EnumType> {
|
||||
Option::None
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Define register types and fields.
|
||||
#[macro_export]
|
||||
macro_rules! register_bitfields {
|
||||
{
|
||||
$valtype:ident, $( $(#[$inner:meta])* $vis:vis $reg:ident $fields:tt ),* $(,)?
|
||||
} => {
|
||||
$(
|
||||
#[allow(non_snake_case)]
|
||||
$(#[$inner])*
|
||||
$vis mod $reg {
|
||||
// Visibility note: This is left always `pub` as it is not
|
||||
// meaningful to restrict access to the `Register` element of
|
||||
// the register module if the module itself is in scope
|
||||
//
|
||||
// (if you can access $reg, you can access $reg::Register)
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Register;
|
||||
impl $crate::registers::RegisterLongName for Register {}
|
||||
|
||||
use $crate::registers::Field;
|
||||
|
||||
$crate::register_bitmasks!( $valtype, Register, $fields );
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! register_fields {
|
||||
// Macro entry point.
|
||||
(@root $(#[$attr_struct:meta])* $vis_struct:vis $name:ident $(<$life:lifetime>)? { $($input:tt)* } ) => {
|
||||
$crate::register_fields!(
|
||||
@munch (
|
||||
$($input)*
|
||||
) -> {
|
||||
$vis_struct struct $(#[$attr_struct])* $name $(<$life>)?
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Print the struct once all fields have been munched.
|
||||
(@munch
|
||||
(
|
||||
$(#[$attr_end:meta])*
|
||||
($offset:expr => @END),
|
||||
)
|
||||
-> {$vis_struct:vis struct $(#[$attr_struct:meta])* $name:ident $(<$life:lifetime>)? $(
|
||||
$(#[$attr:meta])*
|
||||
($vis:vis $id:ident: $ty:ty)
|
||||
)*}
|
||||
) => {
|
||||
$(#[$attr_struct])*
|
||||
#[repr(C)]
|
||||
$vis_struct struct $name $(<$life>)? {
|
||||
$(
|
||||
$(#[$attr])*
|
||||
$vis $id: $ty
|
||||
),*
|
||||
}
|
||||
};
|
||||
|
||||
// Munch field.
|
||||
(@munch
|
||||
(
|
||||
$(#[$attr:meta])*
|
||||
($offset_start:expr => $vis:vis $field:ident: $ty:ty),
|
||||
$($after:tt)*
|
||||
)
|
||||
-> {$($output:tt)*}
|
||||
) => {
|
||||
$crate::register_fields!(
|
||||
@munch (
|
||||
$($after)*
|
||||
) -> {
|
||||
$($output)*
|
||||
$(#[$attr])*
|
||||
($vis $field: $ty)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Munch padding.
|
||||
(@munch
|
||||
(
|
||||
$(#[$attr:meta])*
|
||||
($offset_start:expr => $padding:ident),
|
||||
$(#[$attr_next:meta])*
|
||||
($offset_end:expr => $($next:tt)*),
|
||||
$($after:tt)*
|
||||
)
|
||||
-> {$($output:tt)*}
|
||||
) => {
|
||||
$crate::register_fields!(
|
||||
@munch (
|
||||
$(#[$attr_next])*
|
||||
($offset_end => $($next)*),
|
||||
$($after)*
|
||||
) -> {
|
||||
$($output)*
|
||||
$(#[$attr])*
|
||||
($padding: [u8; $offset_end - $offset_start])
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! test_fields {
|
||||
// Macro entry point.
|
||||
(@root $struct:ident $(<$life:lifetime>)? { $($input:tt)* } ) => {
|
||||
$crate::test_fields!(@munch $struct $(<$life>)? sum ($($input)*) -> {});
|
||||
};
|
||||
|
||||
// Print the tests once all fields have been munched.
|
||||
// We wrap the tests in a "detail" function that potentially takes a lifetime parameter, so that
|
||||
// the lifetime is declared inside it - therefore all types using the lifetime are well-defined.
|
||||
(@munch $struct:ident $(<$life:lifetime>)? $sum:ident
|
||||
(
|
||||
$(#[$attr_end:meta])*
|
||||
($size:expr => @END),
|
||||
)
|
||||
-> {$($stmts:block)*}
|
||||
) => {
|
||||
{
|
||||
fn detail $(<$life>)? ()
|
||||
{
|
||||
let mut $sum: usize = 0;
|
||||
$($stmts)*
|
||||
let size = core::mem::size_of::<$struct $(<$life>)?>();
|
||||
assert!(
|
||||
size == $size,
|
||||
"Invalid size for struct {} (expected {:#X} but was {:#X})",
|
||||
stringify!($struct),
|
||||
$size,
|
||||
size
|
||||
);
|
||||
}
|
||||
|
||||
detail();
|
||||
}
|
||||
};
|
||||
|
||||
// Munch field.
|
||||
(@munch $struct:ident $(<$life:lifetime>)? $sum:ident
|
||||
(
|
||||
$(#[$attr:meta])*
|
||||
($offset_start:expr => $vis:vis $field:ident: $ty:ty),
|
||||
$(#[$attr_next:meta])*
|
||||
($offset_end:expr => $($next:tt)*),
|
||||
$($after:tt)*
|
||||
)
|
||||
-> {$($output:block)*}
|
||||
) => {
|
||||
$crate::test_fields!(
|
||||
@munch $struct $(<$life>)? $sum (
|
||||
$(#[$attr_next])*
|
||||
($offset_end => $($next)*),
|
||||
$($after)*
|
||||
) -> {
|
||||
$($output)*
|
||||
{
|
||||
assert!(
|
||||
$sum == $offset_start,
|
||||
"Invalid start offset for field {} (expected {:#X} but was {:#X})",
|
||||
stringify!($field),
|
||||
$offset_start,
|
||||
$sum
|
||||
);
|
||||
let align = core::mem::align_of::<$ty>();
|
||||
assert!(
|
||||
$sum & (align - 1) == 0,
|
||||
"Invalid alignment for field {} (expected alignment of {:#X} but offset was {:#X})",
|
||||
stringify!($field),
|
||||
align,
|
||||
$sum
|
||||
);
|
||||
$sum += core::mem::size_of::<$ty>();
|
||||
assert!(
|
||||
$sum == $offset_end,
|
||||
"Invalid end offset for field {} (expected {:#X} but was {:#X})",
|
||||
stringify!($field),
|
||||
$offset_end,
|
||||
$sum
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Munch padding.
|
||||
(@munch $struct:ident $(<$life:lifetime>)? $sum:ident
|
||||
(
|
||||
$(#[$attr:meta])*
|
||||
($offset_start:expr => $padding:ident),
|
||||
$(#[$attr_next:meta])*
|
||||
($offset_end:expr => $($next:tt)*),
|
||||
$($after:tt)*
|
||||
)
|
||||
-> {$($output:block)*}
|
||||
) => {
|
||||
$crate::test_fields!(
|
||||
@munch $struct $(<$life>)? $sum (
|
||||
$(#[$attr_next])*
|
||||
($offset_end => $($next)*),
|
||||
$($after)*
|
||||
) -> {
|
||||
$($output)*
|
||||
{
|
||||
assert!(
|
||||
$sum == $offset_start,
|
||||
"Invalid start offset for padding {} (expected {:#X} but was {:#X})",
|
||||
stringify!($padding),
|
||||
$offset_start,
|
||||
$sum
|
||||
);
|
||||
$sum = $offset_end;
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_std_unit_tests"))]
|
||||
#[macro_export]
|
||||
macro_rules! register_structs {
|
||||
{
|
||||
$(
|
||||
$(#[$attr:meta])*
|
||||
$vis_struct:vis $name:ident $(<$life:lifetime>)? {
|
||||
$( $fields:tt )*
|
||||
}
|
||||
),*
|
||||
} => {
|
||||
$( $crate::register_fields!(@root $(#[$attr])* $vis_struct $name $(<$life>)? { $($fields)* } ); )*
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_register_structs {
|
||||
$(
|
||||
#[allow(non_snake_case)]
|
||||
mod $name {
|
||||
use super::super::*;
|
||||
#[test]
|
||||
fn test_offsets() {
|
||||
$crate::test_fields!(@root $name $(<$life>)? { $($fields)* } )
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "no_std_unit_tests")]
|
||||
#[macro_export]
|
||||
macro_rules! register_structs {
|
||||
{
|
||||
$(
|
||||
$(#[$attr:meta])*
|
||||
$vis_struct:vis $name:ident $(<$life:lifetime>)? {
|
||||
$( $fields:tt )*
|
||||
}
|
||||
),*
|
||||
} => {
|
||||
$( $crate::register_fields!(@root $(#[$attr])* $vis_struct $name $(<$life>)? { $($fields)* } ); )*
|
||||
};
|
||||
}
|
|
@ -0,0 +1,925 @@
|
|||
//! Implementation of registers and bitfields.
|
||||
//!
|
||||
//! Provides efficient mechanisms to express and use type-checked memory mapped
|
||||
//! registers and bitfields.
|
||||
//!
|
||||
//! ```rust
|
||||
//! # fn main() {}
|
||||
//!
|
||||
//! use tock_registers::registers::{ReadOnly, ReadWrite};
|
||||
//! use tock_registers::register_bitfields;
|
||||
//!
|
||||
//! // Register maps are specified like this:
|
||||
//! #[repr(C)]
|
||||
//! struct Registers {
|
||||
//! // Control register: read-write
|
||||
//! cr: ReadWrite<u32, Control::Register>,
|
||||
//! // Status register: read-only
|
||||
//! s: ReadOnly<u32, Status::Register>,
|
||||
//! }
|
||||
//!
|
||||
//! // Register fields and definitions look like this:
|
||||
//! register_bitfields![u32,
|
||||
//! // Simpler bitfields are expressed concisely:
|
||||
//! Control [
|
||||
//! /// Stop the Current Transfer
|
||||
//! STOP 8,
|
||||
//! /// Software Reset
|
||||
//! SWRST 7,
|
||||
//! /// Master Disable
|
||||
//! MDIS 1,
|
||||
//! /// Master Enable
|
||||
//! MEN 0
|
||||
//! ],
|
||||
//!
|
||||
//! // More complex registers can express subtypes:
|
||||
//! Status [
|
||||
//! TXCOMPLETE OFFSET(0) NUMBITS(1) [],
|
||||
//! TXINTERRUPT OFFSET(1) NUMBITS(1) [],
|
||||
//! RXCOMPLETE OFFSET(2) NUMBITS(1) [],
|
||||
//! RXINTERRUPT OFFSET(3) NUMBITS(1) [],
|
||||
//! MODE OFFSET(4) NUMBITS(3) [
|
||||
//! FullDuplex = 0,
|
||||
//! HalfDuplex = 1,
|
||||
//! Loopback = 2,
|
||||
//! Disabled = 3
|
||||
//! ],
|
||||
//! ERRORCOUNT OFFSET(6) NUMBITS(3) []
|
||||
//! ]
|
||||
//! ];
|
||||
//! ```
|
||||
//!
|
||||
//! Author
|
||||
//! ------
|
||||
//! - Shane Leonard <shanel@stanford.edu>
|
||||
|
||||
// The register interface uses `+` in a way that is fine for bitfields, but
|
||||
// looks unusual (and perhaps problematic) to a linter. We just ignore those
|
||||
// lints for this file.
|
||||
#![allow(clippy::suspicious_op_assign_impl)]
|
||||
#![allow(clippy::suspicious_arithmetic_impl)]
|
||||
|
||||
use core::cell::UnsafeCell;
|
||||
use core::fmt;
|
||||
use core::marker::PhantomData;
|
||||
use core::ops::{Add, AddAssign, BitAnd, BitOr, BitOrAssign, Not, Shl, Shr};
|
||||
|
||||
/// IntLike properties needed to read/write/modify a register.
|
||||
pub trait IntLike:
|
||||
BitAnd<Output = Self>
|
||||
+ BitOr<Output = Self>
|
||||
+ BitOrAssign
|
||||
+ Not<Output = Self>
|
||||
+ Eq
|
||||
+ Shr<usize, Output = Self>
|
||||
+ Shl<usize, Output = Self>
|
||||
+ Copy
|
||||
+ Clone
|
||||
{
|
||||
fn zero() -> Self;
|
||||
}
|
||||
|
||||
macro_rules! IntLike_impl_for {
|
||||
( $( $type:ty ),+ $(,)? ) => {
|
||||
$(
|
||||
impl IntLike for $type {
|
||||
fn zero() -> Self {
|
||||
0
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
IntLike_impl_for!(u8, u16, u32, u64, u128);
|
||||
|
||||
/// Descriptive name for each register.
|
||||
pub trait RegisterLongName {}
|
||||
|
||||
impl RegisterLongName for () {}
|
||||
|
||||
/// Conversion of raw register value into enumerated values member.
|
||||
/// Implemented inside register_bitfields! macro for each bit field.
|
||||
pub trait TryFromValue<V> {
|
||||
type EnumType;
|
||||
|
||||
fn try_from(v: V) -> Option<Self::EnumType>;
|
||||
}
|
||||
|
||||
/// Read/Write registers.
|
||||
// To successfully alias this structure onto hardware registers in memory, this
|
||||
// struct must be exactly the size of the `T`.
|
||||
#[repr(transparent)]
|
||||
pub struct ReadWrite<T: IntLike, R: RegisterLongName = ()> {
|
||||
value: UnsafeCell<T>,
|
||||
associated_register: PhantomData<R>,
|
||||
}
|
||||
|
||||
/// Read-only registers.
|
||||
// To successfully alias this structure onto hardware registers in memory, this
|
||||
// struct must be exactly the size of the `T`.
|
||||
#[repr(transparent)]
|
||||
pub struct ReadOnly<T: IntLike, R: RegisterLongName = ()> {
|
||||
value: T,
|
||||
associated_register: PhantomData<R>,
|
||||
}
|
||||
|
||||
/// Write-only registers.
|
||||
// To successfully alias this structure onto hardware registers in memory, this
|
||||
// struct must be exactly the size of the `T`.
|
||||
#[repr(transparent)]
|
||||
pub struct WriteOnly<T: IntLike, R: RegisterLongName = ()> {
|
||||
value: UnsafeCell<T>,
|
||||
associated_register: PhantomData<R>,
|
||||
}
|
||||
|
||||
/// Read-only and write-only registers aliased to the same address.
|
||||
///
|
||||
/// Unlike the `ReadWrite` register, this represents a register which has different meanings based
|
||||
/// on if it is written or read. This might be found on a device where control and status
|
||||
/// registers are accessed via the same memory address via writes and reads, respectively.
|
||||
// To successfully alias this structure onto hardware registers in memory, this
|
||||
// struct must be exactly the size of the `T`.
|
||||
#[repr(transparent)]
|
||||
pub struct Aliased<T: IntLike, R: RegisterLongName = (), W: RegisterLongName = ()> {
|
||||
value: UnsafeCell<T>,
|
||||
associated_register: PhantomData<(R, W)>,
|
||||
}
|
||||
|
||||
impl<T: IntLike, R: RegisterLongName> ReadWrite<T, R> {
|
||||
#[inline]
|
||||
/// Get the raw register value
|
||||
pub fn get(&self) -> T {
|
||||
unsafe { ::core::ptr::read_volatile(self.value.get()) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Set the raw register value
|
||||
pub fn set(&self, value: T) {
|
||||
unsafe { ::core::ptr::write_volatile(self.value.get(), value) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Read the value of the given field
|
||||
pub fn read(&self, field: Field<T, R>) -> T {
|
||||
field.read(self.get())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Read value of the given field as an enum member
|
||||
pub fn read_as_enum<E: TryFromValue<T, EnumType = E>>(&self, field: Field<T, R>) -> Option<E> {
|
||||
field.read_as_enum(self.get())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Make a local copy of the register
|
||||
pub fn extract(&self) -> LocalRegisterCopy<T, R> {
|
||||
LocalRegisterCopy::new(self.get())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Write the value of one or more fields, overwriting the other fields with zero
|
||||
pub fn write(&self, field: FieldValue<T, R>) {
|
||||
self.set(field.value);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Write the value of one or more fields, leaving the other fields unchanged
|
||||
pub fn modify(&self, field: FieldValue<T, R>) {
|
||||
self.set(field.modify(self.get()));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Write the value of one or more fields, maintaining the value of unchanged fields via a
|
||||
/// provided original value, rather than a register read.
|
||||
pub fn modify_no_read(&self, original: LocalRegisterCopy<T, R>, field: FieldValue<T, R>) {
|
||||
self.set(field.modify(original.get()));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Check if one or more bits in a field are set
|
||||
pub fn is_set(&self, field: Field<T, R>) -> bool {
|
||||
field.is_set(self.get())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Check if any specified parts of a field match
|
||||
pub fn matches_any(&self, field: FieldValue<T, R>) -> bool {
|
||||
field.matches_any(self.get())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Check if all specified parts of a field match
|
||||
pub fn matches_all(&self, field: FieldValue<T, R>) -> bool {
|
||||
field.matches_all(self.get())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IntLike, R: RegisterLongName> ReadOnly<T, R> {
|
||||
#[inline]
|
||||
/// Get the raw register value
|
||||
pub fn get(&self) -> T {
|
||||
unsafe { ::core::ptr::read_volatile(&self.value) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Read the value of the given field
|
||||
pub fn read(&self, field: Field<T, R>) -> T {
|
||||
field.read(self.get())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Read value of the given field as an enum member
|
||||
pub fn read_as_enum<E: TryFromValue<T, EnumType = E>>(&self, field: Field<T, R>) -> Option<E> {
|
||||
field.read_as_enum(self.get())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Make a local copy of the register
|
||||
pub fn extract(&self) -> LocalRegisterCopy<T, R> {
|
||||
LocalRegisterCopy::new(self.get())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Check if one or more bits in a field are set
|
||||
pub fn is_set(&self, field: Field<T, R>) -> bool {
|
||||
field.is_set(self.get())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Check if any specified parts of a field match
|
||||
pub fn matches_any(&self, field: FieldValue<T, R>) -> bool {
|
||||
field.matches_any(self.get())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Check if all specified parts of a field match
|
||||
pub fn matches_all(&self, field: FieldValue<T, R>) -> bool {
|
||||
field.matches_all(self.get())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IntLike, R: RegisterLongName> WriteOnly<T, R> {
|
||||
#[inline]
|
||||
/// Set the raw register value
|
||||
pub fn set(&self, value: T) {
|
||||
unsafe { ::core::ptr::write_volatile(self.value.get(), value) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Write the value of one or more fields, overwriting the other fields with zero
|
||||
pub fn write(&self, field: FieldValue<T, R>) {
|
||||
self.set(field.value);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IntLike, R: RegisterLongName, W: RegisterLongName> Aliased<T, R, W> {
|
||||
#[inline]
|
||||
/// Get the raw register value
|
||||
pub fn get(&self) -> T {
|
||||
unsafe { ::core::ptr::read_volatile(self.value.get()) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Set the raw register value
|
||||
pub fn set(&self, value: T) {
|
||||
unsafe { ::core::ptr::write_volatile(self.value.get(), value) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Read the value of the given field
|
||||
pub fn read(&self, field: Field<T, R>) -> T {
|
||||
field.read(self.get())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Read value of the given field as an enum member
|
||||
pub fn read_as_enum<E: TryFromValue<T, EnumType = E>>(&self, field: Field<T, R>) -> Option<E> {
|
||||
field.read_as_enum(self.get())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Make a local copy of the register
|
||||
pub fn extract(&self) -> LocalRegisterCopy<T, R> {
|
||||
LocalRegisterCopy::new(self.get())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Write the value of one or more fields, overwriting the other fields with zero
|
||||
pub fn write(&self, field: FieldValue<T, W>) {
|
||||
self.set(field.value);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Check if one or more bits in a field are set
|
||||
pub fn is_set(&self, field: Field<T, R>) -> bool {
|
||||
field.is_set(self.get())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Check if any specified parts of a field match
|
||||
pub fn matches_any(&self, field: FieldValue<T, R>) -> bool {
|
||||
field.matches_any(self.get())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Check if all specified parts of a field match
|
||||
pub fn matches_all(&self, field: FieldValue<T, R>) -> bool {
|
||||
field.matches_all(self.get())
|
||||
}
|
||||
}
|
||||
|
||||
/// A read-write copy of register contents.
|
||||
///
|
||||
/// This behaves very similarly to a read-write register, but instead of doing a
|
||||
/// volatile read to MMIO to get the value for each function call, a copy of the
|
||||
/// register contents are stored locally in memory. This allows a peripheral
|
||||
/// to do a single read on a register, and then check which bits are set without
|
||||
/// having to do a full MMIO read each time. It also allows the value of the
|
||||
/// register to be "cached" in case the peripheral driver needs to clear the
|
||||
/// register in hardware yet still be able to check the bits.
|
||||
/// You can write to a local register, which will modify the stored value.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct LocalRegisterCopy<T: IntLike, R: RegisterLongName = ()> {
|
||||
value: T,
|
||||
associated_register: PhantomData<R>,
|
||||
}
|
||||
|
||||
impl<T: IntLike, R: RegisterLongName> LocalRegisterCopy<T, R> {
|
||||
pub const fn new(value: T) -> Self {
|
||||
LocalRegisterCopy {
|
||||
value: value,
|
||||
associated_register: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the raw register value
|
||||
#[inline]
|
||||
pub fn get(&self) -> T {
|
||||
self.value
|
||||
}
|
||||
|
||||
/// Set the raw register value
|
||||
#[inline]
|
||||
pub fn set(&mut self, value: T) {
|
||||
self.value = value;
|
||||
}
|
||||
|
||||
/// Read the value of the given field
|
||||
#[inline]
|
||||
pub fn read(&self, field: Field<T, R>) -> T {
|
||||
field.read(self.get())
|
||||
}
|
||||
|
||||
/// Read value of the given field as an enum member
|
||||
#[inline]
|
||||
pub fn read_as_enum<E: TryFromValue<T, EnumType = E>>(&self, field: Field<T, R>) -> Option<E> {
|
||||
field.read_as_enum(self.get())
|
||||
}
|
||||
|
||||
/// Write the value of one or more fields, overwriting the other fields with zero
|
||||
#[inline]
|
||||
pub fn write(&mut self, field: FieldValue<T, R>) {
|
||||
self.set(field.value);
|
||||
}
|
||||
|
||||
/// Write the value of one or more fields, leaving the other fields unchanged
|
||||
#[inline]
|
||||
pub fn modify(&mut self, field: FieldValue<T, R>) {
|
||||
self.set(field.modify(self.get()));
|
||||
}
|
||||
|
||||
/// Check if one or more bits in a field are set
|
||||
#[inline]
|
||||
pub fn is_set(&self, field: Field<T, R>) -> bool {
|
||||
field.is_set(self.get())
|
||||
}
|
||||
|
||||
/// Check if any specified parts of a field match
|
||||
#[inline]
|
||||
pub fn matches_any(&self, field: FieldValue<T, R>) -> bool {
|
||||
field.matches_any(self.get())
|
||||
}
|
||||
|
||||
/// Check if all specified parts of a field match
|
||||
#[inline]
|
||||
pub fn matches_all(&self, field: FieldValue<T, R>) -> bool {
|
||||
field.matches_all(self.get())
|
||||
}
|
||||
|
||||
/// Do a bitwise AND operation of the stored value and the passed in value
|
||||
/// and return a new LocalRegisterCopy.
|
||||
#[inline]
|
||||
pub fn bitand(&self, rhs: T) -> LocalRegisterCopy<T, R> {
|
||||
LocalRegisterCopy::new(self.value & rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IntLike + fmt::Debug, R: RegisterLongName> fmt::Debug for LocalRegisterCopy<T, R> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{:?}", self.value)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! From_impl_for {
|
||||
( $( $type:ty ),+ $(,)? ) => {
|
||||
$(
|
||||
impl<R: RegisterLongName> From<LocalRegisterCopy<$type, R>> for $type {
|
||||
fn from(r: LocalRegisterCopy<$type, R>) -> $type {
|
||||
r.value
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
From_impl_for!(u8, u16, u32, u64, u128);
|
||||
|
||||
/// In memory volatile register.
|
||||
// To successfully alias this structure onto hardware registers in memory, this
|
||||
// struct must be exactly the size of the `T`.
|
||||
#[repr(transparent)]
|
||||
pub struct InMemoryRegister<T: IntLike, R: RegisterLongName = ()> {
|
||||
value: UnsafeCell<T>,
|
||||
associated_register: PhantomData<R>,
|
||||
}
|
||||
|
||||
impl<T: IntLike, R: RegisterLongName> InMemoryRegister<T, R> {
|
||||
pub const fn new(value: T) -> Self {
|
||||
InMemoryRegister {
|
||||
value: UnsafeCell::new(value),
|
||||
associated_register: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get(&self) -> T {
|
||||
unsafe { ::core::ptr::read_volatile(self.value.get()) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set(&self, value: T) {
|
||||
unsafe { ::core::ptr::write_volatile(self.value.get(), value) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn read(&self, field: Field<T, R>) -> T {
|
||||
field.read(self.get())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn read_as_enum<E: TryFromValue<T, EnumType = E>>(&self, field: Field<T, R>) -> Option<E> {
|
||||
field.read_as_enum(self.get())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn extract(&self) -> LocalRegisterCopy<T, R> {
|
||||
LocalRegisterCopy::new(self.get())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn write(&self, field: FieldValue<T, R>) {
|
||||
self.set(field.value);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn modify(&self, field: FieldValue<T, R>) {
|
||||
self.set(field.modify(self.get()));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn modify_no_read(&self, original: LocalRegisterCopy<T, R>, field: FieldValue<T, R>) {
|
||||
self.set(field.modify(original.get()));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_set(&self, field: Field<T, R>) -> bool {
|
||||
field.is_set(self.get())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn matches_any(&self, field: FieldValue<T, R>) -> bool {
|
||||
field.matches_any(self.get())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn matches_all(&self, field: FieldValue<T, R>) -> bool {
|
||||
field.matches_all(self.get())
|
||||
}
|
||||
}
|
||||
|
||||
/// Specific section of a register.
|
||||
///
|
||||
/// For the Field, the mask is unshifted, ie. the LSB should always be set.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Field<T: IntLike, R: RegisterLongName> {
|
||||
mask: T,
|
||||
pub shift: usize,
|
||||
associated_register: PhantomData<R>,
|
||||
}
|
||||
|
||||
impl<T: IntLike, R: RegisterLongName> Field<T, R> {
|
||||
pub const fn new(mask: T, shift: usize) -> Field<T, R> {
|
||||
Field {
|
||||
mask: mask,
|
||||
shift: shift,
|
||||
associated_register: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the raw bitmask used by this Field.
|
||||
#[inline]
|
||||
pub fn mask(&self) -> T {
|
||||
(self.mask as T) << self.shift
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn read(self, val: T) -> T {
|
||||
(val & (self.mask << self.shift)) >> self.shift
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Check if one or more bits in a field are set
|
||||
pub fn is_set(self, val: T) -> bool {
|
||||
val & (self.mask << self.shift) != T::zero()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Read value of the field as an enum member
|
||||
pub fn read_as_enum<E: TryFromValue<T, EnumType = E>>(self, val: T) -> Option<E> {
|
||||
E::try_from(self.read(val))
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! Field_impl_for {
|
||||
( $( $type:ty ),+ $(,)? ) => {
|
||||
$(
|
||||
impl<R: RegisterLongName> Field<$type, R> {
|
||||
pub fn val(&self, value: $type) -> FieldValue<$type, R> {
|
||||
FieldValue::<$type, R>::new(self.mask, self.shift, value)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
Field_impl_for!(u8, u16, u32, u64, u128);
|
||||
|
||||
/// Values for the specific register fields.
|
||||
///
|
||||
/// For the FieldValue, the masks and values are shifted into their actual
|
||||
/// location in the register.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct FieldValue<T: IntLike, R: RegisterLongName> {
|
||||
mask: T,
|
||||
pub value: T,
|
||||
associated_register: PhantomData<R>,
|
||||
}
|
||||
|
||||
macro_rules! FieldValue_impl_for {
|
||||
( $( $type:ty ),+ $(,)? ) => {
|
||||
$(
|
||||
// Necessary to split the implementation of new() out because the bitwise
|
||||
// math isn't treated as const when the type is generic.
|
||||
// Tracking issue: https://github.com/rust-lang/rfcs/pull/2632
|
||||
impl<R: RegisterLongName> FieldValue<$type, R> {
|
||||
pub const fn new(mask: $type, shift: usize, value: $type) -> Self {
|
||||
FieldValue {
|
||||
mask: mask << shift,
|
||||
value: (value & mask) << shift,
|
||||
associated_register: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
// Necessary to split the implementation of From<> out because of the orphan rule
|
||||
// for foreign trait implementation (see [E0210](https://doc.rust-lang.org/error-index.html#E0210)).
|
||||
impl<R: RegisterLongName> From<FieldValue<$type, R>> for $type {
|
||||
fn from(val: FieldValue<$type, R>) -> $type {
|
||||
val.value
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
FieldValue_impl_for!(u8, u16, u32, u64, u128);
|
||||
|
||||
impl<T: IntLike, R: RegisterLongName> FieldValue<T, R> {
|
||||
/// Get the raw bitmask represented by this FieldValue.
|
||||
#[inline]
|
||||
pub fn mask(&self) -> T {
|
||||
self.mask as T
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn read(&self, field: Field<T, R>) -> T {
|
||||
field.read(self.value)
|
||||
}
|
||||
|
||||
/// Modify fields in a register value
|
||||
#[inline]
|
||||
pub fn modify(self, val: T) -> T {
|
||||
(val & !self.mask) | self.value
|
||||
}
|
||||
|
||||
/// Check if any specified parts of a field match
|
||||
#[inline]
|
||||
pub fn matches_any(self, val: T) -> bool {
|
||||
val & self.mask != T::zero()
|
||||
}
|
||||
|
||||
/// Check if all specified parts of a field match
|
||||
#[inline]
|
||||
pub fn matches_all(self, val: T) -> bool {
|
||||
val & self.mask == self.value
|
||||
}
|
||||
}
|
||||
|
||||
// Combine two fields with the addition operator
|
||||
impl<T: IntLike, R: RegisterLongName> Add for FieldValue<T, R> {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn add(self, rhs: Self) -> Self {
|
||||
FieldValue {
|
||||
mask: self.mask | rhs.mask,
|
||||
value: self.value | rhs.value,
|
||||
associated_register: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Combine two fields with the += operator
|
||||
impl<T: IntLike, R: RegisterLongName> AddAssign for FieldValue<T, R> {
|
||||
#[inline]
|
||||
fn add_assign(&mut self, rhs: FieldValue<T, R>) {
|
||||
self.mask |= rhs.mask;
|
||||
self.value |= rhs.value;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_std_unit_tests"))]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum Foo {
|
||||
Foo0,
|
||||
Foo1,
|
||||
Foo2,
|
||||
Foo3,
|
||||
Foo4,
|
||||
Foo5,
|
||||
Foo6,
|
||||
Foo7,
|
||||
}
|
||||
|
||||
impl super::TryFromValue<u16> for Foo {
|
||||
type EnumType = Foo;
|
||||
|
||||
fn try_from(v: u16) -> Option<Self::EnumType> {
|
||||
Self::try_from(v as u32)
|
||||
}
|
||||
}
|
||||
impl super::TryFromValue<u32> for Foo {
|
||||
type EnumType = Foo;
|
||||
|
||||
fn try_from(v: u32) -> Option<Self::EnumType> {
|
||||
match v {
|
||||
0 => Some(Foo::Foo0),
|
||||
1 => Some(Foo::Foo1),
|
||||
2 => Some(Foo::Foo2),
|
||||
3 => Some(Foo::Foo3),
|
||||
4 => Some(Foo::Foo4),
|
||||
5 => Some(Foo::Foo5),
|
||||
6 => Some(Foo::Foo6),
|
||||
7 => Some(Foo::Foo7),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod field {
|
||||
use super::super::{Field, TryFromValue};
|
||||
use super::Foo;
|
||||
|
||||
#[test]
|
||||
fn test_new() {
|
||||
let field8 = Field::<u8, ()>::new(0x12, 3);
|
||||
assert_eq!(field8.mask, 0x12_u8);
|
||||
assert_eq!(field8.shift, 3);
|
||||
let field16 = Field::<u16, ()>::new(0x1234, 5);
|
||||
assert_eq!(field16.mask, 0x1234_u16);
|
||||
assert_eq!(field16.shift, 5);
|
||||
let field32 = Field::<u32, ()>::new(0x12345678, 9);
|
||||
assert_eq!(field32.mask, 0x12345678_u32);
|
||||
assert_eq!(field32.shift, 9);
|
||||
let field64 = Field::<u64, ()>::new(0x12345678_9abcdef0, 1);
|
||||
assert_eq!(field64.mask, 0x12345678_9abcdef0_u64);
|
||||
assert_eq!(field64.shift, 1);
|
||||
let field128 = Field::<u128, ()>::new(0x12345678_9abcdef0_0fedcba9_87654321, 1);
|
||||
assert_eq!(field128.mask, 0x12345678_9abcdef0_0fedcba9_87654321_u128);
|
||||
assert_eq!(field128.shift, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read() {
|
||||
let field = Field::<u32, ()>::new(0xFF, 4);
|
||||
assert_eq!(field.read(0x123), 0x12);
|
||||
let field = Field::<u32, ()>::new(0xF0F, 4);
|
||||
assert_eq!(field.read(0x1234), 0x103);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_set() {
|
||||
let field = Field::<u16, ()>::new(0xFF, 4);
|
||||
assert_eq!(field.is_set(0), false);
|
||||
assert_eq!(field.is_set(0xFFFF), true);
|
||||
assert_eq!(field.is_set(0x0FF0), true);
|
||||
assert_eq!(field.is_set(0x1000), false);
|
||||
assert_eq!(field.is_set(0x0100), true);
|
||||
assert_eq!(field.is_set(0x0010), true);
|
||||
assert_eq!(field.is_set(0x0001), false);
|
||||
|
||||
for shift in 0..24 {
|
||||
let field = Field::<u32, ()>::new(0xFF, shift);
|
||||
for x in 1..=0xFF {
|
||||
assert_eq!(field.is_set(x << shift), true);
|
||||
}
|
||||
assert_eq!(field.is_set(!(0xFF << shift)), false);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_as_enum() {
|
||||
let field = Field::<u16, ()>::new(0x7, 4);
|
||||
assert_eq!(field.read_as_enum(0x1234), Some(Foo::Foo3));
|
||||
assert_eq!(field.read_as_enum(0x5678), Some(Foo::Foo7));
|
||||
assert_eq!(field.read_as_enum(0xFFFF), Some(Foo::Foo7));
|
||||
assert_eq!(field.read_as_enum(0x0000), Some(Foo::Foo0));
|
||||
assert_eq!(field.read_as_enum(0x0010), Some(Foo::Foo1));
|
||||
assert_eq!(field.read_as_enum(0x1204), Some(Foo::Foo0));
|
||||
|
||||
for shift in 0..29 {
|
||||
let field = Field::<u32, ()>::new(0x7, shift);
|
||||
for x in 0..8 {
|
||||
assert_eq!(field.read_as_enum(x << shift), Foo::try_from(x));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod field_value {
|
||||
use super::super::Field;
|
||||
|
||||
#[test]
|
||||
fn test_from() {
|
||||
let field = Field::<u32, ()>::new(0xFF, 4);
|
||||
assert_eq!(u32::from(field.val(0)), 0);
|
||||
assert_eq!(u32::from(field.val(0xFFFFFFFF)), 0xFF0);
|
||||
assert_eq!(u32::from(field.val(0x12)), 0x120);
|
||||
assert_eq!(u32::from(field.val(0x123)), 0x230);
|
||||
|
||||
for shift in 0..32 {
|
||||
let field = Field::<u32, ()>::new(0xFF, shift);
|
||||
for x in 0..=0xFF {
|
||||
assert_eq!(u32::from(field.val(x)), x << shift);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_same_field() {
|
||||
let field = Field::<u32, ()>::new(0xFF, 4);
|
||||
assert_eq!(field.val(0).read(field), 0);
|
||||
assert_eq!(field.val(0xFFFFFFFF).read(field), 0xFF);
|
||||
assert_eq!(field.val(0x12).read(field), 0x12);
|
||||
assert_eq!(field.val(0x123).read(field), 0x23);
|
||||
|
||||
for shift in 0..24 {
|
||||
let field = Field::<u32, ()>::new(0xFF, shift);
|
||||
for x in 0..=0xFF {
|
||||
assert_eq!(field.val(x).read(field), x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_disjoint_fields() {
|
||||
for shift in 0..24 {
|
||||
let field1 = Field::<u32, ()>::new(0xF0, shift);
|
||||
let field2 = Field::<u32, ()>::new(0x0F, shift);
|
||||
for x in 0..=0xFF {
|
||||
assert_eq!(field1.val(x).read(field2), 0);
|
||||
assert_eq!(field2.val(x).read(field1), 0);
|
||||
}
|
||||
}
|
||||
for shift in 0..24 {
|
||||
let field1 = Field::<u32, ()>::new(0xF, shift);
|
||||
let field2 = Field::<u32, ()>::new(0xF, shift + 4);
|
||||
for x in 0..=0xFF {
|
||||
assert_eq!(field1.val(x).read(field2), 0);
|
||||
assert_eq!(field2.val(x).read(field1), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_modify() {
|
||||
let field = Field::<u32, ()>::new(0xFF, 4);
|
||||
assert_eq!(field.val(0x23).modify(0x0000), 0x0230);
|
||||
assert_eq!(field.val(0x23).modify(0xFFFF), 0xF23F);
|
||||
assert_eq!(field.val(0x23).modify(0x1234), 0x1234);
|
||||
assert_eq!(field.val(0x23).modify(0x5678), 0x5238);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_matches_any() {
|
||||
let field = Field::<u32, ()>::new(0xFF, 4);
|
||||
assert_eq!(field.val(0x23).matches_any(0x1234), true);
|
||||
assert_eq!(field.val(0x23).matches_any(0x5678), true);
|
||||
assert_eq!(field.val(0x23).matches_any(0x5008), false);
|
||||
|
||||
for shift in 0..24 {
|
||||
let field = Field::<u32, ()>::new(0xFF, shift);
|
||||
for x in 0..=0xFF {
|
||||
let field_value = field.val(x);
|
||||
for y in 1..=0xFF {
|
||||
assert_eq!(field_value.matches_any(y << shift), true);
|
||||
}
|
||||
assert_eq!(field_value.matches_any(0), false);
|
||||
assert_eq!(field_value.matches_any(!(0xFF << shift)), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_matches_all() {
|
||||
let field = Field::<u32, ()>::new(0xFF, 4);
|
||||
assert_eq!(field.val(0x23).matches_all(0x1234), true);
|
||||
assert_eq!(field.val(0x23).matches_all(0x5678), false);
|
||||
|
||||
for shift in 0..24 {
|
||||
let field = Field::<u32, ()>::new(0xFF, shift);
|
||||
for x in 0..=0xFF {
|
||||
assert_eq!(field.val(x).matches_all(x << shift), true);
|
||||
assert_eq!(field.val(x + 1).matches_all(x << shift), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_disjoint_fields() {
|
||||
let field1 = Field::<u32, ()>::new(0xFF, 24);
|
||||
let field2 = Field::<u32, ()>::new(0xFF, 16);
|
||||
let field3 = Field::<u32, ()>::new(0xFF, 8);
|
||||
let field4 = Field::<u32, ()>::new(0xFF, 0);
|
||||
assert_eq!(
|
||||
u32::from(
|
||||
field1.val(0x12) + field2.val(0x34) + field3.val(0x56) + field4.val(0x78)
|
||||
),
|
||||
0x12345678
|
||||
);
|
||||
|
||||
for shift in 0..24 {
|
||||
let field1 = Field::<u32, ()>::new(0xF, shift);
|
||||
let field2 = Field::<u32, ()>::new(0xF, shift + 4);
|
||||
for x in 0..=0xF {
|
||||
for y in 0..=0xF {
|
||||
assert_eq!(
|
||||
u32::from(field1.val(x) + field2.val(y)),
|
||||
(x | (y << 4)) << shift
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_assign_disjoint_fields() {
|
||||
let field1 = Field::<u32, ()>::new(0xFF, 24);
|
||||
let field2 = Field::<u32, ()>::new(0xFF, 16);
|
||||
let field3 = Field::<u32, ()>::new(0xFF, 8);
|
||||
let field4 = Field::<u32, ()>::new(0xFF, 0);
|
||||
|
||||
let mut value = field1.val(0x12);
|
||||
value += field2.val(0x34);
|
||||
value += field3.val(0x56);
|
||||
value += field4.val(0x78);
|
||||
assert_eq!(u32::from(value), 0x12345678);
|
||||
|
||||
for shift in 0..24 {
|
||||
let field1 = Field::<u32, ()>::new(0xF, shift);
|
||||
let field2 = Field::<u32, ()>::new(0xF, shift + 4);
|
||||
for x in 0..=0xF {
|
||||
for y in 0..=0xF {
|
||||
let mut value = field1.val(x);
|
||||
value += field2.val(y);
|
||||
assert_eq!(u32::from(value), (x | (y << 4)) << shift);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: More unit tests here.
|
||||
}
|
|
@ -1,7 +1,65 @@
|
|||
This is our Code of Conduct and Ethics (the “Code”)
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
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:
|
||||
## Our Pledge
|
||||
|
||||
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).
|
||||
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
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1,18 @@
|
|||
%! Author = berkus
|
||||
%! Date = 04.12.2020
|
||||
|
||||
% Preamble
|
||||
\documentclass[11pt]{article}
|
||||
|
||||
% Packages
|
||||
\usepackage{amsmath}
|
||||
|
||||
% Document
|
||||
\begin{document}
|
||||
|
||||
\title{Capabilities}
|
||||
|
||||
|
||||
|
||||
|
||||
\end{document}
|
|
@ -0,0 +1,9 @@
|
|||
The different libraries seem to target different use-cases, though. For example, `snafu` with its
|
||||
strongly typed errors and contexts seems to be a good fit for libraries. On the other hand,
|
||||
anyhow with its focus on the type-erased Error and on creating string errors and contexts seems
|
||||
to be more useful for applications. After all, errors produced by libraries need to be understood
|
||||
by other code, errors produced by executables need to be understood by humans.
|
||||
-- https://lukaskalbertodt.github.io/2019/11/14/thoughts-on-error-handling-in-rust.html
|
||||
|
||||
@see also https://blog.yoshuawuyts.com/error-handling-survey/
|
||||
@see also https://www.reddit.com/r/rust/comments/dfs1zk/2019_q4_error_patterns_snafu_vs_errderive_anyhow/
|
|
@ -1,34 +1,32 @@
|
|||
# Broadcom bcm2837 on Raspberry Pi 3 as JTAG target
|
||||
# Broadcom 2837 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"
|
||||
telnet_port 4444
|
||||
gdb_port 5555
|
||||
|
||||
transport select jtag
|
||||
|
||||
# we need to enable srst even though we don't connect it
|
||||
reset_config trst_and_srst
|
||||
|
||||
adapter_khz 4000
|
||||
jtag_ntrst_delay 500
|
||||
|
||||
if { [info exists CHIPNAME] } {
|
||||
set _CHIPNAME $CHIPNAME
|
||||
} else {
|
||||
set _CHIPNAME bcm2837
|
||||
set _CHIPNAME rpi3
|
||||
}
|
||||
|
||||
#
|
||||
# Main DAP
|
||||
#
|
||||
if { [info exists DAP_TAPID] } {
|
||||
set _DAP_TAPID $DAP_TAPID
|
||||
} else {
|
||||
set _DAP_TAPID 0x4ba00477
|
||||
}
|
||||
|
||||
adapter speed 4000
|
||||
|
||||
transport select jtag
|
||||
# we need to enable srst even though we don't connect it
|
||||
reset_config trst_and_srst
|
||||
|
||||
jtag_ntrst_delay 500
|
||||
|
||||
telnet_port 4444
|
||||
gdb_port 5555
|
||||
|
||||
#
|
||||
# Main DAP
|
||||
#
|
||||
jtag newtap $_CHIPNAME tap -irlen 4 -ircapture 0x1 -irmask 0xf -expected-id $_DAP_TAPID -enable
|
||||
dap create $_CHIPNAME.dap -chain-position $_CHIPNAME.tap
|
||||
|
||||
|
@ -37,28 +35,17 @@ set _CTINAME $_CHIPNAME.cti
|
|||
|
||||
set DBGBASE {0x80010000 0x80012000 0x80014000 0x80016000}
|
||||
set CTIBASE {0x80018000 0x80019000 0x8001a000 0x8001b000}
|
||||
|
||||
set _cores 4
|
||||
set _smp_command ""
|
||||
|
||||
for { set _core 0 } { $_core < $_cores } { incr _core } {
|
||||
|
||||
cti create $_CTINAME.$_core -dap $_CHIPNAME.dap -ap-num 0 \
|
||||
-baseaddr [lindex $CTIBASE $_core]
|
||||
-ctibase [lindex $CTIBASE $_core]
|
||||
|
||||
target create $_TARGETNAME.$_core aarch64 \
|
||||
-dap $_CHIPNAME.dap -coreid $_core \
|
||||
-dbgbase [lindex $DBGBASE $_core] -cti $_CTINAME.$_core
|
||||
|
||||
if {$_core != 0} {
|
||||
set _smp_command "$_smp_command ${_TARGETNAME}.${_core}"
|
||||
} else {
|
||||
set _smp_command "target smp ${_TARGETNAME}.${_core}"
|
||||
}
|
||||
|
||||
$_TARGETNAME.$_core configure -event reset-assert-post "aarch64 dbginit"
|
||||
$_TARGETNAME.$_core configure -event gdb-attach { halt }
|
||||
}
|
||||
|
||||
eval $_smp_command
|
||||
targets $_TARGETNAME.0
|
|
@ -1 +0,0 @@
|
|||
zellij-config.sh
|
|
@ -1,12 +0,0 @@
|
|||
#########################################################
|
||||
# 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. #
|
||||
#########################################################
|
|
@ -1,39 +0,0 @@
|
|||
---
|
||||
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"]
|
|
@ -1,35 +0,0 @@
|
|||
#!/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}
|
|
@ -1,5 +0,0 @@
|
|||
QEMU=
|
||||
QEMU_OPTS=
|
||||
QEMU_RUNNER_OPTS=
|
||||
CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY=
|
||||
KERNEL_BIN=
|
|
@ -1,40 +0,0 @@
|
|||
[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 }
|
|
@ -1,10 +0,0 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
*/
|
||||
pub mod console;
|
||||
pub mod serial;
|
||||
|
||||
pub use {
|
||||
console::{Console, ConsoleOps},
|
||||
serial::SerialOps,
|
||||
};
|
|
@ -1,12 +0,0 @@
|
|||
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);
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
#![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",
|
||||
));
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
* 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 _) }
|
||||
}
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
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) }
|
||||
}
|
||||
}
|
|
@ -1,109 +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;
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
|
@ -1,448 +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, 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);
|
||||
}
|
||||
}
|
|
@ -1,39 +1,47 @@
|
|||
[package]
|
||||
name = "nucleus"
|
||||
name = "vesper"
|
||||
version = "0.0.1"
|
||||
authors = ["Berkus Decker <berkus+vesper@metta.systems>"]
|
||||
description = "Vesper nanokernel binary"
|
||||
description = "Vesper exokernel"
|
||||
documentation = "https://docs.metta.systems/vesper"
|
||||
homepage = "https://github.com/metta-systems/vesper"
|
||||
homepage = "https://metta.systems/products/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"
|
||||
edition = "2018"
|
||||
|
||||
[badges]
|
||||
maintenance = { status = "experimental" }
|
||||
|
||||
[features]
|
||||
noserial = ["machine/noserial"]
|
||||
# unstable = []
|
||||
# realtime = []
|
||||
noserial = []
|
||||
# Enable JTAG debugging of kernel - enable jtag helpers and
|
||||
# block waiting for JTAG probe attach at the start of kernel main.
|
||||
jtag = ["machine/jtag"]
|
||||
jtag = []
|
||||
# jlink = [] #'jlink_rtt'
|
||||
# Build for running under QEMU with semihosting, so various halt/reboot options would for example quit QEMU instead.
|
||||
qemu = ["machine/qemu"]
|
||||
# Mutually exclusive features to choose a target board
|
||||
rpi3 = ["machine/rpi3"]
|
||||
rpi4 = ["machine/rpi4"]
|
||||
qemu = ["qemu-exit"]
|
||||
|
||||
[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"
|
||||
qemu-exit = { version = "1.0", optional = true }
|
||||
cortex-a = "5.1"
|
||||
register = "1.0"
|
||||
ux = { version = "0.1.3", default-features = false }
|
||||
usize_conversions = "0.2.0"
|
||||
bit_field = "0.10.0"
|
||||
bitflags = "1.2.1"
|
||||
cfg-if = "1.0"
|
||||
snafu = { version = "0.7", default-features = false }
|
||||
snafu = { version = "0.6", default-features = false }
|
||||
paste = "1.0"
|
||||
vesper-user = { path = "../vesper-user" }
|
||||
|
||||
#embedded-serial = "0.5.0"
|
||||
# jlink_rtt = { version = "0.1.0", optional = true }
|
||||
# static_assertions = { version = "1.0.0", features = ["nightly"] }
|
||||
#rcstring = "0.2.1"
|
||||
# arrayvec = { version = "0.5.1", default-features = false }
|
||||
|
|
|
@ -3,42 +3,86 @@
|
|||
#
|
||||
# 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]
|
||||
env = { "BINARY_FILE" = "${KERNEL_ELF}" }
|
||||
run_task = "custom-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"]
|
||||
|
||||
[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"
|
||||
|
||||
[tasks.qemu-cb]
|
||||
disabled = true
|
||||
env = { "QEMU_RUNNER_OPTS" = "${QEMU_SERIAL_OPTS}" }
|
||||
|
||||
[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.zellij-nucleus]
|
||||
env = { "KERNEL_BIN" = "${KERNEL_BIN}" }
|
||||
run_task = "zellij-config"
|
||||
|
||||
[tasks.zellij-cb]
|
||||
disabled = true
|
||||
|
||||
[tasks.zellij-cb-gdb]
|
||||
disabled = true
|
||||
[tasks.openocd]
|
||||
dependencies = ["build", "kernel-binary"]
|
||||
script = [
|
||||
"${OPENOCD} -f interface/jlink.cfg -f ../doc/rpi2rpi_jtag/rpi3_target.cfg"
|
||||
]
|
||||
|
||||
[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"
|
||||
writefile ${GDB_CONNECT_FILE} "target remote :5555\n"
|
||||
'''
|
||||
]
|
||||
|
||||
# Problem: this eats STDIN! need to run in subshell or sth
|
||||
[tasks.gdb]
|
||||
dependencies = ["build", "kernel-binary", "gdb-config"]
|
||||
env = { "RUST_GDB" = "${GDB}" }
|
||||
|
@ -46,34 +90,33 @@ 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", "install-nm", "install-rustfilt"]
|
||||
dependencies = ["build", "kernel-binary"]
|
||||
script = [
|
||||
"${NM} -- ${KERNEL_ELF} | sort -k 1 | rustfilt"
|
||||
"${NM} -- ${KERNEL_ELF} | sort"
|
||||
]
|
||||
#install_crate = "cargo-binutils"
|
||||
|
||||
[tasks.sdcard]
|
||||
dependencies = ["build", "kernel-binary"]
|
||||
script_runner = "@duckscript"
|
||||
script = [
|
||||
'''
|
||||
kernelImage = set "kernel8.img"
|
||||
kernelImage = basename ${KERNEL_BIN}
|
||||
cp ${KERNEL_BIN} ${VOLUME}/${kernelImage}
|
||||
echo "Copied nucleus to ${VOLUME}/${kernelImage}"
|
||||
'''
|
||||
]
|
||||
|
||||
[tasks.cb-eject]
|
||||
disabled = true
|
||||
[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.hopper]
|
||||
dependencies = ["build", "kernel-binary"]
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# Vesper Kernel
|
||||
|
||||
This directory contains binary for the vesper kernel.
|
||||
|
||||
----
|
||||
|
||||
For more information please re-read.
|
|
@ -1,6 +0,0 @@
|
|||
const LINKER_SCRIPT: &str = "linker/aarch64.ld";
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed={}", LINKER_SCRIPT);
|
||||
println!("cargo:rustc-link-arg=--script={}", LINKER_SCRIPT);
|
||||
}
|
|
@ -0,0 +1,349 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
//! Syscall API for calling kernel functions.
|
||||
//!
|
||||
//! Arch-specific kernel ABI decodes syscall invocations and calls API functions to perform actual
|
||||
//! operations.
|
||||
|
||||
use vesper_user::SysCall as SysCall;
|
||||
|
||||
// Syscalls (kernel API)
|
||||
trait API {
|
||||
// Three below (send, nb_send, call) are "invocation" syscalls.
|
||||
fn send(cap: Cap, msg_info: MessageInfo);
|
||||
fn nb_send(dest: Cap, msg_info: MessageInfo);
|
||||
fn call(cap: Cap, msg_info: MessageInfo) -> Result<(MessageInfo, Option<&Badge>)>;
|
||||
// Wait for message, when it is received,
|
||||
// return object Badge and block caller on `reply`.
|
||||
fn recv(src: Cap, reply: Cap) -> Result<(MessageInfo, Option<&Badge>)>;
|
||||
fn reply(msg_info: MessageInfo);
|
||||
// As Recv but invoke `reply` first.
|
||||
fn reply_recv(
|
||||
src: Cap,
|
||||
reply: Cap,
|
||||
msg_info: MessageInfo,
|
||||
) -> Result<(MessageInfo, Option<&Badge>)>;
|
||||
fn nb_recv(src: Cap) -> Result<(MessageInfo, Option<&Badge>)>;
|
||||
fn r#yield();
|
||||
// -- end of default seL4 syscall list --
|
||||
// As ReplyRecv but invoke `dest` not `reply`.
|
||||
fn nb_send_recv(
|
||||
dest: Cap,
|
||||
msg_info: MessageInfo,
|
||||
src: Cap,
|
||||
reply: Cap,
|
||||
) -> Result<(MessageInfo, Options<&Badge>)>;
|
||||
// As NBSendRecv, with no reply. Donation is not possible.
|
||||
fn nb_send_wait(
|
||||
cap: Cap,
|
||||
msg_info: MessageInfo,
|
||||
src: Cap,
|
||||
) -> Result<(MessageInfo, Option<&Badge>)>;
|
||||
// As per Recv, but donation not possible.
|
||||
fn wait(src: Cap) -> Result<(MessageInfo, Option<&Badge>)>;
|
||||
// Plus some debugging calls...
|
||||
}
|
||||
|
||||
fn handle_syscall(syscall: SysCall) -> Result<()> {
|
||||
match syscall {
|
||||
SysCall::Send => {
|
||||
let result = handle_invocation(false, true);
|
||||
if result.is_err() {
|
||||
let irq = get_active_irq();
|
||||
if irq.is_ok() {
|
||||
handle_interrupt(irq.unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
SysCall::NBSend => {
|
||||
let result = handle_invocation(false, false);
|
||||
if result.is_err() {
|
||||
let irq = get_active_irq();
|
||||
if irq.is_ok() {
|
||||
handle_interrupt(irq.unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
SysCall::Call => {
|
||||
let result = handle_invocation(true, true);
|
||||
if result.is_err() {
|
||||
let irq = get_active_irq();
|
||||
if irq.is_ok() {
|
||||
handle_interrupt(irq.unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
SysCall::Recv => handle_receive(true),
|
||||
SysCall::Reply => handle_reply(),
|
||||
SysCall::ReplyRecv => {
|
||||
handle_reply();
|
||||
handle_receive(true)
|
||||
}
|
||||
SysCall::NBRecv => handle_receive(false),
|
||||
SysCall::Yield => handle_yield(),
|
||||
}
|
||||
|
||||
Scheduler::schedule();
|
||||
Scheduler::activate_thread();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_invocation(is_call: bool, is_blocking: bool) -> Result<()> {
|
||||
let thread: &TCB = KernelCurrentThread;
|
||||
|
||||
let infoRegister = thread.get_register(msgInfoRegister);
|
||||
let info: MessageInfo = messageInfoFromWord(infoRegister);
|
||||
let cap_ptr: CapPath = thread.get_register(capRegister);
|
||||
|
||||
result = thread.lookup_cap_and_slot(cap_ptr);
|
||||
|
||||
if result.is_err() {
|
||||
println!(
|
||||
"<<vesper[T{} \"{}\" @{}]: Invocation of invalid cap {}.>>",
|
||||
thread,
|
||||
thread.name,
|
||||
thread.get_restart_pc(),
|
||||
cap_ptr,
|
||||
);
|
||||
|
||||
if is_blocking {
|
||||
handle_fault(thread);
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let buffer = thread.lookup_ipc_buffer(false);
|
||||
|
||||
let status = thread.lookup_extra_caps(buffer, info);
|
||||
|
||||
if status.is_err() {
|
||||
println!(
|
||||
"<<vesper[T{} \"{}\" @{}]: Lookup of extra caps failed.>>",
|
||||
thread,
|
||||
thread.name,
|
||||
thread.get_restart_pc(),
|
||||
);
|
||||
|
||||
if is_blocking {
|
||||
handle_fault(thread);
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut length = info.length();
|
||||
if length > n_MsgRegisters && !buffer {
|
||||
length = n_MsgRegisters;
|
||||
}
|
||||
|
||||
let status = decode_invocation(
|
||||
info.label(),
|
||||
length,
|
||||
cap_ptr,
|
||||
result.slot,
|
||||
result.cap,
|
||||
current_extra_caps,
|
||||
is_blocking,
|
||||
is_call,
|
||||
buffer,
|
||||
);
|
||||
|
||||
if status.is_err() {
|
||||
return match status {
|
||||
Err(Preempted) => status,
|
||||
Err(SysCallError) => {
|
||||
if is_call {
|
||||
thread.replyFromKernel_error();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if thread.get_state() == ThreadState::Restart {
|
||||
if is_call {
|
||||
thread.replyFromKernel_success_empty();
|
||||
}
|
||||
thread.set_state(ThreadState::Running);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_receive(is_blocking: bool) {
|
||||
let endpoint_cap_ptr = KernelCurrentThread.get_register(capRegister);
|
||||
|
||||
let result = KernelCurrentThread.lookup_cap(endpoint_cap_ptr);
|
||||
|
||||
if result.is_err() {
|
||||
KernelCurrentFault = Fault_CapFault::new(endpoint_cap_ptr, true);
|
||||
handle_fault(KernelCurrentThread);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match result.cap.get_type() {
|
||||
endpoint => ,
|
||||
notification => ,
|
||||
_ => fault,
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_reply() {
|
||||
let caller_slot = KernelCurrentThread.get_caller_slot();
|
||||
let caller_cap = caller_slot.capability;
|
||||
match caller_cap.get_type() {
|
||||
ReplyCap::Type.value => {
|
||||
// if (cap_reply_cap_get_capReplyMaster(callerCap)) {
|
||||
// break;
|
||||
// }
|
||||
// caller = ((tcb_t *)(cap_reply_cap_get_capTCBPtr(callerCap)));
|
||||
// if(!(caller != ksCurThread)) _assert_fail("caller must not be the current thread", "src/api/syscall.c", 313, __FUNCTION__);
|
||||
// do_reply_transfer(ksCurThread, caller, callerSlot);
|
||||
},
|
||||
NullCap::Type.value => {
|
||||
println!("<<vesper[T{} \"{}\" @{}]: Attempted reply operation when no reply capability present.>>", KernelCurrentThread, KernelCurrentThread.name, KernelCurrentThread.get_restart_pc());
|
||||
},
|
||||
_ => {
|
||||
panic!("<<vesper[T{} \"{}\" @{}]: Invalid caller capability.>>", KernelCurrentThread, KernelCurrentThread.name, KernelCurrentThread.get_restart_pc());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn do_reply_transfer() {}
|
||||
|
||||
fn handle_yield() {
|
||||
Scheduler::dequeue(KernelCurrentThread);
|
||||
Scheduler::append(KernelCurrentThread);
|
||||
Scheduler::reschedule_required();
|
||||
}
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
enum Fault {
|
||||
#[snafu(display("null fault"))]
|
||||
Null,
|
||||
#[snafu(display("capability fault in {} phase at address {:x}", if in_receive_phase { "receive" } else { "send" }, address))]
|
||||
Capability {
|
||||
in_receive_phase: bool,
|
||||
address: PhysAddr,
|
||||
},
|
||||
#[snafu(display("vm fault on {} at address {:x} with status {:x}", if is_instruction_fault { "code" } else { "data" }, address, fsr))]
|
||||
VM {
|
||||
is_instruction_fault: bool,
|
||||
address: PhysAddr,
|
||||
fsr: u64, // status
|
||||
},
|
||||
#[snafu(display("unknown syscall {:x}", syscall_number))]
|
||||
UnknownSyscall {
|
||||
syscall_number: u64,
|
||||
},
|
||||
#[snafu(display("user exception {:x} code {:x}", number, code))]
|
||||
UserException {
|
||||
number: u64,
|
||||
code: u64,
|
||||
},
|
||||
}
|
||||
|
||||
fn handle_fault(thread: &TCB) {
|
||||
let fault = KernelCurrentFault;
|
||||
|
||||
let result = thread.send_fault_ipc();
|
||||
if result.is_err() {
|
||||
handle_double_fault(thread, fault);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_double_fault(thread: &TCB, fault1: Fault) {
|
||||
let fault2 = KernelCurrentFault;
|
||||
|
||||
println!("Caught {} while trying to handle {}", fault2, fault1);
|
||||
println!("in thread T{} \"{}\"", thread, thread.name);
|
||||
println!("at address {}", thread.get_restart_pc());
|
||||
println!("with stack trace:");
|
||||
arch::user_stack_trace(thread);
|
||||
|
||||
thread.set_state(ThreadState::Inactive);
|
||||
}
|
||||
|
||||
fn handle_unknown_syscall() {
|
||||
// handles
|
||||
// - SysDebugPutChar
|
||||
// - SysDebugHalt
|
||||
// - SysDebugSnapshot
|
||||
// - SysDebugCapIdentify
|
||||
// - SysDebugNameThread
|
||||
// - Fault_UnknownSyscall
|
||||
}
|
||||
|
||||
fn handle_interrupt_entry() -> Result<()> {
|
||||
let irq = get_active_irq();
|
||||
if irq.is_ok() {
|
||||
handle_interrupt(irq.unwrap());
|
||||
} else {
|
||||
handle_spurious_irq();
|
||||
}
|
||||
|
||||
Scheduler::schedule();
|
||||
Scheduler::activate_thread();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
//handleSyscall(syscall) in the slowpath()
|
||||
// these are expressed in terms of
|
||||
// handleInvocation(bool isCall, bool isBlocking)
|
||||
// handleRecv(block)
|
||||
// handleReply()
|
||||
// replyRecv: -- handleReply+handleRecv
|
||||
// handleYield()
|
||||
|
||||
// slowpath() called in c_handle_syscall() in abi
|
||||
// Call and ReplyRecv have fastpath handlers
|
||||
// the rest goes through slowpath
|
||||
|
||||
// c_handle_syscall called directly from SVC vector entry
|
||||
|
||||
struct Scheduler;
|
||||
|
||||
impl Scheduler {
|
||||
/* Values of 0 and ~0 encode ResumeCurrentThread and ChooseNewThread
|
||||
* respectively; other values encode SwitchToThread and must be valid
|
||||
* tcb pointers */
|
||||
//KernelSchedulerAction
|
||||
|
||||
fn schedule() {
|
||||
let action = KernelSchedulerAction;
|
||||
if action == !0 { // all ones..
|
||||
if KernelCurrentThread.is_runnable() {
|
||||
Scheduler::enqueue(KernelCurrentThread);
|
||||
}
|
||||
if KernelDomainTime == 0 {
|
||||
next_domain();
|
||||
}
|
||||
Scheduler::choose_thread();
|
||||
KernelSchedulerAction = 0;
|
||||
} else if action != 0 {
|
||||
if KernelCurrentThread.is_runnable() {
|
||||
Scheduler::enqueue(KernelCurrentThread);
|
||||
}
|
||||
Scheduler::switch_to_thread(KernelSchedulerAction);
|
||||
KernelSchedulerAction = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn activate_thread() {}
|
||||
|
||||
fn dequeue(thread: &mut TCB);
|
||||
fn append(thread: &mut TCB);
|
||||
fn reschedule_required();
|
||||
}
|
||||
|
||||
struct Nucleus {}
|
||||
|
||||
impl API for Nucleus {
|
||||
//...
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
//! Syscall ABI for calling kernel functions.
|
||||
//!
|
||||
//! Principally, there are two syscalls - one does not use capabilities, `Yield` and one is performing
|
||||
//! a capability invocation, `InvokeCapability`. However internally the invocation is dispatched to
|
||||
//! multiple available kernel functions, specific to each capability.
|
||||
|
||||
/// Parse syscall and invoke API functions.
|
||||
///
|
||||
/// Implements C ABI to easily parse passed in parameters.
|
||||
/// @todo Move this to aarch64-specific part.
|
||||
#[no_mangle]
|
||||
extern "C" pub(crate) syscall_entry(syscall_no: u64) {}
|
|
@ -1,8 +1,8 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*
|
||||
* Based on ideas from Jorge Aparicio, Andre Richter, Phil Oppenheimer, Sergio Benitez.
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
//! Low-level boot of the Raspberry's processor
|
||||
|
@ -10,10 +10,11 @@
|
|||
|
||||
use {
|
||||
crate::endless_sleep,
|
||||
cortex_a::{asm, registers::*},
|
||||
tock_registers::interfaces::{Readable, Writeable},
|
||||
cortex_a::{asm, regs::*},
|
||||
};
|
||||
|
||||
//use crate::arch::caps::{CapNode, Capability};
|
||||
|
||||
// Stack placed before first executable instruction
|
||||
const STACK_START: u64 = 0x0008_0000; // Keep in sync with linker script
|
||||
|
||||
|
@ -24,7 +25,6 @@ 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;
|
||||
|
@ -45,7 +45,6 @@ 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;
|
||||
}
|
||||
|
@ -68,7 +67,7 @@ unsafe fn reset() -> ! {
|
|||
// #[inline]
|
||||
// fn enable_armv6_unaligned_access() {
|
||||
// unsafe {
|
||||
// core::arch::asm!(
|
||||
// asm!(
|
||||
// "mrc p15, 0, {u}, c1, c0, 0",
|
||||
// "or {u}, {u}, {CR_U}",
|
||||
// "mcr p15, 0, {u}, c1, c0, 0",
|
||||
|
@ -221,3 +220,225 @@ pub unsafe extern "C" fn _boot_cores() -> ! {
|
|||
// if not core0 or not EL3/EL2/EL1, infinitely wait for events
|
||||
endless_sleep()
|
||||
}
|
||||
|
||||
/*
|
||||
// caps and mem regions init
|
||||
|
||||
enum KernelInitError {}
|
||||
|
||||
fn map_kernel_window() {}
|
||||
|
||||
/**
|
||||
* This and only this function initialises the CPU.
|
||||
* It does NOT initialise any kernel state.
|
||||
*/
|
||||
fn init_cpu() -> Result<(), KernelInitError> {
|
||||
activate_global_pd();
|
||||
}
|
||||
|
||||
/**
|
||||
* This and only this function initialises the platform.
|
||||
* It does NOT initialise any kernel state.
|
||||
*/
|
||||
fn init_plat() -> Result<(), KernelInitError> {
|
||||
initIRQController();
|
||||
initTimer();
|
||||
initL2Cache();
|
||||
}
|
||||
|
||||
fn arch_init_freemem() -> Result<(), KernelInitError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn create_domain_cap() -> Result<(), KernelInitError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn init_irqs() -> Result<(), KernelInitError> {
|
||||
for (irq_t i = 0; i <= maxIRQ; i++) {
|
||||
setIRQState(IRQInactive, i);
|
||||
}
|
||||
setIRQState(IRQTimer, GPT9_IRQ);
|
||||
/* provide the IRQ control cap */
|
||||
write_slot((((slot_ptr_t)((pptr_t)cap_get_capPtr(root_cnode_cap))) + (seL4_CapIRQControl)), cap_irq_control_cap_new());
|
||||
}
|
||||
|
||||
fn create_bootinfo_cap() -> Result<(), KernelInitError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn create_asid_pool_for_initial_thread() -> Result<(), KernelInitError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn create_idle_thread() -> Result<(), KernelInitError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn clean_invalidate_l1_caches() -> Result<(), KernelInitError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn create_initial_thread() -> Result<(), KernelInitError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn init_core_state(_: Result<(), KernelInitError>) -> Result<(), KernelInitError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn create_untypeds() -> Result<(), KernelInitError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn finalise_bootinfo() -> Result<(), KernelInitError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn invalidate_local_tlb() -> Result<(), KernelInitError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn lock_kernel_node() -> Result<(), KernelInitError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn schedule() {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn activate_thread() {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
#[link_section = ".text.boot"]
|
||||
// #[used]
|
||||
fn try_init_kernel() -> Result<(), KernelInitError> {
|
||||
map_kernel_window();
|
||||
init_cpu()?;
|
||||
init_plat()?;
|
||||
|
||||
println!("Booting kernel");
|
||||
|
||||
init_free_memory()?; // arch_init_freemem()
|
||||
|
||||
let root_capnode_cap = create_root_capnode()?;
|
||||
create_domain_cap(root_capnode_cap);
|
||||
// ...create IRQ CapNode...
|
||||
init_irqs(root_capnode_cap)?;
|
||||
|
||||
//fill in boot info and
|
||||
// create bootinfo frame
|
||||
|
||||
// create initial address space covering init thread
|
||||
// user image and ipc buffer and bootinfo frame
|
||||
|
||||
// create and map bootinfo frame cap
|
||||
create_bootinfo_cap();
|
||||
|
||||
// create initial thread IPC buffer
|
||||
|
||||
// create userland image frames
|
||||
|
||||
// create initial thread ASID pool
|
||||
let it_asid_pool_cap = create_asid_pool_for_initial_thread(root_capnode_cap)?;
|
||||
|
||||
// create the idle thread
|
||||
create_idle_thread()?;
|
||||
|
||||
/* Before creating the initial thread (which also switches to it)
|
||||
* we clean the cache so that any page table information written
|
||||
* as a result of calling create_frames_of_region will be correctly
|
||||
* read by the hardware page table walker */
|
||||
clean_invalidate_l1_caches();
|
||||
|
||||
let it = create_initial_thread(root_capnode_cap)?;
|
||||
|
||||
/* create all of the untypeds. Both devices and kernel window memory */
|
||||
create_untypeds(root_capnode_cap)?;
|
||||
|
||||
finalise_bootinfo();
|
||||
|
||||
/* make everything written by the kernel visible to userland. Cleaning to PoC is not
|
||||
* strictly neccessary, but performance is not critical here so clean and invalidate
|
||||
* everything to PoC */
|
||||
clean_invalidate_l1_caches();
|
||||
invalidate_local_tlb();
|
||||
|
||||
/* Export selected CPU features for access by EL0 */
|
||||
arch_init_user_access();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn try_init_kernel_secondary_core() -> Result<(), KernelInitError>
|
||||
{
|
||||
init_cpu();
|
||||
|
||||
/* Enable per-CPU timer interrupts */
|
||||
maskInterrupt(false, KERNEL_TIMER_IRQ);
|
||||
|
||||
lock_kernel_node;
|
||||
|
||||
ksNumCPUs++; // increase global cpu counter - this should be done differently?
|
||||
|
||||
init_core_state(SchedulerAction_ResumeCurrentThread);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_kernel() {
|
||||
try_init_kernel()?;
|
||||
// or for AP:
|
||||
// try_init_kernel_secondary_core();
|
||||
schedule();
|
||||
activate_thread();
|
||||
}
|
||||
|
||||
const CONFIG_ROOT_CAPNODE_SIZE_BITS: usize = 12;
|
||||
const wordBits: usize = 64;
|
||||
|
||||
fn create_root_capnode() -> Capability // Attr(BOOT_CODE)
|
||||
{
|
||||
// write the number of root CNode slots to global state
|
||||
boot_info.max_slot_pos = 1 << CONFIG_ROOT_CAPNODE_SIZE_BITS; // 12 bits => 4096 slots
|
||||
|
||||
// seL4_SlotBits = 32 bytes per entry, 4096 entries =>
|
||||
// create an empty root CapNode
|
||||
// this goes into the kernel startup/heap memory (one of the few items that kernel DOES allocate).
|
||||
let region_size = core::mem::size_of::<Capability> * boot_info.max_slot_pos; // 12 + 5 => 131072 (128Kb)
|
||||
let pptr = alloc_region(region_size); // GlobalAllocator::alloc_zeroed instead?
|
||||
if pptr.is_none() {
|
||||
println!("Kernel init failing: could not create root capnode");
|
||||
return Capability(NullCap::Type::value);
|
||||
}
|
||||
let Some(pptr) = pptr;
|
||||
memzero(pptr, region_size); // CTE_PTR(pptr) ?
|
||||
|
||||
// transmute into a type? (you can use ptr.write() to just write a type into memory location)
|
||||
|
||||
let cap = CapNode::new_root(pptr);
|
||||
|
||||
// this cnode contains a cap to itself...
|
||||
/* write the root CNode cap into the root CNode */
|
||||
// @todo rootCapNode.write_slot(CapInitThreadCNode, cap); -- where cap and rootCapNode are synonyms!
|
||||
write_slot(SLOT_PTR(pptr, seL4_CapInitThreadCNode), cap);
|
||||
|
||||
cap // reference to pptr is here
|
||||
}
|
||||
*/
|
||||
|
||||
// create initial thread
|
||||
// - vspace
|
||||
// - cpace
|
||||
// - tcb
|
||||
//
|
||||
// requires:
|
||||
// - alloc_region
|
||||
// - copy_global_mappings
|
||||
// - create pt/pd caps -- this is arch-specific?
|
||||
// - root capnode with write_slot()
|
||||
//
|
||||
// init thread domain = 0
|
||||
// init thread asid = 1 (asidInvalid = 0)
|
||||
//
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
use {
|
||||
crate::{
|
||||
capdef,
|
||||
caps::{CapError, Capability},
|
||||
},
|
||||
core::convert::TryFrom,
|
||||
paste::paste,
|
||||
register::{register_bitfields, LocalRegisterCopy},
|
||||
};
|
||||
|
||||
//=====================
|
||||
// Cap definition
|
||||
//=====================
|
||||
|
||||
register_bitfields! {
|
||||
u128,
|
||||
AsidControlCap [
|
||||
Type OFFSET(0) NUMBITS(6) [
|
||||
value = 11
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
capdef! { AsidControl }
|
||||
|
||||
//=====================
|
||||
// Cap implementation
|
||||
//=====================
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
use {
|
||||
crate::{
|
||||
capdef,
|
||||
caps::{CapError, Capability},
|
||||
},
|
||||
core::convert::TryFrom,
|
||||
paste::paste,
|
||||
register::{register_bitfields, LocalRegisterCopy},
|
||||
};
|
||||
|
||||
//=====================
|
||||
// Cap definition
|
||||
//=====================
|
||||
|
||||
register_bitfields! {
|
||||
u128,
|
||||
AsidPoolCap [
|
||||
Type OFFSET(0) NUMBITS(6) [
|
||||
value = 13
|
||||
],
|
||||
ASIDBase OFFSET(64) NUMBITS(16) [],
|
||||
ASIDPool OFFSET(80) NUMBITS(37) [],
|
||||
]
|
||||
}
|
||||
|
||||
capdef! { AsidPool }
|
||||
|
||||
//=====================
|
||||
// Cap implementation
|
||||
//=====================
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
use {
|
||||
crate::{
|
||||
capdef,
|
||||
caps::{CapError, Capability},
|
||||
},
|
||||
core::convert::TryFrom,
|
||||
paste::paste,
|
||||
register::{register_bitfields, LocalRegisterCopy},
|
||||
};
|
||||
|
||||
//=====================
|
||||
// Cap definition
|
||||
//=====================
|
||||
|
||||
register_bitfields! {
|
||||
u128,
|
||||
FrameCap [
|
||||
Type OFFSET(0) NUMBITS(6) [
|
||||
value = 1
|
||||
],
|
||||
Size OFFSET(6) NUMBITS(2) [],
|
||||
VMRights OFFSET(8) NUMBITS(2) [],
|
||||
IsDevice OFFSET(10) NUMBITS(1) [],
|
||||
BasePtr OFFSET(16) NUMBITS(48) [], // PhysAddr
|
||||
MappedAddress OFFSET(64) NUMBITS(48) [], // VirtAddr
|
||||
MappedASID OFFSET(112) NUMBITS(16) [],
|
||||
]
|
||||
}
|
||||
|
||||
capdef! { Frame }
|
||||
|
||||
//=====================
|
||||
// Cap implementation
|
||||
//=====================
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
//! AArch64-specific capabilities.
|
||||
|
||||
mod asid_control_cap;
|
||||
mod asid_pool_cap;
|
||||
mod frame;
|
||||
mod page_directory_cap;
|
||||
mod page_global_directory_cap;
|
||||
mod page_table_cap;
|
||||
mod page_upper_directory_cap;
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
use {
|
||||
crate::{
|
||||
arch::memory::{PhysAddr, VirtAddr, ASID},
|
||||
capdef,
|
||||
caps::{CapError, Capability},
|
||||
},
|
||||
core::convert::TryFrom,
|
||||
paste::paste,
|
||||
register::{register_bitfields, LocalRegisterCopy},
|
||||
};
|
||||
|
||||
//=====================
|
||||
// Cap definition
|
||||
//=====================
|
||||
|
||||
register_bitfields! {
|
||||
u128,
|
||||
PageDirectoryCap [
|
||||
Type OFFSET(0) NUMBITS(6) [
|
||||
value = 5
|
||||
],
|
||||
IsMapped OFFSET(6) NUMBITS(1) [],
|
||||
BasePtr OFFSET(16) NUMBITS(48) [], // PhysAddr
|
||||
MappedAddress OFFSET(64) NUMBITS(48) [], // VirtAddr
|
||||
MappedASID OFFSET(112) NUMBITS(16) [],
|
||||
]
|
||||
}
|
||||
|
||||
capdef! { PageDirectory }
|
||||
|
||||
//=====================
|
||||
// Cap implementation
|
||||
//=====================
|
||||
|
||||
impl PageDirectoryCapability {
|
||||
pub(crate) fn base_address(&self) -> PhysAddr {
|
||||
PhysAddr::new(self.0.read(PageDirectoryCap::BasePtr))
|
||||
}
|
||||
|
||||
pub(crate) fn is_mapped(&self) -> bool {
|
||||
self.0.read(PageDirectoryCap::IsMapped) == 1
|
||||
}
|
||||
|
||||
pub(crate) fn mapped_address(&self) -> VirtAddr {
|
||||
VirtAddr::new(self.0.read(PageDirectoryCap::MappedAddress))
|
||||
}
|
||||
|
||||
pub(crate) fn mapped_asid(&self) -> ASID {
|
||||
self.0.read(PageDirectoryCap::MappedASID)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
use {
|
||||
crate::{
|
||||
arch::memory::{PhysAddr, VirtAddr, ASID},
|
||||
capdef,
|
||||
caps::{CapError, Capability},
|
||||
},
|
||||
core::convert::TryFrom,
|
||||
paste::paste,
|
||||
register::{register_bitfields, LocalRegisterCopy},
|
||||
};
|
||||
|
||||
//=====================
|
||||
// Cap definition
|
||||
//=====================
|
||||
|
||||
register_bitfields! {
|
||||
u128,
|
||||
PageGlobalDirectoryCap [
|
||||
Type OFFSET(0) NUMBITS(6) [
|
||||
value = 9
|
||||
],
|
||||
IsMapped OFFSET(6) NUMBITS(1) [],
|
||||
BasePtr OFFSET(16) NUMBITS(48) [], // PhysAddr
|
||||
MappedASID OFFSET(112) NUMBITS(16) [],
|
||||
]
|
||||
}
|
||||
|
||||
capdef! { PageGlobalDirectory }
|
||||
|
||||
//=====================
|
||||
// Cap implementation
|
||||
//=====================
|
||||
|
||||
impl PageGlobalDirectoryCapability {
|
||||
pub(crate) fn base_address(&self) -> PhysAddr {
|
||||
PhysAddr::new(self.0.read(PageGlobalDirectoryCap::BasePtr))
|
||||
}
|
||||
|
||||
pub(crate) fn is_mapped(&self) -> bool {
|
||||
self.0.read(PageGlobalDirectoryCap::IsMapped) == 1
|
||||
}
|
||||
|
||||
// Global directory does not give access to mapped addresses,
|
||||
// instead, it links to lower page directory levels.
|
||||
|
||||
pub(crate) fn mapped_asid(&self) -> ASID {
|
||||
self.0.read(PageGlobalDirectoryCap::MappedASID)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
use {
|
||||
crate::{
|
||||
arch::memory::{PhysAddr, VirtAddr, ASID},
|
||||
capdef,
|
||||
caps::{CapError, Capability},
|
||||
},
|
||||
core::convert::TryFrom,
|
||||
paste::paste,
|
||||
register::{register_bitfields, LocalRegisterCopy},
|
||||
};
|
||||
|
||||
//=====================
|
||||
// Cap definition
|
||||
//=====================
|
||||
|
||||
register_bitfields! {
|
||||
u128,
|
||||
PageTableCap [
|
||||
Type OFFSET(0) NUMBITS(6) [
|
||||
value = 3
|
||||
],
|
||||
IsMapped OFFSET(6) NUMBITS(1) [],
|
||||
BasePtr OFFSET(16) NUMBITS(48) [], // PhysAddr
|
||||
MappedAddress OFFSET(64) NUMBITS(48) [], // VirtAddr
|
||||
MappedASID OFFSET(112) NUMBITS(16) [],
|
||||
],
|
||||
}
|
||||
|
||||
capdef! { PageTable }
|
||||
|
||||
//=====================
|
||||
// Cap implementation
|
||||
//=====================
|
||||
|
||||
impl PageTableCapability {
|
||||
pub(crate) fn base_address(&self) -> PhysAddr {
|
||||
PhysAddr::new(self.0.read(PageTableCap::BasePtr))
|
||||
}
|
||||
|
||||
pub(crate) fn is_mapped(&self) -> bool {
|
||||
self.0.read(PageTableCap::IsMapped) == 1
|
||||
}
|
||||
|
||||
pub(crate) fn mapped_address(&self) -> VirtAddr {
|
||||
VirtAddr::new(self.0.read(PageTableCap::MappedAddress))
|
||||
}
|
||||
|
||||
pub(crate) fn mapped_asid(&self) -> ASID {
|
||||
self.0.read(PageTableCap::MappedASID)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
use {
|
||||
crate::{
|
||||
capdef,
|
||||
caps::{CapError, Capability},
|
||||
},
|
||||
core::convert::TryFrom,
|
||||
paste::paste,
|
||||
register::{register_bitfields, LocalRegisterCopy},
|
||||
};
|
||||
|
||||
//=====================
|
||||
// Cap definition
|
||||
//=====================
|
||||
|
||||
register_bitfields! {
|
||||
u128,
|
||||
PageUpperDirectoryCap [
|
||||
Type OFFSET(0) NUMBITS(6) [
|
||||
value = 7
|
||||
],
|
||||
IsMapped OFFSET(6) NUMBITS(1) [],
|
||||
BasePtr OFFSET(16) NUMBITS(48) [], // PhysAddr
|
||||
MappedAddress OFFSET(64) NUMBITS(48) [], // VirtAddr
|
||||
MappedASID OFFSET(112) NUMBITS(16) [],
|
||||
]
|
||||
}
|
||||
|
||||
capdef! { PageUpperDirectory }
|
||||
|
||||
//=====================
|
||||
// Cap implementation
|
||||
//=====================
|
|
@ -1,17 +1,21 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
//! JTAG helper functions.
|
||||
|
||||
use {
|
||||
core::ptr::{read_volatile, write_volatile},
|
||||
cortex_a::asm,
|
||||
};
|
||||
use 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 continue running.
|
||||
/// from inside this function's frame to contiue running.
|
||||
pub fn wait_debugger() {
|
||||
use core::ptr::{read_volatile, write_volatile};
|
||||
|
||||
while unsafe { read_volatile(&WAIT_FLAG) } {
|
||||
asm::nop();
|
||||
}
|
|
@ -3,5 +3,4 @@
|
|||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub type ASID = u16;
|
|
@ -7,4 +7,6 @@ mod asid;
|
|||
mod phys_addr;
|
||||
mod virt_addr;
|
||||
|
||||
pub use {asid::*, phys_addr::*, virt_addr::*};
|
||||
pub use asid::*;
|
||||
pub use phys_addr::*;
|
||||
pub use virt_addr::*;
|
|
@ -73,7 +73,6 @@ 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<u64>,
|
||||
|
@ -84,7 +83,6 @@ 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<u64>,
|
|
@ -108,7 +108,6 @@ 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<u64>,
|
||||
|
@ -119,7 +118,6 @@ 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<u64>,
|
|
@ -0,0 +1,4 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
|
@ -13,29 +13,37 @@
|
|||
|
||||
use {
|
||||
crate::{
|
||||
arch::aarch64::memory::{get_virt_addr_properties, AttributeFields},
|
||||
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::{
|
||||
asm::barrier,
|
||||
registers::{ID_AA64MMFR0_EL1, SCTLR_EL1, TCR_EL1, TTBR0_EL1},
|
||||
barrier,
|
||||
regs::{ID_AA64MMFR0_EL1, SCTLR_EL1, TCR_EL1, TTBR0_EL1},
|
||||
},
|
||||
tock_registers::{
|
||||
fields::FieldValue,
|
||||
interfaces::{ReadWriteable, Readable, Writeable},
|
||||
register::{
|
||||
cpu::{RegisterReadOnly, RegisterReadWrite},
|
||||
register_bitfields,
|
||||
},
|
||||
// ux::*,
|
||||
};
|
||||
|
||||
mod mair {
|
||||
use {cortex_a::registers::MAIR_EL1, tock_registers::interfaces::Writeable};
|
||||
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
|
||||
|
@ -288,7 +296,7 @@ register_bitfields! {
|
|||
/// attributes of the MMU.
|
||||
fn into_mmu_attributes(
|
||||
attribute_fields: AttributeFields,
|
||||
) -> FieldValue<u64, STAGE1_DESCRIPTOR::Register> {
|
||||
) -> register::FieldValue<u64, STAGE1_DESCRIPTOR::Register> {
|
||||
use super::{AccessPermissions, MemAttributes};
|
||||
|
||||
// Memory attributes
|
||||
|
@ -394,7 +402,7 @@ impl PageSize for Size2MiB {
|
|||
|
||||
impl NotGiantPageSize for Size2MiB {}
|
||||
|
||||
type EntryFlags = tock_registers::fields::FieldValue<u64, STAGE1_DESCRIPTOR::Register>;
|
||||
type EntryFlags = register::FieldValue<u64, STAGE1_DESCRIPTOR::Register>;
|
||||
// type EntryRegister = register::LocalRegisterCopy<u64, STAGE1_DESCRIPTOR::Register>;
|
||||
|
||||
/// L0 table -- only pointers to L1 tables
|
|
@ -11,9 +11,12 @@ use {
|
|||
};
|
||||
|
||||
mod addr;
|
||||
mod cache;
|
||||
pub mod mmu;
|
||||
|
||||
pub use addr::{PhysAddr, VirtAddr};
|
||||
pub use addr::PhysAddr;
|
||||
pub use addr::VirtAddr;
|
||||
pub use addr::ASID;
|
||||
|
||||
// aarch64 granules and page sizes howto:
|
||||
// https://stackoverflow.com/questions/34269185/simultaneous-existence-of-different-sized-pages-on-aarch64
|
|
@ -8,11 +8,16 @@
|
|||
use cortex_a::asm;
|
||||
|
||||
mod boot;
|
||||
pub mod caps;
|
||||
pub use self::caps::*;
|
||||
#[cfg(feature = "jtag")]
|
||||
pub mod jtag;
|
||||
pub mod memory;
|
||||
pub mod objects;
|
||||
pub mod traps;
|
||||
|
||||
pub(crate) use objects::thread::user_stack_trace;
|
||||
|
||||
/// Loop forever in sleep mode.
|
||||
#[inline]
|
||||
pub fn endless_sleep() -> ! {
|
||||
|
@ -39,14 +44,3 @@ 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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
// implemented for x86 and arm
|
||||
trait ASIDControl {
|
||||
// fn make_pool(untyped: Untyped, target_cap_space_cap: CapNodeRootedPath) -> Result<()>;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
// implemented for x86 and arm
|
||||
trait ASIDPool {
|
||||
// fn assign(virt_space: VirtSpace /*Cap*/) -> Result<()>;
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
use crate::memory::{PhysAddr, VirtAddr};
|
||||
|
||||
mod asid_control;
|
||||
mod asid_pool;
|
||||
mod page;
|
||||
mod page_directory;
|
||||
mod page_global_directory;
|
||||
mod page_table;
|
||||
mod page_upper_directory;
|
||||
pub(crate) mod thread;
|
||||
|
||||
// Allocation details
|
||||
|
||||
// 1. should be possible to map non-SAS style
|
||||
// 2. should be easy to map SAS style
|
||||
// 3. should not allocate any memory dynamically
|
||||
// ^ problem with the above API is FrameAllocator
|
||||
// ^ clients should supply their own memory for frames... from FrameCaps
|
||||
|
||||
// https://github.com/seL4/seL4_libs/tree/master/libsel4allocman
|
||||
|
||||
// Allocation overview
|
||||
|
||||
// Allocation is complex due to the circular dependencies that exist on allocating resources. These dependencies are loosely described as
|
||||
|
||||
// Capability slots: Allocated from untypeds, book kept in memory.
|
||||
// Untypeds / other objects (including frame objects): Allocated from other untypeds, into capability slots, book kept in memory.
|
||||
// memory: Requires frame object.
|
||||
|
||||
//=============================================================================
|
||||
|
||||
// ActivePageTable (--> impl VirtSpace for ActivePageTable etc...)
|
||||
// * translate(VirtAddr)->PhysAddr
|
||||
// * translate_page(Page)->PhysAddr
|
||||
// * map_to(Page, PhysFrame, Flags, FrameAllocator)->()
|
||||
// * map(Page, Flags, FrameAllocator)->()
|
||||
// * identity_map(PhysFrame, Flags, FrameAllocator)->()
|
||||
// * unmap(Page, FrameAllocator)->()
|
||||
|
||||
trait VirtSpace {
|
||||
fn map_to(
|
||||
virt_space: VirtSpace, /*Cap*/
|
||||
vaddr: VirtAddr,
|
||||
rights: u32, //CapRights,
|
||||
attr: u32, //VMAttributes,
|
||||
) -> Result<()>;
|
||||
/// ??
|
||||
fn unmap() -> Result<()>; // ??
|
||||
fn remap(
|
||||
virt_space: VirtSpace, /*Cap*/
|
||||
rights: u32, //CapRights,
|
||||
attr: u32, //VMAttributes,
|
||||
) -> Result<()>;
|
||||
/// ??
|
||||
fn get_address() -> Result<PhysAddr>; //??
|
||||
}
|
||||
|
||||
// ARM AArch64 processors have a four-level page-table structure, where the
|
||||
// VirtSpace is realised as a PageGlobalDirectory. All paging structures are
|
||||
// indexed by 9 bits of the virtual address.
|
||||
|
||||
// AArch64 page hierarchy:
|
||||
//
|
||||
// PageGlobalDirectory (L0) -- aka VirtSpace
|
||||
// +--PageUpperDirectory (L1)
|
||||
// +--Page<Size1GiB> -- aka HugePage
|
||||
// | or
|
||||
// +--PageDirectory (L2)
|
||||
// +--Page<Size2MiB> -- aka LargePage
|
||||
// | or
|
||||
// +--PageTable (L3)
|
||||
// +--Page<Size4KiB> -- aka Page
|
||||
|
||||
/// Cache data management.
|
||||
trait PageCacheManagement {
|
||||
/// Cleans the data cache out to RAM.
|
||||
/// The start and end are relative to the page being serviced.
|
||||
fn clean_data(start_offset: usize, end_offset: usize) -> Result<()>;
|
||||
/// Clean and invalidates the cache range within the given page.
|
||||
/// The range will be flushed out to RAM. The start and end are relative
|
||||
/// to the page being serviced.
|
||||
fn clean_invalidate_data(start_offset: usize, end_offset: usize) -> Result<()>;
|
||||
/// Invalidates the cache range within the given page.
|
||||
/// The start and end are relative to the page being serviced and should
|
||||
/// be aligned to a cache line boundary where possible. An additional
|
||||
/// clean is performed on the outer cache lines if the start and end are
|
||||
/// not aligned, to clean out the bytes between the requested and
|
||||
/// the cache line boundary.
|
||||
fn invalidate_data(start_offset: usize, end_offset: usize) -> Result<()>;
|
||||
/// Cleans data lines to point of unification, invalidates
|
||||
/// corresponding instruction lines to point of unification, then
|
||||
/// invalidates branch predictors.
|
||||
/// The start and end are relative to the page being serviced.
|
||||
fn unify_instruction_cache(start_offset: usize, end_offset: usize) -> Result<()>;
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
use crate::arch::{
|
||||
memory::{PhysAddr, VirtAddr},
|
||||
objects::PageCacheManagement,
|
||||
};
|
||||
|
||||
struct Page {}
|
||||
|
||||
impl Page {
|
||||
// VirtSpace-like interface.
|
||||
/// Get the physical address of the underlying frame.
|
||||
fn get_address() -> Result<PhysAddr> {
|
||||
todo!()
|
||||
}
|
||||
// fn map(
|
||||
// virt_space: VirtSpace, /*Cap*/
|
||||
// vaddr: VirtAddr,
|
||||
// rights: CapRights,
|
||||
// attr: VMAttributes,
|
||||
// ) -> Result<()> {
|
||||
// todo!()
|
||||
// }
|
||||
/// Changes the permissions of an existing mapping.
|
||||
// fn remap(
|
||||
// virt_space: VirtSpace, /*Cap*/
|
||||
// rights: CapRights,
|
||||
// attr: VMAttributes,
|
||||
// ) -> Result<()> {
|
||||
// todo!()
|
||||
// }
|
||||
fn unmap() -> Result<()> {
|
||||
todo!()
|
||||
}
|
||||
// MMIO space.
|
||||
// fn map_io(iospace: IoSpace /*Cap*/, rights: CapRights, ioaddr: VirtAddr) -> Result<()> {
|
||||
// todo!()
|
||||
// }
|
||||
}
|
||||
|
||||
impl PageCacheManagement for Page {
|
||||
fn clean_data(start_offset: usize, end_offset: usize) -> _ {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn clean_invalidate_data(start_offset: usize, end_offset: usize) -> _ {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn invalidate_data(start_offset: usize, end_offset: usize) -> _ {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn unify_instruction_cache(start_offset: usize, end_offset: usize) -> _ {
|
||||
todo!()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
use crate::memory::{mmu::PageUpperDirectory, VirtAddr};
|
||||
|
||||
// probably just impl some Mapping trait for these "structs"?
|
||||
|
||||
// L2 table
|
||||
struct PageDirectory {}
|
||||
|
||||
impl PageDirectory {
|
||||
fn map(
|
||||
pud: PageUpperDirectory, /*Cap*/
|
||||
vaddr: VirtAddr,
|
||||
attr: u32, //VMAttributes,
|
||||
) -> Result<()> {
|
||||
todo!()
|
||||
}
|
||||
fn unmap() -> Result<()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
use crate::arch::{memory::PhysAddr, objects::PageCacheManagement};
|
||||
|
||||
// L0 table
|
||||
struct PageGlobalDirectory {
|
||||
// @todo should also impl VirtSpace to be able to map shit?
|
||||
// or the Page's impl will do this?
|
||||
}
|
||||
|
||||
impl PageCacheManagement for PageGlobalDirectory {
|
||||
fn clean_data(start_offset: usize, end_offset: usize) -> ! {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn clean_invalidate_data(start_offset: usize, end_offset: usize) -> ! {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn invalidate_data(start_offset: usize, end_offset: usize) -> ! {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn unify_instruction_cache(start_offset: usize, end_offset: usize) -> ! {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PhysAddr> for PageGlobalDirectory {}
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
// L3 tables
|
||||
struct PageTable {}
|
||||
|
||||
impl PageTable {
|
||||
// fn map_to(virt_space: VirtSpace /*Cap*/, vaddr: VirtAddr, attr: VMAttributes) -> Result<()> {
|
||||
// todo!()
|
||||
// }
|
||||
fn unmap() -> Result<()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
use crate::memory::{mmu::PageGlobalDirectory, VirtAddr};
|
||||
|
||||
// L1 table
|
||||
struct PageUpperDirectory {}
|
||||
|
||||
impl PageUpperDirectory {
|
||||
fn map(
|
||||
pgd: PageGlobalDirectory, /*Cap*/
|
||||
vaddr: VirtAddr,
|
||||
attr: u32, //VMAttributes,
|
||||
) -> Result<()> {
|
||||
todo!()
|
||||
}
|
||||
fn unmap() -> Result<()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
//! Arch-specific part of the TCB
|
||||
|
||||
struct UserContext {
|
||||
registers: [u64; 32],
|
||||
}
|
||||
|
||||
pub(crate) struct TCB {
|
||||
register_context: UserContext,
|
||||
}
|
||||
|
||||
pub(crate) fn user_stack_trace(thread: &TCB) {}
|
|
@ -52,17 +52,14 @@
|
|||
use {
|
||||
crate::{arch::endless_sleep, println},
|
||||
cortex_a::{
|
||||
asm::barrier,
|
||||
registers::{ESR_EL1, FAR_EL1, VBAR_EL1},
|
||||
barrier,
|
||||
regs::{RegisterReadOnly, RegisterReadWrite, ESR_EL1, FAR_EL1, VBAR_EL1},
|
||||
},
|
||||
register::{register_bitfields, LocalRegisterCopy},
|
||||
snafu::Snafu,
|
||||
tock_registers::{
|
||||
interfaces::{Readable, Writeable},
|
||||
register_bitfields, LocalRegisterCopy,
|
||||
},
|
||||
};
|
||||
|
||||
core::arch::global_asm!(include_str!("vectors.S"));
|
||||
global_asm!(include_str!("vectors.S"));
|
||||
|
||||
/// Errors possibly returned from the traps module.
|
||||
#[derive(Debug, Snafu)]
|
||||
|
@ -129,6 +126,13 @@ unsafe extern "C" fn default_exception_handler() -> ! {
|
|||
/// Totally unsafe in the land of the hardware.
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn current_el0_synchronous(e: &mut ExceptionContext) {
|
||||
let cause = ESR_EL1.read(ESR_EL1::EC);
|
||||
|
||||
if cause == ESR_EL1::EC::SVC64.read(ESR_EL1::EC) {
|
||||
let syscall = ESR_EL1.read(ESR_EL1::ISS);
|
||||
return crate::api::handle_syscall(syscall);
|
||||
}
|
||||
|
||||
println!("[!] USER synchronous exception happened.");
|
||||
synchronous_common(e)
|
||||
}
|
||||
|
@ -312,8 +316,9 @@ fn iss_dfsc_to_string(iss: IssForDataAbort) -> &'static str {
|
|||
/// Helper function to 1) display current exception, 2) skip the offending asm instruction.
|
||||
/// Not for production use!
|
||||
fn synchronous_common(e: &mut ExceptionContext) {
|
||||
println!(" ESR_EL1: {:#010x} (syndrome)", ESR_EL1.get());
|
||||
let cause = ESR_EL1.read(ESR_EL1::EC);
|
||||
|
||||
println!(" ESR_EL1: {:#010x} (syndrome)", ESR_EL1.get());
|
||||
println!(
|
||||
" EC: {:#06b} (cause) -- {}",
|
||||
cause,
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_arch = "aarch64")] {
|
||||
#[macro_use]
|
||||
pub mod aarch64;
|
||||
pub use self::aarch64::*;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
use {
|
||||
super::{
|
||||
captable::CapTableEntry, derivation_tree::DerivationTreeNode, CapError, Capability, TryFrom,
|
||||
},
|
||||
crate::{arch::aarch64::memory::PhysAddr, capdef},
|
||||
paste::paste,
|
||||
register::{register_bitfields, LocalRegisterCopy},
|
||||
ux::u6,
|
||||
};
|
||||
|
||||
/// CapNode capability points to a CapTable containing
|
||||
/// a number of CapTableEntries.
|
||||
/// While CapTable is merely a storage, CapNode capability
|
||||
/// provides information about the guard and the size
|
||||
/// of that table.
|
||||
|
||||
//=====================
|
||||
// Cap definition
|
||||
//=====================
|
||||
|
||||
register_bitfields! {
|
||||
u128,
|
||||
CapNodeCap [
|
||||
Type OFFSET(0) NUMBITS(6) [
|
||||
value = 10
|
||||
],
|
||||
GuardSize OFFSET(6) NUMBITS(6) [],
|
||||
Radix OFFSET(12) NUMBITS(6) [],
|
||||
Ptr OFFSET(16) NUMBITS(48) [],
|
||||
// Guard is 19 bits in seL4 arm32 (?)
|
||||
Guard OFFSET(64) NUMBITS(64) [],
|
||||
]
|
||||
}
|
||||
|
||||
capdef! { CapNode }
|
||||
|
||||
//=====================
|
||||
// Cap implementation
|
||||
//=====================
|
||||
|
||||
impl CapNodeCapability {
|
||||
/// Create a capability to CapNode.
|
||||
///
|
||||
/// CapNode capabilities allow to address a capability node tree entry.
|
||||
pub fn new(capnode_ptr: PhysAddr, radix: u6, guard_size: u6, guard: u64) -> CapNodeCapability {
|
||||
CapNodeCapability(LocalRegisterCopy::new(u128::from(
|
||||
CapNodeCap::Type::value
|
||||
+ CapNodeCap::Radix.val(radix.into())
|
||||
+ CapNodeCap::GuardSize.val(guard_size.into())
|
||||
+ CapNodeCap::Guard.val(guard.into())
|
||||
+ CapNodeCap::Ptr.val(capnode_ptr.into()),
|
||||
)))
|
||||
}
|
||||
|
||||
/// Create new root node.
|
||||
pub fn new_root(capnode_ptr: PhysAddr) -> CapNodeCapability {
|
||||
const CONFIG_ROOT_CAPNODE_SIZE_BITS: u32 = 12;
|
||||
const WORD_BITS: u32 = 64;
|
||||
|
||||
CapNodeCapability::new(
|
||||
capnode_ptr,
|
||||
CONFIG_ROOT_CAPNODE_SIZE_BITS,
|
||||
WORD_BITS - CONFIG_ROOT_CAPNODE_SIZE_BITS,
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
||||
// pub const fn from_capability(cap: dyn Capability) -> CapNodeCapability {
|
||||
// let reg = LocalRegisterCopy::<_, CapNodeCap::Register>::new(cap.as_u128());
|
||||
// //assert_eq!(
|
||||
// // reg.read(CapNodeCap::Type),
|
||||
// // u128::from(CapNodeCap::Type::value)
|
||||
// //);
|
||||
// CapNodeCapability(reg)
|
||||
// }
|
||||
|
||||
/// @internal
|
||||
pub fn write_slot(&mut self, slot: usize, cap: &dyn Capability) {
|
||||
let ptr = self.0.read(CapNodeCap::Ptr);
|
||||
let size =
|
||||
(1usize << self.0.read(CapNodeCap::Radix)) * core::mem::size_of::<CapTableEntry>();
|
||||
let slice = unsafe { core::slice::from_raw_parts_mut(ptr as *mut CapTableEntry, size) };
|
||||
slice[slot].capability = cap.as_u128();
|
||||
slice[slot].derivation = DerivationTreeNode::empty()
|
||||
.set_revocable(true)
|
||||
.set_first_badged(true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,273 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
use crate::caps::Capability;
|
||||
use snafu::Snafu;
|
||||
use {super::derivation_tree::DerivationTreeNode, /*crate::memory::PhysAddr,*/ core::fmt};
|
||||
|
||||
// * Capability slots: 16 bytes of memory per slot (exactly one capability). --?
|
||||
// CapNode describes `a given number of capability slots` with `a given guard`
|
||||
// of `a given guard size` bits.
|
||||
|
||||
// @todo const generic on number of capabilities contained in the node? currently only contains a Cap
|
||||
// capnode_cap has a pptr, guard_size, guard and radix
|
||||
// this is enough to address a cap in the capnode contents
|
||||
// by having a root capnode cap we can traverse the whole tree.
|
||||
|
||||
// -- cte_t from seL4
|
||||
// structures.h:140
|
||||
// /* Capability table entry (CTE) */
|
||||
// struct cte {
|
||||
// cap_t cap; // two words
|
||||
// mdb_node_t cteMDBNode; // two words
|
||||
// }; // -- four words: u256, 32 bytes.
|
||||
// typedef struct cte cte_t;
|
||||
/// Each entry in capability tree contains capability value and its position in the derivation tree.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct CapTableEntry {
|
||||
pub(crate) capability: u128,
|
||||
pub(crate) derivation: DerivationTreeNode,
|
||||
}
|
||||
|
||||
impl fmt::Debug for CapTableEntry {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{:x}", self.capability) // @todo
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CapTableEntry {
|
||||
fn default() -> Self {
|
||||
Self::empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl CapTableEntry {
|
||||
/// Temporary for testing:
|
||||
fn empty() -> CapTableEntry {
|
||||
CapTableEntry {
|
||||
capability: 0,
|
||||
derivation: DerivationTreeNode::empty(),
|
||||
}
|
||||
}
|
||||
// We need to pass reference to the parent entry so that we can set up derivation pointers.
|
||||
// @todo should be &mut since we need to set up Next pointer in parent also.
|
||||
// @fixme this cannot work well unless we modify already allocated cap table entry in the table.
|
||||
// (otherwise Next pointer will be invalid)
|
||||
// sel4: cteInsert()
|
||||
fn derived_from(&mut self, _parent: &mut CapTableEntry) {
|
||||
// self.derivation
|
||||
// .set_prev(parent as *mut CapTableEntry as PhysAddr);
|
||||
// parent
|
||||
// .derivation
|
||||
// .set_next(self as *mut CapTableEntry as PhysAddr);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
struct CapNodePath {
|
||||
/// Index contains `depth` lowermost bits of the path.
|
||||
index: u64,
|
||||
/// Depth specifies the remaining amount of bits left to traverse in the path.
|
||||
/// Once depth reaches zero, the selected CapNode slot is the final target.
|
||||
depth: usize,
|
||||
}
|
||||
|
||||
struct CapNodeRootedPath {
|
||||
root: CapNode,
|
||||
path: CapNodePath,
|
||||
}
|
||||
|
||||
// sel4: cnode_capdata_t
|
||||
// @todo just use CapNodeCap
|
||||
//struct CapNodeConfig { <-- for each CapTable we would need these..
|
||||
// guard: u64,
|
||||
// guard_bits: usize,
|
||||
//}
|
||||
|
||||
// @note src and dest are swapped here, compared to seL4 api
|
||||
impl CapNode { // actually an impl CapPtr
|
||||
// Derives a capability into a new, less powerful one, with potentially added Badge.
|
||||
fn mint(
|
||||
src: CapNodeRootedPath, // can be just CapNodePath since it's relative (is it?) to this CapNode.
|
||||
dest: CapNodePath,
|
||||
rights: CapRights,
|
||||
badge: Badge,
|
||||
) -> Result<(), CapError> {
|
||||
unimplemented!();
|
||||
}
|
||||
// [wip] is copy a derivation too? - yes it is - kernel_final.c:15769
|
||||
fn copy(src: CapNodeRootedPath, dest: CapNodePath, rights: CapRights) -> Result<(), CapError> {
|
||||
unimplemented!();
|
||||
}
|
||||
fn r#move(src: CapNodeRootedPath, dest: CapNodePath) -> Result<(), CapError> {
|
||||
unimplemented!();
|
||||
}
|
||||
fn mutate(src: CapNodeRootedPath, dest: CapNodePath, badge: Badge) -> Result<(), CapError> {
|
||||
unimplemented!();
|
||||
}
|
||||
fn rotate(
|
||||
src: CapNodeRootedPath,
|
||||
dest: CapNodePath,
|
||||
dest_badge: Badge,
|
||||
pivot: CapNodeRootedPath,
|
||||
pivot_badge: Badge,
|
||||
) -> Result<(), CapError> {
|
||||
unimplemented!();
|
||||
}
|
||||
fn delete(path: CapNodePath) -> Result<(), CapError> {
|
||||
unimplemented!();
|
||||
}
|
||||
fn revoke(path: CapNodePath) -> Result<(), CapError> {
|
||||
unimplemented!();
|
||||
}
|
||||
fn save_caller(r#where: CapNodePath) -> Result<(), CapError> { // save_reply_cap() in sel4
|
||||
unimplemented!();
|
||||
}
|
||||
fn cancel_badged_sends(path: CapNodePath) -> Result<(), CapError> {
|
||||
unimplemented!();
|
||||
}
|
||||
}*/
|
||||
|
||||
/// Structure holding a number of capabilities.
|
||||
// In seL4 the capnode is capability to an object called CapTable btw:
|
||||
// case seL4_CapTableObject:
|
||||
// return cap_cnode_cap_new(userSize, 0, 0, CTE_REF(regionBase));
|
||||
struct CapTable<const SIZE_BITS: usize>
|
||||
where
|
||||
[CapTableEntry; 1 << SIZE_BITS]: Sized,
|
||||
{
|
||||
items: [CapTableEntry; 1 << SIZE_BITS],
|
||||
}
|
||||
|
||||
/// Conceptually a thread’s CapSpace is the portion of the directed graph that is reachable
|
||||
/// starting with the CapNode capability that is its CapSpace root.
|
||||
struct CapSpace {
|
||||
// cap_space_root: CapNodePath, -- probably not a path but direct CapNode pointer??
|
||||
}
|
||||
//impl CapNode for CapSpace {} -- ?
|
||||
|
||||
type CapPath = u64;
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub(crate) enum LookupFault {
|
||||
InvalidRoot,
|
||||
GuardMismatch,
|
||||
DepthMismatch { expected: usize, actual: usize },
|
||||
NoResolvedBits,
|
||||
}
|
||||
|
||||
type Slot = u64; // @temp
|
||||
type BitsRemaining = usize; // @temp
|
||||
|
||||
// seL4: resolveAddressBits(nodeCap, capptr, n_bits)
|
||||
pub(crate) fn resolve_address_bits(
|
||||
node_cap: &dyn Capability,
|
||||
capptr: CapPath, // CapPtr = u64, aka CapPath
|
||||
n_bits: usize,
|
||||
) -> Result<(Slot, BitsRemaining), LookupFault> {
|
||||
if node_cap.get_type() != CapNodeCapability {
|
||||
return Err(LookupFault::InvalidRoot);
|
||||
}
|
||||
let mut n_bits = n_bits;
|
||||
let mut node_cap = node_cap;
|
||||
loop {
|
||||
let radix_bits = node_cap.radixBits();
|
||||
let guard_bits = node_cap.guardBits();
|
||||
let level_bits = radix_bits + guard_bits;
|
||||
|
||||
if level_bits == 0 {
|
||||
return Err(LookupFault::NoResolvedBits);
|
||||
}
|
||||
|
||||
let cap_guard = node_cap.guard();
|
||||
// @todo common code to extract guard_bits from an int?
|
||||
let guard = (capptr >> core::cmp::min(n_bits - guard_bits, 63)) & ((1 << guard_bits) - 1);
|
||||
|
||||
if guard_bits > n_bits || guard != cap_guard {
|
||||
return Err(LookupFault::GuardMismatch);
|
||||
}
|
||||
|
||||
if level_bits > n_bits {
|
||||
return Err(LookupFault::DepthMismatch {
|
||||
expected: level_bits,
|
||||
actual: n_bits,
|
||||
});
|
||||
}
|
||||
|
||||
let offset = (capptr >> (n_bits - level_bits)) & ((1 << radix_bits) - 1);
|
||||
let slot = node_cap.getPtr() + offset; // @todo Need to turn this into CapTableEntry ptr
|
||||
|
||||
// actually == here since > case has errored above
|
||||
if level_bits == n_bits {
|
||||
return Ok((slot, 0));
|
||||
}
|
||||
|
||||
n_bits -= level_bits;
|
||||
node_cap = slot.capability;
|
||||
|
||||
if node_cap.get_type() != CapNodeCapability {
|
||||
return Ok((slot, n_bits));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
super::{derivation_tree::DerivationTreeError, null_cap::NullCapability},
|
||||
*,
|
||||
};
|
||||
|
||||
#[test_case]
|
||||
fn create_empty_cap_table() {
|
||||
let table = CapTable::<5> {
|
||||
items: Default::default(),
|
||||
};
|
||||
assert_eq!(table.items[0].capability, NullCapability::new().into());
|
||||
assert_eq!(table.items[31].capability, NullCapability::new().into());
|
||||
// Doesn't even compile:
|
||||
// assert_eq!(table.items[32].capability, NullCapability::new().into());
|
||||
}
|
||||
|
||||
#[test_case]
|
||||
fn first_capability_derivation_has_no_prev_link() {
|
||||
let entry = CapTableEntry::empty();
|
||||
assert!(entry
|
||||
.derivation
|
||||
.try_get_prev()
|
||||
.contains_err(&DerivationTreeError::InvalidPrev));
|
||||
}
|
||||
|
||||
// Impl strategy
|
||||
// 1. Make capabilities list
|
||||
// 2. Fill it with capabilities
|
||||
// 3. Test capability manipulation functions - mint/clone/revoke
|
||||
// 4. Validate capability path, capability contents and capability derivation chain at each step
|
||||
// 5. Start with Untyped capabilities and implement Retype()
|
||||
// typedef enum api_object { -- basic list of API types of objects:
|
||||
// seL4_UntypedObject,
|
||||
// seL4_TCBObject,
|
||||
// seL4_EndpointObject,
|
||||
// seL4_NotificationObject,
|
||||
// seL4_CapTableObject,
|
||||
// 6. Retype to TCB and implement Thread capability to run threads (in priv mode first?)
|
||||
}
|
||||
|
||||
/* caps with fixed slot positions in the root (boot) CNode */
|
||||
// enum {
|
||||
// seL4_CapNull = 0, /* null cap */
|
||||
// seL4_CapInitThreadTCB = 1, /* initial thread's TCB cap */
|
||||
// seL4_CapInitThreadCNode = 2, /* initial thread's root CNode cap */
|
||||
// seL4_CapInitThreadVSpace = 3, /* initial thread's VSpace cap */
|
||||
// seL4_CapIRQControl = 4, /* global IRQ controller cap */
|
||||
// seL4_CapASIDControl = 5, /* global ASID controller cap */
|
||||
// seL4_CapInitThreadASIDPool = 6, /* initial thread's ASID pool cap */
|
||||
// seL4_CapIOPort = 7, /* global IO port cap (null cap if not supported) */
|
||||
// seL4_CapIOSpace = 8, /* global IO space cap (null cap if no IOMMU support) */
|
||||
// seL4_CapBootInfoFrame = 9, /* bootinfo frame cap */
|
||||
// seL4_CapInitThreadIPCBuffer = 10, /* initial thread's IPC buffer frame cap */
|
||||
// seL4_CapDomain = 11, /* global domain controller cap */
|
||||
// seL4_NumInitialCaps = 12
|
||||
// };
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
//! DerivationTree nodes record the tree of inheritance for caps:
|
||||
//! See the picture on derivation from seL4 manual for how this works: each cap contains a ref to
|
||||
//! DerivationTree node, which records the previous cap and the following cap(s).
|
||||
|
||||
use {
|
||||
super::captable::CapTableEntry,
|
||||
crate::memory::PhysAddr,
|
||||
register::{register_bitfields, LocalRegisterCopy},
|
||||
snafu::Snafu,
|
||||
};
|
||||
|
||||
//-- Mapping database (MDB) node: size = 16 bytes
|
||||
//block mdb_node {
|
||||
//padding 16 -- highest in word[1]
|
||||
//field_high mdbNext 46 <-- field_high means "will need sign-extension", also value has 2 lower bits just dropped when setting
|
||||
//field mdbRevocable 1 -- second bit in word[1]
|
||||
//field mdbFirstBadged 1 -- lowest in word[1]
|
||||
//field mdbPrev 64 -- enter lowest word (word[0]) in sel4
|
||||
//}
|
||||
|
||||
register_bitfields! {
|
||||
u128,
|
||||
CapDerivationNode [
|
||||
FirstBadged OFFSET(0) NUMBITS(1) [
|
||||
Disable = 0,
|
||||
Enable = 1
|
||||
],
|
||||
Revocable OFFSET(1) NUMBITS(1) [
|
||||
Disable = 0,
|
||||
Enable = 1
|
||||
],
|
||||
// -- 2 bits still free here --
|
||||
// Next CTE node address -- per cteInsert this is address of the entire CTE slot
|
||||
// cap derivation slots are supposedly aligned in u128 boundary (16 bytes) this means we can
|
||||
// drop bottom 4 bits from it in these fields.
|
||||
Next OFFSET(4) NUMBITS(44) [], // 16-bytes-aligned, size of canonical phys address is 48 bits
|
||||
// -- 16 bits still free here --
|
||||
// -- New word boundary --
|
||||
// -- 4 bits still free here --
|
||||
// Prev CTE node address -- per cteInsert this is address of the entire CTE slot
|
||||
Prev OFFSET(68) NUMBITS(44) []
|
||||
// -- 16 bits still free here --
|
||||
]
|
||||
}
|
||||
|
||||
/// Wrapper for CapDerivationNode
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
pub struct DerivationTreeNode(LocalRegisterCopy<u128, CapDerivationNode::Register>);
|
||||
|
||||
/// Errors that may happen in capability derivation tree operations.
|
||||
#[derive(Debug, PartialEq, Snafu)]
|
||||
pub enum DerivationTreeError {
|
||||
/// Previous link is invalid.
|
||||
InvalidPrev,
|
||||
/// Next link is invalid.
|
||||
InvalidNext,
|
||||
}
|
||||
|
||||
// In seL4, the MDB is stored as a doubly-linked list, representing the **preorder-DFS** through
|
||||
// the hierarchy of capabilities. This data structure allows easy insertion of a capability
|
||||
// given its immediate ancestor or a copy, and easy checking for existence of copies and descendants.
|
||||
// But when no relations are known beforehand, finding the position to place a new capability
|
||||
// requires a O(n) linear scan through the list, as does finding ancestors and descendants
|
||||
// of a capability given just the capability’s value. This operation is performed in
|
||||
// the non-preemptable kernel, creating a scheduling hole that is problematic for real-time applications.
|
||||
// To reduce the complexity of operations described above, we replace the MDB’s linked list with
|
||||
// a more suitable search data structure.
|
||||
// -- nevill-master-thesis Using Capabilities for OS Resource Management
|
||||
// sel4: mdb_node_t
|
||||
impl DerivationTreeNode {
|
||||
const ADDR_BIT_SHIFT: usize = 4;
|
||||
|
||||
pub(crate) fn empty() -> Self {
|
||||
Self(LocalRegisterCopy::new(0))
|
||||
}
|
||||
|
||||
// Unlike mdb_node_new we do not pass revocable and firstBadged flags here, they are enabled
|
||||
// using builder interface set_first_badged() and set_revocable().
|
||||
pub(crate) fn new(prev_ptr: PhysAddr, next_ptr: PhysAddr) -> Self {
|
||||
Self::empty().set_prev(prev_ptr).set_next(next_ptr)
|
||||
}
|
||||
|
||||
/// Get previous link in derivation tree.
|
||||
/// Previous link exists if this is a derived capability.
|
||||
///
|
||||
/// SAFETY: it is UB to get prev reference from a null Prev pointer.
|
||||
pub(crate) unsafe fn get_prev(&self) -> CapTableEntry {
|
||||
let ptr =
|
||||
(self.0.read(CapDerivationNode::Prev) << Self::ADDR_BIT_SHIFT) as *const CapTableEntry;
|
||||
(*ptr).clone()
|
||||
}
|
||||
|
||||
/// Try to get previous link in derivation tree.
|
||||
/// Previous link exists if this is a derived capability.
|
||||
pub(crate) fn try_get_prev(&self) -> Result<CapTableEntry, DerivationTreeError> {
|
||||
if self.0.read(CapDerivationNode::Prev) == 0 {
|
||||
Err(DerivationTreeError::InvalidPrev)
|
||||
} else {
|
||||
Ok(unsafe { self.get_prev() })
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_prev(&mut self, prev_ptr: PhysAddr) -> Self {
|
||||
self.0
|
||||
.write(CapDerivationNode::Prev.val((prev_ptr >> Self::ADDR_BIT_SHIFT).into()));
|
||||
*self
|
||||
}
|
||||
|
||||
/// Get next link in derivation tree.
|
||||
/// Next link exists if this capability has derived capabilities or siblings.
|
||||
///
|
||||
/// SAFETY: it is UB to get next reference from a null Next pointer.
|
||||
pub(crate) unsafe fn get_next(&self) -> CapTableEntry {
|
||||
let ptr =
|
||||
(self.0.read(CapDerivationNode::Next) << Self::ADDR_BIT_SHIFT) as *const CapTableEntry;
|
||||
(*ptr).clone()
|
||||
}
|
||||
|
||||
/// Try to get next link in derivation tree.
|
||||
/// Next link exists if this capability has derived capabilities or siblings.
|
||||
pub(crate) fn try_get_next(&self) -> Result<CapTableEntry, DerivationTreeError> {
|
||||
if self.0.read(CapDerivationNode::Next) == 0 {
|
||||
Err(DerivationTreeError::InvalidNext)
|
||||
} else {
|
||||
Ok(unsafe { self.get_next() })
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_next(&mut self, next_ptr: PhysAddr) -> Self {
|
||||
self.0
|
||||
.write(CapDerivationNode::Next.val((next_ptr >> Self::ADDR_BIT_SHIFT).into()));
|
||||
*self
|
||||
}
|
||||
|
||||
/// Builder interface to modify firstBadged flag
|
||||
/// @todo Describe the firstBadged flag and what it does.
|
||||
pub(crate) fn set_first_badged(mut self, enable: bool) -> Self {
|
||||
self.0.modify(if enable {
|
||||
CapDerivationNode::FirstBadged::Enable
|
||||
} else {
|
||||
CapDerivationNode::FirstBadged::Disable
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
/// Builder interface to modify revocable flag
|
||||
/// @todo Describe the revocable flag and what it does.
|
||||
pub(crate) fn set_revocable(mut self, enable: bool) -> Self {
|
||||
self.0.modify(if enable {
|
||||
CapDerivationNode::Revocable::Enable
|
||||
} else {
|
||||
CapDerivationNode::Revocable::Disable
|
||||
});
|
||||
self
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
use {
|
||||
super::{CapError, Capability, TryFrom},
|
||||
crate::capdef,
|
||||
paste::paste,
|
||||
register::{register_bitfields, LocalRegisterCopy},
|
||||
};
|
||||
|
||||
//=====================
|
||||
// Cap definition
|
||||
//=====================
|
||||
|
||||
register_bitfields! {
|
||||
u128,
|
||||
DomainCap [
|
||||
Type OFFSET(0) NUMBITS(6) [
|
||||
value = 20
|
||||
]
|
||||
],
|
||||
}
|
||||
|
||||
capdef! { Domain }
|
||||
|
||||
//=====================
|
||||
// Cap implementation
|
||||
//=====================
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
use {
|
||||
super::{CapError, Capability, TryFrom},
|
||||
crate::capdef,
|
||||
paste::paste,
|
||||
register::{register_bitfields, LocalRegisterCopy},
|
||||
};
|
||||
|
||||
//=====================
|
||||
// Cap definition
|
||||
//=====================
|
||||
|
||||
register_bitfields! {
|
||||
u128,
|
||||
EndpointCap [
|
||||
Type OFFSET(0) NUMBITS(6) [
|
||||
value = 4
|
||||
],
|
||||
// @todo Badge has 4 lower bits all-zero - why?
|
||||
Badge OFFSET(0) NUMBITS(64) [],
|
||||
CanGrantReply OFFSET(69) NUMBITS(1) [],
|
||||
CanGrant OFFSET(70) NUMBITS(1) [],
|
||||
CanReceive OFFSET(71) NUMBITS(1) [],
|
||||
CanSend OFFSET(72) NUMBITS(1) [],
|
||||
Ptr OFFSET(80) NUMBITS(48) [],
|
||||
]
|
||||
}
|
||||
|
||||
capdef! { Endpoint }
|
||||
|
||||
//=====================
|
||||
// Cap implementation
|
||||
//=====================
|
||||
|
||||
// Endpoints support all 10 IPC variants (see COMP9242 slides by Gernot)
|
||||
impl EndpointCapability {}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
use {
|
||||
super::{CapError, Capability, TryFrom},
|
||||
crate::capdef,
|
||||
paste::paste,
|
||||
register::{register_bitfields, LocalRegisterCopy},
|
||||
};
|
||||
|
||||
//=====================
|
||||
// Cap definition
|
||||
//=====================
|
||||
|
||||
register_bitfields! {
|
||||
u128,
|
||||
IrqControlCap [
|
||||
Type OFFSET(0) NUMBITS(6) [
|
||||
value = 14
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
capdef! { IrqControl }
|
||||
|
||||
//=====================
|
||||
// Cap implementation
|
||||
//=====================
|
||||
|
||||
impl IrqControlCapability {}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
use {
|
||||
super::{CapError, Capability, TryFrom},
|
||||
crate::capdef,
|
||||
paste::paste,
|
||||
register::{register_bitfields, LocalRegisterCopy},
|
||||
};
|
||||
|
||||
//=====================
|
||||
// Cap definition
|
||||
//=====================
|
||||
|
||||
register_bitfields! {
|
||||
u128,
|
||||
IrqHandlerCap [
|
||||
Type OFFSET(0) NUMBITS(6) [
|
||||
value = 16
|
||||
],
|
||||
Irq OFFSET(52) NUMBITS(12) [],
|
||||
]
|
||||
}
|
||||
|
||||
capdef! { IrqHandler }
|
||||
|
||||
//=====================
|
||||
// Cap implementation
|
||||
//=====================
|
||||
|
||||
impl IrqHandlerCapability {}
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
//! Implementation of system capabilities.
|
||||
|
||||
// ☐ Rust implementation of capabilities - ?
|
||||
// ☐ Need to implement in kernel entries storage and lookup
|
||||
// ☐ cte = cap table entry (a cap_t plus mdb_node_t)
|
||||
// ☐ mdb = ? (mdb_node_new)
|
||||
// ☐ sameObjectAs()
|
||||
|
||||
// cap_get_capType();//generated
|
||||
// lookupCapAndSlot();
|
||||
|
||||
// cap_domain_cap_new() etc //generated
|
||||
// create_mapped_it_frame_cap(); //vspace.c
|
||||
|
||||
// pptr_of_cap(); -- extracts cap.pptr from cnode_cap
|
||||
// deriveCap();
|
||||
|
||||
// @todo Use bitmatch over cap Type field?
|
||||
// Could be interesting if usable. See https://github.com/porglezomp/bitmatch
|
||||
// Maybe look at https://lib.rs/crates/enumflags2 too
|
||||
|
||||
use {crate::memory::PhysAddr, core::convert::TryFrom, snafu::Snafu};
|
||||
|
||||
mod capnode_cap;
|
||||
pub(crate) mod captable;
|
||||
mod derivation_tree;
|
||||
mod domain_cap;
|
||||
mod endpoint_cap;
|
||||
mod irq_control_cap;
|
||||
mod irq_handler_cap;
|
||||
mod notification_cap;
|
||||
pub(crate) mod null_cap;
|
||||
pub(crate) mod reply_cap;
|
||||
mod resume_cap;
|
||||
mod thread_cap;
|
||||
mod untyped_cap;
|
||||
mod zombie_cap;
|
||||
|
||||
pub use null_cap::NullCapability;
|
||||
pub use reply_cap::ReplyCapability;
|
||||
|
||||
/// Opaque capability object, manipulated by the kernel.
|
||||
pub trait Capability {
|
||||
///
|
||||
/// Is this capability arch-specific?
|
||||
///
|
||||
fn is_arch(&self) -> bool;
|
||||
|
||||
///
|
||||
/// Retrieve this capability as scalar value.
|
||||
///
|
||||
fn as_u128(&self) -> u128;
|
||||
}
|
||||
|
||||
/// Errors in capability operations.
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum CapError {
|
||||
/// Unable to create capability, exact reason TBD.
|
||||
CannotCreate,
|
||||
/// Capability has a type incompatible with the requested operation.
|
||||
InvalidCapabilityType,
|
||||
}
|
||||
|
||||
/// Implement default fns and traits for the capability.
|
||||
#[macro_export]
|
||||
macro_rules! capdef {
|
||||
($name:ident) => {
|
||||
paste! {
|
||||
#[doc = "Wrapper representing `" $name "Capability`."]
|
||||
pub struct [<$name Capability>](LocalRegisterCopy<u128, [<$name Cap>]::Register>);
|
||||
impl [<$name Capability>] {
|
||||
//@todo must be part of trait impl then? See rust-lang/rust#8995
|
||||
// type Type = [<$name Cap>]::Register;
|
||||
}
|
||||
impl Capability for [<$name Capability>] {
|
||||
#[inline]
|
||||
fn as_u128(&self) -> u128 {
|
||||
self.0.into()
|
||||
}
|
||||
#[inline]
|
||||
fn is_arch(&self) -> bool {
|
||||
([<$name Cap>]::Type::Value::value as u8) % 2 != 0
|
||||
}
|
||||
}
|
||||
impl TryFrom<u128> for [<$name Capability>] {
|
||||
type Error = CapError;
|
||||
fn try_from(v: u128) -> Result<[<$name Capability>], Self::Error> {
|
||||
let reg = LocalRegisterCopy::<_, [<$name Cap>]::Register>::new(v);
|
||||
if reg.read([<$name Cap>]::Type) == u128::from([<$name Cap>]::Type::value) {
|
||||
Ok([<$name Capability>](LocalRegisterCopy::new(v)))
|
||||
} else {
|
||||
Err(Self::Error::InvalidCapabilityType)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<[<$name Capability>]> for u128 {
|
||||
#[inline]
|
||||
fn from(v: [<$name Capability>]) -> u128 {
|
||||
v.as_u128()
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
//! @todo replace with Event
|
||||
|
||||
use {
|
||||
super::{CapError, Capability, TryFrom},
|
||||
crate::capdef,
|
||||
paste::paste,
|
||||
register::{register_bitfields, LocalRegisterCopy},
|
||||
};
|
||||
|
||||
//=====================
|
||||
// Cap definition
|
||||
//=====================
|
||||
|
||||
register_bitfields! {
|
||||
u128,
|
||||
NotificationCap [
|
||||
Type OFFSET(0) NUMBITS(6) [
|
||||
value = 6
|
||||
],
|
||||
Badge OFFSET(0) NUMBITS(64) [],
|
||||
CanReceive OFFSET(69) NUMBITS(1) [],
|
||||
CanSend OFFSET(70) NUMBITS(1) [],
|
||||
Ptr OFFSET(80) NUMBITS(48) [],
|
||||
]
|
||||
}
|
||||
|
||||
capdef! { Notification }
|
||||
|
||||
//=====================
|
||||
// Cap implementation
|
||||
//=====================
|
||||
|
||||
// Notifications support NBSend (Signal), Wait and NBWait (Poll) (see COMP9242 slides by Gernot)
|
||||
// Other objects support only Call() (see COMP9242 slides by Gernot)
|
||||
// Appear as (kernel-implemented) servers
|
||||
// • Each has a kernel-defined protocol
|
||||
// • operations encoded in message tag
|
||||
// • parameters passed in message words
|
||||
// • Mostly hidden behind “syscall” wrappers
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
use {
|
||||
super::{CapError, Capability, TryFrom},
|
||||
crate::capdef,
|
||||
paste::paste,
|
||||
register::{register_bitfields, LocalRegisterCopy},
|
||||
};
|
||||
|
||||
//=====================
|
||||
// Cap definition
|
||||
//=====================
|
||||
|
||||
register_bitfields! {
|
||||
u128,
|
||||
NullCap [
|
||||
Type OFFSET(0) NUMBITS(6) [
|
||||
value = 0
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
capdef! { Null }
|
||||
|
||||
//=====================
|
||||
// Cap implementation
|
||||
//=====================
|
||||
|
||||
impl NullCapability {
|
||||
/// Create a Null capability.
|
||||
///
|
||||
/// Such capabilities are invalid and can not be used for anything.
|
||||
pub fn new() -> NullCapability {
|
||||
NullCapability(LocalRegisterCopy::new(u128::from(NullCap::Type::value)))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
use {
|
||||
super::{CapError, Capability, TryFrom},
|
||||
crate::capdef,
|
||||
paste::paste,
|
||||
register::{register_bitfields, LocalRegisterCopy},
|
||||
};
|
||||
|
||||
//=====================
|
||||
// Cap definition
|
||||
//=====================
|
||||
|
||||
register_bitfields! {
|
||||
u128,
|
||||
ReplyCap [
|
||||
Type OFFSET(0) NUMBITS(6) [
|
||||
value = 8
|
||||
],
|
||||
ReplyCanGrant OFFSET(62) NUMBITS(1) [],
|
||||
ReplyMaster OFFSET(63) NUMBITS(1) [],
|
||||
TCBPtr OFFSET(64) NUMBITS(64) [],
|
||||
]
|
||||
}
|
||||
|
||||
capdef! { Reply }
|
||||
|
||||
//=====================
|
||||
// Cap implementation
|
||||
//=====================
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
use {
|
||||
super::{CapError, Capability, TryFrom},
|
||||
crate::capdef,
|
||||
paste::paste,
|
||||
register::{register_bitfields, LocalRegisterCopy},
|
||||
};
|
||||
|
||||
//=====================
|
||||
// Cap definition
|
||||
//=====================
|
||||
|
||||
register_bitfields! {
|
||||
u128,
|
||||
// https://ts.data61.csiro.au/publications/csiro_full_text/Lyons_MAH_18.pdf
|
||||
// Resume objects, modelled after KeyKOS [Bomberger et al.1992], are a new object type
|
||||
// that generalise the “reply capabilities” of baseline seL4. These were capabilities
|
||||
// to virtual objects created by the kernel on-the-fly in seL4’s RPC-style call() operation,
|
||||
// which sends a message to an endpoint and blocks on a reply. The receiver of the message
|
||||
// (i.e. the server) receives the reply capability in a magic “reply slot” in its
|
||||
// capability space. The server replies by invoking that capability. Resume objects
|
||||
// remove the magic by explicitly representing the reply channel (and the SC-donation chain).
|
||||
// They also provide more efficient support for stateful servers that handle concurrent client
|
||||
// sessions.
|
||||
// The introduction of Resume objects requires some changes to the IPC system-call API.
|
||||
// The client-style call() operation is unchanged, but server-side equivalent, ReplyRecv
|
||||
// (previously ReplyWait) replies to a previous request and then blocks on the next one.
|
||||
// It now must provide an explicit Resume capability; on the send phase, that capability
|
||||
// identifies the client and returns the SC if appropriate, on the receive phase it is
|
||||
// populated with new values. The new API makes stateful server implementation more efficient.
|
||||
// In baseline seL4, the server would have to use at least two extra system calls to save the
|
||||
// reply cap and later move it back into its magic slot, removing the magic also removes
|
||||
// the need for the extra system calls.
|
||||
|
||||
ResumeCap [
|
||||
Type OFFSET(0) NUMBITS(6) [
|
||||
value = 22
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
capdef! { Resume }
|
||||
|
||||
//=====================
|
||||
// Cap implementation
|
||||
//=====================
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
use {
|
||||
super::{CapError, Capability, TryFrom},
|
||||
crate::{arch::memory::PhysAddr, capdef},
|
||||
paste::paste,
|
||||
register::{register_bitfields, LocalRegisterCopy},
|
||||
};
|
||||
|
||||
//=====================
|
||||
// Cap definition
|
||||
//=====================
|
||||
|
||||
register_bitfields! {
|
||||
u128,
|
||||
ThreadCap [
|
||||
Type OFFSET(0) NUMBITS(6) [
|
||||
value = 12
|
||||
],
|
||||
TCBPtr OFFSET(64) NUMBITS(48) [],
|
||||
]
|
||||
}
|
||||
|
||||
capdef! { Thread }
|
||||
|
||||
//=====================
|
||||
// Cap implementation
|
||||
//=====================
|
||||
|
||||
impl ThreadCapability {
|
||||
pub(crate) fn ptr(&self) -> PhysAddr {
|
||||
0.into() // @todo
|
||||
}
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
use {
|
||||
super::{CapError, Capability, PhysAddr, TryFrom},
|
||||
crate::capdef,
|
||||
paste::paste,
|
||||
register::{register_bitfields, LocalRegisterCopy},
|
||||
};
|
||||
|
||||
//=====================
|
||||
// Cap definition
|
||||
//=====================
|
||||
|
||||
register_bitfields! {
|
||||
u128,
|
||||
// The combination of freeIndex and blockSize must match up with the
|
||||
// definitions of MIN_SIZE_BITS and MAX_SIZE_BITS
|
||||
// -- https://github.com/seL4/seL4/blob/master/include/object/structures_32.bf#L18
|
||||
//
|
||||
// /* It is assumed that every untyped is within seL4_MinUntypedBits and seL4_MaxUntypedBits
|
||||
// * (inclusive). This means that every untyped stored as seL4_MinUntypedBits
|
||||
// * subtracted from its size before it is stored in capBlockSize, and
|
||||
// * capFreeIndex counts in chunks of size 2^seL4_MinUntypedBits. The seL4_MaxUntypedBits
|
||||
// * is the minimal untyped that can be stored when considering both how
|
||||
// * many bits of capBlockSize there are, and the largest offset that can
|
||||
// * be stored in capFreeIndex */
|
||||
// +#define MAX_FREE_INDEX(sizeBits) (BIT( (sizeBits) - seL4_MinUntypedBits ))
|
||||
// +#define FREE_INDEX_TO_OFFSET(freeIndex) ((freeIndex)<<seL4_MinUntypedBits)
|
||||
// #define GET_FREE_REF(base,freeIndex) ((word_t)(((word_t)(base)) + FREE_INDEX_TO_OFFSET(freeIndex)))
|
||||
// #define GET_FREE_INDEX(base,free) (((word_t)(free) - (word_t)(base))>>seL4_MinUntypedBits)
|
||||
// #define GET_OFFSET_FREE_PTR(base, offset) ((void *)(((word_t)(base)) + (offset)))
|
||||
// +#define OFFSET_TO_FREE_INDEX(offset) ((offset)>>seL4_MinUntypedBits)
|
||||
//
|
||||
// exception_t decodeUntypedInvocation(word_t invLabel, word_t length,
|
||||
// cte_t *slot, cap_t cap,
|
||||
// extra_caps_t excaps, bool_t call,
|
||||
// word_t *buffer);
|
||||
// exception_t invokeUntyped_Retype(cte_t *srcSlot, bool_t reset,
|
||||
// void *retypeBase, object_t newType,
|
||||
// word_t userSize, slot_range_t destSlots,
|
||||
// bool_t deviceMemory);
|
||||
// // -- https://github.com/seL4/seL4/blob/master/src/object/untyped.c#L276
|
||||
// -- https://github.com/seL4/seL4/blob/master/include/object/untyped.h
|
||||
//
|
||||
// /* Untyped size limits */
|
||||
// #define seL4_MinUntypedBits 4
|
||||
// #define seL4_MaxUntypedBits 47
|
||||
// -- https://github.com/seL4/seL4/blob/master/libsel4/sel4_arch_include/aarch64/sel4/sel4_arch/constants.h#L234
|
||||
//
|
||||
// /*
|
||||
// * Determine where in the Untyped region we should start allocating new
|
||||
// * objects.
|
||||
// *
|
||||
// * If we have no children, we can start allocating from the beginning of
|
||||
// * our untyped, regardless of what the "free" value in the cap states.
|
||||
// * (This may happen if all of the objects beneath us got deleted).
|
||||
// *
|
||||
// * If we have children, we just keep allocating from the "free" value
|
||||
// * recorded in the cap.
|
||||
// */
|
||||
// -- https://github.com/seL4/seL4/blob/master/src/object/untyped.c#L175
|
||||
// /*
|
||||
// * Determine the maximum number of objects we can create, and return an
|
||||
// * error if we don't have enough space.
|
||||
// *
|
||||
// * We don't need to worry about alignment in this case, because if anything
|
||||
// * fits, it will also fit aligned up (by packing it on the right hand side
|
||||
// * of the untyped).
|
||||
// */
|
||||
// -- https://github.com/seL4/seL4/blob/master/src/object/untyped.c#L196
|
||||
|
||||
UntypedCap [
|
||||
Type OFFSET(0) NUMBITS(6) [
|
||||
value = 2
|
||||
],
|
||||
/// Index of the first unoccupied byte within this Untyped.
|
||||
/// This index is limited between MIN_UNTYPED_BITS and max bits number in BlockSizePower.
|
||||
/// To occupy less bits, the free index is shifted right by MIN_UNTYPED_BITS.
|
||||
///
|
||||
/// Free index is used only if this untyped has children, which may be occupying only
|
||||
/// part of its space.
|
||||
/// This means an Untyped can be retyped multiple times as long as there is
|
||||
/// free space left in it.
|
||||
FreeIndexShifted OFFSET(0) NUMBITS(48) [],
|
||||
/// Device mapped untypeds cannot be touched by the kernel.
|
||||
IsDevice OFFSET(57) NUMBITS(1) [],
|
||||
/// Untyped is 2**BlockSizePower bytes in size
|
||||
BlockSizePower OFFSET(58) NUMBITS(6) [],
|
||||
/// Physical address of untyped.
|
||||
Ptr OFFSET(80) NUMBITS(48) [],
|
||||
]
|
||||
}
|
||||
|
||||
capdef! { Untyped }
|
||||
|
||||
//=====================
|
||||
// Cap implementation
|
||||
//=====================
|
||||
|
||||
// @todo retyping a device capability requires specifying memory base exactly, can't just pick next frame?
|
||||
|
||||
/// Capability to a block of untyped memory.
|
||||
/// Can be retyped into more usable types.
|
||||
impl UntypedCapability {
|
||||
const MIN_BITS: usize = 4;
|
||||
const MAX_BITS: usize = 47;
|
||||
|
||||
/// This untyped belongs to device memory (will not be zeroed on allocation).
|
||||
pub fn is_device(&self) -> bool {
|
||||
self.0.read(UntypedCap::IsDevice) == 1
|
||||
}
|
||||
|
||||
/// Return untyped block size in bytes.
|
||||
pub fn block_size(&self) -> usize {
|
||||
1 << self.0.read(UntypedCap::BlockSizePower)
|
||||
}
|
||||
// FreeIndex OFFSET(0) NUMBITS(48) [],
|
||||
/// Return free area offset in this block in bytes.
|
||||
pub fn free_area_offset(&self) -> usize {
|
||||
use core::convert::TryInto;
|
||||
Self::free_index_to_offset(
|
||||
self.0
|
||||
.read(UntypedCap::FreeIndexShifted)
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Return start address of this untyped block.
|
||||
pub fn base(&self) -> PhysAddr {
|
||||
(self.0.read(UntypedCap::Ptr) as u64).into() // @todo implement TryFrom<u128> for PhysAddr
|
||||
}
|
||||
|
||||
// #define MAX_FREE_INDEX(sizeBits) (BIT( (sizeBits) - seL4_MinUntypedBits ))
|
||||
/// Calculate maximum free index value based on allowed size bits.
|
||||
pub fn max_free_index_from_bits(size_bits: usize) -> usize {
|
||||
assert!(size_bits >= Self::MIN_BITS);
|
||||
assert!(size_bits <= Self::MAX_BITS);
|
||||
1 << (size_bits - Self::MIN_BITS)
|
||||
}
|
||||
|
||||
// #define FREE_INDEX_TO_OFFSET(freeIndex) ((freeIndex)<<seL4_MinUntypedBits)
|
||||
/// Convert free index to byte offset.
|
||||
fn free_index_to_offset(index: usize) -> usize {
|
||||
index << Self::MIN_BITS
|
||||
}
|
||||
|
||||
// #define OFFSET_TO_FREE_INDEX(offset) ((offset)>>seL4_MinUntypedBits)
|
||||
/// Convert byte offset to free index.
|
||||
/// @todo Check proper offset alignment!
|
||||
fn offset_to_free_index(offset: usize) -> usize {
|
||||
offset >> Self::MIN_BITS
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
use {
|
||||
super::{CapError, Capability, TryFrom},
|
||||
crate::capdef,
|
||||
paste::paste,
|
||||
register::{register_bitfields, LocalRegisterCopy},
|
||||
};
|
||||
|
||||
//=====================
|
||||
// Cap definition
|
||||
//=====================
|
||||
|
||||
register_bitfields! {
|
||||
u128,
|
||||
ZombieCap [
|
||||
Type OFFSET(0) NUMBITS(6) [
|
||||
value = 18
|
||||
],
|
||||
ZombieType OFFSET(58) NUMBITS(6) [],
|
||||
ZombieID OFFSET(64) NUMBITS(64) [],
|
||||
]
|
||||
}
|
||||
|
||||
capdef! { Zombie }
|
||||
|
||||
//=====================
|
||||
// Cap implementation
|
||||
//=====================
|
|
@ -1,54 +1,31 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
use {
|
||||
crate::{devices::SerialOps, platform},
|
||||
core::fmt,
|
||||
};
|
||||
use crate::platform;
|
||||
use core::fmt;
|
||||
|
||||
/// A trait that must be implemented by devices that are candidates for the
|
||||
/// global console.
|
||||
#[allow(unused_variables)]
|
||||
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;
|
||||
pub trait ConsoleOps {
|
||||
fn putc(&self, c: char) {}
|
||||
fn puts(&self, string: &str) {}
|
||||
fn getc(&self) -> char {
|
||||
' '
|
||||
}
|
||||
fn flush(&self) {}
|
||||
}
|
||||
|
||||
/// A dummy console that just ignores its inputs.
|
||||
pub struct NullConsole;
|
||||
|
||||
impl Drop for NullConsole {
|
||||
fn drop(&mut self) {}
|
||||
}
|
||||
|
||||
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) {}
|
||||
}
|
||||
impl ConsoleOps for NullConsole {}
|
||||
|
||||
/// Possible outputs which the console can store.
|
||||
pub enum Output {
|
||||
|
@ -89,6 +66,7 @@ impl Console {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn current_ptr(&self) -> &dyn ConsoleOps {
|
||||
match &self.output {
|
||||
Output::None(i) => i,
|
||||
|
@ -107,15 +85,15 @@ impl Console {
|
|||
|
||||
/// A command prompt.
|
||||
pub fn command_prompt<'a>(&self, buf: &'a mut [u8]) -> &'a [u8] {
|
||||
self.write_string("\n$> ");
|
||||
self.puts("\n$> ");
|
||||
|
||||
let mut i = 0;
|
||||
let mut input;
|
||||
loop {
|
||||
input = self.read_char();
|
||||
input = self.getc();
|
||||
|
||||
if input == '\n' {
|
||||
self.write_char('\n'); // do \r\n output
|
||||
self.puts("\n"); // do \r\n output
|
||||
return &buf[..i];
|
||||
} else {
|
||||
if i < buf.len() {
|
||||
|
@ -125,7 +103,7 @@ impl Console {
|
|||
return &buf[..i];
|
||||
}
|
||||
|
||||
self.write_char(input);
|
||||
self.putc(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -137,32 +115,21 @@ impl Drop for Console {
|
|||
|
||||
/// Dispatch the respective function to the currently stored output device.
|
||||
impl ConsoleOps for Console {
|
||||
fn write_char(&self, c: char) {
|
||||
self.current_ptr().write_char(c);
|
||||
fn putc(&self, c: char) {
|
||||
self.current_ptr().putc(c);
|
||||
}
|
||||
|
||||
fn write_string(&self, string: &str) {
|
||||
self.current_ptr().write_string(string);
|
||||
fn puts(&self, string: &str) {
|
||||
self.current_ptr().puts(string);
|
||||
}
|
||||
|
||||
fn read_char(&self) -> char {
|
||||
self.current_ptr().read_char()
|
||||
fn getc(&self) -> char {
|
||||
self.current_ptr().getc()
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -171,7 +138,8 @@ impl SerialOps for Console {
|
|||
/// See src/macros.rs.
|
||||
impl fmt::Write for Console {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
self.current_ptr().write_string(s);
|
||||
self.current_ptr().puts(s);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
pub mod console;
|
||||
|
||||
pub use console::{Console, ConsoleOps};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue