Compare commits
194 Commits
develop
...
first-work
Author | SHA1 | Date |
---|---|---|
|
0b86eeaf90 | |
|
e324795ab9 | |
|
4bbbddef6e | |
|
3644bcc18f | |
|
53c6139f20 | |
|
dba700ad7a | |
|
e7526e7fc3 | |
|
7ea82d90d7 | |
|
006abc39dd | |
|
37868c3101 | |
|
70ec2ab852 | |
|
925fedd351 | |
|
e5ab830594 | |
|
0cbda1201a | |
|
d50c680576 | |
|
0f6e37a29b | |
|
c75da94619 | |
|
6691ebb89e | |
|
8967d4a518 | |
|
211062405b | |
|
d73c084bb7 | |
|
5cb811e525 | |
|
b3230e1c18 | |
|
b4871d52f3 | |
|
d588405d28 | |
|
bf6d3f7f16 | |
|
76dca06039 | |
|
47ff40167d | |
|
791a61ffed | |
|
9f27449cd6 | |
|
d4f8eab75b | |
|
d5c8b3c98f | |
|
f780ab3ff6 | |
|
388d395abe | |
|
703b9fb4db | |
|
39ae164aec | |
|
3923e7c838 | |
|
673edf2261 | |
|
2e4bc42cd5 | |
|
837268a712 | |
|
2a8dc7eb33 | |
|
963a1a2bbf | |
|
53f2596665 | |
|
8325ce5c28 | |
|
b7812aa5a2 | |
|
80fb9ede35 | |
|
21303497cd | |
|
23d778de01 | |
|
ec5606c16a | |
|
ce05c157a7 | |
|
b2b7de2c96 | |
|
239d137b1c | |
|
97ccf3ceb5 | |
|
593544a6ec | |
|
1ba6c3f4d7 | |
|
63d2133a1e | |
|
31d87c75c4 | |
|
771319ed2e | |
|
9c220f9bb2 | |
|
94324f3f8c | |
|
bd68e95e27 | |
|
98be0a304c | |
|
a34153028c | |
|
57aa6a840c | |
|
96fad1e46d | |
|
aae3d6cfc0 | |
|
96727a2a24 | |
|
e926f6c3af | |
|
95b9ab5e40 | |
|
1ea5e0a08b | |
|
f42a3879cc | |
|
a073ceb272 | |
|
15c55dc0cd | |
|
9a91382a78 | |
|
8bbde88bfb | |
|
0680986533 | |
|
e1da59220b | |
|
a9d25b74e9 | |
|
b7d2fdf0b2 | |
|
551f081892 | |
|
db5948075e | |
|
1e8735c1f6 | |
|
75295e0b0d | |
|
ee3ed7dc79 | |
|
81b6ee5c5d | |
|
c25a2b887c | |
|
ae7e74c8ad | |
|
e90b78c8bf | |
|
fb141be9d5 | |
|
d8b50411fc | |
|
58be472573 | |
|
12b01105f4 | |
|
3e97a34223 | |
|
53b967c826 | |
|
c4cb106f79 | |
|
4858ae0159 | |
|
6f9e51cd7b | |
|
fa5dccb89c | |
|
4090f055f2 | |
|
5f6ad0ae7e | |
|
b7275ecc18 | |
|
21d0bdec6b | |
|
a0207e2710 | |
|
0ecbb7d23b | |
|
bb7ee23bc2 | |
|
7faf26cbac | |
|
25f069d54c | |
|
6f419b22f7 | |
|
6d3d1de559 | |
|
9bd14aadf2 | |
|
9c2701f434 | |
|
90fbfd6d7e | |
|
c4f255ff27 | |
|
3a8d99b08e | |
|
a04f2e4f17 | |
|
abafec12c6 | |
|
79218ff2dd | |
|
d04c844bbb | |
|
12765de456 | |
|
5666fcbec9 | |
|
6cf5551efb | |
|
93ced19f29 | |
|
ace49ed1ca | |
|
befa1c5db8 | |
|
a2ea1f59a6 | |
|
aac393381d | |
|
c5d99b3b82 | |
|
401dfdd06d | |
|
87b3007c10 | |
|
d92dea35af | |
|
6018ac2b3d | |
|
2abf70fe90 | |
|
ef2796a583 | |
|
e75770a335 | |
|
ed9cbe5d4e | |
|
98acc888bd | |
|
7630f5a558 | |
|
73d852f57c | |
|
1b5a1a87aa | |
|
2308d7118e | |
|
6f9c5d9546 | |
|
9765f3e081 | |
|
f39ff81922 | |
|
fac5bf1f50 | |
|
64b2afaaa6 | |
|
351d77d4dc | |
|
7974c645a2 | |
|
aaabaa6369 | |
|
d4c3db81bc | |
|
f0c022deb0 | |
|
f8fe6cf9e5 | |
|
d931e70b89 | |
|
3299b39ee3 | |
|
bc63f7c068 | |
|
3cff2d9dac | |
|
32753f47bf | |
|
1dc4e2483a | |
|
41a9187056 | |
|
b96ed7c328 | |
|
185f6916ac | |
|
cb8932d68e | |
|
c2bdfafb43 | |
|
3831a411b9 | |
|
67b3b95d9a | |
|
e81c6ddf84 | |
|
b0ed5d0c17 | |
|
9e8deba0e1 | |
|
1e37fc791c | |
|
dc3369a8c2 | |
|
f079b380ee | |
|
28ca96da7b | |
|
5d78f15823 | |
|
26652f30dc | |
|
37c942626f | |
|
be7bfd842b | |
|
1c0243ef58 | |
|
d43a4d5b08 | |
|
a424300aa2 | |
|
94f61b6a8a | |
|
4367a53b91 | |
|
3ec600c6bb | |
|
d34b214ed0 | |
|
9d3ee6ab06 | |
|
1be4059fd0 | |
|
dfcf7093b9 | |
|
0221f3128d | |
|
72d4e52009 | |
|
0ecdb774f5 | |
|
59c079dbbd | |
|
3a8043a617 | |
|
80ab7d9fa6 | |
|
60d61d4e7a | |
|
072e0a05aa | |
|
3cb7dc3025 |
|
@ -0,0 +1,5 @@
|
||||||
|
[target.aarch64-vesper-metta]
|
||||||
|
rustflags = [
|
||||||
|
"-C", "target-feature=-fp-armv8",
|
||||||
|
"-C", "target-cpu=cortex-a53",
|
||||||
|
]
|
|
@ -0,0 +1,8 @@
|
||||||
|
target/
|
||||||
|
*.lock
|
||||||
|
.idea/
|
||||||
|
.ninja_*
|
||||||
|
kernel8.img
|
||||||
|
kernel8
|
||||||
|
gdb-connect
|
||||||
|
.gdb_history
|
|
@ -0,0 +1,51 @@
|
||||||
|
[package]
|
||||||
|
name = "vesper"
|
||||||
|
version = "1.0.0"
|
||||||
|
authors = ["Berkus Decker <berkus+cargo@metta.systems>"]
|
||||||
|
description = "Vesper exokernel"
|
||||||
|
documentation = "https://docs.metta.systems/vesper"
|
||||||
|
homepage = "https://github.com/metta-systems/vesper"
|
||||||
|
repository = "https://github.com/metta-systems/vesper"
|
||||||
|
readme = "README.md"
|
||||||
|
license = "BSL-1.0"
|
||||||
|
categories = ["no-std", "embedded", "os"]
|
||||||
|
publish = false
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[package.metadata.cargo-xbuild]
|
||||||
|
memcpy = true
|
||||||
|
|
||||||
|
[package.metadata.bootimage]
|
||||||
|
default-target = "targets/aarch64-vesper-metta.json"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
unstable = []
|
||||||
|
realtime = []
|
||||||
|
noserial = []
|
||||||
|
jlink = [] #'jlink_rtt'
|
||||||
|
|
||||||
|
#[lib]
|
||||||
|
#name = "nucleus"
|
||||||
|
#path = "src/lib.rs"
|
||||||
|
#crate-type = ["staticlib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
r0 = "0.2.2"
|
||||||
|
rlibc = "1.0.0"
|
||||||
|
panic-abort = "0.3.1"
|
||||||
|
bitflags = "1.0.1"
|
||||||
|
register = "0.3.2"
|
||||||
|
cortex-a = "2.4"
|
||||||
|
#embedded-serial = "0.5.0"
|
||||||
|
# jlink_rtt = { version = "0.1.0", optional = true }
|
||||||
|
static_assertions = { version = "0.3.1", features = ["nightly"] }
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
panic = "abort" # @todo try panic_rtt when feature jlink
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
panic = "abort"
|
||||||
|
debug = true
|
||||||
|
lto = true
|
||||||
|
# https://github.com/rust-lang/cargo/pull/6564#issuecomment-457971499
|
||||||
|
incremental = false
|
|
@ -0,0 +1,23 @@
|
||||||
|
Boost Software License - Version 1.0 - August 17th, 2003
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person or organization
|
||||||
|
obtaining a copy of the software and accompanying documentation covered by
|
||||||
|
this license (the "Software") to use, reproduce, display, distribute,
|
||||||
|
execute, and transmit the Software, and to prepare derivative works of the
|
||||||
|
Software, and to permit third-parties to whom the Software is furnished to
|
||||||
|
do so, all subject to the following:
|
||||||
|
|
||||||
|
The copyright notices in the Software and this entire statement, including
|
||||||
|
the above license grant, this restriction and the following disclaimer,
|
||||||
|
must be included in all copies of the Software, in whole or in part, and
|
||||||
|
all derivative works of the Software, unless such copies or derivative
|
||||||
|
works are solely in the form of machine-executable object code generated by
|
||||||
|
a source language processor.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||||
|
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||||
|
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,104 @@
|
||||||
|
#
|
||||||
|
# MIT License
|
||||||
|
#
|
||||||
|
# Copyright (c) 2018 Andre Richter <andre.o.richter@gmail.com>
|
||||||
|
# Copyright (c) 2019 Berkus Decker <berkus+github@metta.systems>
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in all
|
||||||
|
# copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
# SOFTWARE.
|
||||||
|
#
|
||||||
|
|
||||||
|
TARGET = aarch64-vesper-metta
|
||||||
|
TARGET_JSON = targets/$(TARGET).json
|
||||||
|
|
||||||
|
SOURCES = $(shell find src -name "*.rs" -o -name "*.S") $(shell find linker -name "*.ld")
|
||||||
|
|
||||||
|
DEVICE_FEATURES = --features "noserial"
|
||||||
|
QEMU_FEATURES =
|
||||||
|
|
||||||
|
OBJCOPY = cargo objcopy --
|
||||||
|
OBJCOPY_PARAMS = --strip-all -O binary
|
||||||
|
|
||||||
|
UTILS_CONTAINER = andrerichter/raspi3-utils
|
||||||
|
DOCKER_CMD = docker run -it --rm -v $(shell pwd):/work -w /work -p 5900:5900
|
||||||
|
QEMU_CMD = qemu-system-aarch64
|
||||||
|
|
||||||
|
# -d in_asm,unimp,int -S
|
||||||
|
QEMU_OPTS = -M raspi3 -d int
|
||||||
|
QEMU_SERIAL = -serial null -serial stdio
|
||||||
|
QEMU = /usr/local/Cellar/qemu/HEAD-3365de01b5-custom/bin/qemu-system-aarch64
|
||||||
|
|
||||||
|
GDB = /usr/local/opt/gdb-8.2.1-aarhc64/bin/aarch64-linux-elf-gdb
|
||||||
|
|
||||||
|
OPENOCD = /usr/local/openocd-aeb7b327-rtt/bin/openocd
|
||||||
|
|
||||||
|
.PHONY: all qemu clippy clean objdump nm
|
||||||
|
|
||||||
|
all: kernel8.img
|
||||||
|
|
||||||
|
target/$(TARGET)/release/vesper: $(SOURCES)
|
||||||
|
cargo xbuild --target=$(TARGET_JSON) --release --features="jlink"
|
||||||
|
|
||||||
|
kernel8.img: target/$(TARGET)/release/vesper $(SOURCES)
|
||||||
|
cp $< ./kernel8
|
||||||
|
$(OBJCOPY) $(OBJCOPY_PARAMS) $< kernel8.img
|
||||||
|
|
||||||
|
docker_qemu: all
|
||||||
|
$(DOCKER_CMD) $(UTILS_CONTAINER) $(QEMU_CMD) $(QEMU_OPTS) -serial stdio -kernel kernel8.img
|
||||||
|
|
||||||
|
qemu: all
|
||||||
|
$(QEMU) $(QEMU_OPTS) $(QEMU_SERIAL) -kernel kernel8.img
|
||||||
|
|
||||||
|
sdcard: all
|
||||||
|
cp kernel8.img /Volumes/BOOT/
|
||||||
|
|
||||||
|
sdeject: sdcard
|
||||||
|
diskutil unmount /Volumes/BOOT/
|
||||||
|
|
||||||
|
clippy:
|
||||||
|
cargo xclippy --target=$(TARGET_JSON)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
cargo clean
|
||||||
|
|
||||||
|
objdump:
|
||||||
|
cargo objdump --target $(TARGET_JSON) -- -disassemble -print-imm-hex kernel8
|
||||||
|
|
||||||
|
nm:
|
||||||
|
cargo nm --target $(TARGET_JSON) -- kernel8 | sort
|
||||||
|
|
||||||
|
hopper: all
|
||||||
|
hopperv4 -l RAW --base-address 0x80000 --entrypoint 0x80000 --file-offset 0 --aarch64 -e kernel8.img
|
||||||
|
|
||||||
|
openocd:
|
||||||
|
$(OPENOCD) -f interface/jlink.cfg -f ./doc/rpi3_jlink_suse.cfg
|
||||||
|
|
||||||
|
openocd_naotako:
|
||||||
|
$(OPENOCD) -f interface/jlink.cfg -f ./doc/rpi3_jlink_naotako.cfg
|
||||||
|
|
||||||
|
gdb: kernel8.img
|
||||||
|
make nm | grep _SEGGER_RTT | awk '{print $$1}' | ./make-gdb-connect.sh
|
||||||
|
env RUST_GDB=$(GDB) rust-gdb -x gdb-connect kernel8
|
||||||
|
|
||||||
|
gdbdash: kernel8.img
|
||||||
|
make nm | grep _SEGGER_RTT | awk '{print $$1}' | ./make-gdb-connect.sh
|
||||||
|
env RUST_GDB=$(GDB) rust-gdb -x gdb-connect -x ~/.gdbinit_dashboard kernel8
|
||||||
|
|
||||||
|
gdbgui:
|
||||||
|
gdbgui -g $(GDB) --gdb-args='--init-eval-command="set startup-with-shell off"'
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
# Vesper
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
* Single-address-space is a mechanism for providing pointer transparency between processes. Sharing a buffer is nearly as simple as passing out its address. Even though single-address-space is not a basic requirement and may be seen as an imposed policy decision, it does provide mechanism for efficient data passing between protection domains. This is important for modern media-rich communications.
|
||||||
|
|
||||||
|
* IPC is a mechanism providing secure interaction between processes.
|
||||||
|
|
||||||
|
* Capabilities are a mechanism providing access rights control and universal authority delegation for OS objects.
|
||||||
|
|
||||||
|
* Interrupts come from hardware, usually in privileged mode and kernel is responsible for translating them into invocations of the device drivers' handlers.
|
||||||
|
|
||||||
|
### Scheduling
|
||||||
|
|
||||||
|
Scheduling can be viewed as the process of multiplexing the CPU resource between computational tasks. The schedulable entity of an operating system often places constraints both on the scheduling algorithms which may be employed and the functionality provided to the application. The recent gain in popularity of multi-threaded programming due to languages such as Modula-3 [Nelson 91] has led many operating system designers to provide kernel-level thread support mechanisms [Accetta 86, Rozier 90]. The kernel therefore schedules threads rather than processes. Whilst this reduces the functionality required in applications and usually results in more efficient processor context-switches, the necessary thread scheduling policy decisions must also be migrated into the kernel. As pointed out in [Barham 96], this is highly undesirable.
|
||||||
|
|
||||||
|
The desire to move such decisions out of the kernel make interesting variants where actual scheduling is performed by the user-level domain scheduler upon an **upcall** from the kernel. TBD
|
||||||
|
|
||||||
|
## Real Time
|
||||||
|
|
||||||
|
At the moment this is not a real-time kernel. It has a small number of potentially long-running kernel operations that are not preemptable (e.g., endpoint deletion and recycling, scheduling, frame and CNode initialisation). This may change in future versions.
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
Vesper has been influenced by the kernels in L4 family, notably seL4. Fawn and Nemesis provided inspiration for single-address-space and vertical integration of the applications.
|
||||||
|
|
||||||
|
## Build instructions
|
||||||
|
|
||||||
|
Use rustc nightly 2018-04-01 or later because of [bugs fixed](https://github.com/rust-lang/rust/issues/48884).
|
||||||
|
|
||||||
|
```
|
||||||
|
make qemu
|
||||||
|
```
|
||||||
|
|
||||||
|
Will run `cargo xbuild` to create kernel and run it in qemu emulator.
|
||||||
|
|
||||||
|
```
|
||||||
|
make device
|
||||||
|
```
|
||||||
|
|
||||||
|
Will build kernel, and copy it to sdcard at /Volumes/BOOT/
|
||||||
|
|
||||||
|
## OSdev help
|
||||||
|
|
||||||
|
Based on [Raspi3 tutorials by Andre Richter](https://github.com/rust-embedded/rust-raspi3-tutorial/blob/master/05_uart0/src/uart.rs),
|
||||||
|
which are in turn based on [Raspi3 tutorials by bzt](https://github.com/bztsrc/raspi3-tutorial/).
|
||||||
|
Various references from [OSDev Wiki](https://wiki.osdev.org/Raspberry_Pi_Bare_Bones) and [RaspberryPi.org manuals](https://www.raspberrypi.org/app/uploads/2012/02/BCM2835-ARM-Peripherals.pdf).
|
|
@ -0,0 +1,50 @@
|
||||||
|
TARGET = aarch64-vesper-metta
|
||||||
|
|
||||||
|
UTILS_CONTAINER = andrerichter/raspi3-utils
|
||||||
|
DOCKER_CMD = docker run -it --rm -v $$(pwd):/work -w /work
|
||||||
|
QEMU_CMD = qemu-system-aarch64
|
||||||
|
|
||||||
|
# -d in_asm,unimp,int -S
|
||||||
|
QEMU_OPTS = -M raspi3 -d int -serial null -serial stdio
|
||||||
|
|
||||||
|
QEMU = /usr/local/Cellar/qemu/HEAD-3365de01b5-custom/bin/qemu-system-aarch64
|
||||||
|
|
||||||
|
rule cargo_build
|
||||||
|
command = cargo xbuild --target=targets/$TARGET.json --release $FEATURES
|
||||||
|
description = Building kernel
|
||||||
|
|
||||||
|
rule strip
|
||||||
|
command = cargo objcopy -- --strip-all -O binary $in $out
|
||||||
|
description = Converting kernel ELF to binary
|
||||||
|
|
||||||
|
rule docker_qemulate
|
||||||
|
command = $DOCKER_CMD $UTILS_CONTAINER $QEMU_CMD $QEMU_OPTS -kernel $in
|
||||||
|
description = Running QEMU in Docker
|
||||||
|
|
||||||
|
rule qemulate
|
||||||
|
command = $QEMU $QEMU_OPTS -kernel $in
|
||||||
|
description = Running QEMU
|
||||||
|
|
||||||
|
rule copy_file
|
||||||
|
command = cp -f $in /Volumes/BOOT/
|
||||||
|
description = Copying binary image to sdcard
|
||||||
|
|
||||||
|
# todo this doesn't have deps so builds always...
|
||||||
|
build target/${TARGET}/release/vesper | kernel: cargo_build
|
||||||
|
FEATURES = --features "noserial"
|
||||||
|
description = Building device kernel
|
||||||
|
|
||||||
|
#build target/${TARGET}/release/vesper qemu_kernel: cargo_build
|
||||||
|
# FEATURES =
|
||||||
|
# description = Building QEMU kernel
|
||||||
|
|
||||||
|
build kernel8.img: strip target/${TARGET}/release/vesper | kernel
|
||||||
|
|
||||||
|
build qemu_kernel8.img: strip target/${TARGET}/release/vesper | qemu_kernel
|
||||||
|
|
||||||
|
build qemu: qemulate qemu_kernel8.img
|
||||||
|
|
||||||
|
build device: copy_file kernel8.img
|
||||||
|
|
||||||
|
default kernel8.img
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/sh
|
||||||
|
cargo xbuild --target=targets/aarch64-vesper-metta.json --release --features "noserial" && \
|
||||||
|
sh .cargo/runscript.sh target/aarch64-vesper-metta/release/vesper && \
|
||||||
|
cp target/aarch64-vesper-metta/release/vesper.bin /Volumes/boot/vesper && \
|
||||||
|
diskutil eject /Volumes/boot/
|
|
@ -0,0 +1,65 @@
|
||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
In the interest of fostering an open and welcoming environment, we as
|
||||||
|
contributors and maintainers pledge to making participation in our project and
|
||||||
|
our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality,
|
||||||
|
personal appearance, race, religion, or sexual identity and orientation.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to creating a positive environment include:
|
||||||
|
|
||||||
|
* Using welcoming and inclusive language
|
||||||
|
* Being respectful of differing viewpoints and experiences
|
||||||
|
* Gracefully accepting constructive criticism
|
||||||
|
* Focusing on what is best for the community
|
||||||
|
* Showing empathy towards other community members
|
||||||
|
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||||
|
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others’ private information, such as a physical or electronic address, without explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||||
|
|
||||||
|
|
||||||
|
## Our Responsibilities
|
||||||
|
|
||||||
|
Maintainers are responsible for clarifying the standards of acceptable behavior
|
||||||
|
and are expected to take appropriate and fair corrective action in response to
|
||||||
|
any instances of unacceptable behavior.
|
||||||
|
|
||||||
|
Maintainers have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||||
|
not aligned to this Code of Conduct, or to ban temporarily or permanently any
|
||||||
|
contributor for other behaviors that they deem inappropriate, threatening,
|
||||||
|
offensive, or harmful.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies both within project spaces and in public spaces
|
||||||
|
when an individual is representing the project or its community. Examples of
|
||||||
|
representing a project or community include using an official project e-mail
|
||||||
|
address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported by contacting the Technical Advisory Board (TAB) at
|
||||||
|
<tab@metta.systems>. All complaints will be reviewed and
|
||||||
|
investigated and will result in a response that is deemed necessary and
|
||||||
|
appropriate to the circumstances. The TAB is obligated to maintain
|
||||||
|
confidentiality with regard to the reporter of an incident. Further details of
|
||||||
|
specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
|
Maintainers who do not follow or enforce the Code of Conduct in good faith may
|
||||||
|
face temporary or permanent repercussions as determined by other members of the
|
||||||
|
project’s leadership.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
|
@ -0,0 +1,40 @@
|
||||||
|
# Canadian-Cross-build rustc for arvm7 RPi3 to build for aarch64-unknown
|
||||||
|
|
||||||
|
Japaric's explanations of [building rustc](https://www.reddit.com/r/rust/comments/5ag60z/how_do_i_bootstrap_rust_to_crosscompile_for_a_new/) with notes on used repos, namely `rust-buildbot` and crosstool-ng examples.
|
||||||
|
|
||||||
|
Crosstool for gcc toolchain: `git clone git@github.com:asymptotik/crosstool-arm-osx.git` -- this is shit, avoid!
|
||||||
|
|
||||||
|
`https://stackoverflow.com/questions/50955843/linking-with-arm-linux-gnueabihf-gcc-failed-when-cross-compiling-a-rust-applic`
|
||||||
|
|
||||||
|
Crosstool for llvm toolchain: https://medium.com/@zw3rk/making-a-raspbian-cross-compilation-sdk-830fe56d75ba
|
||||||
|
|
||||||
|
```
|
||||||
|
./configure --prefix="/Users/berkus/Hobby/Metta/cross-tools/rpi3-cross-llvm/prebuilt" \
|
||||||
|
--target=arm-linux-gnueabihf \
|
||||||
|
--enable-gold=yes \
|
||||||
|
--enable-ld=yes \
|
||||||
|
--enable-targets=arm-linux-gnueabihf \
|
||||||
|
--enable-multilib \
|
||||||
|
--enable-interwork \
|
||||||
|
--disable-werror \
|
||||||
|
--quiet
|
||||||
|
make && make install
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
mkdir sdkroot
|
||||||
|
rsync -rzLR --safe-links \
|
||||||
|
pi@172.20.10.2:/usr/lib/arm-linux-gnueabihf \
|
||||||
|
pi@172.20.10.2:/usr/lib/gcc/arm-linux-gnueabihf \
|
||||||
|
pi@172.20.10.2:/usr/include \
|
||||||
|
pi@172.20.10.2:/lib/arm-linux-gnueabihf \
|
||||||
|
sysroot/
|
||||||
|
```
|
||||||
|
|
||||||
|
`cp /Users/berkus/Hobby/Metta/cross-tools/rpi3-cross-llvm/sysroot/usr/lib/gcc/arm-linux-gnueabihf/6.3.0/crt* /Users/berkus/Hobby/Metta/cross-tools/rpi3-cross-llvm/prebuilt/arm-linux-gnueabihf/lib/`
|
||||||
|
|
||||||
|
`cp -r /Users/berkus/Hobby/Metta/cross-tools/rpi3-cross-llvm/sysroot/usr/lib/gcc /Users/berkus/Hobby/Metta/cross-tools/rpi3-cross-llvm/prebuilt/arm-linux-gnueabihf/lib/`
|
||||||
|
|
||||||
|
`set -x PATH /Users/berkus/Hobby/Metta/cross-tools/rpi3-cross-llvm/prebuilt/bin $PATH`
|
||||||
|
`./x.py build --host armv7-unknown-linux-gnueabihf --target aarch64-unknown-none --stage 1 src/libtest`
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
The Device memory type has several attributes:
|
||||||
|
|
||||||
|
G or nG - Gathering or non-Gathering. Multiple accesses to a device can be merged into a single transaction except for operations with memory ordering semantics, for example, memory barrier instructions, load acquire/store release.
|
||||||
|
|
||||||
|
R or nR - Reordering.
|
||||||
|
|
||||||
|
E or nE - Early Write Acknowledge (similar to bufferable).
|
||||||
|
|
||||||
|
Only four combinations of these attributes are valid:
|
||||||
|
|
||||||
|
Device-nGnRnE <-- "Strongly Ordered"
|
||||||
|
Device-nGnRE <-- "Device Memory"
|
||||||
|
Device-nGRE
|
||||||
|
Device-GRE
|
||||||
|
|
||||||
|
Typically peripheral control registers must be either `Device-nGnRE`, or `Device-nGnRnE`. This prevents reordering of the transactions in the programming sequences.
|
||||||
|
|
||||||
|
`Device-nGRE` and `Device-GRE` memory types can be useful for peripherals where memory access sequence and ordering does not affect results, for example, in bitmap or display buffers in a display interface. If the bus interface of such peripheral can only accept certain transfer sizes, the peripheral must be set to `Device-nGRE`.
|
||||||
|
|
||||||
|
Device memory is shareable, and must be cached.
|
||||||
|
|
||||||
|
[source](https://developer.arm.com/products/architecture/cpu-architecture/m-profile/docs/100699/latest/memory-type-definitions-in-armv8-m-architecture)
|
|
@ -0,0 +1,13 @@
|
||||||
|
cec_osd_name=Jellyfish
|
||||||
|
|
||||||
|
# Keep rainbow splash screen on boot
|
||||||
|
disable_splash=0
|
||||||
|
|
||||||
|
# Set jtag debug pins to alt4, uses GPIO26 for TDI
|
||||||
|
gpio=22-27=a4
|
||||||
|
|
||||||
|
# Start with 128Mb of GPU memory
|
||||||
|
start_x=1
|
||||||
|
|
||||||
|
# Don't fill kernel ATAGs
|
||||||
|
disable_commandline_tags=1
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Broadcom 2835 on Raspberry Pi as JTAG host
|
||||||
|
|
||||||
|
interface bcm2835gpio
|
||||||
|
|
||||||
|
bcm2835gpio_peripheral_base 0x3F000000
|
||||||
|
|
||||||
|
# Transition delay calculation: SPEED_COEFF/khz - SPEED_OFFSET
|
||||||
|
# These depend on system clock, calibrated for stock 700MHz
|
||||||
|
# bcm2835gpio_speed SPEED_COEFF SPEED_OFFSET
|
||||||
|
bcm2835gpio_speed_coeffs 194938 48
|
||||||
|
|
||||||
|
# Each of the JTAG lines need a gpio number set: tck tms tdi tdo
|
||||||
|
# Header pin numbers: 23 22 19 21
|
||||||
|
bcm2835gpio_jtag_nums 11 25 10 9
|
||||||
|
|
||||||
|
# If you define trst or srst, use appropriate reset_config
|
||||||
|
# Header pin numbers: TRST - 26, SRST - 12
|
||||||
|
|
||||||
|
bcm2835gpio_trst_num 7
|
|
@ -0,0 +1,46 @@
|
||||||
|
#
|
||||||
|
# Docker image naotaco/openocd:armv8 configures JTAG similar to this
|
||||||
|
# Extracted via:
|
||||||
|
# $ docker run --rm naotaco/openocd:armv8 /bin/sh -c "cat /usr/local/share/openocd/scripts/target/rpi3.cfg"
|
||||||
|
#
|
||||||
|
transport select jtag
|
||||||
|
|
||||||
|
# we need to enable srst even though we don't connect it
|
||||||
|
reset_config trst_and_srst
|
||||||
|
|
||||||
|
adapter_khz 4000
|
||||||
|
jtag_ntrst_delay 500
|
||||||
|
|
||||||
|
if { [info exists CHIPNAME] } {
|
||||||
|
set _CHIPNAME $CHIPNAME
|
||||||
|
} else {
|
||||||
|
set _CHIPNAME rpi3
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Main DAP
|
||||||
|
#
|
||||||
|
if { [info exists DAP_TAPID] } {
|
||||||
|
set _DAP_TAPID $DAP_TAPID
|
||||||
|
} else {
|
||||||
|
set _DAP_TAPID 0x4ba00477
|
||||||
|
}
|
||||||
|
|
||||||
|
jtag newtap $_CHIPNAME dap -irlen 4 -ircapture 0x1 -irmask 0xf -expected-id $_DAP_TAPID -enable
|
||||||
|
|
||||||
|
set _TARGETNAME $_CHIPNAME.cpu
|
||||||
|
|
||||||
|
set DBGBASE {0x80010000 0x80012000 0x80014000 0x80016000}
|
||||||
|
set CTIBASE {0x80018000 0x80019000 0x8001a000 0x8001b000}
|
||||||
|
set _cores 4
|
||||||
|
|
||||||
|
for { set _core 0 } { $_core < $_cores } { incr _core } {
|
||||||
|
|
||||||
|
target create $_TARGETNAME.$_core aarch64 \
|
||||||
|
-chain-position $_CHIPNAME.dap -coreid $_core \
|
||||||
|
-dbgbase [lindex $DBGBASE $_core] -ctibase [lindex $CTIBASE $_core]
|
||||||
|
}
|
||||||
|
|
||||||
|
# In contrast with SUSE config, naotaco only configures core0 events
|
||||||
|
$_TARGETNAME.0 configure -event reset-assert-post "aarch64 dbginit"
|
||||||
|
$_TARGETNAME.0 configure -event gdb-attach { halt }
|
|
@ -0,0 +1,48 @@
|
||||||
|
#
|
||||||
|
# https://www.suse.com/c/debugging-raspberry-pi-3-with-jtag/ configures JTAG like this
|
||||||
|
#
|
||||||
|
transport select jtag
|
||||||
|
|
||||||
|
# we need to enable srst even though we don't connect it
|
||||||
|
reset_config trst_and_srst
|
||||||
|
|
||||||
|
adapter_khz 4000
|
||||||
|
jtag_ntrst_delay 500
|
||||||
|
|
||||||
|
if { [info exists CHIPNAME] } {
|
||||||
|
set _CHIPNAME $CHIPNAME
|
||||||
|
} else {
|
||||||
|
set _CHIPNAME rpi3
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Main DAP
|
||||||
|
#
|
||||||
|
if { [info exists DAP_TAPID] } {
|
||||||
|
set _DAP_TAPID $DAP_TAPID
|
||||||
|
} else {
|
||||||
|
set _DAP_TAPID 0x4ba00477
|
||||||
|
}
|
||||||
|
|
||||||
|
jtag newtap $_CHIPNAME tap -irlen 4 -ircapture 0x1 -irmask 0xf -expected-id $_DAP_TAPID -enable
|
||||||
|
dap create $_CHIPNAME.dap -chain-position $_CHIPNAME.tap
|
||||||
|
|
||||||
|
set _TARGETNAME $_CHIPNAME.a53
|
||||||
|
set _CTINAME $_CHIPNAME.cti
|
||||||
|
|
||||||
|
set DBGBASE {0x80010000 0x80012000 0x80014000 0x80016000}
|
||||||
|
set CTIBASE {0x80018000 0x80019000 0x8001a000 0x8001b000}
|
||||||
|
set _cores 4
|
||||||
|
|
||||||
|
for { set _core 0 } { $_core < $_cores } { incr _core } {
|
||||||
|
|
||||||
|
cti create $_CTINAME.$_core -dap $_CHIPNAME.dap -ap-num 0 \
|
||||||
|
-ctibase [lindex $CTIBASE $_core]
|
||||||
|
|
||||||
|
target create $_TARGETNAME.$_core aarch64 \
|
||||||
|
-dap $_CHIPNAME.dap -coreid $_core \
|
||||||
|
-dbgbase [lindex $DBGBASE $_core] -cti $_CTINAME.$_core
|
||||||
|
|
||||||
|
$_TARGETNAME.$_core configure -event reset-assert-post "aarch64 dbginit"
|
||||||
|
$_TARGETNAME.$_core configure -event gdb-attach { halt }
|
||||||
|
}
|
|
@ -0,0 +1,162 @@
|
||||||
|
JTAG boards:
|
||||||
|
|
||||||
|
* RasPi3
|
||||||
|
* Segger J-Link V9
|
||||||
|
* TinCanTools Flyswatter
|
||||||
|
* OpenMoko DebugBoard_v3 - [one i have](http://wiki.openmoko.org/wiki/Debug_Board_v3)
|
||||||
|
|
||||||
|
# RPi3 to RPi3 jtag
|
||||||
|
|
||||||
|
Helpful RPi3 GPIO header pinouts from element14 [for Model B](https://www.element14.com/community/docs/DOC-73950/l/raspberry-pi-3-model-b-gpio-40-pin-block-pinout) and [here for Model B+](https://www.element14.com/community/docs/DOC-88824/l/raspberry-pi-3-model-b-gpio-40-pin-block-poe-header-pinout) (which is the same).
|
||||||
|
|
||||||
|
## Host configuration:
|
||||||
|
|
||||||
|
These are regular GPIO functions, which we specify in OpenOCD interface configuration to enable driving JTAG interface.
|
||||||
|
|
||||||
|
```
|
||||||
|
FUNC | GPIO | PIN #
|
||||||
|
------+--------+-------
|
||||||
|
TCK | GPIO11 | 23
|
||||||
|
TMS | GPIO25 | 22
|
||||||
|
TDI | GPIO10 | 19
|
||||||
|
TDO | GPIO9 | 21
|
||||||
|
TRST* | GPIO7 | 26
|
||||||
|
GND | GND | 20
|
||||||
|
```
|
||||||
|
|
||||||
|
RPi doesn't expose SRST so we ignore it.
|
||||||
|
|
||||||
|
[Source](https://movr0.com/2016/09/02/use-raspberry-pi-23-as-a-jtagswd-adapter/)
|
||||||
|
|
||||||
|
## Target configuration:
|
||||||
|
|
||||||
|
These are real JTAG pins of bcm2837, enabled on target RPi via config.txt options (see below).
|
||||||
|
|
||||||
|
```
|
||||||
|
FUNC | GPIO | PIN # | MODE
|
||||||
|
------+--------+---------+------
|
||||||
|
TCK | GPIO25 | 22 | Alt4
|
||||||
|
TMS | GPIO27 | 13 | Alt4
|
||||||
|
TDI | GPIO26 | 37 | Alt4
|
||||||
|
TDO | GPIO24 | 18 | Alt4
|
||||||
|
TRST | GPIO22 | 15 | Alt4
|
||||||
|
RTCK | GPIO23 | 16 | Alt4
|
||||||
|
GND | GND | 20 |
|
||||||
|
```
|
||||||
|
|
||||||
|
Connecting TDI to pin 7 (GPIO4) did not work!
|
||||||
|
|
||||||
|
[Source (section 6.2 Alternative Function Assignments)](https://www.raspberrypi.org/app/uploads/2012/02/BCM2835-ARM-Peripherals.pdf)
|
||||||
|
|
||||||
|
In config.txt:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Set GPIO pins for JTAG debugger connection on rpi3
|
||||||
|
gpio=22-27=a4
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, just specify @todo - verify this works with all alt4 pins
|
||||||
|
|
||||||
|
```
|
||||||
|
enable_jtag_gpio=1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Connection between boards
|
||||||
|
|
||||||
|
```
|
||||||
|
Func | Host Pin | Wire color | Target pin
|
||||||
|
-----+----------+------------+-----------
|
||||||
|
TCK | 23 | yellow | 22
|
||||||
|
TMS | 22 | brown | 13
|
||||||
|
TDI | 19 | green | 37
|
||||||
|
TDO | 21 | orange | 18
|
||||||
|
TRST | 26 | red | 15
|
||||||
|
GND | 20 | black | 20
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## OpenOCD configuration on the host
|
||||||
|
|
||||||
|
You need two files: interface file for driving the host GPIO correctly, and target file for detecting the JTAG circuitry on the target RPi.
|
||||||
|
|
||||||
|
Interface configuration: [rpi3_interface.cfg](./rpi3_interface.cfg)
|
||||||
|
|
||||||
|
[Source](https://movr0.com/2016/09/02/use-raspberry-pi-23-as-a-jtagswd-adapter/), [source #2 - rpi3 speed_coeffs](https://forum.doozan.com/read.php?3,21789)
|
||||||
|
|
||||||
|
Target configuration: [rpi3_target.cfg](./rpi3_target.cfg)
|
||||||
|
|
||||||
|
[Source #1](https://electronics.stackexchange.com/questions/249008/how-to-use-rpi-2-to-debug-rpi-model-b-via-jtag-with-openocd/419724#419724), [source #2](https://sysprogs.com/tutorials/preparing-raspberry-pi-for-jtag-debugging/), [source #3](http://openocd.org/doc/html/Reset-Configuration.html), [source #4](http://infocenter.arm.com/help/topic/com.arm.doc.faqs/ka3854.html), [source #5](https://www.raspberrypi.org/forums/viewtopic.php?p=1013802), [source #6 - proper rpi3 ocd config](https://www.suse.com/c/debugging-raspberry-pi-3-with-jtag/), [source #7 - simpler rpi3 ocd config](https://github.com/daniel-k/openocd/blob/armv8/tcl/target/rpi3.cfg), [source #8 - explanations about SRST](https://catch22.eu/baremetal/openocd_sysfs_stm32/), [source #9 - example RPi target config](https://github.com/OP-TEE/build/blob/master/rpi3/debugger/pi3.cfg), [source #10 - some JTAG debug hints on rpi](https://www.raspberrypi.org/forums/viewtopic.php?p=1013802), [source #11 - jtag vs swd and CoreSight info links](https://electronics.stackexchange.com/questions/53571/jtag-vs-swd-debugging?rq=1)
|
||||||
|
|
||||||
|
> If an SoC provides a JTAG debug interface and contains any CoreSight debug components (including any Cortex processor) you should expect to see the standard JTAG IDCODE of a single CoreSight SWJ-DP as one TAP on the JTAG chain.
|
||||||
|
|
||||||
|
|
||||||
|
## Run OpenOCD, GDB and attach to target
|
||||||
|
|
||||||
|
Need to verify if the following bug is still valid:
|
||||||
|
> There is a bug in OpenOCD that will prevent Raspberry PI from continuing correctly after a stop unless the initialization is done twice. Close OpenOCD with Ctrl-C and re-run it again. Now the debugging will be usable.
|
||||||
|
|
||||||
|
[Source](https://sysprogs.com/tutorials/preparing-raspberry-pi-for-jtag-debugging/)
|
||||||
|
|
||||||
|
Run `openocd -f rpi3_interface.cfg -f rpi3_target.cfg`
|
||||||
|
|
||||||
|
Run `gdb kernel.elf` and connect to device:
|
||||||
|
|
||||||
|
```
|
||||||
|
target remote :5555
|
||||||
|
x/10i $pc
|
||||||
|
stepi
|
||||||
|
x/2i $pc
|
||||||
|
```
|
||||||
|
|
||||||
|
If `stepi` command causes CPU to make one instruction step, everything is working.
|
||||||
|
|
||||||
|
[Source](https://sysprogs.com/tutorials/preparing-raspberry-pi-for-jtag-debugging/), [source #2](https://www.op-tee.org/docs/rpi3/#6-openocd-and-jtag), [source #3 - monitor reset halt](http://www.openstm32.org/forumthread823)
|
||||||
|
|
||||||
|
|
||||||
|
I got RPi3-to-RPi3 JTAG working and even debugged a bit directly on the CPU, but a few things impeded my happiness:
|
||||||
|
|
||||||
|
* RPi is a bit too slow for bitbanging and oftentimes opening a browser window, or running some other command caused OpenOCD to spew JTAG synchronization errors.
|
||||||
|
* To properly debug my kernel from RPi I would need to compile it locally (otherwise all the paths in the debug info are wrong and GDB will not find the source files, I did not want to mess around with symlinks).
|
||||||
|
|
||||||
|
Fortunately, at this point a Segger J-Link 9 arrived and I went to use it.
|
||||||
|
|
||||||
|
# J-Link to RPi3 jtag
|
||||||
|
|
||||||
|
https://www.segger.com/downloads/jlink/
|
||||||
|
https://habr.com/ru/post/259205/
|
||||||
|
|
||||||
|
JTAG pinout on segger is in UM08001_JLink.pdf distributed with the J-Link software kit, in section 17.1.1.
|
||||||
|
|
||||||
|
This adds VTref for target voltage detection.
|
||||||
|
|
||||||
|
Pinout:
|
||||||
|
|
||||||
|
J-Link and connection to Raspi3:
|
||||||
|
|
||||||
|
```
|
||||||
|
Func | J-Link Pin | Wire color | Target pin
|
||||||
|
------+--------------+-------------+-----------
|
||||||
|
VTref | 1 | white | 1
|
||||||
|
TCK | 9 | yellow | 22
|
||||||
|
TMS | 7 | brown | 13
|
||||||
|
TDI | 5 | green | 37
|
||||||
|
TDO | 13 | orange | 18
|
||||||
|
nTRST | 3 | red | 15
|
||||||
|
RTCK | 11 | magenta | 16
|
||||||
|
GND | 4 | black | 20
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
[Useful article](https://www.suse.com/c/debugging-raspberry-pi-3-with-jtag/)
|
||||||
|
|
||||||
|
|
||||||
|
Rebuild openocd from git and voila, it works with
|
||||||
|
|
||||||
|
`openocd -f interface/jlink.cfg -f rpi3_jtag.cfg`
|
||||||
|
|
||||||
|
|
||||||
|
# Andre Richter's tutorials
|
||||||
|
|
||||||
|
Finally, while I was messing with all this, Andre Richter has created another entry in his excellent Raspi tutorials dedicated exactly to [JTAG debugging](fixme) and he has also provided useful docker containers with configured [gdb-dashboard](https://github.com/rust-embedded/rust-raspi3-OS-tutorials/blob/JTAG/docker/raspi3-gdb/)
|
||||||
|
|
||||||
|
So debugging is a lot easier now - just drop specifically-built JTAG enabler binary to sdcard, connect over JTAG via openocd and gdb and go load your kernel!
|
|
@ -0,0 +1,35 @@
|
||||||
|
# Connecting RPi3 UART
|
||||||
|
|
||||||
|
## Using cp2104 usb-to-ttl converter
|
||||||
|
|
||||||
|
Download drivers from: https://www.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers
|
||||||
|
|
||||||
|
Install `minicom`: `brew install minicom`
|
||||||
|
|
||||||
|
Configure minicom to use `/dev/tty.SLAB_USBtoUART` port.
|
||||||
|
|
||||||
|
Connect rpi wires to cp2014:
|
||||||
|
|
||||||
|
```
|
||||||
|
UART0
|
||||||
|
|
||||||
|
FUNC | GPIO | PIN # | MODE | Wire color
|
||||||
|
------+--------+---------+--------+------------
|
||||||
|
RXD0 | GPIO15 | 10 | Alt0 | Brown
|
||||||
|
TXD0 | GPIO14 | 8 | Alt0 | Red
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
MiniUart (UART1)
|
||||||
|
|
||||||
|
FUNC | GPIO | PIN # | MODE | Wire color
|
||||||
|
------+--------+---------+--------+------------
|
||||||
|
RXD1 | GPIO15 | 10 | Alt5 | Brown
|
||||||
|
TXD1 | GPIO14 | 8 | Alt5 | Red
|
||||||
|
GND | GND | 6 | | Green
|
||||||
|
```
|
||||||
|
|
||||||
|
NB: SWAP the TXD and RXD wires.
|
||||||
|
|
||||||
|
I.e. RXD pin of CP2104 should go to TXD pin on RPi and vice versa.
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
# Broadcom 2837 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
|
||||||
|
|
||||||
|
#reset_config trst_only
|
||||||
|
|
||||||
|
#adapter_khz 500
|
||||||
|
#adapter_nsrst_delay 100
|
||||||
|
#jtag_ntrst_delay 100
|
||||||
|
|
||||||
|
if { [info exists CHIPNAME] } {
|
||||||
|
set _CHIPNAME $CHIPNAME
|
||||||
|
} else {
|
||||||
|
set _CHIPNAME rpi3
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Main DAP
|
||||||
|
#
|
||||||
|
if { [info exists DAP_TAPID] } {
|
||||||
|
set _DAP_TAPID $DAP_TAPID
|
||||||
|
} else {
|
||||||
|
set _DAP_TAPID 0x4ba00477
|
||||||
|
}
|
||||||
|
|
||||||
|
jtag newtap $_CHIPNAME tap -irlen 4 -ircapture 0x1 -irmask 0xf -expected-id $_DAP_TAPID -enable
|
||||||
|
dap create $_CHIPNAME.dap -chain-position $_CHIPNAME.tap
|
||||||
|
|
||||||
|
set _TARGETNAME $_CHIPNAME.a53
|
||||||
|
set _CTINAME $_CHIPNAME.cti
|
||||||
|
|
||||||
|
set DBGBASE {0x80010000 0x80012000 0x80014000 0x80016000}
|
||||||
|
set CTIBASE {0x80018000 0x80019000 0x8001a000 0x8001b000}
|
||||||
|
set _cores 4
|
||||||
|
|
||||||
|
for { set _core 0 } { $_core < $_cores } { incr _core } {
|
||||||
|
|
||||||
|
cti create $_CTINAME.$_core -dap $_CHIPNAME.dap -ap-num 0 \
|
||||||
|
-ctibase [lindex $CTIBASE $_core]
|
||||||
|
|
||||||
|
target create $_TARGETNAME.$_core aarch64 \
|
||||||
|
-dap $_CHIPNAME.dap -coreid $_core \
|
||||||
|
-dbgbase [lindex $DBGBASE $_core] -cti $_CTINAME.$_core
|
||||||
|
|
||||||
|
$_TARGETNAME.$_core configure -event reset-assert-post "aarch64 dbginit"
|
||||||
|
$_TARGETNAME.$_core configure -event gdb-attach { halt }
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
#########################################################
|
||||||
|
# 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. #
|
||||||
|
#########################################################
|
||||||
|
|
||||||
|
=== QEMU ===
|
|
@ -0,0 +1,46 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# MIT License
|
||||||
|
#
|
||||||
|
# Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in all
|
||||||
|
# copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
# SOFTWARE.
|
||||||
|
#
|
||||||
|
|
||||||
|
QEMU=/usr/local/Cellar/qemu/HEAD-3365de01b5-custom/bin/qemu-system-aarch64
|
||||||
|
QEMU_OPTS=-M raspi3 -d int
|
||||||
|
|
||||||
|
tmux new-session -d -s raspi3 &
|
||||||
|
sleep 1
|
||||||
|
tmux new-window -t raspi3:1
|
||||||
|
tmux new-window -t raspi3:2
|
||||||
|
|
||||||
|
tmux send-keys -t raspi3:0 "clear; echo '=== MiniUart ==='; bash /dev/ptmx" C-m
|
||||||
|
tmux send-keys -t raspi3:1 "clear; printf '=== PL011 Uart ===\n\n';bash /dev/ptmx" C-m
|
||||||
|
|
||||||
|
FIRST=$(ps aux | grep ptmx | sort | awk '{print $7}' | sed '1q;d')
|
||||||
|
SECOND=$(ps aux | grep ptmx | sort | awk '{print $7}' | sed '2q;d')
|
||||||
|
|
||||||
|
tmux send-keys -t raspi3:2 "clear; cat emulation/instructions.txt && ${QEMU} ${QEMU_OPTS} -kernel kernel8.img -serial /dev/$SECOND -serial /dev/$FIRST && tmux kill-session" C-m
|
||||||
|
|
||||||
|
tmux join-pane -s raspi3:0 -t 2
|
||||||
|
tmux join-pane -s raspi3:1 -t 2
|
||||||
|
|
||||||
|
tmux select-pane -t 1
|
||||||
|
tmux attach-session -t raspi3
|
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/sh
|
||||||
|
hopperv4 -e target/aarch64-vesper-metta/release/vesper.bin -R --base-address 0x80000 --entrypoint 0x80000 --file-offset 0 --aarch64
|
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018 Andre Richter <andre.o.richter@gmail.com>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
ENTRY(_boot_cores);
|
||||||
|
|
||||||
|
SECTIONS
|
||||||
|
{
|
||||||
|
. = 0x80000; /* AArch64 boot address is 0x80000, 4K-aligned */
|
||||||
|
__ro_start = .;
|
||||||
|
.text :
|
||||||
|
{
|
||||||
|
KEEP(*(.text.boot)) *(.text .text.*)
|
||||||
|
}
|
||||||
|
|
||||||
|
.vectors ALIGN(2048):
|
||||||
|
{
|
||||||
|
*(.vectors)
|
||||||
|
}
|
||||||
|
|
||||||
|
.rodata ALIGN(4):
|
||||||
|
{
|
||||||
|
*(.rodata .rodata.*)
|
||||||
|
FILL(0x00)
|
||||||
|
}
|
||||||
|
. = ALIGN(4096); /* Fill up to 4KiB */
|
||||||
|
__ro_end = .;
|
||||||
|
|
||||||
|
.data :
|
||||||
|
{
|
||||||
|
*(.data .data.*)
|
||||||
|
FILL(0x00)
|
||||||
|
}
|
||||||
|
|
||||||
|
.bss ALIGN(8):
|
||||||
|
{
|
||||||
|
__bss_start = .;
|
||||||
|
*(.bss .bss.*)
|
||||||
|
*(COMMON)
|
||||||
|
__bss_end = .;
|
||||||
|
}
|
||||||
|
|
||||||
|
/DISCARD/ : { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) }
|
||||||
|
}
|
||||||
|
|
||||||
|
PROVIDE(current_el0_synchronous = default_exception_handler);
|
||||||
|
PROVIDE(current_el0_irq = default_exception_handler);
|
||||||
|
PROVIDE(current_el0_fiq = default_exception_handler);
|
||||||
|
PROVIDE(current_el0_serror = default_exception_handler);
|
||||||
|
|
||||||
|
PROVIDE(current_elx_synchronous = default_exception_handler);
|
||||||
|
PROVIDE(current_elx_irq = default_exception_handler);
|
||||||
|
PROVIDE(current_elx_fiq = default_exception_handler);
|
||||||
|
PROVIDE(current_elx_serror = default_exception_handler);
|
||||||
|
|
||||||
|
PROVIDE(lower_aarch64_synchronous = default_exception_handler);
|
||||||
|
PROVIDE(lower_aarch64_irq = default_exception_handler);
|
||||||
|
PROVIDE(lower_aarch64_fiq = default_exception_handler);
|
||||||
|
PROVIDE(lower_aarch64_serror = default_exception_handler);
|
||||||
|
|
||||||
|
PROVIDE(lower_aarch32_synchronous = default_exception_handler);
|
||||||
|
PROVIDE(lower_aarch32_irq = default_exception_handler);
|
||||||
|
PROVIDE(lower_aarch32_fiq = default_exception_handler);
|
||||||
|
PROVIDE(lower_aarch32_serror = default_exception_handler);
|
|
@ -0,0 +1,11 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# Generate gdb-connect script with given RTT block address, to avoid typing it in manually
|
||||||
|
|
||||||
|
# Hint from this answer https://superuser.com/a/747905/355774
|
||||||
|
[ $# -ge 1 ] && ADDR="$1" || ADDR=$(cat)
|
||||||
|
|
||||||
|
cat <<EOF > gdb-connect
|
||||||
|
target remote :3333
|
||||||
|
monitor rttserver start 19021 0
|
||||||
|
monitor rtt setup 0x$ADDR 24 "SEGGER RTT"
|
||||||
|
EOF
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/sh
|
||||||
|
cargo xbuild --target=targets/aarch64-vesper-metta.json --release && \
|
||||||
|
sh .cargo/runscript.sh target/aarch64-vesper-metta/release/vesper
|
|
@ -0,0 +1 @@
|
||||||
|
nightly
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Architecture-specific code
|
||||||
|
|
||||||
|
This directory contains code specific to a certain architecture.
|
||||||
|
|
||||||
|
Implementations of arch-specific kernel calls are also placed here.
|
|
@ -0,0 +1,150 @@
|
||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018 Jorge Aparicio
|
||||||
|
* Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
|
||||||
|
* Copyright (c) 2019 Berkus Decker <berkus+vesper@metta.systems>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
#![deny(warnings)]
|
||||||
|
|
||||||
|
//! Low-level boot of the Raspberry's processor
|
||||||
|
//! http://infocenter.arm.com/help/topic/com.arm.doc.dai0527a/DAI0527A_baremetal_boot_code_for_ARMv8_A_processors.pdf
|
||||||
|
|
||||||
|
extern crate panic_abort;
|
||||||
|
|
||||||
|
/// Type check the user-supplied entry function.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! entry {
|
||||||
|
($path:path) => {
|
||||||
|
#[export_name = "main"]
|
||||||
|
pub unsafe fn __main() -> ! {
|
||||||
|
// type check the given path
|
||||||
|
let f: fn() -> ! = $path;
|
||||||
|
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset function.
|
||||||
|
///
|
||||||
|
/// Initializes the bss section before calling into the user's `main()`.
|
||||||
|
unsafe fn reset() -> ! {
|
||||||
|
extern "C" {
|
||||||
|
// Boundaries of the .bss section, provided by the linker script
|
||||||
|
static mut __bss_start: u64;
|
||||||
|
static mut __bss_end: u64;
|
||||||
|
}
|
||||||
|
|
||||||
|
use cortex_a::regs::*;
|
||||||
|
const STACK_START: u64 = 0x80_000;
|
||||||
|
SP.set(STACK_START);
|
||||||
|
|
||||||
|
// Zeroes the .bss section
|
||||||
|
r0::zero_bss(&mut __bss_start, &mut __bss_end);
|
||||||
|
|
||||||
|
extern "Rust" {
|
||||||
|
fn main() -> !;
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepare and execute transition from EL2 to EL1.
|
||||||
|
#[inline]
|
||||||
|
fn setup_and_enter_el1_from_el2() -> ! {
|
||||||
|
use cortex_a::{asm, regs::*};
|
||||||
|
|
||||||
|
const STACK_START: u64 = 0x80_000;
|
||||||
|
|
||||||
|
// Enable timer counter registers for EL1
|
||||||
|
CNTHCTL_EL2.write(CNTHCTL_EL2::EL1PCEN::SET + CNTHCTL_EL2::EL1PCTEN::SET);
|
||||||
|
|
||||||
|
// No virtual offset for reading the counters
|
||||||
|
CNTVOFF_EL2.set(0);
|
||||||
|
|
||||||
|
// Set EL1 execution state to AArch64
|
||||||
|
// TODO: Explain the SWIO bit (SWIO hardwired on Pi3)
|
||||||
|
HCR_EL2.write(HCR_EL2::RW::EL1IsAarch64 + HCR_EL2::SWIO::SET);
|
||||||
|
|
||||||
|
// Set up a simulated exception return.
|
||||||
|
//
|
||||||
|
// First, fake a saved program status, where all interrupts were
|
||||||
|
// masked and SP_EL1 was used as a stack pointer.
|
||||||
|
SPSR_EL2.write(
|
||||||
|
SPSR_EL2::D::Masked
|
||||||
|
+ SPSR_EL2::A::Masked
|
||||||
|
+ SPSR_EL2::I::Masked
|
||||||
|
+ SPSR_EL2::F::Masked
|
||||||
|
+ SPSR_EL2::M::EL1h, // Use SP_EL1
|
||||||
|
);
|
||||||
|
|
||||||
|
// Second, let the link register point to reset().
|
||||||
|
ELR_EL2.set(reset as *const () as u64);
|
||||||
|
|
||||||
|
// Set up SP_EL1 (stack pointer), which will be used by EL1 once
|
||||||
|
// we "return" to it.
|
||||||
|
SP_EL1.set(STACK_START);
|
||||||
|
|
||||||
|
// Use `eret` to "return" to EL1. This will result in execution of
|
||||||
|
// `reset()` in EL1.
|
||||||
|
asm::eret()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Processors enter EL3 after reset.
|
||||||
|
// ref: http://infocenter.arm.com/help/topic/com.arm.doc.dai0527a/DAI0527A_baremetal_boot_code_for_ARMv8_A_processors.pdf
|
||||||
|
// section: 5.5.1
|
||||||
|
// However, GPU init code must be switching it down to EL2?
|
||||||
|
|
||||||
|
/// Entrypoint of the processor.
|
||||||
|
///
|
||||||
|
/// Parks all cores except core0 and checks if we started in EL2. If
|
||||||
|
/// so, proceeds with setting up EL1.
|
||||||
|
///
|
||||||
|
/// This is invoked from the linker script, does arch-specific init
|
||||||
|
/// and passes control to the kernel boot function reset().
|
||||||
|
#[link_section = ".text.boot"]
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn _boot_cores() -> ! {
|
||||||
|
use cortex_a::{asm, regs::*};
|
||||||
|
|
||||||
|
// crate::arch::aarch64::jtag_dbg_wait();
|
||||||
|
|
||||||
|
const CORE_0: u64 = 0;
|
||||||
|
const CORE_MASK: u64 = 0x3;
|
||||||
|
const EL1: u32 = CurrentEL::EL::EL1.value;
|
||||||
|
const EL2: u32 = CurrentEL::EL::EL2.value;
|
||||||
|
|
||||||
|
if CORE_0 == MPIDR_EL1.get() & CORE_MASK {
|
||||||
|
if EL2 == CurrentEL.get() {
|
||||||
|
setup_and_enter_el1_from_el2()
|
||||||
|
} else if EL1 == CurrentEL.get() {
|
||||||
|
reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if not core0 or EL2/EL1, infinitely wait for events
|
||||||
|
loop {
|
||||||
|
asm::wfe();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
use super::{Frame, FrameAllocator};
|
||||||
|
use multiboot2::{MemoryArea, MemoryAreaIter}; // replace with DTB?
|
||||||
|
|
||||||
|
pub struct AreaFrameAllocator {
|
||||||
|
next_free_frame: Frame,
|
||||||
|
current_area: Option<&'static MemoryArea>,
|
||||||
|
areas: MemoryAreaIter,
|
||||||
|
kernel_start: Frame,
|
||||||
|
kernel_end: Frame,
|
||||||
|
multiboot_start: Frame,
|
||||||
|
multiboot_end: Frame,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FrameAllocator for AreaFrameAllocator {
|
||||||
|
fn allocate_frame(&mut self) -> Option<Frame> {
|
||||||
|
if let Some(_area) = self.current_area {
|
||||||
|
// "Clone" the frame to return it if it's free. Frame doesn't
|
||||||
|
// implement Clone, but we can construct an identical frame.
|
||||||
|
let frame = Frame {
|
||||||
|
number: self.next_free_frame.number,
|
||||||
|
};
|
||||||
|
|
||||||
|
// the last frame of the current area
|
||||||
|
let current_area_last_frame = Frame::containing_address(0x3f00_0000);
|
||||||
|
// {
|
||||||
|
// let address = area.base_addr + area.length - 1;
|
||||||
|
// Frame::containing_address(address as usize)
|
||||||
|
// };
|
||||||
|
|
||||||
|
if frame > current_area_last_frame {
|
||||||
|
// all frames of current area are used, switch to next area
|
||||||
|
// self.choose_next_area();
|
||||||
|
unimplemented!();
|
||||||
|
} else if frame >= self.kernel_start && frame <= self.kernel_end {
|
||||||
|
// `frame` is used by the kernel
|
||||||
|
self.next_free_frame = Frame {
|
||||||
|
number: self.kernel_end.number + 1,
|
||||||
|
};
|
||||||
|
} else if frame >= self.multiboot_start && frame <= self.multiboot_end {
|
||||||
|
// `frame` is used by the multiboot information structure
|
||||||
|
self.next_free_frame = Frame {
|
||||||
|
number: self.multiboot_end.number + 1,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// frame is unused, increment `next_free_frame` and return it
|
||||||
|
self.next_free_frame.number += 1;
|
||||||
|
return Some(frame);
|
||||||
|
}
|
||||||
|
// `frame` was not valid, try it again with the updated `next_free_frame`
|
||||||
|
self.allocate_frame()
|
||||||
|
} else {
|
||||||
|
None // no free frames left
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deallocate_frame(&mut self, _frame: Frame) {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fixme: no multiboot, but dtb instead with avail memory regions
|
||||||
|
// Need dtb parser here!
|
||||||
|
|
||||||
|
impl AreaFrameAllocator {
|
||||||
|
pub fn new(
|
||||||
|
kernel_start: usize,
|
||||||
|
kernel_end: usize,
|
||||||
|
multiboot_start: usize,
|
||||||
|
multiboot_end: usize,
|
||||||
|
memory_areas: MemoryAreaIter,
|
||||||
|
) -> AreaFrameAllocator {
|
||||||
|
let mut allocator = AreaFrameAllocator {
|
||||||
|
next_free_frame: Frame::containing_address(0),
|
||||||
|
current_area: None,
|
||||||
|
areas: memory_areas,
|
||||||
|
kernel_start: Frame::containing_address(kernel_start),
|
||||||
|
kernel_end: Frame::containing_address(kernel_end),
|
||||||
|
multiboot_start: Frame::containing_address(multiboot_start),
|
||||||
|
multiboot_end: Frame::containing_address(multiboot_end),
|
||||||
|
};
|
||||||
|
// allocator.choose_next_area();
|
||||||
|
allocator.next_free_frame = Frame::containing_address(0x100000); // start from 1Mb
|
||||||
|
allocator
|
||||||
|
}
|
||||||
|
|
||||||
|
fn choose_next_area(&mut self) {
|
||||||
|
self.current_area = self
|
||||||
|
.areas
|
||||||
|
.clone()
|
||||||
|
.filter(|area| {
|
||||||
|
let address = area.base_addr + area.length - 1;
|
||||||
|
Frame::containing_address(address as usize) >= self.next_free_frame
|
||||||
|
})
|
||||||
|
.min_by_key(|area| area.base_addr);
|
||||||
|
|
||||||
|
if let Some(area) = self.current_area {
|
||||||
|
let start_frame = Frame::containing_address(area.base_addr as usize);
|
||||||
|
if self.next_free_frame < start_frame {
|
||||||
|
self.next_free_frame = start_frame;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019 Andre Richter <andre.o.richter@gmail.com>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::println;
|
||||||
|
use core::alloc::{Alloc, AllocErr, Layout};
|
||||||
|
use core::mem;
|
||||||
|
use core::ptr::NonNull;
|
||||||
|
use core::slice;
|
||||||
|
|
||||||
|
pub struct BumpAllocator {
|
||||||
|
next: usize,
|
||||||
|
pool_end: usize,
|
||||||
|
name: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Alloc for BumpAllocator {
|
||||||
|
unsafe fn alloc(&mut self, layout: Layout) -> Result<NonNull<u8>, AllocErr> {
|
||||||
|
let start = crate::memory::aligned_addr_unchecked(self.next, layout.align());
|
||||||
|
let end = start + layout.size();
|
||||||
|
|
||||||
|
if end <= self.pool_end {
|
||||||
|
self.next = end;
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"[i] {}:\n Allocated Addr {:#010X} Size {:#X}",
|
||||||
|
self.name,
|
||||||
|
start,
|
||||||
|
layout.size()
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(NonNull::new_unchecked(start as *mut u8))
|
||||||
|
} else {
|
||||||
|
Err(AllocErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A bump allocator doesn't care
|
||||||
|
unsafe fn dealloc(&mut self, _ptr: NonNull<u8>, _layout: Layout) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BumpAllocator {
|
||||||
|
pub const fn new(pool_start: usize, pool_end: usize, name: &'static str) -> Self {
|
||||||
|
Self {
|
||||||
|
next: pool_start,
|
||||||
|
pool_end,
|
||||||
|
name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allocate a zeroed slice
|
||||||
|
pub fn alloc_slice_zeroed<'a, T>(
|
||||||
|
&mut self,
|
||||||
|
count_of_items: usize,
|
||||||
|
alignment: usize,
|
||||||
|
) -> Result<&'a mut [T], ()> {
|
||||||
|
let l;
|
||||||
|
let size_in_byte = count_of_items * mem::size_of::<T>();
|
||||||
|
match Layout::from_size_align(size_in_byte, alignment) {
|
||||||
|
Ok(layout) => l = layout,
|
||||||
|
|
||||||
|
Err(_) => {
|
||||||
|
println!("[e] Layout Error!");
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let ptr;
|
||||||
|
match unsafe { self.alloc_zeroed(l) } {
|
||||||
|
Ok(i) => ptr = i.as_ptr(),
|
||||||
|
|
||||||
|
Err(_) => {
|
||||||
|
println!("[e] Layout Error!");
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(unsafe { slice::from_raw_parts_mut(ptr as *mut T, count_of_items) })
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,335 @@
|
||||||
|
// mod arch::aarch64::memory
|
||||||
|
|
||||||
|
// mod area_frame_allocator;
|
||||||
|
// mod paging;
|
||||||
|
|
||||||
|
// pub use self::area_frame_allocator::AreaFrameAllocator;
|
||||||
|
|
||||||
|
mod bump_allocator;
|
||||||
|
pub use bump_allocator::BumpAllocator;
|
||||||
|
|
||||||
|
pub type PhysicalAddress = usize;
|
||||||
|
pub type VirtualAddress = usize;
|
||||||
|
|
||||||
|
// use self::paging::PAGE_SIZE;
|
||||||
|
pub const PAGE_SIZE: usize = 4096;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frame is an addressable unit of the physical address space.
|
||||||
|
*/
|
||||||
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct Frame {
|
||||||
|
number: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Frame {
|
||||||
|
fn containing_address(address: usize) -> Frame {
|
||||||
|
Frame {
|
||||||
|
number: address / PAGE_SIZE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_address(&self) -> PhysicalAddress {
|
||||||
|
self.number * PAGE_SIZE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait FrameAllocator {
|
||||||
|
fn allocate_frame(&mut self) -> Option<Frame>;
|
||||||
|
fn deallocate_frame(&mut self, frame: Frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------
|
||||||
|
|
||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019 Andre Richter <andre.o.richter@gmail.com>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::println;
|
||||||
|
use core::fmt;
|
||||||
|
use core::ops::RangeInclusive;
|
||||||
|
|
||||||
|
/// System memory map.
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub mod map {
|
||||||
|
pub const START: usize = 0x0000_0000;
|
||||||
|
pub const END: usize = 0x3FFF_FFFF;
|
||||||
|
|
||||||
|
pub mod physical {
|
||||||
|
pub const VIDEOMEM_BASE: usize = 0x3e00_0000;
|
||||||
|
pub const MMIO_BASE: usize = 0x3F00_0000;
|
||||||
|
pub const VIDEOCORE_MBOX_BASE: usize = MMIO_BASE + 0x0000_B880;
|
||||||
|
pub const GPIO_BASE: usize = MMIO_BASE + 0x0020_0000;
|
||||||
|
pub const PL011_UART_BASE: usize = MMIO_BASE + 0x0020_1000;
|
||||||
|
pub const MINI_UART_BASE: usize = MMIO_BASE + 0x0021_5000;
|
||||||
|
pub const MMIO_END: usize = super::END;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod virt {
|
||||||
|
pub const KERN_STACK_START: usize = super::START;
|
||||||
|
pub const KERN_STACK_END: usize = 0x0007_FFFF;
|
||||||
|
|
||||||
|
// The second 2 MiB block.
|
||||||
|
pub const DMA_HEAP_START: usize = 0x0020_0000;
|
||||||
|
pub const DMA_HEAP_END: usize = 0x005F_FFFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Types used for compiling the virtual memory layout of the kernel using
|
||||||
|
/// address ranges.
|
||||||
|
pub mod kernel_mem_range {
|
||||||
|
use core::ops::RangeInclusive;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub enum MemAttributes {
|
||||||
|
CacheableDRAM,
|
||||||
|
NonCacheableDRAM,
|
||||||
|
Device,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub enum AccessPermissions {
|
||||||
|
ReadOnly,
|
||||||
|
ReadWrite,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub enum Translation {
|
||||||
|
Identity,
|
||||||
|
Offset(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct AttributeFields {
|
||||||
|
pub mem_attributes: MemAttributes,
|
||||||
|
pub acc_perms: AccessPermissions,
|
||||||
|
pub execute_never: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AttributeFields {
|
||||||
|
fn default() -> AttributeFields {
|
||||||
|
AttributeFields {
|
||||||
|
mem_attributes: MemAttributes::CacheableDRAM,
|
||||||
|
acc_perms: AccessPermissions::ReadWrite,
|
||||||
|
execute_never: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Descriptor {
|
||||||
|
pub name: &'static str,
|
||||||
|
pub virtual_range: fn() -> RangeInclusive<usize>,
|
||||||
|
pub translation: Translation,
|
||||||
|
pub attribute_fields: AttributeFields,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use kernel_mem_range::*;
|
||||||
|
|
||||||
|
/// A virtual memory layout that is agnostic of the paging granularity that the
|
||||||
|
/// hardware MMU will use.
|
||||||
|
///
|
||||||
|
/// Contains only special ranges, aka anything that is _not_ normal cacheable
|
||||||
|
/// DRAM.
|
||||||
|
static KERNEL_VIRTUAL_LAYOUT: [Descriptor; 5] = [
|
||||||
|
Descriptor {
|
||||||
|
name: "Kernel stack",
|
||||||
|
virtual_range: || {
|
||||||
|
RangeInclusive::new(map::virt::KERN_STACK_START, map::virt::KERN_STACK_END)
|
||||||
|
},
|
||||||
|
translation: Translation::Identity,
|
||||||
|
attribute_fields: AttributeFields {
|
||||||
|
mem_attributes: MemAttributes::CacheableDRAM,
|
||||||
|
acc_perms: AccessPermissions::ReadWrite,
|
||||||
|
execute_never: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Descriptor {
|
||||||
|
name: "Kernel code and RO data",
|
||||||
|
virtual_range: || {
|
||||||
|
// Using the linker script, we ensure that the RO area is consecutive and 4
|
||||||
|
// KiB aligned, and we export the boundaries via symbols:
|
||||||
|
//
|
||||||
|
// [__ro_start, __ro_end)
|
||||||
|
extern "C" {
|
||||||
|
// The inclusive start of the read-only area, aka the address of the
|
||||||
|
// first byte of the area.
|
||||||
|
static __ro_start: u64;
|
||||||
|
|
||||||
|
// The exclusive end of the read-only area, aka the address of
|
||||||
|
// the first byte _after_ the RO area.
|
||||||
|
static __ro_end: u64;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
// Notice the subtraction to turn the exclusive end into an
|
||||||
|
// inclusive end
|
||||||
|
RangeInclusive::new(
|
||||||
|
&__ro_start as *const _ as usize,
|
||||||
|
&__ro_end as *const _ as usize - 1,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
translation: Translation::Identity,
|
||||||
|
attribute_fields: AttributeFields {
|
||||||
|
mem_attributes: MemAttributes::CacheableDRAM,
|
||||||
|
acc_perms: AccessPermissions::ReadOnly,
|
||||||
|
execute_never: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Descriptor {
|
||||||
|
name: "Kernel data and BSS",
|
||||||
|
virtual_range: || {
|
||||||
|
extern "C" {
|
||||||
|
static __ro_end: u64;
|
||||||
|
static __bss_end: u64;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
RangeInclusive::new(
|
||||||
|
&__ro_end as *const _ as usize,
|
||||||
|
&__bss_end as *const _ as usize - 1,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
translation: Translation::Identity,
|
||||||
|
attribute_fields: AttributeFields {
|
||||||
|
mem_attributes: MemAttributes::CacheableDRAM,
|
||||||
|
acc_perms: AccessPermissions::ReadWrite,
|
||||||
|
execute_never: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Descriptor {
|
||||||
|
name: "DMA heap pool",
|
||||||
|
virtual_range: || RangeInclusive::new(map::virt::DMA_HEAP_START, map::virt::DMA_HEAP_END),
|
||||||
|
translation: Translation::Identity,
|
||||||
|
attribute_fields: AttributeFields {
|
||||||
|
mem_attributes: MemAttributes::NonCacheableDRAM,
|
||||||
|
acc_perms: AccessPermissions::ReadWrite,
|
||||||
|
execute_never: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Descriptor {
|
||||||
|
name: "Device MMIO",
|
||||||
|
virtual_range: || {
|
||||||
|
RangeInclusive::new(map::physical::VIDEOMEM_BASE, map::physical::MMIO_END)
|
||||||
|
},
|
||||||
|
translation: Translation::Identity,
|
||||||
|
attribute_fields: AttributeFields {
|
||||||
|
mem_attributes: MemAttributes::Device,
|
||||||
|
acc_perms: AccessPermissions::ReadWrite,
|
||||||
|
execute_never: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/// For a given virtual address, find and return the output address and
|
||||||
|
/// according attributes.
|
||||||
|
///
|
||||||
|
/// If the address is not covered in VIRTUAL_LAYOUT, return a default for normal
|
||||||
|
/// cacheable DRAM.
|
||||||
|
pub fn get_virt_addr_properties(
|
||||||
|
virt_addr: usize,
|
||||||
|
) -> Result<(usize, AttributeFields), &'static str> {
|
||||||
|
if virt_addr > map::END {
|
||||||
|
return Err("Address out of range.");
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in KERNEL_VIRTUAL_LAYOUT.iter() {
|
||||||
|
if (i.virtual_range)().contains(&virt_addr) {
|
||||||
|
let output_addr = match i.translation {
|
||||||
|
Translation::Identity => virt_addr,
|
||||||
|
Translation::Offset(a) => a + (virt_addr - (i.virtual_range)().start()),
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ok((output_addr, i.attribute_fields));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((virt_addr, AttributeFields::default()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Human-readable output of a Descriptor.
|
||||||
|
impl fmt::Display for Descriptor {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
// Call the function to which self.range points, and dereference the
|
||||||
|
// result, which causes Rust to copy the value.
|
||||||
|
let start = *(self.virtual_range)().start();
|
||||||
|
let end = *(self.virtual_range)().end();
|
||||||
|
let size = end - start + 1;
|
||||||
|
|
||||||
|
// log2(1024)
|
||||||
|
const KIB_RSHIFT: u32 = 10;
|
||||||
|
|
||||||
|
// log2(1024 * 1024)
|
||||||
|
const MIB_RSHIFT: u32 = 20;
|
||||||
|
|
||||||
|
let (size, unit) = if (size >> MIB_RSHIFT) > 0 {
|
||||||
|
(size >> MIB_RSHIFT, "MiB")
|
||||||
|
} else if (size >> KIB_RSHIFT) > 0 {
|
||||||
|
(size >> KIB_RSHIFT, "KiB")
|
||||||
|
} else {
|
||||||
|
(size, "Byte")
|
||||||
|
};
|
||||||
|
|
||||||
|
let attr = match self.attribute_fields.mem_attributes {
|
||||||
|
MemAttributes::CacheableDRAM => "C",
|
||||||
|
MemAttributes::NonCacheableDRAM => "NC",
|
||||||
|
MemAttributes::Device => "Dev",
|
||||||
|
};
|
||||||
|
|
||||||
|
let acc_p = match self.attribute_fields.acc_perms {
|
||||||
|
AccessPermissions::ReadOnly => "RO",
|
||||||
|
AccessPermissions::ReadWrite => "RW",
|
||||||
|
};
|
||||||
|
|
||||||
|
let xn = if self.attribute_fields.execute_never {
|
||||||
|
"PXN"
|
||||||
|
} else {
|
||||||
|
"PX"
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
" {:#010X} - {:#010X} | {: >3} {} | {: <3} {} {: <3} | {}",
|
||||||
|
start, end, size, unit, attr, acc_p, xn, self.name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Print the kernel memory layout.
|
||||||
|
pub fn print_layout() {
|
||||||
|
println!("[i] Kernel memory layout:");
|
||||||
|
|
||||||
|
for i in KERNEL_VIRTUAL_LAYOUT.iter() {
|
||||||
|
println!("{}", i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate the next possible aligned address without sanity checking the
|
||||||
|
/// input parameters.
|
||||||
|
#[inline]
|
||||||
|
fn aligned_addr_unchecked(addr: usize, alignment: usize) -> usize {
|
||||||
|
(addr + (alignment - 1)) & !(alignment - 1)
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
use super::super::Frame;
|
||||||
|
|
||||||
|
pub struct Entry(u64);
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
pub struct EntryFlags: u64 {
|
||||||
|
const VALID = 1 << 0;
|
||||||
|
const TABLE = 1 << 1; // If set, a table entry, otherwise block entry
|
||||||
|
// ATTR_INDEX_MASK = 7 << 2;
|
||||||
|
const NON_SECURE = 1 << 5; // block/page descriptor lower attributes
|
||||||
|
const ACCESS = 1 << 10; // block/page descriptor lower attributes
|
||||||
|
const NOT_GLOBAL = 1 << 11; // nG, block/page descriptor lower attributes
|
||||||
|
const DIRTY = 1 << 51; // block/page descriptor upper attributes
|
||||||
|
const CONTIGUOUS = 1 << 52; // block/page descriptor upper attributes
|
||||||
|
const EL1_EXEC_NEVER = 1 << 53; // block/page descriptor upper attributes
|
||||||
|
const EXEC_NEVER = 1 << 54; // block/page descriptor upper attributes
|
||||||
|
const PXN_TABLE = 1 << 59; // table descriptor, next level table attributes
|
||||||
|
const XN_TABLE = 1 << 60; // table descriptor, next level table attributes
|
||||||
|
const AP_TABLE = 1 << 61; // table descriptor, next level table attributes, 2 bits
|
||||||
|
const NS_TABLE = 1 << 63; // table descriptor, next level table attributes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Entry {
|
||||||
|
pub fn is_unused(&self) -> bool {
|
||||||
|
self.0 == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_unused(&mut self) {
|
||||||
|
self.0 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn flags(&self) -> EntryFlags {
|
||||||
|
EntryFlags::from_bits_truncate(self.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pointed_frame(&self) -> Option<Frame> {
|
||||||
|
if self.flags().contains(EntryFlags::VALID) {
|
||||||
|
Some(Frame::containing_address(
|
||||||
|
self.0 as usize & 0x0000_ffff_ffff_f000,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&mut self, frame: Frame, flags: EntryFlags) {
|
||||||
|
assert!(frame.start_address() & !0x0000_ffff_ffff_f000 == 0);
|
||||||
|
self.0 = (frame.start_address() as u64) | flags.bits();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,198 @@
|
||||||
|
// mod arch::aarch64::memory::paging
|
||||||
|
|
||||||
|
//! Some code was borrowed from [Phil Opp's Blog](https://os.phil-opp.com/page-tables/)
|
||||||
|
//! Paging is mostly based on https://os.phil-opp.com/page-tables/ and ARM ARM
|
||||||
|
|
||||||
|
// AArch64:
|
||||||
|
// Table D4-8-2021: check supported granule sizes, select alloc policy based on results.
|
||||||
|
// TTBR_ELx is the pdbr for specific page tables
|
||||||
|
|
||||||
|
// Page 2068 actual page descriptor formats
|
||||||
|
|
||||||
|
/*
|
||||||
|
* With 4k page granule, a virtual address is split into 4 lookup parts
|
||||||
|
* spanning 9 bits each:
|
||||||
|
*
|
||||||
|
* _______________________________________________
|
||||||
|
* | | | | | | |
|
||||||
|
* | signx | Lv0 | Lv1 | Lv2 | Lv3 | off |
|
||||||
|
* |_______|_______|_______|_______|_______|_______|
|
||||||
|
* 63-48 47-39 38-30 29-21 20-12 11-00
|
||||||
|
*
|
||||||
|
* mask page size
|
||||||
|
*
|
||||||
|
* Lv0: FF8000000000 --
|
||||||
|
* Lv1: 7FC0000000 1G
|
||||||
|
* Lv2: 3FE00000 2M
|
||||||
|
* Lv3: 1FF000 4K
|
||||||
|
* off: FFF
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub use self::entry::*;
|
||||||
|
use self::table::{Level0, Table};
|
||||||
|
use super::{Frame, FrameAllocator, PhysicalAddress, VirtualAddress};
|
||||||
|
use core::ptr::Unique;
|
||||||
|
|
||||||
|
mod entry;
|
||||||
|
mod table;
|
||||||
|
|
||||||
|
pub const PAGE_SIZE: usize = 4096;
|
||||||
|
|
||||||
|
pub const ENTRY_COUNT: usize = 512;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page is an addressable unit of the virtual address space.
|
||||||
|
*/
|
||||||
|
pub struct Page {
|
||||||
|
number: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Page {
|
||||||
|
pub fn containing_address(address: VirtualAddress) -> Page {
|
||||||
|
assert!(
|
||||||
|
address < 0x0000_8000_0000_0000 || address >= 0xffff_8000_0000_0000,
|
||||||
|
"invalid address: 0x{:x}",
|
||||||
|
address
|
||||||
|
);
|
||||||
|
Page {
|
||||||
|
number: address / PAGE_SIZE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_address(&self) -> usize {
|
||||||
|
self.number * PAGE_SIZE
|
||||||
|
}
|
||||||
|
|
||||||
|
fn l0_index(&self) -> usize {
|
||||||
|
(self.number >> 27) & 0o777
|
||||||
|
}
|
||||||
|
fn l1_index(&self) -> usize {
|
||||||
|
(self.number >> 18) & 0o777
|
||||||
|
}
|
||||||
|
fn l2_index(&self) -> usize {
|
||||||
|
(self.number >> 9) & 0o777
|
||||||
|
}
|
||||||
|
fn l3_index(&self) -> usize {
|
||||||
|
(self.number >> 0) & 0o777
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ActivePageTable {
|
||||||
|
l0: Unique<Table<Level0>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActivePageTable {
|
||||||
|
pub unsafe fn new() -> ActivePageTable {
|
||||||
|
ActivePageTable {
|
||||||
|
l0: Unique::new_unchecked(table::L0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn l0(&self) -> &Table<Level0> {
|
||||||
|
unsafe { self.l0.as_ref() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn l0_mut(&mut self) -> &mut Table<Level0> {
|
||||||
|
unsafe { self.l0.as_mut() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn translate(&self, virtual_address: VirtualAddress) -> Option<PhysicalAddress> {
|
||||||
|
let offset = virtual_address % PAGE_SIZE;
|
||||||
|
self.translate_page(Page::containing_address(virtual_address))
|
||||||
|
.map(|frame| frame.number * PAGE_SIZE + offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn translate_page(&self, page: Page) -> Option<Frame> {
|
||||||
|
use self::entry::EntryFlags;
|
||||||
|
|
||||||
|
let l1 = self.l0().next_table(page.l0_index());
|
||||||
|
|
||||||
|
let huge_page = || {
|
||||||
|
l1.and_then(|l1| {
|
||||||
|
let l1_entry = &l1[page.l1_index()];
|
||||||
|
// 1GiB page?
|
||||||
|
if let Some(start_frame) = l1_entry.pointed_frame() {
|
||||||
|
if !l1_entry.flags().contains(EntryFlags::TABLE) {
|
||||||
|
// address must be 1GiB aligned
|
||||||
|
assert!(start_frame.number % (ENTRY_COUNT * ENTRY_COUNT) == 0);
|
||||||
|
return Some(Frame {
|
||||||
|
number: start_frame.number
|
||||||
|
+ page.l2_index() * ENTRY_COUNT
|
||||||
|
+ page.l3_index(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(l2) = l1.next_table(page.l1_index()) {
|
||||||
|
let l2_entry = &l2[page.l2_index()];
|
||||||
|
// 2MiB page?
|
||||||
|
if let Some(start_frame) = l2_entry.pointed_frame() {
|
||||||
|
if !l2_entry.flags().contains(EntryFlags::TABLE) {
|
||||||
|
// address must be 2MiB aligned
|
||||||
|
assert!(start_frame.number % ENTRY_COUNT == 0);
|
||||||
|
return Some(Frame {
|
||||||
|
number: start_frame.number + page.l3_index(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
l1.and_then(|l1| l1.next_table(page.l1_index()))
|
||||||
|
.and_then(|l2| l2.next_table(page.l2_index()))
|
||||||
|
.and_then(|l3| l3[page.l3_index()].pointed_frame())
|
||||||
|
.or_else(huge_page)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map_to<A>(&mut self, page: Page, frame: Frame, flags: EntryFlags, allocator: &mut A)
|
||||||
|
where
|
||||||
|
A: FrameAllocator,
|
||||||
|
{
|
||||||
|
let l0 = self.l0_mut();
|
||||||
|
let l1 = l0.next_table_create(page.l0_index(), allocator);
|
||||||
|
let l2 = l1.next_table_create(page.l1_index(), allocator);
|
||||||
|
let l3 = l2.next_table_create(page.l2_index(), allocator);
|
||||||
|
|
||||||
|
assert!(l3[page.l3_index()].is_unused());
|
||||||
|
l3[page.l3_index()].set(frame, flags | EntryFlags::VALID);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map<A>(&mut self, page: Page, flags: EntryFlags, allocator: &mut A)
|
||||||
|
where
|
||||||
|
A: FrameAllocator,
|
||||||
|
{
|
||||||
|
let frame = allocator.allocate_frame().expect("out of memory");
|
||||||
|
self.map_to(page, frame, flags, allocator)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn identity_map<A>(&mut self, frame: Frame, flags: EntryFlags, allocator: &mut A)
|
||||||
|
where
|
||||||
|
A: FrameAllocator,
|
||||||
|
{
|
||||||
|
let page = Page::containing_address(frame.start_address());
|
||||||
|
self.map_to(page, frame, flags, allocator)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unmap<A>(&mut self, page: Page, _allocator: &mut A)
|
||||||
|
where
|
||||||
|
A: FrameAllocator,
|
||||||
|
{
|
||||||
|
// use aarch64::instructions::tlb;
|
||||||
|
// use x86_64::VirtualAddress;
|
||||||
|
|
||||||
|
assert!(self.translate(page.start_address()).is_some());
|
||||||
|
|
||||||
|
let l3 = self
|
||||||
|
.l0_mut()
|
||||||
|
.next_table_mut(page.l0_index())
|
||||||
|
.and_then(|l1| l1.next_table_mut(page.l1_index()))
|
||||||
|
.and_then(|l2| l2.next_table_mut(page.l2_index()))
|
||||||
|
.expect("mapping code does not support huge pages");
|
||||||
|
let _frame = l3[page.l3_index()].pointed_frame().unwrap();
|
||||||
|
l3[page.l3_index()].set_unused();
|
||||||
|
// tlb::flush(VirtualAddress(page.start_address()));
|
||||||
|
// TODO free p(1,2,3) table if empty
|
||||||
|
//allocator.deallocate_frame(frame);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
use super::super::FrameAllocator;
|
||||||
|
use super::ENTRY_COUNT;
|
||||||
|
use arch::aarch64::memory::paging::entry::*;
|
||||||
|
use core::marker::PhantomData;
|
||||||
|
use core::ops::{Index, IndexMut};
|
||||||
|
|
||||||
|
pub const L0: *mut Table<Level0> = 0xffff_ffff_ffff_f000 as *mut _; // L0 0o177777_777_777_777_777_0000
|
||||||
|
// L1 0o177777_777_777_777_XXX_0000, XXX is the L0 index
|
||||||
|
// L2 0o177777_777_777_XXX_YYY_0000, YYY is the L1 index
|
||||||
|
// L3 0o177777_777_XXX_YYY_ZZZ_0000, ZZZ is the L2 index
|
||||||
|
|
||||||
|
// L1 = (L0 << 9) | (XXX << 12)
|
||||||
|
// L2 = (L1 << 9) | (YYY << 12)
|
||||||
|
// L3 = (L2 << 9) | (ZZZ << 12)
|
||||||
|
|
||||||
|
pub struct Table<L: TableLevel> {
|
||||||
|
entries: [Entry; ENTRY_COUNT],
|
||||||
|
level: PhantomData<L>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L> Table<L>
|
||||||
|
where
|
||||||
|
L: TableLevel,
|
||||||
|
{
|
||||||
|
pub fn zero(&mut self) {
|
||||||
|
for entry in self.entries.iter_mut() {
|
||||||
|
entry.set_unused();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L> Table<L>
|
||||||
|
where
|
||||||
|
L: HierarchicalLevel,
|
||||||
|
{
|
||||||
|
fn next_table_address(&self, index: usize) -> Option<usize> {
|
||||||
|
let entry_flags = self[index].flags();
|
||||||
|
if entry_flags.contains(EntryFlags::VALID | EntryFlags::TABLE) {
|
||||||
|
let table_address = self as *const _ as usize;
|
||||||
|
Some((table_address << 9) | (index << 12))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_table(&self, index: usize) -> Option<&Table<L::NextLevel>> {
|
||||||
|
self.next_table_address(index)
|
||||||
|
.map(|address| unsafe { &*(address as *const _) })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_table_mut(&mut self, index: usize) -> Option<&mut Table<L::NextLevel>> {
|
||||||
|
self.next_table_address(index)
|
||||||
|
.map(|address| unsafe { &mut *(address as *mut _) })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_table_create<A>(
|
||||||
|
&mut self,
|
||||||
|
index: usize,
|
||||||
|
allocator: &mut A,
|
||||||
|
) -> &mut Table<L::NextLevel>
|
||||||
|
where
|
||||||
|
A: FrameAllocator,
|
||||||
|
{
|
||||||
|
if self.next_table(index).is_none() {
|
||||||
|
assert!(
|
||||||
|
self.entries[index].flags().contains(EntryFlags::TABLE),
|
||||||
|
"mapping code does not support huge pages"
|
||||||
|
);
|
||||||
|
let frame = allocator.allocate_frame().expect("no frames available");
|
||||||
|
self.entries[index].set(frame, EntryFlags::VALID /*| WRITABLE*/);
|
||||||
|
self.next_table_mut(index).unwrap().zero();
|
||||||
|
}
|
||||||
|
self.next_table_mut(index).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L> Index<usize> for Table<L>
|
||||||
|
where
|
||||||
|
L: TableLevel,
|
||||||
|
{
|
||||||
|
type Output = Entry;
|
||||||
|
|
||||||
|
fn index(&self, index: usize) -> &Entry {
|
||||||
|
&self.entries[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L> IndexMut<usize> for Table<L>
|
||||||
|
where
|
||||||
|
L: TableLevel,
|
||||||
|
{
|
||||||
|
fn index_mut(&mut self, index: usize) -> &mut Entry {
|
||||||
|
&mut self.entries[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait TableLevel {}
|
||||||
|
|
||||||
|
pub enum Level0 {}
|
||||||
|
pub enum Level1 {}
|
||||||
|
pub enum Level2 {}
|
||||||
|
pub enum Level3 {}
|
||||||
|
|
||||||
|
impl TableLevel for Level0 {}
|
||||||
|
impl TableLevel for Level1 {}
|
||||||
|
impl TableLevel for Level2 {}
|
||||||
|
impl TableLevel for Level3 {}
|
||||||
|
|
||||||
|
pub trait HierarchicalLevel: TableLevel {
|
||||||
|
type NextLevel: TableLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HierarchicalLevel for Level0 {
|
||||||
|
type NextLevel = Level1;
|
||||||
|
}
|
||||||
|
impl HierarchicalLevel for Level1 {
|
||||||
|
type NextLevel = Level2;
|
||||||
|
}
|
||||||
|
impl HierarchicalLevel for Level2 {
|
||||||
|
type NextLevel = Level3;
|
||||||
|
}
|
|
@ -0,0 +1,366 @@
|
||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
|
||||||
|
* Copyright (c) 2019 Berkus Decker <berkus+github@metta.systems>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//! MMU initialisation.
|
||||||
|
//! [ARMv8 memory addressing](https://static.docs.arm.com/100940/0100/armv8_a_address%20translation_100940_0100_en.pdf)
|
||||||
|
|
||||||
|
use crate::arch::aarch64::memory::{get_virt_addr_properties, AttributeFields};
|
||||||
|
use crate::println;
|
||||||
|
use cortex_a::{barrier, regs::*};
|
||||||
|
use register::register_bitfields;
|
||||||
|
|
||||||
|
/// Parse the ID_AA64MMFR0_EL1 register for runtime information about supported MMU features.
|
||||||
|
pub fn print_features() {
|
||||||
|
let mmfr = ID_AA64MMFR0_EL1.extract();
|
||||||
|
|
||||||
|
if let Some(ID_AA64MMFR0_EL1::TGran4::Value::Supported) =
|
||||||
|
mmfr.read_as_enum(ID_AA64MMFR0_EL1::TGran4)
|
||||||
|
{
|
||||||
|
println!("[i] MMU: 4 KiB granule supported!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_40) =
|
||||||
|
mmfr.read_as_enum(ID_AA64MMFR0_EL1::PARange)
|
||||||
|
{
|
||||||
|
println!("[i] MMU: Up to 40 Bit physical address range supported!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
register_bitfields! {u64,
|
||||||
|
// AArch64 Reference Manual page 2150
|
||||||
|
STAGE1_DESCRIPTOR [
|
||||||
|
/// Privileged execute-never
|
||||||
|
PXN OFFSET(53) NUMBITS(1) [
|
||||||
|
False = 0,
|
||||||
|
True = 1
|
||||||
|
],
|
||||||
|
|
||||||
|
/// Various address fields, depending on use case
|
||||||
|
LVL2_OUTPUT_ADDR_4KiB OFFSET(21) NUMBITS(27) [], // [47:21]
|
||||||
|
NEXT_LVL_TABLE_ADDR_4KiB OFFSET(12) NUMBITS(36) [], // [47:12]
|
||||||
|
|
||||||
|
/// Access flag
|
||||||
|
AF OFFSET(10) NUMBITS(1) [
|
||||||
|
False = 0,
|
||||||
|
True = 1
|
||||||
|
],
|
||||||
|
|
||||||
|
/// Shareability field
|
||||||
|
SH OFFSET(8) NUMBITS(2) [
|
||||||
|
OuterShareable = 0b10,
|
||||||
|
InnerShareable = 0b11
|
||||||
|
],
|
||||||
|
|
||||||
|
/// Access Permissions
|
||||||
|
AP OFFSET(6) NUMBITS(2) [
|
||||||
|
RW_EL1 = 0b00,
|
||||||
|
RW_EL1_EL0 = 0b01,
|
||||||
|
RO_EL1 = 0b10,
|
||||||
|
RO_EL1_EL0 = 0b11
|
||||||
|
],
|
||||||
|
|
||||||
|
/// Memory attributes index into the MAIR_EL1 register
|
||||||
|
AttrIndx OFFSET(2) NUMBITS(3) [],
|
||||||
|
|
||||||
|
TYPE OFFSET(1) NUMBITS(1) [
|
||||||
|
Block = 0,
|
||||||
|
Table = 1
|
||||||
|
],
|
||||||
|
|
||||||
|
VALID OFFSET(0) NUMBITS(1) [
|
||||||
|
False = 0,
|
||||||
|
True = 1
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const FOUR_KIB: usize = 4 * 1024;
|
||||||
|
const FOUR_KIB_SHIFT: usize = 12; // log2(4 * 1024)
|
||||||
|
|
||||||
|
const TWO_MIB: usize = 2 * 1024 * 1024;
|
||||||
|
const TWO_MIB_SHIFT: usize = 21; // log2(2 * 1024 * 1024)
|
||||||
|
|
||||||
|
/// A descriptor pointing to the next page table.
|
||||||
|
struct TableDescriptor(register::FieldValue<u64, STAGE1_DESCRIPTOR::Register>);
|
||||||
|
|
||||||
|
impl TableDescriptor {
|
||||||
|
fn new(next_lvl_table_addr: usize) -> Result<TableDescriptor, &'static str> {
|
||||||
|
if next_lvl_table_addr % FOUR_KIB != 0 {
|
||||||
|
return Err("TableDescriptor: Address is not 4 KiB aligned.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let shifted = next_lvl_table_addr >> FOUR_KIB_SHIFT;
|
||||||
|
|
||||||
|
Ok(TableDescriptor(
|
||||||
|
STAGE1_DESCRIPTOR::VALID::True
|
||||||
|
+ STAGE1_DESCRIPTOR::TYPE::Table
|
||||||
|
+ STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(shifted as u64),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value(&self) -> u64 {
|
||||||
|
self.0.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A function that maps the generic memory range attributes to HW-specific
|
||||||
|
/// attributes of the MMU.
|
||||||
|
fn into_mmu_attributes(
|
||||||
|
attribute_fields: AttributeFields,
|
||||||
|
) -> register::FieldValue<u64, STAGE1_DESCRIPTOR::Register> {
|
||||||
|
use crate::memory::{AccessPermissions, MemAttributes};
|
||||||
|
|
||||||
|
// Memory attributes
|
||||||
|
let mut desc = match attribute_fields.mem_attributes {
|
||||||
|
MemAttributes::CacheableDRAM => {
|
||||||
|
STAGE1_DESCRIPTOR::SH::InnerShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL)
|
||||||
|
}
|
||||||
|
MemAttributes::NonCacheableDRAM => {
|
||||||
|
STAGE1_DESCRIPTOR::SH::InnerShareable
|
||||||
|
+ STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL_NON_CACHEABLE)
|
||||||
|
}
|
||||||
|
MemAttributes::Device => {
|
||||||
|
STAGE1_DESCRIPTOR::SH::OuterShareable
|
||||||
|
+ STAGE1_DESCRIPTOR::AttrIndx.val(mair::DEVICE_NGNRE)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Access Permissions
|
||||||
|
desc += match attribute_fields.acc_perms {
|
||||||
|
AccessPermissions::ReadOnly => STAGE1_DESCRIPTOR::AP::RO_EL1,
|
||||||
|
AccessPermissions::ReadWrite => STAGE1_DESCRIPTOR::AP::RW_EL1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Execute Never
|
||||||
|
desc += if attribute_fields.execute_never {
|
||||||
|
STAGE1_DESCRIPTOR::PXN::True
|
||||||
|
} else {
|
||||||
|
STAGE1_DESCRIPTOR::PXN::False
|
||||||
|
};
|
||||||
|
|
||||||
|
desc
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A Level2 block descriptor with 2 MiB aperture.
|
||||||
|
///
|
||||||
|
/// The output points to physical memory.
|
||||||
|
struct Lvl2BlockDescriptor(register::FieldValue<u64, STAGE1_DESCRIPTOR::Register>);
|
||||||
|
|
||||||
|
impl Lvl2BlockDescriptor {
|
||||||
|
fn new(
|
||||||
|
output_addr: usize,
|
||||||
|
attribute_fields: AttributeFields,
|
||||||
|
) -> Result<Lvl2BlockDescriptor, &'static str> {
|
||||||
|
if output_addr % TWO_MIB != 0 {
|
||||||
|
return Err("BlockDescriptor: Address is not 2 MiB aligned.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let shifted = output_addr >> TWO_MIB_SHIFT;
|
||||||
|
|
||||||
|
Ok(Lvl2BlockDescriptor(
|
||||||
|
STAGE1_DESCRIPTOR::VALID::True
|
||||||
|
+ STAGE1_DESCRIPTOR::AF::True
|
||||||
|
+ into_mmu_attributes(attribute_fields)
|
||||||
|
+ STAGE1_DESCRIPTOR::TYPE::Block
|
||||||
|
+ STAGE1_DESCRIPTOR::LVL2_OUTPUT_ADDR_4KiB.val(shifted as u64),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value(&self) -> u64 {
|
||||||
|
self.0.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A page descriptor with 4 KiB aperture.
|
||||||
|
///
|
||||||
|
/// The output points to physical memory.
|
||||||
|
struct PageDescriptor(register::FieldValue<u64, STAGE1_DESCRIPTOR::Register>);
|
||||||
|
|
||||||
|
impl PageDescriptor {
|
||||||
|
fn new(
|
||||||
|
output_addr: usize,
|
||||||
|
attribute_fields: AttributeFields,
|
||||||
|
) -> Result<PageDescriptor, &'static str> {
|
||||||
|
if output_addr % FOUR_KIB != 0 {
|
||||||
|
return Err("PageDescriptor: Address is not 4 KiB aligned.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let shifted = output_addr >> FOUR_KIB_SHIFT;
|
||||||
|
|
||||||
|
Ok(PageDescriptor(
|
||||||
|
STAGE1_DESCRIPTOR::VALID::True
|
||||||
|
+ STAGE1_DESCRIPTOR::AF::True
|
||||||
|
+ into_mmu_attributes(attribute_fields)
|
||||||
|
+ STAGE1_DESCRIPTOR::TYPE::Table
|
||||||
|
+ STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(shifted as u64),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value(&self) -> u64 {
|
||||||
|
self.0.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait BaseAddr {
|
||||||
|
fn base_addr_u64(&self) -> u64;
|
||||||
|
fn base_addr_usize(&self) -> usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BaseAddr for [u64; 512] {
|
||||||
|
fn base_addr_u64(&self) -> u64 {
|
||||||
|
self as *const u64 as u64
|
||||||
|
}
|
||||||
|
|
||||||
|
fn base_addr_usize(&self) -> usize {
|
||||||
|
self as *const u64 as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const NUM_ENTRIES_4KIB: usize = 512;
|
||||||
|
|
||||||
|
// We need a wrapper struct here so that we can make use of the align attribute.
|
||||||
|
#[repr(C)]
|
||||||
|
#[repr(align(4096))]
|
||||||
|
struct PageTable {
|
||||||
|
entries: [u64; NUM_ENTRIES_4KIB],
|
||||||
|
}
|
||||||
|
|
||||||
|
static mut LVL2_TABLE: PageTable = PageTable {
|
||||||
|
entries: [0; NUM_ENTRIES_4KIB],
|
||||||
|
};
|
||||||
|
|
||||||
|
static mut LVL3_TABLE: PageTable = PageTable {
|
||||||
|
entries: [0; NUM_ENTRIES_4KIB],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Setup function for the MAIR_EL1 register.
|
||||||
|
fn set_up_mair() {
|
||||||
|
// Define the three memory types that we will map. Normal DRAM, Uncached and device.
|
||||||
|
MAIR_EL1.write(
|
||||||
|
// Attribute 2
|
||||||
|
MAIR_EL1::Attr2_HIGH::Device
|
||||||
|
+ MAIR_EL1::Attr2_LOW_DEVICE::Device_nGnRE
|
||||||
|
// Attribute 1
|
||||||
|
+ MAIR_EL1::Attr1_HIGH::Memory_OuterNonCacheable
|
||||||
|
+ MAIR_EL1::Attr1_LOW_MEMORY::InnerNonCacheable
|
||||||
|
// Attribute 0
|
||||||
|
+ MAIR_EL1::Attr0_HIGH::Memory_OuterWriteBack_NonTransient_ReadAlloc_WriteAlloc
|
||||||
|
+ MAIR_EL1::Attr0_LOW_MEMORY::InnerWriteBack_NonTransient_ReadAlloc_WriteAlloc,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Three descriptive consts for indexing into the correct MAIR_EL1 attributes.
|
||||||
|
mod mair {
|
||||||
|
pub const NORMAL: u64 = 0;
|
||||||
|
pub const NORMAL_NON_CACHEABLE: u64 = 1;
|
||||||
|
pub const DEVICE_NGNRE: u64 = 2;
|
||||||
|
// DEVICE_GRE
|
||||||
|
// DEVICE_NGNRNE
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set up identity mapped page tables for the first 1 gigabyte of address space.
|
||||||
|
/// default: 880 MB ARM ram, 128MB VC
|
||||||
|
pub unsafe fn init() -> Result<(), &'static str> {
|
||||||
|
// Prepare the memory attribute indirection register.
|
||||||
|
set_up_mair();
|
||||||
|
|
||||||
|
// Point the first 2 MiB of virtual addresses to the follow-up LVL3
|
||||||
|
// page-table.
|
||||||
|
LVL2_TABLE.entries[0] = match TableDescriptor::new(LVL3_TABLE.entries.base_addr_usize()) {
|
||||||
|
Err(s) => return Err(s),
|
||||||
|
Ok(d) => d.value(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fill the rest of the LVL2 (2 MiB) entries as block descriptors.
|
||||||
|
//
|
||||||
|
// Notice the skip(1) which makes the iteration start at the second 2 MiB
|
||||||
|
// block (0x20_0000).
|
||||||
|
for (block_descriptor_nr, entry) in LVL2_TABLE.entries.iter_mut().enumerate().skip(1) {
|
||||||
|
let virt_addr = block_descriptor_nr << TWO_MIB_SHIFT;
|
||||||
|
|
||||||
|
let (output_addr, attribute_fields) = match get_virt_addr_properties(virt_addr) {
|
||||||
|
Err(s) => return Err(s),
|
||||||
|
Ok((a, b)) => (a, b),
|
||||||
|
};
|
||||||
|
|
||||||
|
let block_desc = match Lvl2BlockDescriptor::new(output_addr, attribute_fields) {
|
||||||
|
Err(s) => return Err(s),
|
||||||
|
Ok(desc) => desc,
|
||||||
|
};
|
||||||
|
|
||||||
|
*entry = block_desc.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, fill the single LVL3 table (4 KiB granule).
|
||||||
|
for (page_descriptor_nr, entry) in LVL3_TABLE.entries.iter_mut().enumerate() {
|
||||||
|
let virt_addr = page_descriptor_nr << FOUR_KIB_SHIFT;
|
||||||
|
|
||||||
|
let (output_addr, attribute_fields) = match get_virt_addr_properties(virt_addr) {
|
||||||
|
Err(s) => return Err(s),
|
||||||
|
Ok((a, b)) => (a, b),
|
||||||
|
};
|
||||||
|
|
||||||
|
let page_desc = match PageDescriptor::new(output_addr, attribute_fields) {
|
||||||
|
Err(s) => return Err(s),
|
||||||
|
Ok(desc) => desc,
|
||||||
|
};
|
||||||
|
|
||||||
|
*entry = page_desc.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Point to the LVL2 table base address in TTBR0.
|
||||||
|
TTBR0_EL1.set_baddr(LVL2_TABLE.entries.base_addr_u64());
|
||||||
|
|
||||||
|
// Configure various settings of stage 1 of the EL1 translation regime.
|
||||||
|
let ips = ID_AA64MMFR0_EL1.read(ID_AA64MMFR0_EL1::PARange);
|
||||||
|
TCR_EL1.write(
|
||||||
|
TCR_EL1::TBI0::Ignored
|
||||||
|
+ TCR_EL1::IPS.val(ips)
|
||||||
|
+ TCR_EL1::TG0::KiB_4 // 4 KiB granule
|
||||||
|
+ TCR_EL1::SH0::Inner
|
||||||
|
+ TCR_EL1::ORGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable
|
||||||
|
+ TCR_EL1::IRGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable
|
||||||
|
+ TCR_EL1::EPD0::EnableTTBR0Walks
|
||||||
|
+ TCR_EL1::T0SZ.val(34), // Start walks at level 2
|
||||||
|
);
|
||||||
|
|
||||||
|
// Switch the MMU on.
|
||||||
|
//
|
||||||
|
// First, force all previous changes to be seen before the MMU is enabled.
|
||||||
|
barrier::isb(barrier::SY);
|
||||||
|
|
||||||
|
// Enable the MMU and turn on data and instruction caching.
|
||||||
|
SCTLR_EL1.modify(SCTLR_EL1::M::Enable + SCTLR_EL1::C::Cacheable + SCTLR_EL1::I::Cacheable);
|
||||||
|
|
||||||
|
// Force MMU init to complete before next instruction
|
||||||
|
/*
|
||||||
|
* Invalidate the local I-cache so that any instructions fetched
|
||||||
|
* speculatively from the PoC are discarded, since they may have
|
||||||
|
* been dynamically patched at the PoU.
|
||||||
|
*/
|
||||||
|
barrier::isb(barrier::SY);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1,204 @@
|
||||||
|
// mod arch::aarch64
|
||||||
|
|
||||||
|
mod boot;
|
||||||
|
pub mod memory;
|
||||||
|
pub mod mmu;
|
||||||
|
pub mod traps;
|
||||||
|
pub use self::memory::{PhysicalAddress, VirtualAddress};
|
||||||
|
pub use mmu::*;
|
||||||
|
|
||||||
|
use cortex_a::{asm, regs::*};
|
||||||
|
|
||||||
|
#[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.
|
||||||
|
pub fn jtag_dbg_wait() {
|
||||||
|
use core::ptr::{read_volatile, write_volatile};
|
||||||
|
|
||||||
|
while unsafe { read_volatile(&WAIT_FLAG) } {
|
||||||
|
asm::nop();
|
||||||
|
}
|
||||||
|
// Reset the flag so that next jtag_dbg_wait() would block again.
|
||||||
|
unsafe { write_volatile(&mut WAIT_FLAG, true) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn flushcache(address: usize) {
|
||||||
|
unsafe {
|
||||||
|
asm!("dc ivac, $0" :: "r"(address) :: "volatile");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn read_cpu_id() -> u64 {
|
||||||
|
const CORE_MASK: u64 = 0x3;
|
||||||
|
MPIDR_EL1.get() & CORE_MASK
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn current_el() -> u32 {
|
||||||
|
CurrentEL.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn endless_sleep() -> ! {
|
||||||
|
loop {
|
||||||
|
asm::wfe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn loop_delay(rounds: u32) {
|
||||||
|
for _ in 0..rounds {
|
||||||
|
asm::nop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn loop_until<F: Fn() -> bool>(f: F) {
|
||||||
|
loop {
|
||||||
|
if f() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
asm::nop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_translation_table_base() -> PhysicalAddress {
|
||||||
|
let mut base: PhysicalAddress = 0;
|
||||||
|
unsafe {
|
||||||
|
asm!("mrs $0, ttbr0_el1" : "=r"(base) ::: "volatile");
|
||||||
|
}
|
||||||
|
base
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_translation_control() -> u64 {
|
||||||
|
let mut tcr: u64 = 0;
|
||||||
|
unsafe {
|
||||||
|
asm!("mrs $0, tcr_el1" : "=r"(tcr) ::: "volatile");
|
||||||
|
}
|
||||||
|
tcr
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_mair() -> u64 {
|
||||||
|
let mut mair: u64 = 0;
|
||||||
|
unsafe {
|
||||||
|
asm!("mrs $0, mair_el1" : "=r"(mair) ::: "volatile");
|
||||||
|
}
|
||||||
|
mair
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_translation_table_base(base: PhysicalAddress) {
|
||||||
|
unsafe {
|
||||||
|
asm!("msr ttbr0_el1, $0" :: "r"(base) :: "volatile");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identity-map things for now.
|
||||||
|
//
|
||||||
|
// > but more normal the simplest form is a table with 1024 32 bit entries starting at
|
||||||
|
// a 0x4000 aligned address, where each entry describes a 1 Mb memory part.
|
||||||
|
// On the rpi3 only the bottom 1024 entries are relevant as it has 1 Gb memory.
|
||||||
|
|
||||||
|
// aarch64 granules and page sizes howto:
|
||||||
|
// https://stackoverflow.com/questions/34269185/simultaneous-existence-of-different-sized-pages-on-aarch64
|
||||||
|
|
||||||
|
// Code from redox-os:
|
||||||
|
|
||||||
|
// pub static mut IDTR: DescriptorTablePointer = DescriptorTablePointer {
|
||||||
|
// limit: 0,
|
||||||
|
// base: 0
|
||||||
|
// };
|
||||||
|
|
||||||
|
// pub static mut IDT: [IdtEntry; 256] = [IdtEntry::new(); 256];
|
||||||
|
|
||||||
|
// /// A physical address.
|
||||||
|
// #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||||
|
// pub struct PhysicalAddress(usize);
|
||||||
|
|
||||||
|
// impl PhysicalAddress {
|
||||||
|
// pub fn new(address: usize) -> Self {
|
||||||
|
// PhysicalAddress(address)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// pub fn get(&self) -> usize {
|
||||||
|
// self.0
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /// A virtual address.
|
||||||
|
// #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||||
|
// pub struct VirtualAddress(usize);
|
||||||
|
|
||||||
|
// impl VirtualAddress {
|
||||||
|
// pub fn new(address: usize) -> Self {
|
||||||
|
// VirtualAddress(address)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// pub fn get(&self) -> usize {
|
||||||
|
// self.0
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// bitflags! {
|
||||||
|
// pub struct MemType: u64 {
|
||||||
|
// const DEVICE_NGNRNE = 0 << 2;
|
||||||
|
// const DEVICE_NGNRE = 1 << 2;
|
||||||
|
// const DEVICE_GRE = 2 << 2;
|
||||||
|
// const NORMAL_NC = 3 << 2;
|
||||||
|
// const NORMAL = 4 << 2;
|
||||||
|
|
||||||
|
// const NS = 1 << 5;
|
||||||
|
|
||||||
|
// const NON_SHARE = 0 << 8;
|
||||||
|
// const OUTER_SHARE = 2 << 8;
|
||||||
|
// const INNER_SHARE = 3 << 8;
|
||||||
|
|
||||||
|
// const AF = 1 << 10;
|
||||||
|
// const NG = 1 << 11;
|
||||||
|
// const PXN = 1 << 53;
|
||||||
|
// const UXN = 1 << 54;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// struct MemMapRegion {
|
||||||
|
// virt: VirtualAddress,
|
||||||
|
// phys: PhysicalAddress,
|
||||||
|
// size: usize,
|
||||||
|
// attr: MemType, // MAIR flags
|
||||||
|
// }
|
||||||
|
|
||||||
|
// impl MemMapRegion {}
|
||||||
|
|
||||||
|
// fn setup_paging() {
|
||||||
|
// // test if paging is enabled
|
||||||
|
// // if so, loop here
|
||||||
|
|
||||||
|
// // @todo
|
||||||
|
// // Check mmu and dcache states, loop forever on some setting
|
||||||
|
|
||||||
|
// write_ttbr_tcr_mair(
|
||||||
|
// 1, // EL1
|
||||||
|
// read_translation_table_base(),
|
||||||
|
// read_translation_control(),
|
||||||
|
// read_mair(),
|
||||||
|
// );
|
||||||
|
|
||||||
|
// let _bcm2837_mem_map: [MemMapRegion; 2] = [
|
||||||
|
// MemMapRegion {
|
||||||
|
// virt: 0x0000_0000,
|
||||||
|
// phys: 0x0000_0000,
|
||||||
|
// size: 0x3f00_0000,
|
||||||
|
// attr: MemType::NORMAL | MemType::INNER_SHARE,
|
||||||
|
// },
|
||||||
|
// MemMapRegion {
|
||||||
|
// virt: 0x3f00_0000,
|
||||||
|
// phys: 0x3f00_0000,
|
||||||
|
// size: 0x0100_0000,
|
||||||
|
// attr: MemType::DEVICE_NGNRNE | MemType::NON_SHARE | MemType::PXN | MemType::UXN,
|
||||||
|
// },
|
||||||
|
// ];
|
||||||
|
// }
|
|
@ -0,0 +1,263 @@
|
||||||
|
// Interrupt handling
|
||||||
|
|
||||||
|
// The base address is given by VBAR_ELn and each entry has a defined offset from this
|
||||||
|
// base address. Each table has 16 entries, with each entry being 128 bytes (32 instructions)
|
||||||
|
// in size. The table effectively consists of 4 sets of 4 entries.
|
||||||
|
|
||||||
|
// Minimal implementation to help catch MMU traps
|
||||||
|
// Reads ESR_ELx to understand why trap was taken.
|
||||||
|
|
||||||
|
// VBAR_EL1, VBAR_EL2, VBAR_EL3
|
||||||
|
|
||||||
|
// CurrentEL with SP0: +0x0
|
||||||
|
|
||||||
|
// Synchronous
|
||||||
|
// IRQ/vIRQ
|
||||||
|
// FIQ
|
||||||
|
// SError/vSError
|
||||||
|
|
||||||
|
// CurrentEL with SPx: +0x200
|
||||||
|
|
||||||
|
// Synchronous
|
||||||
|
// IRQ/vIRQ
|
||||||
|
// FIQ
|
||||||
|
// SError/vSError
|
||||||
|
|
||||||
|
// Lower EL using AArch64: +0x400
|
||||||
|
|
||||||
|
// Synchronous
|
||||||
|
// IRQ/vIRQ
|
||||||
|
// FIQ
|
||||||
|
// SError/vSError
|
||||||
|
|
||||||
|
// Lower EL using AArch32: +0x600
|
||||||
|
|
||||||
|
// Synchronous
|
||||||
|
// IRQ/vIRQ
|
||||||
|
// FIQ
|
||||||
|
// SError/vSError
|
||||||
|
|
||||||
|
// When the processor takes an exception to AArch64 execution state,
|
||||||
|
// all of the PSTATE interrupt masks is set automatically. This means
|
||||||
|
// that further exceptions are disabled. If software is to support
|
||||||
|
// nested exceptions, for example, to allow a higher priority interrupt
|
||||||
|
// to interrupt the handling of a lower priority source, then software needs
|
||||||
|
// to explicitly re-enable interrupts
|
||||||
|
use crate::{arch::endless_sleep, println};
|
||||||
|
use cortex_a::{barrier, regs};
|
||||||
|
use register::cpu::RegisterReadWrite;
|
||||||
|
|
||||||
|
global_asm!(include_str!("vectors.S"));
|
||||||
|
|
||||||
|
pub unsafe fn set_vbar_el1_checked(vec_base_addr: u64) -> bool {
|
||||||
|
if vec_base_addr.trailing_zeros() < 11 {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
regs::VBAR_EL1.set(vec_base_addr);
|
||||||
|
|
||||||
|
// Force VBAR update to complete before next instruction.
|
||||||
|
barrier::isb(barrier::SY);
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct GPR {
|
||||||
|
x: [u64; 31],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct ExceptionContext {
|
||||||
|
// General Purpose Registers
|
||||||
|
gpr: GPR,
|
||||||
|
spsr_el1: u64,
|
||||||
|
elr_el1: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The default exception, invoked for every exception type unless the handler
|
||||||
|
/// is overwritten.
|
||||||
|
#[no_mangle]
|
||||||
|
unsafe extern "C" fn default_exception_handler() -> ! {
|
||||||
|
println!("Unexpected exception. Halting CPU.");
|
||||||
|
|
||||||
|
endless_sleep()
|
||||||
|
}
|
||||||
|
|
||||||
|
mod esr_el1 {
|
||||||
|
// use cortex_a::{sys_coproc_read_raw, sys_coproc_write_raw};
|
||||||
|
use register::{cpu::RegisterReadWrite, register_bitfields};
|
||||||
|
|
||||||
|
pub struct Reg;
|
||||||
|
|
||||||
|
register_bitfields! {
|
||||||
|
u64,
|
||||||
|
|
||||||
|
ESR_EL1 [
|
||||||
|
ISS OFFSET(0) NUMBITS(25) [], // @todo Additional ISS encodings
|
||||||
|
IL OFFSET(25) NUMBITS(1) [], // Instruction Length
|
||||||
|
EC OFFSET(26) NUMBITS(6) [
|
||||||
|
Unknown = 0b000_000,
|
||||||
|
TrappedWfiOrWfe = 0b000_001,
|
||||||
|
TrappedMcrOrMrc = 0b000_011,
|
||||||
|
TrappedMcrrOrMrrc = 0b000_100,
|
||||||
|
TrappedMcrOrMrc2 = 0b000_101,
|
||||||
|
TrappedLdcOrStc = 0b000_110,
|
||||||
|
TrappedAdvSIMD = 0b000_111,
|
||||||
|
TrappedMrrc = 0b001_100,
|
||||||
|
IllegalExecState = 0b001_110,
|
||||||
|
SvcInAArch32 = 0b010_001,
|
||||||
|
SvcInAArch64 = 0b010_101,
|
||||||
|
TrappedMrsOrMsr = 0b011_000,
|
||||||
|
TrappedSve = 0b011_001,
|
||||||
|
InsnAbortFromLowerEL = 0b100_000,
|
||||||
|
InsnAbortFromSameEL = 0b100_001,
|
||||||
|
PcAlignmentFault = 0b100_010,
|
||||||
|
DataAbortFromLowerEL = 0b100_100,
|
||||||
|
DataAbortFromSameEL = 0b100_101,
|
||||||
|
SpAlignmentFault = 0b100_110,
|
||||||
|
TrappedFpuFromAArch32 = 0b101_000,
|
||||||
|
TrappedFpuFromAArch64 = 0b101_100,
|
||||||
|
SError = 0b101_111,
|
||||||
|
BreakpointFromLowerEL = 0b110_000,
|
||||||
|
BreakpointFromSameEL = 0b110_001,
|
||||||
|
SoftwareStepFromLowerEL = 0b110_010,
|
||||||
|
SoftwareStepFromSameEL = 0b110_011,
|
||||||
|
WatchpointFromLowerEL = 0b110_100,
|
||||||
|
WatchpointFromSameEL = 0b110_101,
|
||||||
|
BrkptInAArch32 = 0b111_000,
|
||||||
|
BrkInAArch64 = 0b111_100
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RegisterReadWrite<u64, ESR_EL1::Register> for Reg {
|
||||||
|
// sys_coproc_read_raw!(u64, "ESR_EL1");
|
||||||
|
// sys_coproc_write_raw!(u64, "ESR_EL1");
|
||||||
|
|
||||||
|
// Manually unmacroed
|
||||||
|
/// Reads the raw bits of the CPU register.
|
||||||
|
#[inline]
|
||||||
|
fn get(&self) -> u64 {
|
||||||
|
match () {
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
() => {
|
||||||
|
let reg;
|
||||||
|
unsafe {
|
||||||
|
asm!(concat!("mrs", " $0, ", "ESR_EL1") : "=r"(reg) ::: "volatile");
|
||||||
|
}
|
||||||
|
reg
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "aarch64"))]
|
||||||
|
() => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes raw bits to the CPU register.
|
||||||
|
#[cfg_attr(not(target_arch = "aarch64"), allow(unused_variables))]
|
||||||
|
#[inline]
|
||||||
|
fn set(&self, value: u64) {
|
||||||
|
match () {
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
() => unsafe {
|
||||||
|
asm!(concat!("msr", " ", "ESR_EL1", ", $0") :: "r"(value) :: "volatile")
|
||||||
|
},
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "aarch64"))]
|
||||||
|
() => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static ESR_EL1: Reg = Reg {};
|
||||||
|
}
|
||||||
|
|
||||||
|
mod far_el1 {
|
||||||
|
use register::cpu::RegisterReadWrite;
|
||||||
|
|
||||||
|
pub struct Reg;
|
||||||
|
|
||||||
|
impl RegisterReadWrite<u64, ()> for Reg {
|
||||||
|
// sys_coproc_read_raw!(u64, "FAR_EL1");
|
||||||
|
// sys_coproc_write_raw!(u64, "FAR_EL1");
|
||||||
|
|
||||||
|
// Manually unmacroed
|
||||||
|
/// Reads the raw bits of the CPU register.
|
||||||
|
#[inline]
|
||||||
|
fn get(&self) -> u64 {
|
||||||
|
match () {
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
() => {
|
||||||
|
let reg;
|
||||||
|
unsafe {
|
||||||
|
asm!(concat!("mrs", " $0, ", "FAR_EL1") : "=r"(reg) ::: "volatile");
|
||||||
|
}
|
||||||
|
reg
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "aarch64"))]
|
||||||
|
() => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes raw bits to the CPU register.
|
||||||
|
#[cfg_attr(not(target_arch = "aarch64"), allow(unused_variables))]
|
||||||
|
#[inline]
|
||||||
|
fn set(&self, value: u64) {
|
||||||
|
match () {
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
() => unsafe {
|
||||||
|
asm!(concat!("msr", " ", "FAR_EL1", ", $0") :: "r"(value) :: "volatile")
|
||||||
|
},
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "aarch64"))]
|
||||||
|
() => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static FAR_EL1: Reg = Reg {};
|
||||||
|
}
|
||||||
|
|
||||||
|
use esr_el1::ESR_EL1;
|
||||||
|
use far_el1::FAR_EL1;
|
||||||
|
|
||||||
|
// To implement an exception handler, overwrite it by defining the respective
|
||||||
|
// function below.
|
||||||
|
// Don't forget the #[no_mangle] attribute.
|
||||||
|
//
|
||||||
|
// unsafe extern "C" fn current_el0_synchronous(e: &mut ExceptionContext);
|
||||||
|
// unsafe extern "C" fn current_el0_irq(e: &mut ExceptionContext);
|
||||||
|
// unsafe extern "C" fn current_el0_serror(e: &mut ExceptionContext);
|
||||||
|
|
||||||
|
// unsafe extern "C" fn current_elx_synchronous(e: &mut ExceptionContext);
|
||||||
|
// unsafe extern "C" fn current_elx_irq(e: &mut ExceptionContext);
|
||||||
|
// unsafe extern "C" fn current_elx_serror(e: &mut ExceptionContext);
|
||||||
|
|
||||||
|
// unsafe extern "C" fn lower_aarch64_synchronous(e: &mut ExceptionContext);
|
||||||
|
// unsafe extern "C" fn lower_aarch64_irq(e: &mut ExceptionContext);
|
||||||
|
// unsafe extern "C" fn lower_aarch64_serror(e: &mut ExceptionContext);
|
||||||
|
|
||||||
|
// unsafe extern "C" fn lower_aarch32_synchronous(e: &mut ExceptionContext);
|
||||||
|
// unsafe extern "C" fn lower_aarch32_irq(e: &mut ExceptionContext);
|
||||||
|
// unsafe extern "C" fn lower_aarch32_serror(e: &mut ExceptionContext);
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
unsafe extern "C" fn current_elx_synchronous(e: &mut ExceptionContext) {
|
||||||
|
println!("[!] A synchronous exception happened.");
|
||||||
|
println!(" ESR_EL1: {:#010x} (syndrome)", ESR_EL1.get());
|
||||||
|
println!(" EC: {:#06b} (cause)", ESR_EL1.read(ESR_EL1::EC));
|
||||||
|
println!(" FAR_EL1: {:#016x} (location)", FAR_EL1.get());
|
||||||
|
println!(" ELR_EL1: {:#010x}", e.elr_el1);
|
||||||
|
|
||||||
|
println!(
|
||||||
|
" Incrementing ELR_EL1 by 4 now to continue with the first \
|
||||||
|
instruction after the exception!"
|
||||||
|
);
|
||||||
|
|
||||||
|
e.elr_el1 += 4;
|
||||||
|
|
||||||
|
println!(" ELR_EL1 modified: {:#010x}", e.elr_el1);
|
||||||
|
println!(" Returning from exception...\n");
|
||||||
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
//
|
||||||
|
// MIT License
|
||||||
|
//
|
||||||
|
// Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
//
|
||||||
|
|
||||||
|
.macro SAVE_CONTEXT_CALL_HANDLER_AND_RESTORE handler
|
||||||
|
.balign 0x80
|
||||||
|
|
||||||
|
sub sp, sp, #16 * 17
|
||||||
|
|
||||||
|
stp x0, x1, [sp, #16 * 0]
|
||||||
|
stp x2, x3, [sp, #16 * 1]
|
||||||
|
stp x4, x5, [sp, #16 * 2]
|
||||||
|
stp x6, x7, [sp, #16 * 3]
|
||||||
|
stp x8, x9, [sp, #16 * 4]
|
||||||
|
stp x10, x11, [sp, #16 * 5]
|
||||||
|
stp x12, x13, [sp, #16 * 6]
|
||||||
|
stp x14, x15, [sp, #16 * 7]
|
||||||
|
stp x16, x17, [sp, #16 * 8]
|
||||||
|
stp x18, x19, [sp, #16 * 9]
|
||||||
|
stp x20, x21, [sp, #16 * 10]
|
||||||
|
stp x22, x23, [sp, #16 * 11]
|
||||||
|
stp x24, x25, [sp, #16 * 12]
|
||||||
|
stp x26, x27, [sp, #16 * 13]
|
||||||
|
stp x28, x29, [sp, #16 * 14]
|
||||||
|
|
||||||
|
mrs x1, SPSR_EL1
|
||||||
|
mrs x2, ELR_EL1
|
||||||
|
|
||||||
|
stp x30, x1, [sp, #16 * 15]
|
||||||
|
str x2, [sp, #16 * 16]
|
||||||
|
|
||||||
|
mov x0, sp
|
||||||
|
bl \handler
|
||||||
|
b __restore_context
|
||||||
|
.endm
|
||||||
|
|
||||||
|
.macro FIQ_DUMMY
|
||||||
|
.balign 0x80
|
||||||
|
1: wfe
|
||||||
|
b 1b
|
||||||
|
.endm
|
||||||
|
|
||||||
|
// The vector definitions
|
||||||
|
.section .vectors, "ax"
|
||||||
|
.global __exception_vectors_start
|
||||||
|
__exception_vectors_start:
|
||||||
|
SAVE_CONTEXT_CALL_HANDLER_AND_RESTORE current_el0_synchronous // 0x000
|
||||||
|
SAVE_CONTEXT_CALL_HANDLER_AND_RESTORE current_el0_irq // 0x080
|
||||||
|
FIQ_DUMMY // 0x100
|
||||||
|
SAVE_CONTEXT_CALL_HANDLER_AND_RESTORE current_el0_serror // 0x180
|
||||||
|
|
||||||
|
SAVE_CONTEXT_CALL_HANDLER_AND_RESTORE current_elx_synchronous // 0x200
|
||||||
|
SAVE_CONTEXT_CALL_HANDLER_AND_RESTORE current_elx_irq // 0x280
|
||||||
|
FIQ_DUMMY // 0x300
|
||||||
|
SAVE_CONTEXT_CALL_HANDLER_AND_RESTORE current_elx_serror // 0x380
|
||||||
|
|
||||||
|
SAVE_CONTEXT_CALL_HANDLER_AND_RESTORE lower_aarch64_synchronous // 0x400
|
||||||
|
SAVE_CONTEXT_CALL_HANDLER_AND_RESTORE lower_aarch64_irq // 0x480
|
||||||
|
FIQ_DUMMY // 0x500
|
||||||
|
SAVE_CONTEXT_CALL_HANDLER_AND_RESTORE lower_aarch64_serror // 0x580
|
||||||
|
|
||||||
|
SAVE_CONTEXT_CALL_HANDLER_AND_RESTORE lower_aarch32_synchronous // 0x600
|
||||||
|
SAVE_CONTEXT_CALL_HANDLER_AND_RESTORE lower_aarch32_irq // 0x680
|
||||||
|
FIQ_DUMMY // 0x700
|
||||||
|
SAVE_CONTEXT_CALL_HANDLER_AND_RESTORE lower_aarch32_serror // 0x780
|
||||||
|
|
||||||
|
.balign 0x80
|
||||||
|
.global __restore_context
|
||||||
|
__restore_context:
|
||||||
|
ldr x19, [sp, #16 * 16]
|
||||||
|
ldp x30, x20, [sp, #16 * 15]
|
||||||
|
|
||||||
|
msr ELR_EL1, x19
|
||||||
|
msr SPSR_EL1, x20
|
||||||
|
|
||||||
|
ldp x0, x1, [sp, #16 * 0]
|
||||||
|
ldp x2, x3, [sp, #16 * 1]
|
||||||
|
ldp x4, x5, [sp, #16 * 2]
|
||||||
|
ldp x6, x7, [sp, #16 * 3]
|
||||||
|
ldp x8, x9, [sp, #16 * 4]
|
||||||
|
ldp x10, x11, [sp, #16 * 5]
|
||||||
|
ldp x12, x13, [sp, #16 * 6]
|
||||||
|
ldp x14, x15, [sp, #16 * 7]
|
||||||
|
ldp x16, x17, [sp, #16 * 8]
|
||||||
|
ldp x18, x19, [sp, #16 * 9]
|
||||||
|
ldp x20, x21, [sp, #16 * 10]
|
||||||
|
ldp x22, x23, [sp, #16 * 11]
|
||||||
|
ldp x24, x25, [sp, #16 * 12]
|
||||||
|
ldp x26, x27, [sp, #16 * 13]
|
||||||
|
ldp x28, x29, [sp, #16 * 14]
|
||||||
|
|
||||||
|
add sp, sp, #16 * 17
|
||||||
|
|
||||||
|
eret
|
|
@ -0,0 +1,13 @@
|
||||||
|
// mod arch
|
||||||
|
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
#[macro_use]
|
||||||
|
pub mod aarch64;
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
pub use self::aarch64::*;
|
||||||
|
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
#[macro_use]
|
||||||
|
pub mod x86_64;
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
pub use self::x86_64::*;
|
|
@ -0,0 +1 @@
|
||||||
|
// mod arch::x86_64
|
|
@ -0,0 +1,162 @@
|
||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019 Andre Richter <andre.o.richter@gmail.com>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::jlink_rtt::Output as JLinkOutput;
|
||||||
|
use crate::platform;
|
||||||
|
use core::fmt::{self, Write};
|
||||||
|
|
||||||
|
/// A trait that must be implemented by devices that are candidates for the
|
||||||
|
/// global console.
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
pub trait ConsoleOps: Drop {
|
||||||
|
fn putc(&self, c: char) {}
|
||||||
|
fn puts(&self, string: &str) {}
|
||||||
|
fn getc(&self) -> char {
|
||||||
|
' '
|
||||||
|
}
|
||||||
|
fn flush(&self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A dummy console that just ignores its inputs.
|
||||||
|
pub struct NullConsole;
|
||||||
|
impl Drop for NullConsole {
|
||||||
|
fn drop(&mut self) {}
|
||||||
|
}
|
||||||
|
impl ConsoleOps for NullConsole {}
|
||||||
|
|
||||||
|
/// Possible outputs which the console can store.
|
||||||
|
pub enum Output {
|
||||||
|
None(NullConsole),
|
||||||
|
MiniUart(platform::MiniUart),
|
||||||
|
PL011Uart(platform::PL011Uart),
|
||||||
|
RTT(JLinkOutput),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<platform::MiniUart> for Output {
|
||||||
|
fn from(instance: platform::MiniUart) -> Self {
|
||||||
|
Output::MiniUart(instance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<platform::PL011Uart> for Output {
|
||||||
|
fn from(instance: platform::PL011Uart) -> Self {
|
||||||
|
Output::PL011Uart(instance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<JLinkOutput> for Output {
|
||||||
|
fn from(instance: JLinkOutput) -> Self {
|
||||||
|
Output::RTT(instance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Console {
|
||||||
|
output: Output,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Console {
|
||||||
|
pub const fn new() -> Console {
|
||||||
|
Console {
|
||||||
|
output: Output::None(NullConsole {}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn current_ptr(&self) -> &dyn ConsoleOps {
|
||||||
|
match &self.output {
|
||||||
|
Output::None(i) => i,
|
||||||
|
Output::MiniUart(i) => i,
|
||||||
|
Output::PL011Uart(i) => i,
|
||||||
|
Output::RTT(i) => i,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Overwrite the current output. The old output will go out of scope and
|
||||||
|
/// it's Drop function will be called.
|
||||||
|
pub fn replace_with(&mut self, x: Output) {
|
||||||
|
self.current_ptr().flush();
|
||||||
|
|
||||||
|
self.output = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A command prompt.
|
||||||
|
pub fn command_prompt<'a>(&self, buf: &'a mut [u8]) -> &'a [u8] {
|
||||||
|
self.puts("\n$> ");
|
||||||
|
|
||||||
|
let mut i = 0;
|
||||||
|
let mut input;
|
||||||
|
loop {
|
||||||
|
input = self.getc();
|
||||||
|
|
||||||
|
if input == '\n' {
|
||||||
|
self.puts("\n"); // do \r\n output
|
||||||
|
return &buf[..i];
|
||||||
|
} else {
|
||||||
|
if i < buf.len() {
|
||||||
|
buf[i] = input as u8;
|
||||||
|
i += 1;
|
||||||
|
} else {
|
||||||
|
return &buf[..i];
|
||||||
|
}
|
||||||
|
|
||||||
|
self.putc(input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Console {
|
||||||
|
fn drop(&mut self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 puts(&self, string: &str) {
|
||||||
|
self.current_ptr().puts(string);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getc(&self) -> char {
|
||||||
|
self.current_ptr().getc()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&self) {
|
||||||
|
self.current_ptr().flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implementing this trait enables usage of the format_args! macros, which in
|
||||||
|
/// turn are used to implement the kernel's print! and println! macros.
|
||||||
|
///
|
||||||
|
/// See src/macros.rs.
|
||||||
|
impl fmt::Write for Console {
|
||||||
|
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||||
|
self.current_ptr().puts(s);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod console;
|
||||||
|
|
||||||
|
pub use console::{Console, ConsoleOps};
|
|
@ -0,0 +1,286 @@
|
||||||
|
// Custom implementation of JLink RTT debug protocol
|
||||||
|
// jlink_rtt crate has too many strange bugs
|
||||||
|
|
||||||
|
/// This module implements a limited version of the Segger
|
||||||
|
/// Real Time Transfer protocol between the debugger host
|
||||||
|
/// and the target program.
|
||||||
|
/// RTT works by scanning memory to look for a control block
|
||||||
|
/// containing a magic string (it is also possible to tell
|
||||||
|
/// the monitor exactly where to find this block).
|
||||||
|
/// The control block defines a set of "up" channels
|
||||||
|
/// and "down" channels that are named pipes of communication
|
||||||
|
/// between the two systems.
|
||||||
|
/// Each of these channels is implemented as a simple
|
||||||
|
/// ring buffer.
|
||||||
|
/// The cost of logging data to RTT is the cost of formatting
|
||||||
|
/// and writing it to the ring buffer in memory.
|
||||||
|
use core::fmt;
|
||||||
|
use core::mem::size_of;
|
||||||
|
use core::ptr;
|
||||||
|
use static_assertions::const_assert_eq;
|
||||||
|
|
||||||
|
static mut UP_BUF: [u8; 1024] = [0u8; 1024];
|
||||||
|
static mut DOWN_BUF: [u8; 16] = [0u8; 16];
|
||||||
|
|
||||||
|
/// Ring buffer for communicating between target and host.
|
||||||
|
/// This must be binary compatible with the RTT implementation
|
||||||
|
/// in the JLINK device.
|
||||||
|
#[repr(C)]
|
||||||
|
struct Buffer {
|
||||||
|
name: u32, //*const u8,
|
||||||
|
buf_start: u32, // *mut u8,
|
||||||
|
size_of_buffer: u32,
|
||||||
|
/// Position of next item to be written
|
||||||
|
/// Volatile as the host may change it.
|
||||||
|
write_offset: u32,
|
||||||
|
/// Position of next item to be read by host.
|
||||||
|
/// Volatile as the host may change it.
|
||||||
|
read_offset: u32,
|
||||||
|
/// In the segger library these flags control blocking
|
||||||
|
/// or non-blocking behavior.
|
||||||
|
flags: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assumed by OpenOCD and probably JLink too...
|
||||||
|
const_assert_eq!(size_of::<Buffer>(), 24);
|
||||||
|
|
||||||
|
// Operating modes. Define behavior if buffer is full (not enough space for entire message)
|
||||||
|
const NO_BLOCK_SKIP: u32 = 0; // Skip. Do not block, output nothing. (Default)
|
||||||
|
const NO_BLOCK_TRIM: u32 = 1; // Trim: Do not block, output as much as fits.
|
||||||
|
const BLOCK_IF_FULL: u32 = 2; // Block: Wait until there is space in the buffer.
|
||||||
|
|
||||||
|
impl Buffer {
|
||||||
|
fn init(&mut self, buf: &mut [u8]) {
|
||||||
|
self.name = b"Terminal\0".as_ptr() as u32;
|
||||||
|
self.buf_start = buf.as_mut_ptr() as u32;
|
||||||
|
self.size_of_buffer = buf.len() as u32;
|
||||||
|
self.write_offset = 0;
|
||||||
|
self.read_offset = 0;
|
||||||
|
self.flags = BLOCK_IF_FULL; // Blocking mode
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_read_offset(&self) -> u32 {
|
||||||
|
unsafe { ptr::read_volatile(&self.read_offset as *const u32) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
fn set_read_offset(&mut self, offset: u32) {
|
||||||
|
unsafe {
|
||||||
|
ptr::write_volatile(&mut self.read_offset as *mut u32, offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_write_offset(&self) -> u32 {
|
||||||
|
unsafe { ptr::read_volatile(&self.write_offset as *const u32) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_write_offset(&mut self, offset: u32) {
|
||||||
|
unsafe {
|
||||||
|
ptr::write_volatile(&mut self.write_offset as *mut u32, offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write data to the ring buffer.
|
||||||
|
/// Returns true if all of the data was written, which
|
||||||
|
/// will always be the case if blocking==true.
|
||||||
|
/// Returns false if blocking==false and the buffer was
|
||||||
|
/// full.
|
||||||
|
fn write(&mut self, buf: &[u8], blocking: bool) -> bool {
|
||||||
|
let mut buf = buf;
|
||||||
|
let mut write_off = self.get_write_offset() as usize;
|
||||||
|
let size_of_buffer = self.size_of_buffer as usize;
|
||||||
|
while buf.len() > 0 {
|
||||||
|
let read_off = self.get_read_offset() as usize;
|
||||||
|
|
||||||
|
let wrapping_capacity = if read_off > write_off {
|
||||||
|
read_off - write_off - 1
|
||||||
|
} else {
|
||||||
|
size_of_buffer - (write_off - read_off + 1)
|
||||||
|
};
|
||||||
|
|
||||||
|
// If we're full and non-blocking, return now.
|
||||||
|
// Otherwise, we'll spin with a series of 0 byte
|
||||||
|
// length increments until the host consumes data
|
||||||
|
// from the ring buffer.
|
||||||
|
if wrapping_capacity == 0 && !blocking {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let flat_capacity = size_of_buffer - write_off;
|
||||||
|
|
||||||
|
let to_copy = buf.len().min(flat_capacity).min(wrapping_capacity);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
ptr::copy(
|
||||||
|
buf.as_ptr(),
|
||||||
|
(self.buf_start as *mut u8).offset(write_off as isize),
|
||||||
|
to_copy,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
write_off += to_copy;
|
||||||
|
if write_off == size_of_buffer {
|
||||||
|
write_off = 0;
|
||||||
|
}
|
||||||
|
self.set_write_offset(write_off as u32);
|
||||||
|
|
||||||
|
buf = &buf[to_copy..];
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill in buf
|
||||||
|
fn read(&mut self, _buf: &mut [u8], _blocking: bool) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The ControlBlock is the magic struct that the JLINK looks
|
||||||
|
/// for to discover the ring buffers.
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct ControlBlock {
|
||||||
|
/// Initialized to "SEGGER RTT"
|
||||||
|
id: [u8; 16],
|
||||||
|
/// Initialized to 1
|
||||||
|
max_up_buffers: i32,
|
||||||
|
/// Initialized to 1
|
||||||
|
max_down_buffers: i32,
|
||||||
|
/// Note that RTT allows for this to be an array of
|
||||||
|
/// "up" buffers of size max_up_buffers, but for simplicity
|
||||||
|
/// just a single buffer is implemented here.
|
||||||
|
up: Buffer,
|
||||||
|
/// Note that RTT allows for this to be an array of
|
||||||
|
/// "down" buffers of size max_down_buffers, but for simplicity
|
||||||
|
/// just a single buffer is implemented here.
|
||||||
|
down: Buffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
const_assert_eq!(size_of::<ControlBlock>(), 24 + 24 + 24);
|
||||||
|
|
||||||
|
unsafe impl Sync for ControlBlock {}
|
||||||
|
|
||||||
|
impl ControlBlock {
|
||||||
|
fn init(&mut self) {
|
||||||
|
if self.id[0] == b'S' {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unsafe: use of mutable static
|
||||||
|
// mutable statics can be mutated by multiple threads: aliasing violations
|
||||||
|
// or data races will cause undefined behavior
|
||||||
|
unsafe {
|
||||||
|
self.up.init(&mut UP_BUF);
|
||||||
|
self.down.init(&mut DOWN_BUF);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compose the ident string such that we won't
|
||||||
|
// emit the string sequence in flash
|
||||||
|
self.id.copy_from_slice(b"_EGGER:RTT\0\0\0\0\0\0");
|
||||||
|
self.id[0] = b'S';
|
||||||
|
self.id[6] = b' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub static mut _SEGGER_RTT: ControlBlock = ControlBlock {
|
||||||
|
id: [0u8; 16],
|
||||||
|
max_up_buffers: 1,
|
||||||
|
max_down_buffers: 1,
|
||||||
|
up: Buffer {
|
||||||
|
name: 0,
|
||||||
|
buf_start: 0,
|
||||||
|
read_offset: 0,
|
||||||
|
write_offset: 0,
|
||||||
|
flags: BLOCK_IF_FULL,
|
||||||
|
size_of_buffer: 0,
|
||||||
|
},
|
||||||
|
down: Buffer {
|
||||||
|
name: 0,
|
||||||
|
buf_start: 0,
|
||||||
|
write_offset: 0,
|
||||||
|
read_offset: 0,
|
||||||
|
flags: BLOCK_IF_FULL,
|
||||||
|
size_of_buffer: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A blocking output stream allowing data to be logged from the
|
||||||
|
/// target to the host.
|
||||||
|
/// Implements fmt::Write.
|
||||||
|
pub struct Output {}
|
||||||
|
|
||||||
|
impl Output {
|
||||||
|
/// Create a blocking output stream
|
||||||
|
#[inline]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
unsafe {
|
||||||
|
_SEGGER_RTT.init();
|
||||||
|
}
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Output {
|
||||||
|
fn drop(&mut self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::devices::ConsoleOps for Output {
|
||||||
|
fn puts(&self, s: &str) {
|
||||||
|
unsafe {
|
||||||
|
_SEGGER_RTT.up.write(s.as_bytes(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn putc(&self, c: char) {
|
||||||
|
let mut buf = [0u8; 4];
|
||||||
|
let s = c.encode_utf8(&mut buf);
|
||||||
|
self.puts(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getc(&self) -> char {
|
||||||
|
' '
|
||||||
|
// _SEGGER_RTT.down.read(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&self) {} // @todo wait for write buffer to drain
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Write for Output {
|
||||||
|
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||||
|
use crate::devices::console::ConsoleOps;
|
||||||
|
self.puts(s);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A non-blocking output stream allowing data to be logged from the
|
||||||
|
/// target to the host.
|
||||||
|
/// Implements fmt::Write.
|
||||||
|
pub struct NonBlockingOutput {
|
||||||
|
blocked: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NonBlockingOutput {
|
||||||
|
/// Create a non-blocking output stream
|
||||||
|
#[inline]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
unsafe {
|
||||||
|
_SEGGER_RTT.init();
|
||||||
|
}
|
||||||
|
Self { blocked: false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Write for NonBlockingOutput {
|
||||||
|
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||||
|
if !self.blocked {
|
||||||
|
unsafe {
|
||||||
|
if !_SEGGER_RTT.up.write(s.as_bytes(), false) {
|
||||||
|
self.blocked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019 Andre Richter <andre.o.richter@gmail.com>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use core::fmt;
|
||||||
|
|
||||||
|
// https://doc.rust-lang.org/src/std/macros.rs.html
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! print {
|
||||||
|
($($arg:tt)*) => ($crate::macros::_print(format_args!($($arg)*)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://doc.rust-lang.org/src/std/macros.rs.html
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! println {
|
||||||
|
() => (print!("\n"));
|
||||||
|
($($arg:tt)*) => ({
|
||||||
|
$crate::macros::_print(format_args_nl!($($arg)*));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn _print(args: fmt::Arguments) {
|
||||||
|
use core::fmt::Write;
|
||||||
|
|
||||||
|
crate::CONSOLE.lock(|c| {
|
||||||
|
c.write_fmt(args).unwrap();
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,277 @@
|
||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
#![feature(asm)]
|
||||||
|
#![feature(global_asm)]
|
||||||
|
#![feature(const_fn)]
|
||||||
|
#![feature(format_args_nl)]
|
||||||
|
#![feature(lang_items)]
|
||||||
|
#![feature(ptr_internals)] // until we mark with PhantomData instead?
|
||||||
|
#![feature(core_intrinsics)]
|
||||||
|
#![feature(range_contains)]
|
||||||
|
#![feature(underscore_const_names)]
|
||||||
|
#![feature(allocator_api)]
|
||||||
|
#![doc(html_root_url = "https://docs.metta.systems/")]
|
||||||
|
#![allow(dead_code)]
|
||||||
|
#![allow(unused_assignments)]
|
||||||
|
#![allow(unused_must_use)]
|
||||||
|
#![allow(unused_imports)]
|
||||||
|
|
||||||
|
//any(target_arch = "aarch64", target_arch = "x86_64")
|
||||||
|
#[cfg(not(target_arch = "aarch64"))]
|
||||||
|
use architecture_not_supported_sorry;
|
||||||
|
|
||||||
|
extern crate bitflags;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate register;
|
||||||
|
extern crate cortex_a;
|
||||||
|
extern crate rlibc;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
pub mod arch;
|
||||||
|
pub use arch::*;
|
||||||
|
mod devices;
|
||||||
|
mod jlink_rtt;
|
||||||
|
mod macros;
|
||||||
|
pub mod platform;
|
||||||
|
mod sync;
|
||||||
|
mod write_to;
|
||||||
|
|
||||||
|
use core::fmt::Write;
|
||||||
|
#[cfg(feature = "jlink")]
|
||||||
|
use jlink_rtt::Output;
|
||||||
|
use platform::{
|
||||||
|
display::{Color, Size2d},
|
||||||
|
gpio::GPIO,
|
||||||
|
mailbox::{channel, Mailbox},
|
||||||
|
power::Power,
|
||||||
|
vc::VC,
|
||||||
|
};
|
||||||
|
|
||||||
|
// User-facing kernel parts - syscalls and capability invocations.
|
||||||
|
// pub mod vesper; -- no mod exported, because available through syscall interface
|
||||||
|
|
||||||
|
// Actual interfaces to call these syscalls are in vesper-user (similar to libsel4)
|
||||||
|
// pub mod vesper; -- exported from vesper-user
|
||||||
|
|
||||||
|
/// The global console. Output of the print! and println! macros.
|
||||||
|
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<memory::BumpAllocator> =
|
||||||
|
sync::NullLock::new(memory::BumpAllocator::new(
|
||||||
|
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",
|
||||||
|
));
|
||||||
|
|
||||||
|
fn init_jlink_rtt() {
|
||||||
|
CONSOLE.lock(|c| {
|
||||||
|
c.replace_with(Output::new().into());
|
||||||
|
});
|
||||||
|
|
||||||
|
println!("\n[0] JLink RTT is live!");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_uart_serial() {
|
||||||
|
let gpio = GPIO::new_default();
|
||||||
|
|
||||||
|
let uart = platform::MiniUart::new_default();
|
||||||
|
uart.init(&gpio);
|
||||||
|
CONSOLE.lock(|c| {
|
||||||
|
// Moves uart into the global CONSOLE. It is not accessible
|
||||||
|
// anymore for the remaining parts of kernel_entry().
|
||||||
|
c.replace_with(uart.into());
|
||||||
|
});
|
||||||
|
|
||||||
|
println!("[0] MiniUART is live!");
|
||||||
|
|
||||||
|
let uart = platform::PL011Uart::new_default();
|
||||||
|
|
||||||
|
let mut mbox = platform::mailbox::Mailbox::default();
|
||||||
|
|
||||||
|
// uart.init() will reconfigure the GPIO, which causes a race against
|
||||||
|
// the MiniUart that is still putting out characters on the physical
|
||||||
|
// line that are already buffered in its TX FIFO.
|
||||||
|
//
|
||||||
|
// To ensure the CPU doesn't rewire the GPIO before the MiniUart has put
|
||||||
|
// its last character, explicitly flush it before rewiring.
|
||||||
|
//
|
||||||
|
// If you switch to an output that happens to not use the same pair of
|
||||||
|
// 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.init(&mut mbox, &gpio) {
|
||||||
|
Ok(_) => {
|
||||||
|
CONSOLE.lock(|c| {
|
||||||
|
// Moves uart into the global CONSOLE. It is not accessible
|
||||||
|
// anymore for the remaining parts of kernel_entry().
|
||||||
|
c.replace_with(uart.into());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Err(_) => endless_sleep(), // @todo ignore error because MiniUart is still there?
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("[0] UART0 is live!");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_exception_traps() {
|
||||||
|
extern "C" {
|
||||||
|
static __exception_vectors_start: u64;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let exception_vectors_start: u64 = &__exception_vectors_start as *const _ as u64;
|
||||||
|
|
||||||
|
arch::traps::set_vbar_el1_checked(exception_vectors_start);
|
||||||
|
}
|
||||||
|
println!("Exception traps set up");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kernel entry point
|
||||||
|
// arch crate is responsible for calling this
|
||||||
|
fn kmain() -> ! {
|
||||||
|
init_jlink_rtt();
|
||||||
|
init_uart_serial();
|
||||||
|
init_exception_traps();
|
||||||
|
|
||||||
|
//------------------------------------------------------------
|
||||||
|
// Start a command prompt
|
||||||
|
//------------------------------------------------------------
|
||||||
|
'cmd_loop: loop {
|
||||||
|
let mut buf = [0u8; 64];
|
||||||
|
|
||||||
|
match CONSOLE.lock(|c| c.command_prompt(&mut buf)) {
|
||||||
|
b"mmu" => init_mmu(),
|
||||||
|
b"uart" => init_uart_serial(),
|
||||||
|
b"disp" => check_display_init(),
|
||||||
|
b"trap" => check_data_abort_trap(),
|
||||||
|
b"map" => arch::memory::print_layout(),
|
||||||
|
b"led on" => set_led(true),
|
||||||
|
b"led off" => set_led(false),
|
||||||
|
b"help" => print_help(),
|
||||||
|
b"end" => break 'cmd_loop,
|
||||||
|
x => println!("Unknown command {:?}, try 'help'", x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Bye, going to reset now");
|
||||||
|
reboot()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_help() {
|
||||||
|
println!("Supported console commands:");
|
||||||
|
println!(" mmu - initialize MMU");
|
||||||
|
println!(" uart - try to reinitialize UART serial");
|
||||||
|
println!(" disp - try to init VC framebuffer and draw some text");
|
||||||
|
println!(" trap - cause and recover from a data abort exception");
|
||||||
|
println!(" map - show kernel memory layout");
|
||||||
|
println!(" led [on|off] - change RPi LED status");
|
||||||
|
println!(" end - leave console and reset board");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_led(enable: bool) {
|
||||||
|
let mut mbox = Mailbox::default();
|
||||||
|
let index = mbox.request();
|
||||||
|
let index = mbox.set_led_on(index, enable);
|
||||||
|
mbox.end(index);
|
||||||
|
|
||||||
|
mbox.call(channel::PropertyTagsArmToVc).map_err(|e| {
|
||||||
|
println!("Mailbox call returned error {}", e);
|
||||||
|
println!("Mailbox contents: {}", mbox);
|
||||||
|
()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_mmu() {
|
||||||
|
mmu::print_features();
|
||||||
|
unsafe {
|
||||||
|
mmu::init();
|
||||||
|
}
|
||||||
|
println!("MMU initialised");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reboot() -> ! {
|
||||||
|
Power::new().reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_display_init() {
|
||||||
|
if let Some(mut display) = VC::init_fb(Size2d { x: 800, y: 600 }, 32) {
|
||||||
|
println!("Display created");
|
||||||
|
|
||||||
|
display.clear(Color::black()); // Takes A LOONG time, check caching opts?
|
||||||
|
println!("Display cleared");
|
||||||
|
|
||||||
|
display.rect(10, 10, 250, 250, Color::rgb(32, 96, 64));
|
||||||
|
display.draw_text(50, 50, "Hello there!", Color::rgb(128, 192, 255));
|
||||||
|
|
||||||
|
let mut buf = [0u8; 64];
|
||||||
|
// Crashes if uncommenting next line: vvv
|
||||||
|
let s = write_to::show(&mut buf, format_args!("Display width {}", display.width));
|
||||||
|
// So, some rust runtime things are breaking it, why?
|
||||||
|
|
||||||
|
if s.is_err() {
|
||||||
|
display.draw_text(50, 150, "Error displaying", Color::red())
|
||||||
|
} else {
|
||||||
|
display.draw_text(50, 150, s.unwrap(), Color::white());
|
||||||
|
}
|
||||||
|
|
||||||
|
display.draw_text(150, 50, "RED", Color::red());
|
||||||
|
display.draw_text(160, 60, "GREEN", Color::green());
|
||||||
|
display.draw_text(170, 70, "BLUE", Color::blue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_data_abort_trap() {
|
||||||
|
// Cause an exception by accessing a virtual address for which no
|
||||||
|
// address translations have been set up.
|
||||||
|
//
|
||||||
|
// This line of code accesses the address 3 GiB, but page tables are
|
||||||
|
// only set up for the range [0..1] GiB.
|
||||||
|
let big_addr: u64 = 3 * 1024 * 1024 * 1024;
|
||||||
|
unsafe { core::ptr::read_volatile(big_addr as *mut u64) };
|
||||||
|
|
||||||
|
println!("[i] Whoa! We recovered from an exception.");
|
||||||
|
}
|
||||||
|
|
||||||
|
entry!(kmain);
|
||||||
|
|
||||||
|
// From https://stackoverflow.com/a/49930361/895245
|
||||||
|
// @todo specify exit value depending on tests result?
|
||||||
|
// fn qemu_aarch64_exit() -> ! {
|
||||||
|
// unsafe {
|
||||||
|
// asm!("
|
||||||
|
// /* 0x20026 == ADP_Stopped_ApplicationExit */
|
||||||
|
// mov x1, #0x26
|
||||||
|
// movk x1, #2, lsl #16
|
||||||
|
// str x1, [sp,#0]
|
||||||
|
|
||||||
|
// /* Exit status code. Host QEMU process exits with that status. */
|
||||||
|
// mov x0, #0
|
||||||
|
// str x0, [sp,#8]
|
||||||
|
|
||||||
|
// /* x1 contains the address of parameter block.
|
||||||
|
// * Any memory address could be used. */
|
||||||
|
// mov x1, sp
|
||||||
|
|
||||||
|
// /* SYS_EXIT */
|
||||||
|
// mov w0, #0x18
|
||||||
|
|
||||||
|
// /* Do the semihosting call on A64. */
|
||||||
|
// hlt 0xf000"
|
||||||
|
// :::: "volatile");
|
||||||
|
// }
|
||||||
|
// unreachable!();
|
||||||
|
// }
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Board Support Packages
|
||||||
|
|
||||||
|
This directory contains support for specific Boards like RaspberryPi3 etc.
|
|
@ -0,0 +1,183 @@
|
||||||
|
/* Character cells are 8x8 */
|
||||||
|
pub const CHARSIZE_X: u32 = 8;
|
||||||
|
pub const CHARSIZE_Y: u32 = 8;
|
||||||
|
|
||||||
|
pub struct Size2d {
|
||||||
|
pub x: u32,
|
||||||
|
pub y: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Color(pub u32);
|
||||||
|
|
||||||
|
impl Color {
|
||||||
|
pub fn rgb(r: u8, g: u8, b: u8) -> Color {
|
||||||
|
Color(u32::from(b) << 16 | u32::from(g) << 8 | u32::from(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn black() -> Color {
|
||||||
|
Color::rgb(0, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn white() -> Color {
|
||||||
|
Color::rgb(255, 255, 255)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn red() -> Color {
|
||||||
|
Color::rgb(255, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn green() -> Color {
|
||||||
|
Color::rgb(0, 255, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn blue() -> Color {
|
||||||
|
Color::rgb(0, 0, 255)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
pub enum PixelOrder {
|
||||||
|
BGR,
|
||||||
|
RGB,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Display {
|
||||||
|
base: u32,
|
||||||
|
size: u32,
|
||||||
|
depth: u32,
|
||||||
|
pitch: u32,
|
||||||
|
max_x: u32,
|
||||||
|
max_y: u32,
|
||||||
|
pub width: u32,
|
||||||
|
height: u32,
|
||||||
|
order: PixelOrder,
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/david-griffith/rust-bitmap/blob/master/src/lib.rs
|
||||||
|
#[rustfmt::skip]
|
||||||
|
static CHAR_ARRAY: [u64; 95] = [
|
||||||
|
0x0000_0000_0000_0000, // space
|
||||||
|
0x183c_3c18_1800_1800, 0x3636_0000_0000_0000, 0x3636_7f36_7f36_3600, // ! " #
|
||||||
|
0x0c3e_031e_301f_0c00, 0x0063_3318_0c66_6300, 0x1c36_1c6e_3b33_6e00, // $ % &
|
||||||
|
0x0606_0300_0000_0000, 0x180c_0606_060c_1800, 0x060c_1818_180c_0600, // ' ( )
|
||||||
|
0x0066_3cff_3c66_0000, 0x000c_0c3f_0c0c_0000, 0x0000_0000_000c_0c06, // * + ,
|
||||||
|
0x0000_003f_0000_0000, 0x0000_0000_000c_0c00, 0x6030_180c_0603_0100, // - . /
|
||||||
|
0x3e63_737b_6f67_3e00, 0x0c0e_0c0c_0c0c_3f00, 0x1e33_301c_0633_3f00, // 0 1 2
|
||||||
|
0x1e33_301c_3033_1e00, 0x383c_3633_7f30_7800, 0x3f03_1f30_3033_1e00, // 3 4 5
|
||||||
|
0x1c06_031f_3333_1e00, 0x3f33_3018_0c0c_0c00, 0x1e33_331e_3333_1e00, // 6 7 8
|
||||||
|
0x1e33_333e_3018_0e00, 0x000c_0c00_000c_0c00, 0x000c_0c00_000c_0c06, // 9 : ;
|
||||||
|
0x180c_0603_060c_1800, 0x0000_3f00_003f_0000, 0x060c_1830_180c_0600, // < = >
|
||||||
|
0x1e33_3018_0c00_0c00, 0x3e63_7b7b_7b03_1e00, 0x0c1e_3333_3f33_3300, // ? @ A
|
||||||
|
0x3f66_663e_6666_3f00, 0x3c66_0303_0366_3c00, 0x1f36_6666_6636_1f00, // B C D
|
||||||
|
0x7f46_161e_1646_7f00, 0x7f46_161e_1606_0f00, 0x3c66_0303_7366_7c00, // E F G
|
||||||
|
0x3333_333f_3333_3300, 0x1e0c_0c0c_0c0c_1e00, 0x7830_3030_3333_1e00, // H I J
|
||||||
|
0x6766_361e_3666_6700, 0x0f06_0606_4666_7f00, 0x6377_7f7f_6b63_6300, // K L M
|
||||||
|
0x6367_6f7b_7363_6300, 0x1c36_6363_6336_1c00, 0x3f66_663e_0606_0f00, // N O P
|
||||||
|
0x1e33_3333_3b1e_3800, 0x3f66_663e_3666_6700, 0x1e33_070e_3833_1e00, // Q R S
|
||||||
|
0x3f2d_0c0c_0c0c_1e00, 0x3333_3333_3333_3f00, 0x3333_3333_331e_0c00, // T U V
|
||||||
|
0x6363_636b_7f77_6300, 0x6363_361c_1c36_6300, 0x3333_331e_0c0c_1e00, // W X Y
|
||||||
|
0x7f63_3118_4c66_7f00, 0x1e06_0606_0606_1e00, 0x0306_0c18_3060_4000, // Z [ \
|
||||||
|
0x1e18_1818_1818_1e00, 0x081c_3663_0000_0000, 0x0000_0000_0000_00ff, // ] ^ _
|
||||||
|
0x0c0c_1800_0000_0000, 0x0000_1e30_3e33_6e00, 0x0706_063e_6666_3b00, // ` a b
|
||||||
|
0x0000_1e33_0333_1e00, 0x3830_303e_3333_6e00, 0x0000_1e33_3f03_1e00, // c d e
|
||||||
|
0x1c36_060f_0606_0f00, 0x0000_6e33_333e_301f, 0x0706_366e_6666_6700, // f g h
|
||||||
|
0x0c00_0e0c_0c0c_1e00, 0x3000_3030_3033_331e, 0x0706_6636_1e36_6700, // i j k
|
||||||
|
0x0e0c_0c0c_0c0c_1e00, 0x0000_337f_7f6b_6300, 0x0000_1f33_3333_3300, // l m n
|
||||||
|
0x0000_1e33_3333_1e00, 0x0000_3b66_663e_060f, 0x0000_6e33_333e_3078, // o p q
|
||||||
|
0x0000_3b6e_6606_0f00, 0x0000_3e03_1e30_1f00, 0x080c_3e0c_0c2c_1800, // r s t
|
||||||
|
0x0000_3333_3333_6e00, 0x0000_3333_331e_0c00, 0x0000_636b_7f7f_3600, // u v w
|
||||||
|
0x0000_6336_1c36_6300, 0x0000_3333_333e_301f, 0x0000_3f19_0c26_3f00, // x y z
|
||||||
|
0x380c_0c07_0c0c_3800, 0x1818_1800_1818_1800, 0x070c_0c38_0c0c_0700, // { | }
|
||||||
|
0x6e3b_0000_0000_0000, // ~
|
||||||
|
];
|
||||||
|
|
||||||
|
impl Display {
|
||||||
|
pub fn new(
|
||||||
|
base: u32,
|
||||||
|
size: u32,
|
||||||
|
depth: u32,
|
||||||
|
pitch: u32,
|
||||||
|
max_x: u32,
|
||||||
|
max_y: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
order: PixelOrder,
|
||||||
|
) -> Self {
|
||||||
|
Display {
|
||||||
|
base,
|
||||||
|
size,
|
||||||
|
depth,
|
||||||
|
pitch,
|
||||||
|
max_x,
|
||||||
|
max_y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
order,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn color_component(&self, chan: u16) -> u32 {
|
||||||
|
u32::from(if self.order == PixelOrder::BGR {
|
||||||
|
2 - chan
|
||||||
|
} else {
|
||||||
|
chan
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn write_pixel_component(&self, x: u32, y: u32, chan: u16, c: u32) {
|
||||||
|
unsafe {
|
||||||
|
*(self.base as *mut u8).offset(
|
||||||
|
(y * self.pitch + x * (self.depth >> 3) + self.color_component(chan)) as isize,
|
||||||
|
) = c as u8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a pixel value on display at given coordinates.
|
||||||
|
#[inline]
|
||||||
|
pub fn putpixel(&mut self, x: u32, y: u32, color: u32) {
|
||||||
|
self.write_pixel_component(x, y, 0, color & 0xff);
|
||||||
|
self.write_pixel_component(x, y, 1, (color >> 8) & 0xff);
|
||||||
|
self.write_pixel_component(x, y, 2, (color >> 16) & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rect(&mut self, x1: u32, y1: u32, x2: u32, y2: u32, color: Color) {
|
||||||
|
for y in y1..y2 {
|
||||||
|
for x in x1..x2 {
|
||||||
|
self.putpixel(x, y, color.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self, color: Color) {
|
||||||
|
self.rect(0, 0, self.width, self.height, color)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_text(&mut self, x: u32, y: u32, text: &str, color: Color) {
|
||||||
|
for i in 0..8 {
|
||||||
|
// Take an 8 bit slice from each array value.
|
||||||
|
for (char_off, my_char) in text.as_bytes().iter().enumerate() {
|
||||||
|
let off = (char_off * 8) as u32;
|
||||||
|
|
||||||
|
if (*my_char as isize - 0x20 > 95) || (*my_char as isize - 0x20 < 0) {
|
||||||
|
return; // Err("Character not in font.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut myval = CHAR_ARRAY[*my_char as usize - 0x20];
|
||||||
|
myval = myval.swap_bytes();
|
||||||
|
// do initial shr.
|
||||||
|
myval >>= i * 8;
|
||||||
|
for mycount in 0..8 {
|
||||||
|
if myval & 1 == 1 {
|
||||||
|
self.putpixel(x + off + mycount, y + i, color.0);
|
||||||
|
}
|
||||||
|
myval >>= 1;
|
||||||
|
if myval == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,131 @@
|
||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
|
||||||
|
* Copyright (c) 2019 Berkus Decker <berkus+github@metta.systems>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::platform::rpi3::BcmHost;
|
||||||
|
use core::{convert::TryFrom, ops};
|
||||||
|
use register::{mmio::ReadWrite, register_bitfields};
|
||||||
|
|
||||||
|
// Descriptions taken from
|
||||||
|
// https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf
|
||||||
|
register_bitfields! {
|
||||||
|
u32,
|
||||||
|
|
||||||
|
/// GPIO Function Select 1
|
||||||
|
GPFSEL1 [
|
||||||
|
/// Pin 15
|
||||||
|
FSEL15 OFFSET(15) NUMBITS(3) [
|
||||||
|
Input = 0b000,
|
||||||
|
Output = 0b001,
|
||||||
|
RXD0 = 0b100, // UART0 - Alternate function 0
|
||||||
|
RXD1 = 0b010 // Mini UART - Alternate function 5
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
/// Pin 14
|
||||||
|
FSEL14 OFFSET(12) NUMBITS(3) [
|
||||||
|
Input = 0b000,
|
||||||
|
Output = 0b001,
|
||||||
|
TXD0 = 0b100, // UART0 - Alternate function 0
|
||||||
|
TXD1 = 0b010 // Mini UART - Alternate function 5
|
||||||
|
]
|
||||||
|
],
|
||||||
|
|
||||||
|
/// GPIO Pull-up/down Clock Register 0
|
||||||
|
GPPUDCLK0 [
|
||||||
|
/// Pin 15
|
||||||
|
PUDCLK15 OFFSET(15) NUMBITS(1) [
|
||||||
|
NoEffect = 0,
|
||||||
|
AssertClock = 1
|
||||||
|
],
|
||||||
|
|
||||||
|
/// Pin 14
|
||||||
|
PUDCLK14 OFFSET(14) NUMBITS(1) [
|
||||||
|
NoEffect = 0,
|
||||||
|
AssertClock = 1
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// The offsets for reach register.
|
||||||
|
// From https://wiki.osdev.org/Raspberry_Pi_Bare_Bones
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct RegisterBlock {
|
||||||
|
pub GPFSEL0: ReadWrite<u32>, // 0x00
|
||||||
|
pub GPFSEL1: ReadWrite<u32, GPFSEL1::Register>, // 0x04
|
||||||
|
pub GPFSEL2: ReadWrite<u32>, // 0x08
|
||||||
|
pub GPFSEL3: ReadWrite<u32>, // 0x0C
|
||||||
|
pub GPFSEL4: ReadWrite<u32>, // 0x10
|
||||||
|
pub GPFSEL5: ReadWrite<u32>, // 0x14
|
||||||
|
__reserved_0: u32, // 0x18
|
||||||
|
GPSET0: ReadWrite<u32>, // 0x1C
|
||||||
|
GPSET1: ReadWrite<u32>, // 0x20
|
||||||
|
__reserved_1: u32, // 0x24
|
||||||
|
GPCLR0: ReadWrite<u32>, // 0x28
|
||||||
|
__reserved_2: [u32; 2], // 0x2C-0x30
|
||||||
|
GPLEV0: ReadWrite<u32>, // 0x34
|
||||||
|
GPLEV1: ReadWrite<u32>, // 0x38
|
||||||
|
__reserved_3: u32, // 0x3C
|
||||||
|
GPEDS0: ReadWrite<u32>, // 0x40
|
||||||
|
GPEDS1: ReadWrite<u32>, // 0x44
|
||||||
|
__reserved_4: [u32; 7], //
|
||||||
|
GPHEN0: ReadWrite<u32>, // 0x64
|
||||||
|
GPHEN1: ReadWrite<u32>, // 0x68
|
||||||
|
__reserved_5: [u32; 10], //
|
||||||
|
pub GPPUD: ReadWrite<u32>, // 0x94
|
||||||
|
pub GPPUDCLK0: ReadWrite<u32, GPPUDCLK0::Register>, // 0x98
|
||||||
|
pub GPPUDCLK1: ReadWrite<u32>, // 0x9C
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Public interface to the GPIO MMIO area
|
||||||
|
pub struct GPIO {
|
||||||
|
base_addr: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ops::Deref for GPIO {
|
||||||
|
type Target = RegisterBlock;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
unsafe { &*self.ptr() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GPIO {
|
||||||
|
pub fn new_default() -> GPIO {
|
||||||
|
const GPIO_BASE: u32 = BcmHost::get_peripheral_address() + 0x20_0000;
|
||||||
|
GPIO {
|
||||||
|
base_addr: usize::try_from(GPIO_BASE).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 _
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,534 @@
|
||||||
|
use crate::{
|
||||||
|
platform::{display::Size2d, rpi3::BcmHost},
|
||||||
|
println,
|
||||||
|
};
|
||||||
|
use core::ops::Deref;
|
||||||
|
use core::sync::atomic::{compiler_fence, Ordering};
|
||||||
|
use cortex_a::barrier;
|
||||||
|
use register::mmio::*;
|
||||||
|
|
||||||
|
// Public interface to the mailbox.
|
||||||
|
// 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<'a> {
|
||||||
|
pub buffer: &'a mut [u32],
|
||||||
|
base_addr: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAILBOX_ALIGNMENT: usize = 16;
|
||||||
|
const MAILBOX_ITEMS_COUNT: usize = 36;
|
||||||
|
|
||||||
|
// Identity mapped first 1Gb by u-boot
|
||||||
|
const MAILBOX_BASE: u32 = BcmHost::get_peripheral_address() + 0xb880;
|
||||||
|
// Lowest 4-bits are channel ID.
|
||||||
|
const CHANNEL_MASK: u32 = 0xf;
|
||||||
|
|
||||||
|
// Mailbox Peek Read/Write Status Sender Config
|
||||||
|
// 0 0x10 0x00 0x18 0x14 0x1c
|
||||||
|
// 1 0x30 0x20 0x38 0x34 0x3c
|
||||||
|
//
|
||||||
|
// Only mailbox 0's status can trigger interrupts on the ARM, so Mailbox 0 is
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// Based on https://github.com/rust-embedded/rust-raspi3-tutorial/blob/master/04_mailboxes/src/mbox.rs
|
||||||
|
// by Andre Richter of Tock OS.
|
||||||
|
|
||||||
|
register_bitfields! {
|
||||||
|
u32,
|
||||||
|
|
||||||
|
STATUS [
|
||||||
|
/* Bit 31 set in status register if the write mailbox is full */
|
||||||
|
FULL OFFSET(31) NUMBITS(1) [],
|
||||||
|
/* Bit 30 set in status register if the read mailbox is empty */
|
||||||
|
EMPTY OFFSET(30) NUMBITS(1) []
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 MboxError {
|
||||||
|
ResponseError,
|
||||||
|
UnknownError,
|
||||||
|
Timeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::fmt::Display for MboxError {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
match self {
|
||||||
|
MboxError::ResponseError => "ResponseError",
|
||||||
|
MboxError::UnknownError => "UnknownError",
|
||||||
|
MboxError::Timeout => "Timeout",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Result<T> = ::core::result::Result<T, MboxError>;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Source https://elinux.org/RPi_Framebuffer
|
||||||
|
* Source for channels 8 and 9: https://github.com/raspberrypi/firmware/wiki/Mailboxes
|
||||||
|
*/
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
pub mod channel {
|
||||||
|
pub const Power: u32 = 0;
|
||||||
|
pub const FrameBuffer: u32 = 1;
|
||||||
|
pub const VirtualUart: u32 = 2;
|
||||||
|
pub const VChiq: u32 = 3;
|
||||||
|
pub const Leds: u32 = 4;
|
||||||
|
pub const Buttons: u32 = 5;
|
||||||
|
pub const TouchScreen: u32 = 6;
|
||||||
|
// Count = 7,
|
||||||
|
pub const PropertyTagsArmToVc: u32 = 8;
|
||||||
|
pub const PropertyTagsVcToArm: u32 = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FrameBuffer channel supported structure - use with channel::FrameBuffer
|
||||||
|
#[repr(C)]
|
||||||
|
#[repr(align(16))]
|
||||||
|
pub struct GpuFb {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single code indicating request
|
||||||
|
pub const REQUEST: u32 = 0;
|
||||||
|
|
||||||
|
// Possible responses
|
||||||
|
pub mod response {
|
||||||
|
pub const SUCCESS: u32 = 0x8000_0000;
|
||||||
|
pub const ERROR: u32 = 0x8000_0001; // error parsing request buffer (partial response)
|
||||||
|
/** When responding, the VC sets this bit in val_len to indicate a response. */
|
||||||
|
/** Each tag with this bit set will contain VC response data. */
|
||||||
|
pub const VAL_LEN_FLAG: u32 = 0x8000_0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
pub mod tag {
|
||||||
|
pub const GetBoardRev: u32 = 0x0001_0002;
|
||||||
|
pub const GetMacAddress: u32 = 0x0001_0003;
|
||||||
|
pub const GetBoardSerial: u32 = 0x0001_0004;
|
||||||
|
pub const GetArmMemory: u32 = 0x0001_0005;
|
||||||
|
pub const GetPowerState: u32 = 0x0002_0001;
|
||||||
|
pub const SetPowerState: u32 = 0x0002_8001;
|
||||||
|
pub const GetClockRate: u32 = 0x0003_0002;
|
||||||
|
pub const SetClockRate: u32 = 0x0003_8002;
|
||||||
|
// GPU
|
||||||
|
pub const AllocateMemory: u32 = 0x0003_000c; //< Allocate contiguous memory buffer
|
||||||
|
pub const LockMemory: u32 = 0x0003_000d;
|
||||||
|
pub const UnlockMemory: u32 = 0x0003_000e;
|
||||||
|
pub const ReleaseMemory: u32 = 0x003_000f;
|
||||||
|
pub const ExecuteCode: u32 = 0x0003_0010;
|
||||||
|
pub const GetDispmanxResourceMemHandle: u32 = 0x0003_0014;
|
||||||
|
pub const GetEdidBlock: u32 = 0x0003_0020;
|
||||||
|
// FB
|
||||||
|
pub const AllocateBuffer: u32 = 0x0004_0001; //< Allocate framebuffer
|
||||||
|
pub const ReleaseBuffer: u32 = 0x0004_8001;
|
||||||
|
pub const BlankScreen: u32 = 0x0004_0002;
|
||||||
|
/* Physical means output signal */
|
||||||
|
pub const GetPhysicalWH: u32 = 0x0004_0003;
|
||||||
|
pub const TestPhysicalWH: u32 = 0x0004_4003;
|
||||||
|
pub const SetPhysicalWH: u32 = 0x0004_8003;
|
||||||
|
/* Virtual means display buffer */
|
||||||
|
pub const GetVirtualWH: u32 = 0x0004_0004;
|
||||||
|
pub const TestVirtualWH: u32 = 0x0004_4004;
|
||||||
|
pub const SetVirtualWH: u32 = 0x0004_8004;
|
||||||
|
pub const GetDepth: u32 = 0x0004_0005;
|
||||||
|
pub const TestDepth: u32 = 0x0004_4005;
|
||||||
|
pub const SetDepth: u32 = 0x0004_8005;
|
||||||
|
pub const GetPixelOrder: u32 = 0x0004_0006;
|
||||||
|
pub const TestPixelOrder: u32 = 0x0004_4006;
|
||||||
|
pub const SetPixelOrder: u32 = 0x0004_8006;
|
||||||
|
pub const GetAlphaMode: u32 = 0x0004_0007;
|
||||||
|
pub const TestAlphaMode: u32 = 0x0004_4007;
|
||||||
|
pub const SetAlphaMode: u32 = 0x0004_8007;
|
||||||
|
pub const GetPitch: u32 = 0x0004_0008;
|
||||||
|
/* Offset of display window within buffer */
|
||||||
|
pub const GetVirtualOffset: u32 = 0x0004_0009;
|
||||||
|
pub const TestVirtualOffset: u32 = 0x0004_4009;
|
||||||
|
pub const SetVirtualOffset: u32 = 0x0004_8009;
|
||||||
|
pub const GetOverscan: u32 = 0x0004_000a;
|
||||||
|
pub const TestOverscan: u32 = 0x0004_400a;
|
||||||
|
pub const SetOverscan: u32 = 0x0004_800a;
|
||||||
|
pub const GetPalette: u32 = 0x0004_000b;
|
||||||
|
pub const TestPalette: u32 = 0x0004_400b;
|
||||||
|
pub const SetPalette: u32 = 0x0004_800b;
|
||||||
|
pub const SetCursorInfo: u32 = 0x0000_8010;
|
||||||
|
pub const SetCursorState: u32 = 0x0000_8011;
|
||||||
|
pub const GetGpioState: u32 = 0x0003_0041;
|
||||||
|
pub const SetGpioState: u32 = 0x0003_8041;
|
||||||
|
pub const End: u32 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod power {
|
||||||
|
pub const SDHCI: u32 = 0;
|
||||||
|
pub const UART0: u32 = 1;
|
||||||
|
pub const UART1: u32 = 2;
|
||||||
|
pub const USB_HCD: u32 = 3;
|
||||||
|
pub const I2C0: u32 = 4;
|
||||||
|
pub const I2C1: u32 = 5;
|
||||||
|
pub const I2C2: u32 = 6;
|
||||||
|
pub const SPI: u32 = 7;
|
||||||
|
pub const CCP2TX: u32 = 8;
|
||||||
|
|
||||||
|
pub mod response {
|
||||||
|
pub const ON: u32 = 1;
|
||||||
|
pub const NO_DEV: u32 = 2; /* Device doesn't exist */
|
||||||
|
}
|
||||||
|
pub mod request {
|
||||||
|
pub const ON: u32 = 1;
|
||||||
|
pub const WAIT: u32 = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod clock {
|
||||||
|
pub const EMMC: u32 = 1;
|
||||||
|
pub const UART: u32 = 2;
|
||||||
|
pub const ARM: u32 = 3;
|
||||||
|
pub const CORE: u32 = 4;
|
||||||
|
pub const V3D: u32 = 5;
|
||||||
|
pub const H264: u32 = 6;
|
||||||
|
pub const ISP: u32 = 7;
|
||||||
|
pub const SDRAM: u32 = 8;
|
||||||
|
pub const PIXEL: u32 = 9;
|
||||||
|
pub const PWM: u32 = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod alpha_mode {
|
||||||
|
pub const OPAQUE_0: u32 = 0; // 255 is transparent
|
||||||
|
pub const TRANSPARENT_0: u32 = 1; // 255 is opaque
|
||||||
|
pub const IGNORED: u32 = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(regs: &RegisterBlock, buf_ptr: u32, channel: u32) -> Result<()> {
|
||||||
|
let mut count: u32 = 0;
|
||||||
|
|
||||||
|
// let buf_ptr = BcmHost::phys2bus(buf_ptr); not used for PropertyTags channel
|
||||||
|
|
||||||
|
println!("Mailbox::write {:x}/{:x}", buf_ptr, channel);
|
||||||
|
|
||||||
|
// Insert a compiler fence that ensures that all stores to the
|
||||||
|
// mbox 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(MboxError::Timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
barrier::dmb(barrier::SY);
|
||||||
|
}
|
||||||
|
regs.WRITE
|
||||||
|
.set((buf_ptr & !CHANNEL_MASK) | (channel & CHANNEL_MASK));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
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 mbox response");
|
||||||
|
return Err(MboxError::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 mbox 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 {
|
||||||
|
// will return on Timeout if no response received...
|
||||||
|
// return Err(MboxError::ResponseError); //@fixme ignore invalid responses and loop again?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deref to RegisterBlock
|
||||||
|
///
|
||||||
|
/// Allows writing
|
||||||
|
/// ```
|
||||||
|
/// self.STATUS.read()
|
||||||
|
/// ```
|
||||||
|
/// instead of something along the lines of
|
||||||
|
/// ```
|
||||||
|
/// unsafe { (*Mbox::ptr()).STATUS.read() }
|
||||||
|
/// ```
|
||||||
|
impl<'a> Deref for Mailbox<'a> {
|
||||||
|
type Target = RegisterBlock;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
unsafe { &*self.ptr() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> core::fmt::Display for Mailbox<'a> {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||||
|
let count = self.buffer[0] / 4;
|
||||||
|
assert_eq!(self.buffer[0], count * 4);
|
||||||
|
assert!(count <= 36);
|
||||||
|
for i in 0usize..count as usize {
|
||||||
|
writeln!(f, "[{:02}] {:08x}", i, self.buffer[i]);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Default for Mailbox<'a> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new_default().expect("Couldn't allocate a mailbox")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Mailbox<'a> {
|
||||||
|
pub fn new_default() -> ::core::result::Result<Mailbox<'a>, ()> {
|
||||||
|
let ret = crate::DMA_ALLOCATOR
|
||||||
|
.lock(|d| d.alloc_slice_zeroed(MAILBOX_ITEMS_COUNT, MAILBOX_ALIGNMENT));
|
||||||
|
|
||||||
|
if ret.is_err() {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Mailbox {
|
||||||
|
base_addr: MAILBOX_BASE,
|
||||||
|
buffer: ret.unwrap(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(base_addr: usize) -> ::core::result::Result<Mailbox<'a>, ()> {
|
||||||
|
let ret = crate::DMA_ALLOCATOR
|
||||||
|
.lock(|d| d.alloc_slice_zeroed(MAILBOX_ITEMS_COUNT, MAILBOX_ALIGNMENT));
|
||||||
|
|
||||||
|
if ret.is_err() {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
|
||||||
|
use core::convert::TryFrom;
|
||||||
|
let base_addr = u32::try_from(base_addr).unwrap();
|
||||||
|
|
||||||
|
Ok(Mailbox {
|
||||||
|
base_addr,
|
||||||
|
buffer: ret.unwrap(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a pointer to the register block
|
||||||
|
fn ptr(&self) -> *const RegisterBlock {
|
||||||
|
self.base_addr as *const _
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(&self, channel: u32) -> Result<()> {
|
||||||
|
write(self, self.buffer.as_ptr() as u32, channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(&self, channel: u32) -> Result<()> {
|
||||||
|
read(self, self.buffer.as_ptr() as u32, channel)?;
|
||||||
|
|
||||||
|
match self.buffer[1] {
|
||||||
|
response::SUCCESS => {
|
||||||
|
println!("\n######\nMailbox::returning SUCCESS");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
response::ERROR => {
|
||||||
|
println!("\n######\nMailbox::returning ResponseError");
|
||||||
|
Err(MboxError::ResponseError)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
println!("\n######\nMailbox::returning UnknownError");
|
||||||
|
println!("{:x}\n######", self.buffer[1]);
|
||||||
|
Err(MboxError::UnknownError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn call(&self, channel: u32) -> Result<()> {
|
||||||
|
self.write(channel)?;
|
||||||
|
self.read(channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specific mailbox functions
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn request(&mut self) -> usize {
|
||||||
|
self.buffer[1] = REQUEST;
|
||||||
|
2
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn end(&mut self, index: usize) -> () {
|
||||||
|
// @todo return Result
|
||||||
|
self.buffer[index] = tag::End;
|
||||||
|
self.buffer[0] = (index as u32 + 1) * 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_physical_wh(&mut self, index: usize, width: u32, height: u32) -> usize {
|
||||||
|
self.buffer[index] = tag::SetPhysicalWH;
|
||||||
|
self.buffer[index + 1] = 8; // Buffer size // val buf size
|
||||||
|
self.buffer[index + 2] = 8; // Request size // val size
|
||||||
|
self.buffer[index + 3] = width; // Space for horizontal resolution
|
||||||
|
self.buffer[index + 4] = height; // Space for vertical resolution
|
||||||
|
index + 5
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_virtual_wh(&mut self, index: usize, width: u32, height: u32) -> usize {
|
||||||
|
self.buffer[index] = tag::SetVirtualWH;
|
||||||
|
self.buffer[index + 1] = 8; // Buffer size // val buf size
|
||||||
|
self.buffer[index + 2] = 8; // Request size // val size
|
||||||
|
self.buffer[index + 3] = width; // Space for horizontal resolution
|
||||||
|
self.buffer[index + 4] = height; // Space for vertical resolution
|
||||||
|
index + 5
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_depth(&mut self, index: usize, depth: u32) -> usize {
|
||||||
|
self.buffer[index] = tag::SetDepth;
|
||||||
|
self.buffer[index + 1] = 4; // Buffer size // val buf size
|
||||||
|
self.buffer[index + 2] = 4; // Request size // val size
|
||||||
|
self.buffer[index + 3] = depth; // bpp
|
||||||
|
index + 4
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn allocate_buffer_aligned(&mut self, index: usize, alignment: u32) -> usize {
|
||||||
|
self.buffer[index] = tag::AllocateBuffer;
|
||||||
|
self.buffer[index + 1] = 8; // Buffer size // val buf size
|
||||||
|
self.buffer[index + 2] = 4; // Request size // val size
|
||||||
|
self.buffer[index + 3] = alignment; // Alignment = 16 -- fb_ptr will be here
|
||||||
|
self.buffer[index + 4] = 0; // Space for response -- fb_size will be here
|
||||||
|
index + 5
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_led_on(&mut self, index: usize, enable: bool) -> usize {
|
||||||
|
self.buffer[index] = tag::SetGpioState;
|
||||||
|
self.buffer[index + 1] = 8; // Buffer size // val buf size
|
||||||
|
self.buffer[index + 2] = 0; // Response size // val size
|
||||||
|
self.buffer[index + 3] = 130; // Pin Number
|
||||||
|
self.buffer[index + 4] = if enable { 1 } else { 0 };
|
||||||
|
index + 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deref to RegisterBlock
|
||||||
|
///
|
||||||
|
/// Allows writing
|
||||||
|
/// ```
|
||||||
|
/// self.STATUS.read()
|
||||||
|
/// ```
|
||||||
|
/// instead of something along the lines of
|
||||||
|
/// ```
|
||||||
|
/// unsafe { (*Mbox::ptr()).STATUS.read() }
|
||||||
|
/// ```
|
||||||
|
impl Deref for GpuFb {
|
||||||
|
type Target = RegisterBlock;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
unsafe { &*Self::ptr() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::fmt::Display for GpuFb {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"\n\n\n#### GpuFb({}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 GpuFb {
|
||||||
|
pub fn new(size: Size2d, depth: u32) -> GpuFb {
|
||||||
|
GpuFb {
|
||||||
|
width: size.x,
|
||||||
|
height: size.y,
|
||||||
|
vwidth: size.x,
|
||||||
|
vheight: size.y,
|
||||||
|
pitch: 0,
|
||||||
|
depth,
|
||||||
|
x_offset: 0,
|
||||||
|
y_offset: 0,
|
||||||
|
pointer: 0, // could be 4096 for alignment?
|
||||||
|
size: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a pointer to the register block
|
||||||
|
fn ptr() -> *const RegisterBlock {
|
||||||
|
MAILBOX_BASE 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.**
|
||||||
|
pub fn write(&self) -> Result<()> {
|
||||||
|
write(
|
||||||
|
self,
|
||||||
|
BcmHost::phys2bus(&self.width as *const u32 as u32),
|
||||||
|
channel::FrameBuffer,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(&mut self) -> Result<()> {
|
||||||
|
read(self, 0, channel::FrameBuffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn call(&mut self) -> Result<()> {
|
||||||
|
self.write()?;
|
||||||
|
self.read()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,279 @@
|
||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
|
||||||
|
* Copyright (c) 2019 Berkus Decker <berkus+github@metta.systems>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::arch::{loop_delay, loop_until};
|
||||||
|
use crate::devices::ConsoleOps;
|
||||||
|
use crate::platform::{gpio, rpi3::BcmHost};
|
||||||
|
use core::{convert::TryFrom, fmt, ops};
|
||||||
|
use register::{mmio::*, register_bitfields};
|
||||||
|
|
||||||
|
/// Auxilary mini UART registers
|
||||||
|
//
|
||||||
|
// Descriptions taken from
|
||||||
|
// https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf
|
||||||
|
register_bitfields! {
|
||||||
|
u32,
|
||||||
|
|
||||||
|
/// Auxiliary enables
|
||||||
|
AUX_ENABLES [
|
||||||
|
/// If set the mini UART is enabled. The UART will immediately
|
||||||
|
/// start receiving data, especially if the UART1_RX line is
|
||||||
|
/// low.
|
||||||
|
/// If clear the mini UART is disabled. That also disables any
|
||||||
|
/// mini UART register access
|
||||||
|
MINI_UART_ENABLE OFFSET(0) NUMBITS(1) []
|
||||||
|
],
|
||||||
|
|
||||||
|
/// Mini Uart Interrupt Identify
|
||||||
|
AUX_MU_IIR [
|
||||||
|
/// Writing with bit 1 set will clear the receive FIFO
|
||||||
|
/// Writing with bit 2 set will clear the transmit FIFO
|
||||||
|
FIFO_CLEAR OFFSET(1) NUMBITS(2) [
|
||||||
|
Rx = 0b01,
|
||||||
|
Tx = 0b10,
|
||||||
|
All = 0b11
|
||||||
|
]
|
||||||
|
],
|
||||||
|
|
||||||
|
/// Mini Uart Line Control
|
||||||
|
AUX_MU_LCR [
|
||||||
|
/// Mode the UART works in
|
||||||
|
DATA_SIZE OFFSET(0) NUMBITS(2) [
|
||||||
|
SevenBit = 0b00,
|
||||||
|
EightBit = 0b11
|
||||||
|
]
|
||||||
|
],
|
||||||
|
|
||||||
|
/// Mini Uart Line Status
|
||||||
|
AUX_MU_LSR [
|
||||||
|
/// This bit is set if the transmit FIFO is empty and the transmitter is
|
||||||
|
/// idle. (Finished shifting out the last bit).
|
||||||
|
TX_IDLE OFFSET(6) NUMBITS(1) [],
|
||||||
|
|
||||||
|
/// This bit is set if the transmit FIFO can accept at least
|
||||||
|
/// one byte.
|
||||||
|
TX_EMPTY OFFSET(5) NUMBITS(1) [],
|
||||||
|
|
||||||
|
/// This bit is set if the receive FIFO holds at least 1
|
||||||
|
/// symbol.
|
||||||
|
DATA_READY OFFSET(0) NUMBITS(1) []
|
||||||
|
],
|
||||||
|
|
||||||
|
/// Mini Uart Extra Control
|
||||||
|
AUX_MU_CNTL [
|
||||||
|
/// If this bit is set the mini UART transmitter is enabled.
|
||||||
|
/// If this bit is clear the mini UART transmitter is disabled.
|
||||||
|
TX_EN OFFSET(1) NUMBITS(1) [
|
||||||
|
Disabled = 0,
|
||||||
|
Enabled = 1
|
||||||
|
],
|
||||||
|
|
||||||
|
/// If this bit is set the mini UART receiver is enabled.
|
||||||
|
/// If this bit is clear the mini UART receiver is disabled.
|
||||||
|
RX_EN OFFSET(0) NUMBITS(1) [
|
||||||
|
Disabled = 0,
|
||||||
|
Enabled = 1
|
||||||
|
]
|
||||||
|
],
|
||||||
|
|
||||||
|
/// Mini Uart Baudrate
|
||||||
|
AUX_MU_BAUD [
|
||||||
|
/// Mini UART baudrate 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
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MiniUart {
|
||||||
|
base_addr: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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: u32 = BcmHost::get_peripheral_address() + 0x21_5000;
|
||||||
|
|
||||||
|
impl MiniUart {
|
||||||
|
pub fn new_default() -> MiniUart {
|
||||||
|
MiniUart {
|
||||||
|
base_addr: usize::try_from(UART1_BASE).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 _
|
||||||
|
}
|
||||||
|
|
||||||
|
///Set baud rate and characteristics (115200 8N1) and map to GPIO
|
||||||
|
#[cfg(not(feature = "noserial"))]
|
||||||
|
pub fn init(&self, gpio: &gpio::GPIO) {
|
||||||
|
// 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(270)); // 115200 baud
|
||||||
|
|
||||||
|
// map UART1 to GPIO pins
|
||||||
|
gpio.GPFSEL1
|
||||||
|
.modify(gpio::GPFSEL1::FSEL14::TXD1 + gpio::GPFSEL1::FSEL15::RXD1);
|
||||||
|
|
||||||
|
gpio.GPPUD.set(0); // enable pins 14 and 15
|
||||||
|
loop_delay(150);
|
||||||
|
|
||||||
|
gpio.GPPUDCLK0
|
||||||
|
.write(gpio::GPPUDCLK0::PUDCLK14::AssertClock + gpio::GPPUDCLK0::PUDCLK15::AssertClock);
|
||||||
|
loop_delay(150);
|
||||||
|
|
||||||
|
gpio.GPPUDCLK0.set(0);
|
||||||
|
|
||||||
|
self.AUX_MU_CNTL
|
||||||
|
.write(AUX_MU_CNTL::RX_EN::Enabled + AUX_MU_CNTL::TX_EN::Enabled);
|
||||||
|
|
||||||
|
// Clear FIFOs before using the device
|
||||||
|
self.AUX_MU_IIR.write(AUX_MU_IIR::FIFO_CLEAR::All);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "noserial")]
|
||||||
|
pub fn init(&self, _gpio: &gpio::GPIO) {}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "noserial"))]
|
||||||
|
pub fn wait_tx_fifo_empty(&self) {
|
||||||
|
loop_until(|| self.AUX_MU_LSR.is_set(AUX_MU_LSR::TX_IDLE));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "noserial")]
|
||||||
|
pub fn wait_tx_fifo_empty(&self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for MiniUart {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.AUX_ENABLES
|
||||||
|
.modify(AUX_ENABLES::MINI_UART_ENABLE::CLEAR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConsoleOps for MiniUart {
|
||||||
|
/// Send a character
|
||||||
|
#[cfg(not(feature = "noserial"))]
|
||||||
|
fn putc(&self, c: char) {
|
||||||
|
// wait until we can send
|
||||||
|
loop_until(|| self.AUX_MU_LSR.is_set(AUX_MU_LSR::TX_EMPTY));
|
||||||
|
|
||||||
|
// write the character to the buffer
|
||||||
|
self.AUX_MU_IO.set(c as u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "noserial")]
|
||||||
|
fn putc(&self, c: char) {}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
#[cfg(not(feature = "noserial"))]
|
||||||
|
fn getc(&self) -> char {
|
||||||
|
// wait until something is in the buffer
|
||||||
|
loop_until(|| self.AUX_MU_LSR.is_set(AUX_MU_LSR::DATA_READY));
|
||||||
|
|
||||||
|
// read it and return
|
||||||
|
let mut ret = self.AUX_MU_IO.get() as u8 as char;
|
||||||
|
|
||||||
|
// convert carriage return to newline
|
||||||
|
if ret == '\r' {
|
||||||
|
ret = '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "noserial")]
|
||||||
|
pub fn getc(&self) -> char {
|
||||||
|
'\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wait until the TX FIFO is empty, aka all characters have been put on the
|
||||||
|
/// line.
|
||||||
|
fn flush(&self) {
|
||||||
|
self.wait_tx_fifo_empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Write for MiniUart {
|
||||||
|
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||||
|
self.puts(s);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
pub mod display;
|
||||||
|
pub mod gpio;
|
||||||
|
pub mod mailbox;
|
||||||
|
pub mod mini_uart;
|
||||||
|
pub mod power;
|
||||||
|
pub mod rpi3;
|
||||||
|
pub mod uart;
|
||||||
|
pub mod vc;
|
||||||
|
|
||||||
|
pub use mini_uart::MiniUart;
|
||||||
|
pub use uart::PL011Uart;
|
|
@ -0,0 +1,143 @@
|
||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use super::mailbox;
|
||||||
|
use crate::arch::loop_delay;
|
||||||
|
use crate::platform::{gpio, rpi3::BcmHost};
|
||||||
|
use core::ops;
|
||||||
|
use core::sync::atomic::{compiler_fence, Ordering};
|
||||||
|
use register::mmio::*;
|
||||||
|
|
||||||
|
const POWER_BASE: u32 = BcmHost::get_peripheral_address() + 0x100_01C;
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct RegisterBlock {
|
||||||
|
PM_RSTC: ReadWrite<u32>, // 0x1C
|
||||||
|
PM_RSTS: ReadWrite<u32>, // 0x20
|
||||||
|
PM_WDOG: ReadWrite<u32>, // 0x24
|
||||||
|
}
|
||||||
|
|
||||||
|
const PM_PASSWORD: u32 = 0x5a_000_000;
|
||||||
|
const PM_RSTC_WRCFG_CLR: u32 = 0xffff_ffcf;
|
||||||
|
const PM_RSTC_WRCFG_FULL_RESET: u32 = 0x0000_0020;
|
||||||
|
|
||||||
|
// The Raspberry Pi firmware uses the RSTS register to know which
|
||||||
|
// partition to boot from. The partition value is spread into bits 0, 2,
|
||||||
|
// 4, 6, 8, 10. Partition 63 is a special partition used by the
|
||||||
|
// firmware to indicate halt.
|
||||||
|
const PM_RSTS_RASPBERRYPI_HALT: u32 = 0x555;
|
||||||
|
|
||||||
|
pub enum PowerError {
|
||||||
|
MailboxError,
|
||||||
|
}
|
||||||
|
pub type Result<T> = ::core::result::Result<T, PowerError>;
|
||||||
|
|
||||||
|
/// Public interface to the Power subsystem
|
||||||
|
pub struct Power;
|
||||||
|
|
||||||
|
impl ops::Deref for Power {
|
||||||
|
type Target = RegisterBlock;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
unsafe { &*Self::ptr() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Power {
|
||||||
|
pub fn new() -> Power {
|
||||||
|
Power
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a pointer to the register block
|
||||||
|
fn ptr() -> *const RegisterBlock {
|
||||||
|
POWER_BASE as *const _
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shutdown the board
|
||||||
|
/*
|
||||||
|
pub fn off(&self, mbox: &mut mbox::Mbox, gpio: &gpio::GPIO) -> Result<()> {
|
||||||
|
// power off devices one by one
|
||||||
|
for dev_id in 0..16 {
|
||||||
|
mbox.buffer[0] = 8 * 4;
|
||||||
|
mbox.buffer[1] = mbox::REQUEST;
|
||||||
|
mbox.buffer[2] = mbox::tag::SETPOWER;
|
||||||
|
mbox.buffer[3] = 8;
|
||||||
|
mbox.buffer[4] = 8;
|
||||||
|
mbox.buffer[5] = dev_id; // device id
|
||||||
|
mbox.buffer[6] = 0; // bit 0: off, bit 1: no wait
|
||||||
|
mbox.buffer[7] = mbox::tag::LAST;
|
||||||
|
|
||||||
|
// Insert a compiler fence that ensures that all stores to the
|
||||||
|
// mbox buffer are finished before the GPU is signaled (which
|
||||||
|
// is done by a store operation as well).
|
||||||
|
compiler_fence(Ordering::Release);
|
||||||
|
|
||||||
|
if mbox.call(mbox::channel::PROP).is_err() {
|
||||||
|
return Err(PowerError::MailboxError); // Abort if UART clocks couldn't be set
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// power off gpio pins (but not VCC pins)
|
||||||
|
gpio.GPFSEL0.set(0);
|
||||||
|
gpio.GPFSEL1.set(0);
|
||||||
|
gpio.GPFSEL2.set(0);
|
||||||
|
gpio.GPFSEL3.set(0);
|
||||||
|
gpio.GPFSEL4.set(0);
|
||||||
|
gpio.GPFSEL5.set(0);
|
||||||
|
|
||||||
|
gpio.GPPUD.set(0);
|
||||||
|
loop_delay(150);
|
||||||
|
|
||||||
|
gpio.GPPUDCLK0.set(0xffff_ffff);
|
||||||
|
gpio.GPPUDCLK1.set(0xffff_ffff);
|
||||||
|
loop_delay(150);
|
||||||
|
|
||||||
|
// flush GPIO setup
|
||||||
|
gpio.GPPUDCLK0.set(0);
|
||||||
|
gpio.GPPUDCLK1.set(0);
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
val |= PM_PASSWORD | PM_RSTS_RASPBERRYPI_HALT;
|
||||||
|
self.PM_RSTS.set(val);
|
||||||
|
|
||||||
|
// Continue with normal reset mechanism
|
||||||
|
self.reset();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
/// 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();
|
||||||
|
val &= PM_RSTC_WRCFG_CLR;
|
||||||
|
val |= PM_PASSWORD | PM_RSTC_WRCFG_FULL_RESET;
|
||||||
|
self.PM_RSTC.set(val);
|
||||||
|
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
// See BCM2835-ARM-Peripherals.pdf
|
||||||
|
// See https://www.raspberrypi.org/forums/viewtopic.php?t=186090 for more details.
|
||||||
|
|
||||||
|
pub struct BcmHost;
|
||||||
|
|
||||||
|
impl BcmHost {
|
||||||
|
// 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.
|
||||||
|
/// This returns the ARM-side physical address where peripherals are mapped.
|
||||||
|
pub const fn get_peripheral_address() -> u32 {
|
||||||
|
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: u32) -> u32 {
|
||||||
|
bus & !0xc000_0000
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn phys2bus(phys: u32) -> u32 {
|
||||||
|
phys | 0xc000_0000
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,269 @@
|
||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
|
||||||
|
* Copyright (c) 2019 Berkus Decker <berkus+github@metta.systems>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use super::mailbox;
|
||||||
|
use crate::arch::{loop_delay, loop_until};
|
||||||
|
use crate::devices::ConsoleOps;
|
||||||
|
use crate::platform::{gpio, rpi3::BcmHost};
|
||||||
|
use core::{convert::TryFrom, ops};
|
||||||
|
use register::{mmio::*, register_bitfields};
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum PL011UartError {
|
||||||
|
MailboxError,
|
||||||
|
}
|
||||||
|
pub type Result<T> = ::core::result::Result<T, PL011UartError>;
|
||||||
|
|
||||||
|
pub struct PL011Uart {
|
||||||
|
base_addr: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ops::Deref for PL011Uart {
|
||||||
|
type Target = RegisterBlock;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
unsafe { &*self.ptr() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PL011Uart {
|
||||||
|
pub fn new_default() -> PL011Uart {
|
||||||
|
const UART0_BASE: u32 = BcmHost::get_peripheral_address() + 0x20_1000;
|
||||||
|
PL011Uart {
|
||||||
|
base_addr: usize::try_from(UART0_BASE).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 init(&self, mbox: &mut mailbox::Mailbox, gpio: &gpio::GPIO) -> Result<()> {
|
||||||
|
// turn off UART0
|
||||||
|
self.CR.set(0);
|
||||||
|
|
||||||
|
// set up clock for consistent divisor values
|
||||||
|
mbox.buffer[0] = 9 * 4;
|
||||||
|
mbox.buffer[1] = mailbox::REQUEST;
|
||||||
|
mbox.buffer[2] = mailbox::tag::SetClockRate;
|
||||||
|
mbox.buffer[3] = 12;
|
||||||
|
mbox.buffer[4] = 8;
|
||||||
|
mbox.buffer[5] = mailbox::clock::UART; // UART clock
|
||||||
|
mbox.buffer[6] = 4_000_000; // 4Mhz
|
||||||
|
mbox.buffer[7] = 0; // skip turbo setting
|
||||||
|
mbox.buffer[8] = mailbox::tag::End;
|
||||||
|
|
||||||
|
if mbox.call(mailbox::channel::PropertyTagsArmToVc).is_err() {
|
||||||
|
return Err(PL011UartError::MailboxError); // Abort if UART clocks couldn't be set
|
||||||
|
};
|
||||||
|
|
||||||
|
// map UART0 to GPIO pins
|
||||||
|
gpio.GPFSEL1
|
||||||
|
.modify(gpio::GPFSEL1::FSEL14::TXD0 + gpio::GPFSEL1::FSEL15::RXD0);
|
||||||
|
|
||||||
|
gpio.GPPUD.set(0); // enable pins 14 and 15
|
||||||
|
loop_delay(150);
|
||||||
|
|
||||||
|
gpio.GPPUDCLK0.modify(
|
||||||
|
gpio::GPPUDCLK0::PUDCLK14::AssertClock + gpio::GPPUDCLK0::PUDCLK15::AssertClock,
|
||||||
|
);
|
||||||
|
loop_delay(150);
|
||||||
|
|
||||||
|
gpio.GPPUDCLK0.set(0);
|
||||||
|
|
||||||
|
self.ICR.write(ICR::ALL::CLEAR);
|
||||||
|
self.IBRD.write(IBRD::IBRD.val(2)); // Results in 115200 baud
|
||||||
|
self.FBRD.write(FBRD::FBRD.val(0xB));
|
||||||
|
self.LCRH.write(LCRH::WLEN::EightBit); // 8N1
|
||||||
|
|
||||||
|
self.CR
|
||||||
|
.write(CR::UARTEN::Enabled + CR::TXE::Enabled + CR::RXE::Enabled);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for PL011Uart {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.CR
|
||||||
|
.write(CR::UARTEN::Disabled + CR::TXE::Disabled + CR::RXE::Disabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConsoleOps for PL011Uart {
|
||||||
|
/// 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
use crate::{
|
||||||
|
jtag_dbg_wait,
|
||||||
|
platform::{
|
||||||
|
display::{Display, PixelOrder, Size2d, CHARSIZE_X, CHARSIZE_Y},
|
||||||
|
mailbox::{self, channel, response::VAL_LEN_FLAG, tag, Mailbox},
|
||||||
|
rpi3::BcmHost,
|
||||||
|
},
|
||||||
|
println,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct VC;
|
||||||
|
|
||||||
|
impl VC {
|
||||||
|
// Use mailbox framebuffer interface to initialize
|
||||||
|
// https://www.raspberrypi.org/forums/viewtopic.php?f=72&t=185116
|
||||||
|
pub fn init_fb(size: Size2d, depth: u32) -> Option<Display> {
|
||||||
|
// Use property channel
|
||||||
|
let mut mbox = Mailbox::default();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* * All tags in the request are processed in one operation.
|
||||||
|
* * It is not valid to mix Test tags with Get/Set tags
|
||||||
|
* in the same operation and no tags will be returned.
|
||||||
|
* * Get tags will be processed after all Set tags.
|
||||||
|
* * If an allocate buffer tag is omitted when setting parameters,
|
||||||
|
* then no change occurs unless it can be accommodated without changing
|
||||||
|
* the buffer base or size.
|
||||||
|
* * When an allocate buffer response is returned, the old buffer area
|
||||||
|
* (if the base or size has changed) is implicitly freed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
let index = mbox.request();
|
||||||
|
let index = mbox.set_physical_wh(index, size.x, size.y);
|
||||||
|
let index = mbox.set_virtual_wh(index, size.x, size.y);
|
||||||
|
let index = mbox.set_depth(index, depth);
|
||||||
|
let index = mbox.allocate_buffer_aligned(index, 16);
|
||||||
|
mbox.end(index);
|
||||||
|
|
||||||
|
mbox.call(channel::PropertyTagsArmToVc).map_err(|e| {
|
||||||
|
println!("Mailbox call returned error {}", e);
|
||||||
|
println!("Mailbox contents: {}", mbox);
|
||||||
|
()
|
||||||
|
});
|
||||||
|
|
||||||
|
if (mbox.buffer[18] & VAL_LEN_FLAG) == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let fb_ptr = BcmHost::bus2phys(mbox.buffer[19]);
|
||||||
|
let fb_size = mbox.buffer[20];
|
||||||
|
|
||||||
|
mbox.buffer[0] = 15 * 4;
|
||||||
|
mbox.buffer[1] = mailbox::REQUEST;
|
||||||
|
|
||||||
|
// SetPixelOrder doesn't work in QEMU, however TestPixelOrder does.
|
||||||
|
mbox.buffer[2] = tag::TestPixelOrder;
|
||||||
|
mbox.buffer[3] = 4;
|
||||||
|
mbox.buffer[4] = 4;
|
||||||
|
mbox.buffer[5] = 1; // PixelOrder
|
||||||
|
|
||||||
|
mbox.buffer[6] = tag::SetAlphaMode;
|
||||||
|
mbox.buffer[7] = 4;
|
||||||
|
mbox.buffer[8] = 4;
|
||||||
|
mbox.buffer[9] = mailbox::alpha_mode::IGNORED;
|
||||||
|
|
||||||
|
mbox.buffer[10] = tag::GetPitch;
|
||||||
|
mbox.buffer[11] = 4;
|
||||||
|
mbox.buffer[12] = 0;
|
||||||
|
mbox.buffer[13] = 0;
|
||||||
|
|
||||||
|
mbox.buffer[14] = tag::End;
|
||||||
|
|
||||||
|
mbox.call(channel::PropertyTagsArmToVc).map_err(|_| ());
|
||||||
|
|
||||||
|
if (mbox.buffer[4] & VAL_LEN_FLAG) == 0 || (mbox.buffer[12] & VAL_LEN_FLAG) == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let order = match mbox.buffer[5] {
|
||||||
|
0 => PixelOrder::BGR,
|
||||||
|
1 => PixelOrder::RGB,
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let pitch = mbox.buffer[13];
|
||||||
|
|
||||||
|
/* Need to set up max_x/max_y before using Display::write */
|
||||||
|
let max_x = size.x / CHARSIZE_X;
|
||||||
|
let max_y = size.y / CHARSIZE_Y;
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"[i] VC init: {}x{}, {}x{}, d{}, --{}--, +{}x{}, {}@{:x}",
|
||||||
|
size.x,
|
||||||
|
size.y,
|
||||||
|
size.x,
|
||||||
|
size.y,
|
||||||
|
depth,
|
||||||
|
pitch,
|
||||||
|
0, // x_offset
|
||||||
|
0, // y_offset
|
||||||
|
fb_size,
|
||||||
|
fb_ptr
|
||||||
|
);
|
||||||
|
|
||||||
|
Some(Display::new(
|
||||||
|
fb_ptr, fb_size, depth, pitch, max_x, max_y, size.x, size.y, order,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019 Andre Richter <andre.o.richter@gmail.com>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use core::cell::UnsafeCell;
|
||||||
|
|
||||||
|
pub struct NullLock<T> {
|
||||||
|
data: UnsafeCell<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we are instantiating this struct as a static variable, which could
|
||||||
|
// potentially be shared between different threads, we need to tell the compiler
|
||||||
|
// that sharing of this struct is safe by marking it with the Sync trait.
|
||||||
|
//
|
||||||
|
// At this point in time, we can do so without worrying, because the kernel
|
||||||
|
// anyways runs on a single core and interrupts are disabled. In short, multiple
|
||||||
|
// threads don't exist yet in our code.
|
||||||
|
//
|
||||||
|
// Literature:
|
||||||
|
// https://doc.rust-lang.org/beta/nomicon/send-and-sync.html
|
||||||
|
// https://doc.rust-lang.org/book/ch16-04-extensible-concurrency-sync-and-send.html
|
||||||
|
unsafe impl<T> Sync for NullLock<T> {}
|
||||||
|
|
||||||
|
impl<T> NullLock<T> {
|
||||||
|
pub const fn new(data: T) -> NullLock<T> {
|
||||||
|
NullLock {
|
||||||
|
data: UnsafeCell::new(data),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> NullLock<T> {
|
||||||
|
pub fn lock<F, R>(&self, f: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut T) -> R,
|
||||||
|
{
|
||||||
|
// In a real lock, there would be code around this line that ensures
|
||||||
|
// that this mutable reference will ever only be given out to one thread
|
||||||
|
// at a time.
|
||||||
|
f(unsafe { &mut *self.data.get() })
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
// No-alloc write!() implementation from https://stackoverflow.com/a/50201632/145434
|
||||||
|
// Requires you to allocate a buffer somewhere manually.
|
||||||
|
//
|
||||||
|
|
||||||
|
use core::cmp::min;
|
||||||
|
use core::fmt;
|
||||||
|
|
||||||
|
pub struct WriteTo<'a> {
|
||||||
|
buffer: &'a mut [u8],
|
||||||
|
// on write error (i.e. not enough space in buffer) this grows beyond
|
||||||
|
// `buffer.len()`.
|
||||||
|
used: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> WriteTo<'a> {
|
||||||
|
pub fn new(buffer: &'a mut [u8]) -> Self {
|
||||||
|
WriteTo { buffer, used: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_str(self) -> Option<&'a str> {
|
||||||
|
if self.used <= self.buffer.len() {
|
||||||
|
// only successful concats of str - must be a valid str.
|
||||||
|
use core::str::from_utf8_unchecked;
|
||||||
|
Some(unsafe { from_utf8_unchecked(&self.buffer[..self.used]) })
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> fmt::Write for WriteTo<'a> {
|
||||||
|
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||||
|
if self.used > self.buffer.len() {
|
||||||
|
return Err(fmt::Error);
|
||||||
|
}
|
||||||
|
let remaining_buf = &mut self.buffer[self.used..];
|
||||||
|
let raw_s = s.as_bytes();
|
||||||
|
let write_num = min(raw_s.len(), remaining_buf.len());
|
||||||
|
remaining_buf[..write_num].copy_from_slice(&raw_s[..write_num]);
|
||||||
|
self.used += raw_s.len();
|
||||||
|
if write_num < raw_s.len() {
|
||||||
|
Err(fmt::Error)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show<'a>(buffer: &'a mut [u8], args: fmt::Arguments) -> Result<&'a str, fmt::Error> {
|
||||||
|
let mut w = WriteTo::new(buffer);
|
||||||
|
fmt::write(&mut w, args)?;
|
||||||
|
w.as_str().ok_or(fmt::Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test() {
|
||||||
|
let mut buf = [0u8; 64];
|
||||||
|
let s: &str = show(
|
||||||
|
&mut buf,
|
||||||
|
format_args!("write some stuff {:?}: {}", "foo", 42),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(s, "write some stuff foo: 42");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
@todo - factor it out into separate repo
|
||||||
|
@todo - "panic-strategy": "abort" is ok for baremetal, but not for -metta, right?
|
||||||
|
|
||||||
|
# vesper-targets
|
||||||
|
|
||||||
|
These are [target
|
||||||
|
specifications](https://github.com/rust-lang/rfcs/blob/master/text/0131-target-specification.md)
|
||||||
|
suitable for cross-compiling Rust crates for Vesper. Set your `RUST_TARGET_PATH` to point to this directory.
|
||||||
|
|
||||||
|
These are very much based on Robigalia's [sel4-targets](https://gitlab.com/robigalia/sel4-targets).
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Complete for aarch64. Untested for anything else.
|
||||||
|
|
||||||
|
## Generating target specifications:
|
||||||
|
|
||||||
|
See [description in rust docs](https://doc.rust-lang.org/rustc/targets/custom.html).
|
||||||
|
|
||||||
|
To generate a target specification json template, run
|
||||||
|
|
||||||
|
```
|
||||||
|
rustc +nightly -Z unstable-options --target=wasm32-unknown-unknown --print target-spec-json
|
||||||
|
```
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"llvm-target": "aarch64-unknown-none",
|
||||||
|
"data-layout": "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128",
|
||||||
|
"arch": "aarch64",
|
||||||
|
"os": "vesper",
|
||||||
|
"vendor": "metta",
|
||||||
|
"env": "",
|
||||||
|
"executables": true,
|
||||||
|
"panic-strategy": "abort",
|
||||||
|
"linker-flavor": "ld.lld",
|
||||||
|
"linker": "rust-lld",
|
||||||
|
"pre-link-args": {
|
||||||
|
"ld.lld": [
|
||||||
|
"--script=linker/aarch64.ld",
|
||||||
|
"--print-gc-sections"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"disable-redzone": true,
|
||||||
|
"target-endian": "little",
|
||||||
|
"target-c-int-width": "32",
|
||||||
|
"target-pointer-width": "64"
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"data-layout": "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64",
|
||||||
|
"llvm-target": "arm-unknown-linux-musleabihf",
|
||||||
|
"target-endian": "little",
|
||||||
|
"target-pointer-width": "32",
|
||||||
|
"target-c-int-width": "32",
|
||||||
|
"os": "vesper",
|
||||||
|
"env": "metta",
|
||||||
|
"arch": "arm",
|
||||||
|
"linker-is-gnu": true,
|
||||||
|
"executables": true,
|
||||||
|
"linker-flavor": "ld",
|
||||||
|
"linker": "arm-unknown-linux-musleabihf-ld",
|
||||||
|
"ar": "arm-unknown-linux-musleabihf-ar",
|
||||||
|
"position-independent-executables": true,
|
||||||
|
"has-elf-tls": true,
|
||||||
|
"panic-strategy": "abort",
|
||||||
|
"no-default-libraries": true,
|
||||||
|
"pre-link-args": {
|
||||||
|
"gcc": ["-ffreestanding", "-nodefaultlibs", "-nostdlib"]
|
||||||
|
},
|
||||||
|
"default-codegen-units": 1
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"data-layout": "e-m:e-p:32:32-f64:32:64-f80:32-n8:16:32-S128",
|
||||||
|
"llvm-target": "i686-elf",
|
||||||
|
"target-endian": "little",
|
||||||
|
"target-pointer-width": "32",
|
||||||
|
"target-c-int-width": "32",
|
||||||
|
"os": "vesper",
|
||||||
|
"env": "metta",
|
||||||
|
"arch": "x86",
|
||||||
|
"linker-is-gnu": true,
|
||||||
|
"executables": true,
|
||||||
|
"no-compiler-rt": false,
|
||||||
|
"relocation-model": "pic",
|
||||||
|
"position-independent-executables": false,
|
||||||
|
"dynamic-linking": false,
|
||||||
|
"has-elf-tls": true,
|
||||||
|
"panic-strategy": "abort",
|
||||||
|
"no-default-libraries": true,
|
||||||
|
"linker-flavor": "gcc",
|
||||||
|
"pre-link-args": {
|
||||||
|
"gcc": ["-m32", "-ffreestanding", "-nodefaultlibs", "-nostdlib"]
|
||||||
|
},
|
||||||
|
"default-codegen-units": 1
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
|
||||||
|
"llvm-target": "x86_64-elf",
|
||||||
|
"target-endian": "little",
|
||||||
|
"target-pointer-width": "64",
|
||||||
|
"target-c-int-width": "32",
|
||||||
|
"os": "vesper",
|
||||||
|
"env": "metta",
|
||||||
|
"arch": "x86_64",
|
||||||
|
"cpu": "x86-64",
|
||||||
|
"linker-is-gnu": true,
|
||||||
|
"executables": true,
|
||||||
|
"no-compiler-rt": false,
|
||||||
|
"relocation-model": "pic",
|
||||||
|
"position-independent-executables": false,
|
||||||
|
"dynamic_linking": false,
|
||||||
|
"has-elf-tls": true,
|
||||||
|
"panic-strategy": "abort",
|
||||||
|
"disable-redzone": true,
|
||||||
|
"no-default-libraries": true,
|
||||||
|
"linker-flavor": "gcc",
|
||||||
|
"pre-link-args": {
|
||||||
|
"gcc": ["-m64", "-ffreestanding", "-nodefaultlibs", "-nostdlib"]
|
||||||
|
},
|
||||||
|
"default-codegen-units": 1
|
||||||
|
}
|
Loading…
Reference in New Issue