Compare commits

...

39 Commits

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

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

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

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

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

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

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

View File

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

View File

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

844
Cargo.lock generated
View File

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

View File

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

View File

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

View File

@ -17,14 +17,22 @@ 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-8.2.1-aarhc64/bin/aarch64-linux-elf-gdb", condition = { env_not_set = ["GDB"] } }
GDB = { value = "/usr/local/opt/gdb/HEAD-a2c58332-aarch64/bin/aarch64-unknown-elf-gdb", condition = { env_not_set = ["GDB"] } }
# OpenOCD with JLink support 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"] } }
# 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"] } }
# Mounted sdcard partition path
VOLUME = { value = "/Volumes/BOOT", condition = { env_not_set = ["VOLUME"] } }
@ -34,10 +42,12 @@ VOLUME = { value = "/Volumes/BOOT", condition = { env_not_set = ["VOLUME"] } }
#
CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
RUST_LIBS = "-Z build-std=compiler_builtins,core,alloc -Z build-std-features=compiler-builtins-mem"
TARGET_JSON = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/targets/${TARGET}.json"
PLATFORM_TARGET="--target=${TARGET_JSON} --features=${TARGET_FEATURES} ${RUST_LIBS}"
DEVICE_FEATURES = "noserial"
QEMU_FEATURES = "qemu"
QEMU_FEATURES = "qemu,rpi3"
OBJCOPY = "rust-objcopy" # Part of `cargo objcopy` in cargo-binutils
OBJCOPY_PARAMS = "--strip-all -O binary"
@ -50,9 +60,10 @@ QEMU_CONTAINER_CMD = "qemu-system-aarch64"
#
# Could additionally use -nographic to disable GUI -- this shall be useful for automated tests.
#
# -d in_asm,unimp,int
QEMU_OPTS = "-M raspi3 -d int -semihosting"
QEMU_SERIAL_OPTS = "-serial null -serial stdio"
# QEMU has renamed the RasPi machines since version 6.2.0, use just `raspi3` for previous versions.
QEMU_OPTS = "-M ${QEMU_MACHINE} -d int -semihosting"
QEMU_DISASM_OPTS = "-d in_asm,unimp,int"
QEMU_SERIAL_OPTS = "-serial pty -serial stdio"
QEMU_TESTS_OPTS = "-nographic"
# For gdb connection:
# - if this is set, MUST have gdb attached for SYS_WRITE0 to work, otherwise QEMU will crash.
@ -61,8 +72,11 @@ QEMU_GDB_OPTS = "-gdb tcp::5555 -S"
GDB_CONNECT_FILE = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/${TARGET}/gdb-connect"
KERNEL_ELF = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/kernel8"
KERNEL_BIN = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/kernel8.img"
KERNEL_ELF = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/${TARGET}/release/nucleus"
KERNEL_BIN = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/nucleus.bin"
CHAINBOOT_SERIAL = "/dev/tty.SLAB_USBtoUART"
CHAINBOOT_BAUD = 115200
[tasks.default]
alias = "all"
@ -73,3 +87,113 @@ dependencies = ["kernel-binary"]
[tasks.modules]
command = "cargo"
args = ["modules", "tree"]
[tasks.build]
env = { "TARGET_FEATURES" = "${TARGET_BOARD}" }
command = "cargo"
args = ["build", "@@split(PLATFORM_TARGET, )", "--release"]
[tasks.build-qemu]
env = { "TARGET_FEATURES" = "${QEMU_FEATURES}" }
command = "cargo"
args = ["build", "@@split(PLATFORM_TARGET, )", "--release"]
[tasks.qemu-runner]
dependencies = ["build-qemu", "kernel-binary"]
env = { "TARGET_FEATURES" = "${QEMU_FEATURES}" }
script = [
"echo Run QEMU ${QEMU_OPTS} ${QEMU_RUNNER_OPTS} with ${KERNEL_BIN}",
"${QEMU} ${QEMU_OPTS} ${QEMU_RUNNER_OPTS} -dtb ${TARGET_DTB} -kernel ${KERNEL_BIN}"
]
[tasks.expand]
env = { "TARGET_FEATURES" = "" }
command = "cargo"
args = ["expand", "@@split(PLATFORM_TARGET, )", "--release"]
[tasks.test]
env = { "TARGET_FEATURES" = "${QEMU_FEATURES}" }
command = "cargo"
args = ["test", "@@split(PLATFORM_TARGET, )"]
[tasks.docs]
env = { "TARGET_FEATURES" = "" }
command = "cargo"
args = ["doc", "--open", "--no-deps", "@@split(PLATFORM_TARGET, )"]
[tasks.clippy]
env = { "TARGET_FEATURES" = "rpi3", "CLIPPY_FEATURES" = { value = "--features=${CLIPPY_FEATURES}", condition = { env_set = ["CLIPPY_FEATURES"] } } }
command = "cargo"
args = ["clippy", "@@split(PLATFORM_TARGET, )", "@@remove-empty(CLIPPY_FEATURES)", "--", "-D", "warnings"]
# These tasks are written in cargo-make's own script to make it portable across platforms (no `basename` on Windows)
[tasks.custom-binary]
env = { "BINARY_FILE" = "${BINARY_FILE}" }
script_runner = "@duckscript"
script = [
'''
binaryFile = basename ${BINARY_FILE}
outElf = set ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/${binaryFile}.elf
outBin = set ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/${binaryFile}.bin
cp ${BINARY_FILE} ${outElf}
exec --fail-on-error ${OBJCOPY} %{OBJCOPY_PARAMS} ${BINARY_FILE} ${outBin}
echo Copied ${binaryFile} to ${outElf}
echo Converted ${binaryFile} to ${outBin}
'''
]
install_crate = { crate_name = "cargo-binutils", binary = "rust-objcopy", test_arg = ["--help"] }
[tasks.test-binary]
env = { "BINARY_FILE" = "${CARGO_MAKE_TASK_ARGS}" }
run_task = "custom-binary"
[tasks.test-runner]
dependencies = ["test-binary"]
script_runner = "@duckscript"
script = [
'''
binaryFile = basename ${CARGO_MAKE_TASK_ARGS}
exec --fail-on-error ${QEMU} %{QEMU_OPTS} %{QEMU_TESTS_OPTS} -dtb ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/targets/bcm2710-rpi-3-b-plus.dtb -kernel ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/${binaryFile}.bin
'''
]
[tasks.gdb-config]
script_runner = "@duckscript"
script = [
'''
writefile ${GDB_CONNECT_FILE} "target extended-remote :5555\n"
appendfile ${GDB_CONNECT_FILE} "break 0x80000\n"
'''
]
[tasks.zellij-config]
dependencies = ["build-qemu", "kernel-binary"]
script_runner = "@duckscript"
env = { "ZELLIJ_CONFIG_FILE" = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/emulation/zellij-config.sh" }
script = [
'''
writefile ${ZELLIJ_CONFIG_FILE} "QEMU=${QEMU}\n"
appendfile ${ZELLIJ_CONFIG_FILE} "QEMU_OPTS=\"${QEMU_OPTS}\"\n"
appendfile ${ZELLIJ_CONFIG_FILE} "QEMU_RUNNER_OPTS=${QEMU_RUNNER_OPTS}\n"
appendfile ${ZELLIJ_CONFIG_FILE} "CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY=${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}\n"
appendfile ${ZELLIJ_CONFIG_FILE} "TARGET_DTB=${TARGET_DTB}\n"
appendfile ${ZELLIJ_CONFIG_FILE} "KERNEL_BIN=${KERNEL_BIN}\n"
'''
]
install_crate = { crate_name = "zellij", binary = "zellij", test_arg = ["--help"] }
[tasks.openocd]
script = [
"${OPENOCD} -f interface/jlink.cfg -f ../ocd/${TARGET_BOARD}_target.cfg"
]
[tasks.sdeject]
dependencies = ["sdcard"]
script = [
"diskutil unmount ${VOLUME}"
]
[tasks.chainboot]
dependencies = ["build", "kernel-binary"]
command = "echo"
args = ["\n***===***\n", "Run the following command in your terminal:\n", " ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/debug/chainofcommand ${CHAINBOOT_SERIAL} ${CHAINBOOT_BAUD} --kernel ${KERNEL_BIN}\n", "***===***\n\n"]

View File

@ -2,7 +2,7 @@
## About kernel
Vesper is a capability-based single-address-space exokernel, it tries to remain small and secure. To achieve this, kernel functionality is extremely limited - it provides only address space isolation and IPC, after bootup kernel does not allocate any memory itself.
Vesper is a capability-based single-address-space nanokernel, it tries to remain small and secure. To achieve this, kernel functionality is extremely limited - it provides only address space isolation and IPC, after bootup kernel does not allocate any memory itself.
Exokernel's distinctive trait is that it provides mechanisms but not policies. Vesper tries to move as many policy decisions as possible to the library OS.
@ -94,6 +94,14 @@ You need to have [Hopper](https://hopperapp.com) and hopperv4 cli helper install
just disasm
```
### To see other available commands
```
just
```
It will list all just commands with their short descriptions.
## Development flow
`mainline`, `develop` and `released` branches:

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

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

View File

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

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

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

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

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

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

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

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

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

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

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

View File

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

View File

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

View File

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

View File

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

BIN
doc/bcm2711-peripherals.pdf Normal file

Binary file not shown.

1
emulation/.gitignore vendored Normal file
View File

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

View File

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

39
emulation/layout.zellij Normal file
View File

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

35
emulation/qemu_multi_uart.sh Executable file
View File

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

View File

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

40
machine/Cargo.toml Normal file
View File

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

View File

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

View File

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

View File

@ -7,6 +7,4 @@ mod asid;
mod phys_addr;
mod virt_addr;
pub use asid::*;
pub use phys_addr::*;
pub use virt_addr::*;
pub use {asid::*, phys_addr::*, virt_addr::*};

View File

@ -73,6 +73,7 @@ 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>,
@ -83,6 +84,7 @@ 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>,

View File

@ -108,6 +108,7 @@ 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>,
@ -118,6 +119,7 @@ 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>,

View File

@ -13,18 +13,12 @@
use {
crate::{
arch::aarch64::memory::{
get_virt_addr_properties, AttributeFields, /*FrameAllocator, PhysAddr, VirtAddr,*/
},
arch::aarch64::memory::{get_virt_addr_properties, AttributeFields},
println,
},
// bitflags::bitflags,
core::{
// convert::TryInto,
// fmt,
marker::PhantomData,
ops::{Index, IndexMut},
// ptr::Unique,
},
cortex_a::{
asm::barrier,
@ -35,12 +29,10 @@ use {
interfaces::{ReadWriteable, Readable, Writeable},
register_bitfields,
},
// ux::*,
};
mod mair {
use cortex_a::registers::MAIR_EL1;
use tock_registers::interfaces::Writeable;
use {cortex_a::registers::MAIR_EL1, tock_registers::interfaces::Writeable};
/// Setup function for the MAIR_EL1 register.
pub fn set_up() {

View File

@ -13,8 +13,7 @@ use {
mod addr;
pub mod mmu;
pub use addr::PhysAddr;
pub use addr::VirtAddr;
pub use addr::{PhysAddr, VirtAddr};
// aarch64 granules and page sizes howto:
// https://stackoverflow.com/questions/34269185/simultaneous-existence-of-different-sized-pages-on-aarch64

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

6
nucleus/build.rs Normal file
View File

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

View File

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

View File

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

View File

@ -1,5 +0,0 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
pub mod rpi3;

View File

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

View File

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

View File

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

View File

@ -1,32 +1,34 @@
# Broadcom 2837 on Raspberry Pi 3 as JTAG target
# Broadcom bcm2837 on Raspberry Pi 3 as JTAG target
# From https://www.suse.com/c/debugging-raspberry-pi-3-with-jtag/
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
echo "Booting JTAG for Raspberry Pi 3"
if { [info exists CHIPNAME] } {
set _CHIPNAME $CHIPNAME
} else {
set _CHIPNAME rpi3
set _CHIPNAME bcm2837
}
#
# 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
@ -35,17 +37,28 @@ 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 \
-ctibase [lindex $CTIBASE $_core]
-baseaddr [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

64
ocd/rpi4_target.cfg Normal file
View File

@ -0,0 +1,64 @@
# Broadcom bcm2711 on Raspberry Pi 4 as JTAG target
# From https://gist.github.com/tnishinaga/46a3380e1f47f5e892bbb74e55b3cf3e
# See also https://xihan94.gitbook.io/raspberry-pi/raspberry-pi-4-bringup
echo "Booting JTAG for Raspberry Pi 4"
if { [info exists CHIPNAME] } {
set _CHIPNAME $CHIPNAME
} else {
set _CHIPNAME bcm2711
}
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 -expected-id $_DAP_TAPID
dap create $_CHIPNAME.dap -chain-position $_CHIPNAME.tap
set _TARGETNAME $_CHIPNAME.a72
set _CTINAME $_CHIPNAME.cti
set DBGBASE {0x80410000 0x80510000 0x80610000 0x80710000}
set CTIBASE {0x80420000 0x80520000 0x80620000 0x80720000}
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]
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
rustfmt.toml Normal file
View File

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

View File

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

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

Binary file not shown.

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

Binary file not shown.