Compare commits

..

4 Commits

Author SHA1 Message Date
Berkus Decker c618cc18d4 [wip:explore/dtb] Add more dtbs for the boards I have 2021-07-11 22:32:46 +03:00
Berkus Decker 0466589ddf [wip] try to use fdt-rs for DTB parsing 2021-07-11 22:32:29 +03:00
Berkus Decker 5c7de9ceec additional dtb crates to review - fdt-rs seems most active? 2021-07-11 22:31:22 +03:00
Berkus Decker 059566e698 [wip] Add wrapper for device_tree crate, bootup with DTB
This is to easily add necessary functions without touching the original crate - this will probably need to be upstreamed eventually!
2021-07-11 22:31:15 +03:00
80 changed files with 1470 additions and 3749 deletions

View File

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

View File

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

885
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

View File

@ -1,34 +1,32 @@
# Broadcom bcm2837 on Raspberry Pi 3 as JTAG target
# Broadcom 2837 on Raspberry Pi 3 as JTAG target
# From https://www.suse.com/c/debugging-raspberry-pi-3-with-jtag/
echo "Booting JTAG for Raspberry Pi 3"
telnet_port 4444
gdb_port 5555
transport select jtag
# we need to enable srst even though we don't connect it
reset_config trst_and_srst
adapter_khz 4000
jtag_ntrst_delay 500
if { [info exists CHIPNAME] } {
set _CHIPNAME $CHIPNAME
} else {
set _CHIPNAME bcm2837
set _CHIPNAME rpi3
}
#
# Main DAP
#
if { [info exists DAP_TAPID] } {
set _DAP_TAPID $DAP_TAPID
} else {
set _DAP_TAPID 0x4ba00477
}
adapter speed 4000
transport select jtag
# we need to enable srst even though we don't connect it
reset_config trst_and_srst
jtag_ntrst_delay 500
telnet_port 4444
gdb_port 5555
#
# Main DAP
#
jtag newtap $_CHIPNAME tap -irlen 4 -ircapture 0x1 -irmask 0xf -expected-id $_DAP_TAPID -enable
dap create $_CHIPNAME.dap -chain-position $_CHIPNAME.tap
@ -37,28 +35,17 @@ set _CTINAME $_CHIPNAME.cti
set DBGBASE {0x80010000 0x80012000 0x80014000 0x80016000}
set CTIBASE {0x80018000 0x80019000 0x8001a000 0x8001b000}
set _cores 4
set _smp_command ""
for { set _core 0 } { $_core < $_cores } { incr _core } {
cti create $_CTINAME.$_core -dap $_CHIPNAME.dap -ap-num 0 \
-baseaddr [lindex $CTIBASE $_core]
-ctibase [lindex $CTIBASE $_core]
target create $_TARGETNAME.$_core aarch64 \
-dap $_CHIPNAME.dap -coreid $_core \
-dbgbase [lindex $DBGBASE $_core] -cti $_CTINAME.$_core
if {$_core != 0} {
set _smp_command "$_smp_command ${_TARGETNAME}.${_core}"
} else {
set _smp_command "target smp ${_TARGETNAME}.${_core}"
}
$_TARGETNAME.$_core configure -event reset-assert-post "aarch64 dbginit"
$_TARGETNAME.$_core configure -event gdb-attach { halt }
}
eval $_smp_command
targets $_TARGETNAME.0

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
[package]
name = "nucleus"
name = "vesper"
version = "0.0.1"
authors = ["Berkus Decker <berkus+vesper@metta.systems>"]
description = "Vesper nanokernel binary"
description = "Vesper exokernel"
documentation = "https://docs.metta.systems/vesper"
homepage = "https://github.com/metta-systems/vesper"
repository = "https://github.com/metta-systems/vesper"
@ -10,30 +10,34 @@ readme = "README.md"
license = "BlueOak-1.0.0"
categories = ["no-std", "embedded", "os"]
publish = false
edition = "2021"
edition = "2018"
[badges]
maintenance = { status = "experimental" }
[features]
noserial = ["machine/noserial"]
noserial = []
# Enable JTAG debugging of kernel - enable jtag helpers and
# block waiting for JTAG probe attach at the start of kernel main.
jtag = ["machine/jtag"]
jtag = []
# Build for running under QEMU with semihosting, so various halt/reboot options would for example quit QEMU instead.
qemu = ["machine/qemu"]
# Mutually exclusive features to choose a target board
rpi3 = ["machine/rpi3"]
rpi4 = ["machine/rpi4"]
qemu = ["qemu-exit"]
[dependencies]
machine = { path = "../machine" }
r0 = "1.0"
cortex-a = "7.0"
qemu-exit = { version = "2.0", optional = true }
cortex-a = "6.0"
tock-registers = "0.7"
ux = { version = "0.1", default-features = false }
usize_conversions = "0.2"
bit_field = "0.10"
bitflags = "1.3"
ux = { version = "0.1.3", default-features = false }
usize_conversions = "0.2.0"
bit_field = "0.10.1"
bitflags = "1.2"
cfg-if = "1.0"
snafu = { version = "0.7", default-features = false }
snafu = { version = "0.7.0-beta.0", default-features = false }
shrinkwraprs = { version = "0.3", default-features = false }
#dtb = "0.2" # Dec 2019 by Simon Prikhodko
# Alternatives to dtb to look at:
#device_tree = "1.1.0" # Jun 2016 by Marc Brinkmann
# fdt-rs latest 0.4.3 Jul 2021 by Sean Wilson -- interface is more basic than in dtb
fdt-rs = { version = "0.4", default-features = false }
# @todo Added from_raw_pointer() finally

View File

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

View File

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

View File

@ -8,6 +8,14 @@
//! 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>
//! Raspi kernel boot helper: https://github.com/raspberrypi/tools/blob/master/armstubs/armstub8.S
//! In particular, see dtb_ptr32
//! To get memory size from DTB:
//! 1. Find nodes with unit-names `/memory`
//! 2. From those read reg entries, using `/#address-cells` and `/#size-cells` as units
//! 3. Union of all these reg entries will be the available memory. Enter it as mem-regions.
use {
crate::endless_sleep,
cortex_a::{asm, registers::*},
@ -24,12 +32,11 @@ macro_rules! entry {
/// # Safety
/// Only type-checks!
#[export_name = "main"]
#[inline(always)]
pub unsafe fn __main() -> ! {
pub unsafe fn __main(dtb: u32) -> ! {
// type check the given path
let f: fn() -> ! = $path;
let f: fn(u32) -> ! = $path;
f()
f(dtb)
}
};
}
@ -42,10 +49,9 @@ macro_rules! entry {
///
/// Totally unsafe! We're in the hardware land.
#[link_section = ".text.boot"]
unsafe fn reset() -> ! {
unsafe fn reset(dtb: u32) -> ! {
extern "C" {
// Boundaries of the .bss section, provided by the linker script
// The type, `u64`, indicates that the memory is 8-byte aligned
static mut __BSS_START: u64;
static mut __BSS_END: u64;
}
@ -54,10 +60,10 @@ unsafe fn reset() -> ! {
r0::zero_bss(&mut __BSS_START, &mut __BSS_END);
extern "Rust" {
fn main() -> !;
fn main(dtb: u32) -> !;
}
main()
main(dtb)
}
// [ARMv6 unaligned data access restrictions](https://developer.arm.com/documentation/ddi0333/h/unaligned-and-mixed-endian-data-access-support/unaligned-access-support/armv6-unaligned-data-access-restrictions?lang=en)
@ -68,7 +74,7 @@ unsafe fn reset() -> ! {
// #[inline]
// fn enable_armv6_unaligned_access() {
// unsafe {
// core::arch::asm!(
// asm!(
// "mrc p15, 0, {u}, c1, c0, 0",
// "or {u}, {u}, {CR_U}",
// "mcr p15, 0, {u}, c1, c0, 0",
@ -110,11 +116,16 @@ fn shared_setup_and_enter_pre() {
#[link_section = ".text.boot"]
#[inline]
fn shared_setup_and_enter_post() -> ! {
fn shared_setup_and_enter_post(dtb: u32) -> ! {
// Set up SP_EL1 (stack pointer), which will be used by EL1 once
// we "return" to it.
SP_EL1.set(STACK_START);
unsafe {
asm!("mov {dtb:w}, w0", dtb = in(reg) dtb);
// @todo How to enforce dtb being in w0 at this point? -- must be an arg to eret()
}
// Use `eret` to "return" to EL1. This will result in execution of
// `reset()` in EL1.
asm::eret()
@ -125,7 +136,7 @@ fn shared_setup_and_enter_post() -> ! {
/// Prepare and execute transition from EL2 to EL1.
#[link_section = ".text.boot"]
#[inline]
fn setup_and_enter_el1_from_el2() -> ! {
fn setup_and_enter_el1_from_el2(dtb: u32) -> ! {
// Set Saved Program Status Register (EL2)
// Set up a simulated exception return.
//
@ -142,7 +153,7 @@ fn setup_and_enter_el1_from_el2() -> ! {
// Make the Exception Link Register (EL2) point to reset().
ELR_EL2.set(reset as *const () as u64);
shared_setup_and_enter_post()
shared_setup_and_enter_post(dtb)
}
/// QEMU boot-up sequence.
@ -158,7 +169,7 @@ fn setup_and_enter_el1_from_el2() -> ! {
#[cfg(qemu)]
#[link_section = ".text.boot"]
#[inline]
fn setup_and_enter_el1_from_el3() -> ! {
fn setup_and_enter_el1_from_el3(dtb: u32) -> ! {
// Set Secure Configuration Register (EL3)
SCR_EL3.write(SCR_EL3::RW::NextELIsAarch64 + SCR_EL3::NS::NonSecure);
@ -178,7 +189,7 @@ fn setup_and_enter_el1_from_el3() -> ! {
// Make the Exception Link Register (EL3) point to reset().
ELR_EL3.set(reset as *const () as u64);
shared_setup_and_enter_post()
shared_setup_and_enter_post(dtb)
}
/// Entrypoint of the processor.
@ -194,7 +205,7 @@ fn setup_and_enter_el1_from_el3() -> ! {
///
#[no_mangle]
#[link_section = ".text.boot.entry"]
pub unsafe extern "C" fn _boot_cores() -> ! {
pub unsafe extern "C" fn _boot_cores(dtb: u32) -> ! {
const CORE_0: u64 = 0;
const CORE_MASK: u64 = 0x3;
// Can't match values with dots in match, so use intermediate consts.
@ -209,11 +220,14 @@ pub unsafe extern "C" fn _boot_cores() -> ! {
shared_setup_and_enter_pre();
if CORE_0 == MPIDR_EL1.get() & CORE_MASK {
// @todo On entry, w0 should contain the dtb address.
// For non-primary cores it however contains 0.
match CurrentEL.get() {
#[cfg(qemu)]
EL3 => setup_and_enter_el1_from_el3(),
EL2 => setup_and_enter_el1_from_el2(),
EL1 => reset(),
EL3 => setup_and_enter_el1_from_el3(dtb),
EL2 => setup_and_enter_el1_from_el2(dtb),
EL1 => reset(dtb),
_ => endless_sleep(),
}
}

View File

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

View File

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

View File

@ -73,7 +73,6 @@ impl PhysAddr {
/// Aligns the physical address upwards to the given alignment.
///
/// See the `align_up` function for more information.
#[must_use]
pub fn aligned_up<U>(self, align: U) -> Self
where
U: Into<u64>,
@ -84,7 +83,6 @@ impl PhysAddr {
/// Aligns the physical address downwards to the given alignment.
///
/// See the `align_down` function for more information.
#[must_use]
pub fn aligned_down<U>(self, align: U) -> Self
where
U: Into<u64>,

View File

@ -108,7 +108,6 @@ impl VirtAddr {
/// Aligns the virtual address upwards to the given alignment.
///
/// See the `align_up` free function for more information.
#[must_use]
pub fn aligned_up<U>(self, align: U) -> Self
where
U: Into<u64>,
@ -119,7 +118,6 @@ impl VirtAddr {
/// Aligns the virtual address downwards to the given alignment.
///
/// See the `align_down` free function for more information.
#[must_use]
pub fn aligned_down<U>(self, align: U) -> Self
where
U: Into<u64>,

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,32 @@
use shrinkwraprs::Shrinkwrap;
#[derive(Shrinkwrap)]
pub struct DeadTree<'a>(dtb::Reader<'a>);
impl<'a> DeadTree<'a> {
pub fn new(reader: dtb::Reader<'a>) -> Self {
Self(reader)
}
pub fn try_struct_u32_value<'s, P: Into<&'s str>>(&self, path: P) -> Result<u32, dtb::Error> {
let mut buf = [0u8; 4];
Ok(self
.0
.struct_items()
.path_struct_items(path.into())
.next()
.ok_or(dtb::Error::BadPropertyName)?
.0
.value_u32_list(&mut buf)?[0])
}
pub fn try_struct_str_value<'s, P: Into<&'s str>>(&self, path: P) -> Result<&str, dtb::Error> {
self.0
.struct_items()
.path_struct_items(path.into())
.next()
.ok_or(dtb::Error::BadPropertyName)?
.0
.value_str()
}
}

View File

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

View File

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

View File

@ -3,44 +3,81 @@
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
//! Vesper single-address-space nanokernel.
//! Vesper single-address-space exokernel.
//!
//! This crate implements the kernel binary proper.
#![no_std]
#![no_main]
#![feature(asm)]
#![feature(global_asm)]
#![feature(decl_macro)]
#![feature(allocator_api)]
#![feature(ptr_internals)]
#![feature(format_args_nl)]
#![feature(nonnull_slice_from_raw_parts)]
#![feature(custom_test_frameworks)]
#![test_runner(machine::tests::test_runner)]
#![test_runner(crate::tests::test_runner)]
#![reexport_test_harness_main = "test_main"]
#![deny(missing_docs)]
#![deny(warnings)]
#![allow(clippy::nonstandard_macro_braces)] // https://github.com/shepmaster/snafu/issues/296
#![allow(clippy::upper_case_acronyms)]
#![allow(clippy::enum_variant_names)]
#[cfg(not(target_arch = "aarch64"))]
use architecture_not_supported_sorry;
/// Architecture-specific code.
#[macro_use]
pub mod arch;
pub use arch::*;
mod device_tree;
mod devices;
mod macros;
mod mm;
mod panic;
mod platform;
#[cfg(feature = "qemu")]
mod qemu;
mod sync;
#[cfg(test)]
mod tests;
mod write_to;
#[cfg(not(test))]
use core::panic::PanicInfo;
#[allow(unused_imports)]
use machine::devices::SerialOps;
use {
cfg_if::cfg_if,
machine::{
arch, entry, memory,
platform::rpi3::{
display::{Color, DrawError},
mailbox::{channel, Mailbox, MailboxOps},
vc::VC,
},
println, CONSOLE,
crate::platform::rpi3::{
display::{Color, DrawError},
mailbox::{channel, Mailbox, MailboxOps},
vc::VC,
},
cfg_if::cfg_if,
fdt_rs::base::DevTree,
};
entry!(kmain);
#[cfg(not(test))]
#[panic_handler]
fn panicked(info: &PanicInfo) -> ! {
machine::panic::handler(info)
}
/// The global console. Output of the kernel print! and println! macros goes here.
static CONSOLE: sync::NullLock<devices::Console> = sync::NullLock::new(devices::Console::new());
/// The global allocator for DMA-able memory. That is, memory which is tagged
/// non-cacheable in the page tables.
static DMA_ALLOCATOR: sync::NullLock<mm::BumpAllocator> =
sync::NullLock::new(mm::BumpAllocator::new(
// @todo Init this after we loaded boot memory map
memory::map::virt::DMA_HEAP_START as usize,
memory::map::virt::DMA_HEAP_END as usize,
"Global DMA Allocator",
// Try the following arguments instead to see all mailbox operations
// fail. It will cause the allocator to use memory that are marked
// cacheable and therefore not DMA-safe. The answer from the VideoCore
// won't be received by the CPU because it reads an old cached value
// that resembles an error case instead.
// 0x00600000 as usize,
// 0x007FFFFF as usize,
// "Global Non-DMA Allocator",
));
fn print_mmu_state_and_features() {
memory::mmu::print_features();
@ -70,7 +107,7 @@ fn init_exception_traps() {
#[cfg(not(feature = "noserial"))]
fn init_uart_serial() {
use machine::platform::rpi3::{gpio::GPIO, mini_uart::MiniUart, pl011_uart::PL011Uart};
use crate::platform::rpi3::{gpio::GPIO, mini_uart::MiniUart, pl011_uart::PL011Uart};
let gpio = GPIO::default();
let uart = MiniUart::default();
@ -85,6 +122,7 @@ fn init_uart_serial() {
// Then immediately switch to PL011 (just as an example)
let uart = PL011Uart::default();
let mbox = Mailbox::default();
// uart.init() will reconfigure the GPIO, which causes a race against
// the MiniUart that is still putting out characters on the physical
@ -97,9 +135,10 @@ fn init_uart_serial() {
// physical wires (e.g. the Framebuffer), you don't need to do this,
// because flush() is anyways called implicitly by replace_with(). This
// is just a special case.
use crate::devices::console::ConsoleOps;
CONSOLE.lock(|c| c.flush());
match uart.prepare(&gpio) {
match uart.prepare(mbox, &gpio) {
Ok(uart) => {
CONSOLE.lock(|c| {
// Move uart into the global CONSOLE.
@ -114,9 +153,9 @@ fn init_uart_serial() {
/// Kernel entry point.
/// `arch` crate is responsible for calling it.
#[inline]
pub fn kmain() -> ! {
pub fn kmain(dtb: u32) -> ! {
#[cfg(feature = "jtag")]
machine::arch::jtag::wait_debugger();
jtag::wait_debugger();
init_mmu();
init_exception_traps();
@ -127,6 +166,38 @@ pub fn kmain() -> ! {
#[cfg(test)]
test_main();
println!("DTB loaded at {:x}", dtb);
// Safety: we got the address from the bootloader, if it lied - well, we're screwed!
let device_tree =
unsafe { DevTree::from_raw_pointer(dtb as *const _).expect("DeviceTree failed to read") };
// List unusable memory, and remove it from the memory regions for the allocator.
for entry in device_tree.compatible_nodes("memory") {
println!("reserved: {:?} bytes at {:?}", entry.size, entry.address);
}
// Also, remove the DTB memory region.
// To init memory allocation we need to parse memory regions from dtb and add the regions to
// available memory regions list. Then initial BootRegionAllocator will get memory from these
// regions and record their usage into some OTHER structures, removing these allocations from
// the free regions list.
// memory allocation is described by reg attribute of /memory block.
// /#address-cells and /#size-cells specify the sizes of address and size attributes in reg.
let address_cells = device_tree.try_struct_u32_value("/#address-cells");
let size_cells = device_tree.try_struct_u32_value("/#size-cells");
let board = device_tree.try_struct_str_value("/model");
if board.is_ok() {
println!("Running on {}", board.unwrap());
}
println!(
"Memory DTB info: address-cells {:?}, size-cells {:?}",
address_cells, size_cells
);
command_prompt();
reboot()
@ -146,7 +217,7 @@ fn command_prompt() {
b"uart" => init_uart_serial(),
b"disp" => check_display_init(),
b"trap" => check_data_abort_trap(),
b"map" => machine::arch::memory::print_layout(),
b"map" => arch::memory::print_layout(),
b"led on" => set_led(true),
b"led off" => set_led(false),
b"help" => print_help(),
@ -170,7 +241,7 @@ fn print_help() {
}
fn set_led(enable: bool) {
let mut mbox = Mailbox::<8>::default();
let mut mbox = Mailbox::default();
let index = mbox.request();
let index = mbox.set_led_on(index, enable);
let mbox = mbox.end(index);
@ -187,12 +258,12 @@ fn reboot() -> ! {
cfg_if! {
if #[cfg(feature = "qemu")] {
println!("Bye, shutting down QEMU");
machine::qemu::semihosting::exit_success()
qemu::semihosting::exit_success()
} else {
use machine::platform::rpi3::power::Power;
use crate::platform::rpi3::power::Power;
println!("Bye, going to reset now");
Power::default().reset()
Power::new().reset()
}
}
}
@ -216,7 +287,7 @@ fn display_graphics() -> Result<(), DrawError> {
display.draw_text(50, 50, "Hello there!", Color::rgb(128, 192, 255))?;
let mut buf = [0u8; 64];
let s = machine::write_to::show(&mut buf, format_args!("Display width {}", display.width));
let s = write_to::show(&mut buf, format_args!("Display width {}", display.width));
if s.is_err() {
display.draw_text(50, 150, "Error displaying", Color::red())?
@ -245,12 +316,7 @@ fn check_data_abort_trap() {
#[cfg(test)]
mod main_tests {
use {super::*, core::panic::PanicInfo};
#[panic_handler]
fn panicked(info: &PanicInfo) -> ! {
machine::panic::handler_for_tests(info)
}
use super::*;
#[test_case]
fn test_data_abort_trap() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,64 +0,0 @@
# Broadcom bcm2711 on Raspberry Pi 4 as JTAG target
# From https://gist.github.com/tnishinaga/46a3380e1f47f5e892bbb74e55b3cf3e
# See also https://xihan94.gitbook.io/raspberry-pi/raspberry-pi-4-bringup
echo "Booting JTAG for Raspberry Pi 4"
if { [info exists CHIPNAME] } {
set _CHIPNAME $CHIPNAME
} else {
set _CHIPNAME bcm2711
}
if { [info exists DAP_TAPID] } {
set _DAP_TAPID $DAP_TAPID
} else {
set _DAP_TAPID 0x4ba00477
}
adapter speed 4000
transport select jtag
# we need to enable srst even though we don't connect it
reset_config trst_and_srst
jtag_ntrst_delay 500
telnet_port 4444
gdb_port 5555
#
# Main DAP
#
jtag newtap $_CHIPNAME tap -irlen 4 -expected-id $_DAP_TAPID
dap create $_CHIPNAME.dap -chain-position $_CHIPNAME.tap
set _TARGETNAME $_CHIPNAME.a72
set _CTINAME $_CHIPNAME.cti
set DBGBASE {0x80410000 0x80510000 0x80610000 0x80710000}
set CTIBASE {0x80420000 0x80520000 0x80620000 0x80720000}
set _cores 4
set _smp_command ""
for { set _core 0 } { $_core < $_cores } { incr _core } {
cti create $_CTINAME.$_core -dap $_CHIPNAME.dap -ap-num 0 \
-baseaddr [lindex $CTIBASE $_core]
target create ${_TARGETNAME}.${_core} aarch64 \
-dap ${_CHIPNAME}.dap -coreid $_core \
-dbgbase [lindex $DBGBASE $_core] -cti ${_CTINAME}.${_core}
if {$_core != 0} {
set _smp_command "$_smp_command ${_TARGETNAME}.${_core}"
} else {
set _smp_command "target smp ${_TARGETNAME}.${_core}"
}
$_TARGETNAME.$_core configure -event reset-assert-post "aarch64 dbginit"
$_TARGETNAME.$_core configure -event gdb-attach { halt }
}
eval $_smp_command
targets $_TARGETNAME.0

View File

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

View File

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

Binary file not shown.

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.