Compare commits

...

84 Commits

Author SHA1 Message Date
Berkus Decker 910ce22a7c [del] ignore ide files 2021-01-27 19:20:39 +02:00
Berkus Decker deabc2ae70 [wip] api messing 2021-01-27 19:19:36 +02:00
Berkus Decker f842a467e4 [wip:explore/interrupts] Interrupt controller defs from DTB 2021-01-27 19:19:36 +02:00
Berkus Decker 94789e7979 [wip] make it compile 2021-01-27 19:19:36 +02:00
Berkus Decker b594552f23 [wip] compile fixes -- to split 2021-01-27 19:19:36 +02:00
Berkus Decker 0e1c0e45f7 [wip] Wrap scheduler-related calls in struct Scheduler for now, pending actual mod scope 2021-01-27 19:19:36 +02:00
Berkus Decker d3c02f0f5b Add user-space support library 2021-01-27 19:19:36 +02:00
Berkus Decker dd6f186623 [wip] syscalls 2021-01-27 19:19:36 +02:00
Berkus Decker 7e71ff1787 [del] sq improve long hex constants 2021-01-27 19:19:36 +02:00
Berkus Decker f28c4a6440 [wip] start kernel API/ABI for syscalls 2021-01-27 19:18:58 +02:00
Berkus Decker 0dd7aeb44c [wip] refactoring thread cap api 2021-01-27 19:18:58 +02:00
Berkus Decker 8d8a1c819a [sq] add Page caps implementations 2021-01-27 19:18:58 +02:00
Berkus Decker abc84ec6aa [del] Add ASID type 2021-01-27 19:18:58 +02:00
Berkus Decker 0fa4f3bc4e [sq] Fix copyrights 2021-01-27 19:16:49 +02:00
Berkus Decker dda2c2a4ca [wip] captable 2021-01-27 19:16:49 +02:00
Berkus Decker e24c3aaca5 [sq] add missing license 2021-01-27 19:16:49 +02:00
Berkus Decker f0f35ce1ed [wip] shadow kernel boot code 2021-01-27 19:16:49 +02:00
Berkus Decker bc31a43f82 [wip] add resolve_address_bits() 2021-01-27 19:16:49 +02:00
Berkus Decker 4cc40be9eb sq imports 2021-01-27 19:16:49 +02:00
Berkus Decker e04ea59019 [wip] Add dummy cache module
Needs CLIDR_EL1 implementantion in cortex-a first
2021-01-27 19:16:49 +02:00
Berkus Decker 3940352be1 [wip] Split kernel objects by type into actual entities 2021-01-27 19:16:49 +02:00
Berkus Decker 5f7ecca625 wip code review 2021-01-27 19:16:49 +02:00
Berkus Decker fa91a108fd fix typo 2021-01-27 19:16:49 +02:00
Berkus Decker 91f9c2c215 wip add specific types 2021-01-27 19:16:49 +02:00
Berkus Decker d8de631b44 sq DerivationTree comment update 2021-01-27 19:16:49 +02:00
Berkus Decker 8f54e179c1 sq fix captable imports 2021-01-27 19:16:49 +02:00
Berkus Decker 99a0cac3db [del] sq fix PhysAddr test 2021-01-27 19:16:49 +02:00
Berkus Decker 5ba79d78fd wip impl thread_capability 2021-01-27 19:15:57 +02:00
Berkus Decker d04739312c Move caps fields around for more logical arrangement
And allowing easier bitmatch-ing for Type.
2021-01-27 19:15:57 +02:00
Berkus Decker b0bbff20bd [TEMP] make type Copy but we prolly shouldn't - REVISE 2021-01-27 19:15:57 +02:00
Berkus Decker 74730117f2 [del] Split addr module 2021-01-27 19:15:57 +02:00
Berkus Decker 9638ddbb3e [del] Implement additions for PhysAddr 2021-01-27 19:15:13 +02:00
Berkus Decker 8dc3586b55 wip comment out some code 2021-01-27 19:14:05 +02:00
Berkus Decker caa1929a0f Refactor: move all caps to their appropriate modules and files 2021-01-27 19:14:05 +02:00
Berkus Decker 57ae94847d sq update untyped 2021-01-27 19:14:05 +02:00
Berkus Decker b6852b46a5 wip move capnode cap impl 2021-01-27 19:14:05 +02:00
Berkus Decker 5b59eff974 wip documentation 2021-01-27 19:14:05 +02:00
Berkus Decker 25f6fc0e98 wip split arm and generic caps 2021-01-27 19:14:05 +02:00
Berkus Decker 6e6e82f466 sq move null cap 2021-01-27 19:14:05 +02:00
Berkus Decker d129db345f wip derivation node 2021-01-27 19:14:05 +02:00
Berkus Decker 6de2ef38a7 wip caps 2021-01-27 19:14:05 +02:00
Berkus Decker d4b92b106a Update deps versions 2021-01-27 19:14:05 +02:00
Berkus Decker 42f0efcba9 [wip] caps work 2021-01-27 19:14:05 +02:00
Berkus Decker babd5caaab [wip] caps doc 2021-01-27 19:14:05 +02:00
Berkus Decker 4616dfee86 [del] Update rust-toolchain file for new rustup
Rustup since version 1.23.0 supports more detailed specification.
2021-01-27 19:14:05 +02:00
Berkus Decker a01445d0da [wip] ponder at gats 2021-01-27 19:12:55 +02:00
Berkus Decker 4979e82a12 [sq] add one more test that shouldn't compile 2021-01-27 19:12:55 +02:00
Berkus Decker 6cef160595 [wip] comment update 2021-01-27 19:12:55 +02:00
Berkus Decker aad25145bd [sq] Debug derivation node 2021-01-27 19:12:55 +02:00
Berkus Decker c94871fa46 [sq] add more untyped accessors 2021-01-27 19:12:55 +02:00
Berkus Decker afe6510fae [sq] optimize derivation node fields 2021-01-27 19:12:55 +02:00
Berkus Decker 8e7e652a76 [sq] rename derivation_node 2021-01-27 19:12:55 +02:00
Berkus Decker f796788ed5 [del] ignore tock-registers tests 2021-01-27 19:12:55 +02:00
Berkus Decker 0aceb8fc27 [wip] implement cap_table and some tests 2021-01-27 19:12:55 +02:00
Berkus Decker b66a64fe58 [sq] update doc-comments 2021-01-27 19:12:55 +02:00
Berkus Decker d2982e56ff [sq] Update for new macros accepting trailing comma 2021-01-27 19:12:55 +02:00
Berkus Decker eb0aed1730 [wip] Untyped cap 2021-01-27 19:12:55 +02:00
Berkus Decker fc6fa7105a [wip] kernel objs 2021-01-27 19:12:55 +02:00
Berkus Decker 591292c186 [del] Print test names in test_runner
Based on os.phil-opp.com Testing chapter.
2021-01-27 19:12:55 +02:00
Berkus Decker e694c33c61 [del] add documentation target 2021-01-27 19:12:20 +02:00
Berkus Decker 145006b400 wip caps 2020-12-29 19:26:40 +02:00
Berkus Decker eabc36845a wip objs 2020-12-29 19:26:40 +02:00
Berkus Decker f4000966b3 [sq] fix typo 2020-12-29 19:26:40 +02:00
Berkus Decker 8b0468a995 [wip] bump dependencies 2020-12-29 19:26:40 +02:00
Berkus Decker fe2cf95d4e sq typo? 2020-12-29 19:26:40 +02:00
Berkus Decker e6580a5465 sq misc comments 2020-12-29 19:26:40 +02:00
Berkus Decker 56aca62409 sq formatting 2020-12-29 19:26:40 +02:00
Berkus Decker ba8e8ae5ae [del] Make LocalRegisterCopy modifiable 2020-12-29 19:26:40 +02:00
Berkus Decker b14cc2ac22 [del] [wip] all builds 2020-12-29 19:26:40 +02:00
Berkus Decker db810ade9a [del] tock-registers: Add u128 Field test 2020-12-29 19:26:40 +02:00
Berkus Decker e3c9926a43 [del] tock-registers: Inline more aggressively 2020-12-29 19:26:40 +02:00
Berkus Decker b3ddfc5665 [del] tock-registers: Replace manual repeated impls with macros 2020-12-29 19:26:40 +02:00
Berkus Decker 0cf8a88f12 [del] tock-registers: Implement mask() accessor for Field 2020-12-29 19:26:40 +02:00
Berkus Decker b2c99f52c7 [del] Add local copy of tock-registers with u128 impl of IntLike 2020-12-29 19:26:40 +02:00
Berkus Decker b025fb6dd3 Add missing docs 2020-12-29 19:26:40 +02:00
Berkus Decker 4c9703340e Add snafu for errors 2020-12-29 19:26:40 +02:00
Berkus Decker ffde65fb81 [temp] comment out unused boot fn 2020-12-29 19:26:40 +02:00
Berkus Decker f25ace0a80 sq buildable caps 2020-12-29 19:26:40 +02:00
Berkus Decker ee1b5fc57a [blog] Move general comments to separate file 2020-12-29 19:26:40 +02:00
Berkus Decker 15a38d5689 [del] git subrepo clone git@github.com:metta-systems/tock-registers.git crates/tock-registers
subrepo:
  subdir:   "crates/tock-registers"
  merged:   "4d8202452"
upstream:
  origin:   "git@github.com:metta-systems/tock-registers.git"
  branch:   "master"
  commit:   "4d8202452"
git-subrepo:
  version:  "0.3.1"
  origin:   "???"
  commit:   "???"
2020-12-29 19:26:40 +02:00
Berkus Decker be1873b678 [del] temp - disable stuff 2020-12-29 19:26:40 +02:00
Berkus Decker c75db1e2d6 [wip] Replace mashup! with paste! 2020-12-29 19:26:40 +02:00
Berkus Decker ec7172aded [wip] various deps 2020-12-29 19:26:40 +02:00
Berkus Decker 333dece260 [wip] experiment with caps representation 2020-12-29 19:26:40 +02:00
96 changed files with 5315 additions and 248 deletions

View File

@ -11,5 +11,6 @@ rustflags = [
"-C", "target-feature=-fp-armv8",
"-C", "target-cpu=cortex-a53",
"-C", "embed-bitcode=yes",
"-Z", "macro-backtrace",
]
runner = "cargo make test-runner"

2
.gitignore vendored
View File

@ -1,5 +1,7 @@
.idea/
*.iml
.nova/
.vscode/
target/
kernel8*
.gdb_history

18
Cargo.lock generated
View File

@ -33,6 +33,12 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "paste"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7151b083b0664ed58ed669fcdd92f01c3d2fdbf10af4931a301474950b52bfa9"
[[package]]
name = "proc-macro2"
version = "1.0.24"
@ -95,9 +101,9 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.48"
version = "1.0.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac"
checksum = "8833e20724c24de12bbaba5ad230ea61c3eafb05b881c7c9d3cfe8638b187e68"
dependencies = [
"proc-macro2",
"quote",
@ -107,8 +113,6 @@ dependencies = [
[[package]]
name = "tock-registers"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f521a79accce68c417c9c77ce22108056b626126da1932f7e2e9b5bbffee0cea"
[[package]]
name = "unicode-xid"
@ -136,10 +140,16 @@ dependencies = [
"bitflags",
"cfg-if",
"cortex-a",
"paste",
"qemu-exit",
"r0",
"register",
"snafu",
"usize_conversions",
"ux",
"vesper-user",
]
[[package]]
name = "vesper-user"
version = "0.0.1"

View File

@ -1,6 +1,8 @@
[workspace]
members = [
"nucleus"
"nucleus",
"vesper-user",
"crates/tock-registers"
]
[profile.dev]
@ -18,3 +20,7 @@ lto = true
[profile.test]
opt-level = 's'
debug = true
[patch.crates-io]
tock-registers = { path = 'crates/tock-registers' }
# register = { git = 'https://github.com/metta-systems/register-rs', branch = 'update-deps-1' }

View File

@ -50,3 +50,8 @@ nm:
expand:
# Run `cargo expand` on modules
cargo make expand -- nucleus
doc:
# Generate and open documentation
cargo make docs-flow

View File

@ -0,0 +1,11 @@
; DO NOT EDIT (unless you know what you are doing)
;
; This subdirectory is a git "subrepo", and this file is maintained by the
; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
;
[subrepo]
remote = git@github.com:metta-systems/tock-registers.git
branch = master
commit = 4d8202452f5b7f1c056b524eac62445b74ceebce
parent = d78ffd283311c6a257f3cc93df8bc4cfe42bb44a
cmdver = 0.3.1

View File

@ -0,0 +1,55 @@
# Changelog
## master
## v0.6
- #2095: Fix syntax errors and inconsistencies in documentation
- #2071: Clarify bit widths in documentation examples
- #2015: Use UnsafeCell in registers (see issue #2005)
- #1939: Make the Field::mask and FieldValue::mask fields private
- #1823: Allow large unsigned values as bitmasks + add bitmask! helper macro
- #1554: Allow lifetime parameters for `register_structs! { Foo<'a> { ..`
- #1661: Add `Aliased` register type for MMIO with differing R/W behavior
## v0.5
- #1510
- Register visibility granularity: don't automatically make everything
`pub`, rather give creation macro callers visbility control.
- #1489
- Make `register_structs!` unit test generation opt-out, so that
`custom-test-frameworks` environments can disable them.
- #1481
- Add `#[derive(Copy, Clone)]` to InMemoryRegister.
- #1428
- Implement `mask()` for `FieldValue<u16>` which seems to have been
skipped at some point.
- Implement `read()` for `FieldValue` so that individual fields
can be extracted from a register `FieldValue` representation.
- #1461: Update `register_structs` macro to support flexible visibility of each
struct and each field. Also revert to private structs by default.
## v0.4.1
- #1458: Update struct macro to create `pub` structs
## v0.4
- #1368: Remove `new()` and add `InMemoryRegister`
- #1410: Add new macro for generating structs
## v0.3
- #1243: Update to Rust 2018 (nightly)
- #1250: Doc-only: Fix some rustdoc warnings
## v0.2
- #1161: Add `read_as_enum` to `LocalRegisterCopy`; thanks @andre-richter
## v0.1 - Initial Release

View File

@ -0,0 +1,18 @@
[package]
name = "tock-registers"
version = "0.6.0"
authors = ["Tock Project Developers <tock-dev@googlegroups.com>"]
description = "Memory-Mapped I/O and register interface developed for Tock."
homepage = "https://www.tockos.org/"
repository = "https://github.com/tock/tock/tree/master/libraries/tock-register-interface"
readme = "README.md"
keywords = ["tock", "embedded", "registers", "mmio", "bare-metal"]
categories = ["data-structures", "embedded", "no-std"]
license = "MIT/Apache-2.0"
edition = "2018"
[badges]
travis-ci = { repository = "tock/tock", branch = "master" }
[features]
no_std_unit_tests = []

View File

@ -0,0 +1,14 @@
#
# SPDX-License-Identifier: BlueOak-1.0.0
#
# Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
#
[tasks.build]
env = { "TARGET_FEATURES" = "" }
args = ["build", "--target=${TARGET_JSON}", "--release", "--features=${TARGET_FEATURES}"]
[tasks.docs]
clear = true
[tasks.test]
clear = true

View File

@ -0,0 +1,483 @@
# Tock Register Interface
This crate provides an interface for defining and manipulating memory mapped
registers and bitfields.
## Defining registers
The crate provides three types for working with memory mapped registers:
`ReadWrite`, `ReadOnly`, and `WriteOnly`, providing read-write, read-only, and
write-only functionality, respectively.
Defining the registers is done with the `register_structs` macro, which expects
for each register an offset, a field name, and a type. Registers must be
declared in increasing order of offsets and contiguously. Gaps when defining the
registers must be explicitly annotated with an offset and gap identifier (by
convention using a field named `_reservedN`), but without a type. The macro will
then automatically take care of calculating the gap size and inserting a
suitable filler struct. The end of the struct is marked with its size and the
`@END` keyword, effectively pointing to the offset immediately past the list of
registers.
```rust
use tock_registers::registers::{ReadOnly, ReadWrite, WriteOnly};
register_structs! {
Registers {
// Control register: read-write
// The 'Control' parameter constrains this register to only use fields from
// a certain group (defined below in the bitfields section).
(0x000 => cr: ReadWrite<u8, Control::Register>),
// Status register: read-only
(0x001 => s: ReadOnly<u8, Status::Register>),
// Registers can be bytes, halfwords, or words:
// Note that the second type parameter can be omitted, meaning that there
// are no bitfields defined for these registers.
(0x002 => byte0: ReadWrite<u8>),
(0x003 => byte1: ReadWrite<u8>),
(0x004 => short: ReadWrite<u16>),
// Empty space between registers must be marked with a padding field,
// declared as follows. The length of this padding is automatically
// computed by the macro.
(0x006 => _reserved),
(0x008 => word: ReadWrite<u32>),
// The type for a register can be anything. Conveniently, you can use an
// array when there are a bunch of similar registers.
(0x00C => array: [ReadWrite<u32>; 4]),
(0x01C => ... ),
// Etc.
// The end of the struct is marked as follows.
(0x100 => @END),
}
}
```
This generates a C-style struct of the following form.
```rust
#[repr(C)]
struct Registers {
// Control register: read-write
// The 'Control' parameter constrains this register to only use fields from
// a certain group (defined below in the bitfields section).
cr: ReadWrite<u8, Control::Register>,
// Status register: read-only
s: ReadOnly<u8, Status::Register>
// Registers can be bytes, halfwords, or words:
// Note that the second type parameter can be omitted, meaning that there
// are no bitfields defined for these registers.
byte0: ReadWrite<u8>,
byte1: ReadWrite<u8>,
short: ReadWrite<u16>,
// The padding length was automatically computed as 0x008 - 0x006.
_reserved: [u8; 2],
word: ReadWrite<u32>,
// Arrays are expanded as-is, like any other type.
array: [ReadWrite<u32>; 4],
// Etc.
}
```
By default, `std` unit tests for the struct are generated as well (that is,
tests attributed with `#[test]`). The unit tests make sure that the offsets and
padding are consistent with the actual fields in the struct, and that alignment
is correct.
Since those tests would break compilation in `custom-test-frameworks`
environments, it is possible to opt out of the test generation. To do so, add
the following cargo feature:
```toml
[dependencies.tock-registers]
version = "0.4.x"
features = ["no_std_unit_tests"]
```
WARNING: For now, the **unit tests checking offsets and alignments are not yet
run** on `make ci-travis`. This means that leaving an unintentional gap between
registers will **not** be caught. Instead, the `register_structs` macro will
generate a struct with invalid offsets without warning. Please follow the
discussion on https://github.com/tock/tock/pull/1393.
For example, the following call to the macro:
```rust
register_structs! {
Registers {
(0x000 => foo: ReadOnly<u8>),
(0x008 => bar: ReadOnly<u8>),
(0x009 => @END),
}
}
```
will generate the following struct, even though there is an unintentional gap of
4 bytes between addresses `0x004` (the end of register `foo`) and `0x008` (the
intended beginning of register `bar`).
```rust
#[repr(C)]
struct Registers {
foo: ReadOnly<u32>,
bar: ReadOnly<u32>,
}
```
By default, the visibility of the generated structs and fields is private. You
can make them public using the `pub` keyword, just before the struct name or the
field identifier.
For example, the following call to the macro:
```rust
register_structs! {
pub Registers {
(0x000 => foo: ReadOnly<u32>),
(0x004 => pub bar: ReadOnly<u32>),
(0x008 => @END),
}
}
```
will generate the following struct.
```rust
#[repr(C)]
pub struct Registers {
foo: ReadOnly<u32>,
pub bar: ReadOnly<u32>,
}
```
## Defining bitfields
Bitfields are defined through the `register_bitfields!` macro:
```rust
register_bitfields! [
// First parameter is the register width. Can be u8, u16, u32, or u64.
u32,
// Each subsequent parameter is a register abbreviation, its descriptive
// name, and its associated bitfields.
// The descriptive name defines this 'group' of bitfields. Only registers
// defined as ReadWrite<_, Control::Register> can use these bitfields.
Control [
// Bitfields are defined as:
// name OFFSET(shift) NUMBITS(num) [ /* optional values */ ]
// This is a two-bit field which includes bits 4 and 5
RANGE OFFSET(4) NUMBITS(2) [
// Each of these defines a name for a value that the bitfield can be
// written with or matched against. Note that this set is not exclusive--
// the field can still be written with arbitrary constants.
VeryHigh = 0,
High = 1,
Low = 2
],
// A common case is single-bit bitfields, which usually just mean
// 'enable' or 'disable' something.
EN OFFSET(3) NUMBITS(1) [],
INT OFFSET(2) NUMBITS(1) []
],
// Another example:
// Status register
Status [
TXCOMPLETE OFFSET(0) NUMBITS(1) [],
TXINTERRUPT OFFSET(1) NUMBITS(1) [],
RXCOMPLETE OFFSET(2) NUMBITS(1) [],
RXINTERRUPT OFFSET(3) NUMBITS(1) [],
MODE OFFSET(4) NUMBITS(3) [
FullDuplex = 0,
HalfDuplex = 1,
Loopback = 2,
Disabled = 3
],
ERRORCOUNT OFFSET(6) NUMBITS(3) []
],
// In a simple case, offset can just be a number, and the number of bits
// is set to 1:
InterruptFlags [
UNDES 10,
TXEMPTY 9,
NSSR 8,
OVRES 3,
MODF 2,
TDRE 1,
RDRF 0
]
]
```
## Register Interface Summary
There are four types provided by the register interface: `ReadOnly`,
`WriteOnly`, `ReadWrite`, and `Aliased`. They provide the following functions:
```rust
ReadOnly<T: IntLike, R: RegisterLongName = ()>
.get() -> T // Get the raw register value
.read(field: Field<T, R>) -> T // Read the value of the given field
.read_as_enum<E>(field: Field<T, R>) -> Option<E> // Read value of the given field as a enum member
.is_set(field: Field<T, R>) -> bool // Check if one or more bits in a field are set
.matches_any(value: FieldValue<T, R>) -> bool // Check if any specified parts of a field match
.matches_all(value: FieldValue<T, R>) -> bool // Check if all specified parts of a field match
.extract() -> LocalRegisterCopy<T, R> // Make local copy of register
WriteOnly<T: IntLike, R: RegisterLongName = ()>
.set(value: T) // Set the raw register value
.write(value: FieldValue<T, R>) // Write the value of one or more fields,
// overwriting other fields to zero
ReadWrite<T: IntLike, R: RegisterLongName = ()>
.get() -> T // Get the raw register value
.set(value: T) // Set the raw register value
.read(field: Field<T, R>) -> T // Read the value of the given field
.read_as_enum<E>(field: Field<T, R>) -> Option<E> // Read value of the given field as a enum member
.write(value: FieldValue<T, R>) // Write the value of one or more fields,
// overwriting other fields to zero
.modify(value: FieldValue<T, R>) // Write the value of one or more fields,
// leaving other fields unchanged
.modify_no_read( // Write the value of one or more fields,
original: LocalRegisterCopy<T, R>, // leaving other fields unchanged, but pass in
value: FieldValue<T, R>) // the original value, instead of doing a register read
.is_set(field: Field<T, R>) -> bool // Check if one or more bits in a field are set
.matches_any(value: FieldValue<T, R>) -> bool // Check if any specified parts of a field match
.matches_all(value: FieldValue<T, R>) -> bool // Check if all specified parts of a field match
.extract() -> LocalRegisterCopy<T, R> // Make local copy of register
Aliased<T: IntLike, R: RegisterLongName = (), W: RegisterLongName = ()>
.get() -> T // Get the raw register value
.set(value: T) // Set the raw register value
.read(field: Field<T, R>) -> T // Read the value of the given field
.read_as_enum<E>(field: Field<T, R>) -> Option<E> // Read value of the given field as a enum member
.write(value: FieldValue<T, W>) // Write the value of one or more fields,
// overwriting other fields to zero
.is_set(field: Field<T, R>) -> bool // Check if one or more bits in a field are set
.matches_any(value: FieldValue<T, R>) -> bool // Check if any specified parts of a field match
.matches_all(value: FieldValue<T, R>) -> bool // Check if all specified parts of a field match
.extract() -> LocalRegisterCopy<T, R> // Make local copy of register
```
The `Aliased` type represents cases where read-only and write-only registers,
with different meanings, are aliased to the same memory location.
The first type parameter (the `IntLike` type) is `u8`, `u16`, `u32`, or `u64`.
## Example: Using registers and bitfields
Assuming we have defined a `Registers` struct and the corresponding bitfields as
in the previous two sections. We also have an immutable reference to the
`Registers` struct, named `registers`.
```rust
// -----------------------------------------------------------------------------
// RAW ACCESS
// -----------------------------------------------------------------------------
// Get or set the raw value of the register directly. Nothing fancy:
registers.cr.set(registers.cr.get() + 1);
// -----------------------------------------------------------------------------
// READ
// -----------------------------------------------------------------------------
// `range` will contain the value of the RANGE field, e.g. 0, 1, 2, or 3.
// The type annotation is not necessary, but provided for clarity here.
let range: u8 = registers.cr.read(Control::RANGE);
// Or one can read `range` as a enum and `match` over it.
let range = registers.cr.read_as_enum(Control::RANGE);
match range {
Some(Control::RANGE::Value::VeryHigh) => { /* ... */ }
Some(Control::RANGE::Value::High) => { /* ... */ }
Some(Control::RANGE::Value::Low) => { /* ... */ }
None => unreachable!("invalid value")
}
// `en` will be 0 or 1
let en: u8 = registers.cr.read(Control::EN);
// -----------------------------------------------------------------------------
// MODIFY
// -----------------------------------------------------------------------------
// Write a value to a bitfield without altering the values in other fields:
registers.cr.modify(Control::RANGE.val(2)); // Leaves EN, INT unchanged
// Named constants can be used instead of the raw values:
registers.cr.modify(Control::RANGE::VeryHigh);
// Another example of writing a field with a raw value:
registers.cr.modify(Control::EN.val(0)); // Leaves RANGE, INT unchanged
// For one-bit fields, the named values SET and CLEAR are automatically
// defined:
registers.cr.modify(Control::EN::SET);
// Write multiple values at once, without altering other fields:
registers.cr.modify(Control::EN::CLEAR + Control::RANGE::Low); // INT unchanged
// Any number of non-overlapping fields can be combined:
registers.cr.modify(Control::EN::CLEAR + Control::RANGE::High + CR::INT::SET);
// In some cases (such as a protected register) .modify() may not be appropriate.
// To enable updating a register without coupling the read and write, use
// modify_no_read():
let original = registers.cr.extract();
registers.cr.modify_no_read(original, Control::EN::CLEAR);
// -----------------------------------------------------------------------------
// WRITE
// -----------------------------------------------------------------------------
// Same interface as modify, except that all unspecified fields are overwritten to zero.
registers.cr.write(Control::RANGE.val(1)); // implictly sets all other bits to zero
// -----------------------------------------------------------------------------
// BITFLAGS
// -----------------------------------------------------------------------------
// For one-bit fields, easily check if they are set or clear:
let txcomplete: bool = registers.s.is_set(Status::TXCOMPLETE);
// -----------------------------------------------------------------------------
// MATCHING
// -----------------------------------------------------------------------------
// You can also query a specific register state easily with `matches_[any|all]`:
// Doesn't care about the state of any field except TXCOMPLETE and MODE:
let ready: bool = registers.s.matches_all(Status::TXCOMPLETE:SET +
Status::MODE::FullDuplex);
// This is very useful for awaiting for a specific condition:
while !registers.s.matches_all(Status::TXCOMPLETE::SET +
Status::RXCOMPLETE::SET +
Status::TXINTERRUPT::CLEAR) {}
// Or for checking whether any interrupts are enabled:
let any_ints = registers.s.matches_any(Status::TXINTERRUPT + Status::RXINTERRUPT);
// Also you can read a register with set of enumerated values as a enum and `match` over it:
let mode = registers.cr.read_as_enum(Status::MODE);
match mode {
Some(Status::MODE::FullDuplex) => { /* ... */ }
Some(Status::MODE::HalfDuplex) => { /* ... */ }
None => unreachable!("invalid value")
}
// -----------------------------------------------------------------------------
// LOCAL COPY
// -----------------------------------------------------------------------------
// More complex code may want to read a register value once and then keep it in
// a local variable before using the normal register interface functions on the
// local copy.
// Create a copy of the register value as a local variable.
let local = registers.cr.extract();
// Now all the functions for a ReadOnly register work.
let txcomplete: bool = local.is_set(Status::TXCOMPLETE);
// -----------------------------------------------------------------------------
// In-Memory Registers
// -----------------------------------------------------------------------------
// In some cases, code may want to edit a memory location with all of the
// register features described above, but the actual memory location is not a
// fixed MMIO register but instead an arbitrary location in memory. If this
// location is then shared with the hardware (i.e. via DMA) then the code
// must do volatile reads and writes since the value may change without the
// software knowing. To support this, the library includes an `InMemoryRegister`
// type.
let control: InMemoryRegister<u32, Control::Register> = InMemoryRegister::new(0)
control.write(Contol::BYTE_COUNT.val(0) +
Contol::ENABLE::Yes +
Contol::LENGTH.val(10));
```
Note that `modify` performs exactly one volatile load and one volatile store,
`write` performs exactly one volatile store, and `read` performs exactly one
volatile load. Thus, you are ensured that a single call will set or query all
fields simultaneously.
## Performance
Examining the binaries while testing this interface, everything compiles
down to the optimal inlined bit twiddling instructions--in other words, there is
zero runtime cost, as far as an informal preliminary study has found.
## Nice type checking
This interface helps the compiler catch some common types of bugs via type checking.
If you define the bitfields for e.g. a control register, you can give them a
descriptive group name like `Control`. This group of bitfields will only work
with a register of the type `ReadWrite<_, Control>` (or `ReadOnly/WriteOnly`,
etc). For instance, if we have the bitfields and registers as defined above,
```rust
// This line compiles, because registers.cr is associated with the Control group
// of bitfields.
registers.cr.modify(Control::RANGE.val(1));
// This line will not compile, because registers.s is associated with the Status
// group, not the Control group.
let range = registers.s.read(Control::RANGE);
```
## Naming conventions
There are several related names in the register definitions. Below is a
description of the naming convention for each:
```rust
use tock_registers::registers::ReadWrite;
#[repr(C)]
struct Registers {
// The register name in the struct should be a lowercase version of the
// register abbreviation, as written in the datasheet:
cr: ReadWrite<u8, Control::Register>,
}
register_bitfields! [
u8,
// The name should be the long descriptive register name,
// camelcase, without the word 'register'.
Control [
// The field name should be the capitalized abbreviated
// field name, as given in the datasheet.
RANGE OFFSET(4) NUMBITS(3) [
// Each of the field values should be camelcase,
// as descriptive of their value as possible.
VeryHigh = 0,
High = 1,
Low = 2
]
]
]
```

View File

@ -0,0 +1,9 @@
//! Tock Register Interface
//!
//!
#![feature(const_fn)]
#![no_std]
pub mod macros;
pub mod registers;

View File

@ -0,0 +1,437 @@
//! Macros for cleanly defining peripheral registers.
/// Helper macro for computing bitmask of variable number of bits
#[macro_export]
macro_rules! bitmask {
($numbits:expr) => {
(1 << ($numbits - 1)) + ((1 << ($numbits - 1)) - 1)
};
}
/// Helper macro for defining register fields.
#[macro_export]
macro_rules! register_bitmasks {
{
// BITFIELD_NAME OFFSET(x)
$(#[$outer:meta])*
$valtype:ident, $reg_desc:ident, [
$( $(#[$inner:meta])* $field:ident OFFSET($offset:expr)),+ $(,)?
]
} => {
$(#[$outer])*
$( $crate::register_bitmasks!($valtype, $reg_desc, $(#[$inner])* $field, $offset, 1, []); )*
};
{
// BITFIELD_NAME OFFSET
// All fields are 1 bit
$(#[$outer:meta])*
$valtype:ident, $reg_desc:ident, [
$( $(#[$inner:meta])* $field:ident $offset:expr ),+ $(,)?
]
} => {
$(#[$outer])*
$( $crate::register_bitmasks!($valtype, $reg_desc, $(#[$inner])* $field, $offset, 1, []); )*
};
{
// BITFIELD_NAME OFFSET(x) NUMBITS(y)
$(#[$outer:meta])*
$valtype:ident, $reg_desc:ident, [
$( $(#[$inner:meta])* $field:ident OFFSET($offset:expr) NUMBITS($numbits:expr) ),+ $(,)?
]
} => {
$(#[$outer])*
$( $crate::register_bitmasks!($valtype, $reg_desc, $(#[$inner])* $field, $offset, $numbits, []); )*
};
{
// BITFIELD_NAME OFFSET(x) NUMBITS(y) []
$(#[$outer:meta])*
$valtype:ident, $reg_desc:ident, [
$( $(#[$inner:meta])* $field:ident OFFSET($offset:expr) NUMBITS($numbits:expr)
$values:tt ),+ $(,)?
]
} => {
$(#[$outer])*
$( $crate::register_bitmasks!($valtype, $reg_desc, $(#[$inner])* $field, $offset, $numbits,
$values); )*
};
{
$valtype:ident, $reg_desc:ident, $(#[$outer:meta])* $field:ident,
$offset:expr, $numbits:expr,
[$( $(#[$inner:meta])* $valname:ident = $value:expr ),+ $(,)?]
} => { // this match arm is duplicated below with an allowance for 0 elements in the valname -> value array,
// to seperately support the case of zero-variant enums not supporting non-default
// representations.
#[allow(non_upper_case_globals)]
#[allow(unused)]
pub const $field: Field<$valtype, $reg_desc> =
Field::<$valtype, $reg_desc>::new($crate::bitmask!($numbits), $offset);
#[allow(non_snake_case)]
#[allow(unused)]
$(#[$outer])*
pub mod $field {
#[allow(unused_imports)]
use $crate::registers::{FieldValue, TryFromValue};
use super::$reg_desc;
$(
#[allow(non_upper_case_globals)]
#[allow(unused)]
$(#[$inner])*
pub const $valname: FieldValue<$valtype, $reg_desc> =
FieldValue::<$valtype, $reg_desc>::new($crate::bitmask!($numbits),
$offset, $value);
)*
#[allow(non_upper_case_globals)]
#[allow(unused)]
pub const SET: FieldValue<$valtype, $reg_desc> =
FieldValue::<$valtype, $reg_desc>::new($crate::bitmask!($numbits),
$offset, $crate::bitmask!($numbits));
#[allow(non_upper_case_globals)]
#[allow(unused)]
pub const CLEAR: FieldValue<$valtype, $reg_desc> =
FieldValue::<$valtype, $reg_desc>::new($crate::bitmask!($numbits),
$offset, 0);
#[allow(dead_code)]
#[allow(non_camel_case_types)]
#[repr($valtype)] // so that values larger than isize::MAX can be stored
$(#[$outer])*
pub enum Value {
$(
$(#[$inner])*
$valname = $value,
)*
}
impl TryFromValue<$valtype> for Value {
type EnumType = Value;
fn try_from(v: $valtype) -> Option<Self::EnumType> {
match v {
$(
$(#[$inner])*
x if x == Value::$valname as $valtype => Some(Value::$valname),
)*
_ => Option::None
}
}
}
}
};
{
$valtype:ident, $reg_desc:ident, $(#[$outer:meta])* $field:ident,
$offset:expr, $numbits:expr,
[]
} => { //same pattern as previous match arm, for 0 elements in array. Removes code associated with array.
#[allow(non_upper_case_globals)]
#[allow(unused)]
pub const $field: Field<$valtype, $reg_desc> =
Field::<$valtype, $reg_desc>::new($crate::bitmask!($numbits), $offset);
#[allow(non_snake_case)]
#[allow(unused)]
$(#[$outer])*
pub mod $field {
#[allow(unused_imports)]
use $crate::registers::{FieldValue, TryFromValue};
use super::$reg_desc;
#[allow(non_upper_case_globals)]
#[allow(unused)]
pub const SET: FieldValue<$valtype, $reg_desc> =
FieldValue::<$valtype, $reg_desc>::new($crate::bitmask!($numbits),
$offset, $crate::bitmask!($numbits));
#[allow(non_upper_case_globals)]
#[allow(unused)]
pub const CLEAR: FieldValue<$valtype, $reg_desc> =
FieldValue::<$valtype, $reg_desc>::new($crate::bitmask!($numbits),
$offset, 0);
#[allow(dead_code)]
#[allow(non_camel_case_types)]
$(#[$outer])*
pub enum Value {}
impl TryFromValue<$valtype> for Value {
type EnumType = Value;
fn try_from(_v: $valtype) -> Option<Self::EnumType> {
Option::None
}
}
}
};
}
/// Define register types and fields.
#[macro_export]
macro_rules! register_bitfields {
{
$valtype:ident, $( $(#[$inner:meta])* $vis:vis $reg:ident $fields:tt ),* $(,)?
} => {
$(
#[allow(non_snake_case)]
$(#[$inner])*
$vis mod $reg {
// Visibility note: This is left always `pub` as it is not
// meaningful to restrict access to the `Register` element of
// the register module if the module itself is in scope
//
// (if you can access $reg, you can access $reg::Register)
#[derive(Clone, Copy)]
pub struct Register;
impl $crate::registers::RegisterLongName for Register {}
use $crate::registers::Field;
$crate::register_bitmasks!( $valtype, Register, $fields );
}
)*
}
}
#[macro_export]
macro_rules! register_fields {
// Macro entry point.
(@root $(#[$attr_struct:meta])* $vis_struct:vis $name:ident $(<$life:lifetime>)? { $($input:tt)* } ) => {
$crate::register_fields!(
@munch (
$($input)*
) -> {
$vis_struct struct $(#[$attr_struct])* $name $(<$life>)?
}
);
};
// Print the struct once all fields have been munched.
(@munch
(
$(#[$attr_end:meta])*
($offset:expr => @END),
)
-> {$vis_struct:vis struct $(#[$attr_struct:meta])* $name:ident $(<$life:lifetime>)? $(
$(#[$attr:meta])*
($vis:vis $id:ident: $ty:ty)
)*}
) => {
$(#[$attr_struct])*
#[repr(C)]
$vis_struct struct $name $(<$life>)? {
$(
$(#[$attr])*
$vis $id: $ty
),*
}
};
// Munch field.
(@munch
(
$(#[$attr:meta])*
($offset_start:expr => $vis:vis $field:ident: $ty:ty),
$($after:tt)*
)
-> {$($output:tt)*}
) => {
$crate::register_fields!(
@munch (
$($after)*
) -> {
$($output)*
$(#[$attr])*
($vis $field: $ty)
}
);
};
// Munch padding.
(@munch
(
$(#[$attr:meta])*
($offset_start:expr => $padding:ident),
$(#[$attr_next:meta])*
($offset_end:expr => $($next:tt)*),
$($after:tt)*
)
-> {$($output:tt)*}
) => {
$crate::register_fields!(
@munch (
$(#[$attr_next])*
($offset_end => $($next)*),
$($after)*
) -> {
$($output)*
$(#[$attr])*
($padding: [u8; $offset_end - $offset_start])
}
);
};
}
#[macro_export]
macro_rules! test_fields {
// Macro entry point.
(@root $struct:ident $(<$life:lifetime>)? { $($input:tt)* } ) => {
$crate::test_fields!(@munch $struct $(<$life>)? sum ($($input)*) -> {});
};
// Print the tests once all fields have been munched.
// We wrap the tests in a "detail" function that potentially takes a lifetime parameter, so that
// the lifetime is declared inside it - therefore all types using the lifetime are well-defined.
(@munch $struct:ident $(<$life:lifetime>)? $sum:ident
(
$(#[$attr_end:meta])*
($size:expr => @END),
)
-> {$($stmts:block)*}
) => {
{
fn detail $(<$life>)? ()
{
let mut $sum: usize = 0;
$($stmts)*
let size = core::mem::size_of::<$struct $(<$life>)?>();
assert!(
size == $size,
"Invalid size for struct {} (expected {:#X} but was {:#X})",
stringify!($struct),
$size,
size
);
}
detail();
}
};
// Munch field.
(@munch $struct:ident $(<$life:lifetime>)? $sum:ident
(
$(#[$attr:meta])*
($offset_start:expr => $vis:vis $field:ident: $ty:ty),
$(#[$attr_next:meta])*
($offset_end:expr => $($next:tt)*),
$($after:tt)*
)
-> {$($output:block)*}
) => {
$crate::test_fields!(
@munch $struct $(<$life>)? $sum (
$(#[$attr_next])*
($offset_end => $($next)*),
$($after)*
) -> {
$($output)*
{
assert!(
$sum == $offset_start,
"Invalid start offset for field {} (expected {:#X} but was {:#X})",
stringify!($field),
$offset_start,
$sum
);
let align = core::mem::align_of::<$ty>();
assert!(
$sum & (align - 1) == 0,
"Invalid alignment for field {} (expected alignment of {:#X} but offset was {:#X})",
stringify!($field),
align,
$sum
);
$sum += core::mem::size_of::<$ty>();
assert!(
$sum == $offset_end,
"Invalid end offset for field {} (expected {:#X} but was {:#X})",
stringify!($field),
$offset_end,
$sum
);
}
}
);
};
// Munch padding.
(@munch $struct:ident $(<$life:lifetime>)? $sum:ident
(
$(#[$attr:meta])*
($offset_start:expr => $padding:ident),
$(#[$attr_next:meta])*
($offset_end:expr => $($next:tt)*),
$($after:tt)*
)
-> {$($output:block)*}
) => {
$crate::test_fields!(
@munch $struct $(<$life>)? $sum (
$(#[$attr_next])*
($offset_end => $($next)*),
$($after)*
) -> {
$($output)*
{
assert!(
$sum == $offset_start,
"Invalid start offset for padding {} (expected {:#X} but was {:#X})",
stringify!($padding),
$offset_start,
$sum
);
$sum = $offset_end;
}
}
);
};
}
#[cfg(not(feature = "no_std_unit_tests"))]
#[macro_export]
macro_rules! register_structs {
{
$(
$(#[$attr:meta])*
$vis_struct:vis $name:ident $(<$life:lifetime>)? {
$( $fields:tt )*
}
),*
} => {
$( $crate::register_fields!(@root $(#[$attr])* $vis_struct $name $(<$life>)? { $($fields)* } ); )*
#[cfg(test)]
mod test_register_structs {
$(
#[allow(non_snake_case)]
mod $name {
use super::super::*;
#[test]
fn test_offsets() {
$crate::test_fields!(@root $name $(<$life>)? { $($fields)* } )
}
}
)*
}
};
}
#[cfg(feature = "no_std_unit_tests")]
#[macro_export]
macro_rules! register_structs {
{
$(
$(#[$attr:meta])*
$vis_struct:vis $name:ident $(<$life:lifetime>)? {
$( $fields:tt )*
}
),*
} => {
$( $crate::register_fields!(@root $(#[$attr])* $vis_struct $name $(<$life>)? { $($fields)* } ); )*
};
}

View File

@ -0,0 +1,925 @@
//! Implementation of registers and bitfields.
//!
//! Provides efficient mechanisms to express and use type-checked memory mapped
//! registers and bitfields.
//!
//! ```rust
//! # fn main() {}
//!
//! use tock_registers::registers::{ReadOnly, ReadWrite};
//! use tock_registers::register_bitfields;
//!
//! // Register maps are specified like this:
//! #[repr(C)]
//! struct Registers {
//! // Control register: read-write
//! cr: ReadWrite<u32, Control::Register>,
//! // Status register: read-only
//! s: ReadOnly<u32, Status::Register>,
//! }
//!
//! // Register fields and definitions look like this:
//! register_bitfields![u32,
//! // Simpler bitfields are expressed concisely:
//! Control [
//! /// Stop the Current Transfer
//! STOP 8,
//! /// Software Reset
//! SWRST 7,
//! /// Master Disable
//! MDIS 1,
//! /// Master Enable
//! MEN 0
//! ],
//!
//! // More complex registers can express subtypes:
//! Status [
//! TXCOMPLETE OFFSET(0) NUMBITS(1) [],
//! TXINTERRUPT OFFSET(1) NUMBITS(1) [],
//! RXCOMPLETE OFFSET(2) NUMBITS(1) [],
//! RXINTERRUPT OFFSET(3) NUMBITS(1) [],
//! MODE OFFSET(4) NUMBITS(3) [
//! FullDuplex = 0,
//! HalfDuplex = 1,
//! Loopback = 2,
//! Disabled = 3
//! ],
//! ERRORCOUNT OFFSET(6) NUMBITS(3) []
//! ]
//! ];
//! ```
//!
//! Author
//! ------
//! - Shane Leonard <shanel@stanford.edu>
// The register interface uses `+` in a way that is fine for bitfields, but
// looks unusual (and perhaps problematic) to a linter. We just ignore those
// lints for this file.
#![allow(clippy::suspicious_op_assign_impl)]
#![allow(clippy::suspicious_arithmetic_impl)]
use core::cell::UnsafeCell;
use core::fmt;
use core::marker::PhantomData;
use core::ops::{Add, AddAssign, BitAnd, BitOr, BitOrAssign, Not, Shl, Shr};
/// IntLike properties needed to read/write/modify a register.
pub trait IntLike:
BitAnd<Output = Self>
+ BitOr<Output = Self>
+ BitOrAssign
+ Not<Output = Self>
+ Eq
+ Shr<usize, Output = Self>
+ Shl<usize, Output = Self>
+ Copy
+ Clone
{
fn zero() -> Self;
}
macro_rules! IntLike_impl_for {
( $( $type:ty ),+ $(,)? ) => {
$(
impl IntLike for $type {
fn zero() -> Self {
0
}
}
)*
};
}
IntLike_impl_for!(u8, u16, u32, u64, u128);
/// Descriptive name for each register.
pub trait RegisterLongName {}
impl RegisterLongName for () {}
/// Conversion of raw register value into enumerated values member.
/// Implemented inside register_bitfields! macro for each bit field.
pub trait TryFromValue<V> {
type EnumType;
fn try_from(v: V) -> Option<Self::EnumType>;
}
/// Read/Write registers.
// To successfully alias this structure onto hardware registers in memory, this
// struct must be exactly the size of the `T`.
#[repr(transparent)]
pub struct ReadWrite<T: IntLike, R: RegisterLongName = ()> {
value: UnsafeCell<T>,
associated_register: PhantomData<R>,
}
/// Read-only registers.
// To successfully alias this structure onto hardware registers in memory, this
// struct must be exactly the size of the `T`.
#[repr(transparent)]
pub struct ReadOnly<T: IntLike, R: RegisterLongName = ()> {
value: T,
associated_register: PhantomData<R>,
}
/// Write-only registers.
// To successfully alias this structure onto hardware registers in memory, this
// struct must be exactly the size of the `T`.
#[repr(transparent)]
pub struct WriteOnly<T: IntLike, R: RegisterLongName = ()> {
value: UnsafeCell<T>,
associated_register: PhantomData<R>,
}
/// Read-only and write-only registers aliased to the same address.
///
/// Unlike the `ReadWrite` register, this represents a register which has different meanings based
/// on if it is written or read. This might be found on a device where control and status
/// registers are accessed via the same memory address via writes and reads, respectively.
// To successfully alias this structure onto hardware registers in memory, this
// struct must be exactly the size of the `T`.
#[repr(transparent)]
pub struct Aliased<T: IntLike, R: RegisterLongName = (), W: RegisterLongName = ()> {
value: UnsafeCell<T>,
associated_register: PhantomData<(R, W)>,
}
impl<T: IntLike, R: RegisterLongName> ReadWrite<T, R> {
#[inline]
/// Get the raw register value
pub fn get(&self) -> T {
unsafe { ::core::ptr::read_volatile(self.value.get()) }
}
#[inline]
/// Set the raw register value
pub fn set(&self, value: T) {
unsafe { ::core::ptr::write_volatile(self.value.get(), value) }
}
#[inline]
/// Read the value of the given field
pub fn read(&self, field: Field<T, R>) -> T {
field.read(self.get())
}
#[inline]
/// Read value of the given field as an enum member
pub fn read_as_enum<E: TryFromValue<T, EnumType = E>>(&self, field: Field<T, R>) -> Option<E> {
field.read_as_enum(self.get())
}
#[inline]
/// Make a local copy of the register
pub fn extract(&self) -> LocalRegisterCopy<T, R> {
LocalRegisterCopy::new(self.get())
}
#[inline]
/// Write the value of one or more fields, overwriting the other fields with zero
pub fn write(&self, field: FieldValue<T, R>) {
self.set(field.value);
}
#[inline]
/// Write the value of one or more fields, leaving the other fields unchanged
pub fn modify(&self, field: FieldValue<T, R>) {
self.set(field.modify(self.get()));
}
#[inline]
/// Write the value of one or more fields, maintaining the value of unchanged fields via a
/// provided original value, rather than a register read.
pub fn modify_no_read(&self, original: LocalRegisterCopy<T, R>, field: FieldValue<T, R>) {
self.set(field.modify(original.get()));
}
#[inline]
/// Check if one or more bits in a field are set
pub fn is_set(&self, field: Field<T, R>) -> bool {
field.is_set(self.get())
}
#[inline]
/// Check if any specified parts of a field match
pub fn matches_any(&self, field: FieldValue<T, R>) -> bool {
field.matches_any(self.get())
}
#[inline]
/// Check if all specified parts of a field match
pub fn matches_all(&self, field: FieldValue<T, R>) -> bool {
field.matches_all(self.get())
}
}
impl<T: IntLike, R: RegisterLongName> ReadOnly<T, R> {
#[inline]
/// Get the raw register value
pub fn get(&self) -> T {
unsafe { ::core::ptr::read_volatile(&self.value) }
}
#[inline]
/// Read the value of the given field
pub fn read(&self, field: Field<T, R>) -> T {
field.read(self.get())
}
#[inline]
/// Read value of the given field as an enum member
pub fn read_as_enum<E: TryFromValue<T, EnumType = E>>(&self, field: Field<T, R>) -> Option<E> {
field.read_as_enum(self.get())
}
#[inline]
/// Make a local copy of the register
pub fn extract(&self) -> LocalRegisterCopy<T, R> {
LocalRegisterCopy::new(self.get())
}
#[inline]
/// Check if one or more bits in a field are set
pub fn is_set(&self, field: Field<T, R>) -> bool {
field.is_set(self.get())
}
#[inline]
/// Check if any specified parts of a field match
pub fn matches_any(&self, field: FieldValue<T, R>) -> bool {
field.matches_any(self.get())
}
#[inline]
/// Check if all specified parts of a field match
pub fn matches_all(&self, field: FieldValue<T, R>) -> bool {
field.matches_all(self.get())
}
}
impl<T: IntLike, R: RegisterLongName> WriteOnly<T, R> {
#[inline]
/// Set the raw register value
pub fn set(&self, value: T) {
unsafe { ::core::ptr::write_volatile(self.value.get(), value) }
}
#[inline]
/// Write the value of one or more fields, overwriting the other fields with zero
pub fn write(&self, field: FieldValue<T, R>) {
self.set(field.value);
}
}
impl<T: IntLike, R: RegisterLongName, W: RegisterLongName> Aliased<T, R, W> {
#[inline]
/// Get the raw register value
pub fn get(&self) -> T {
unsafe { ::core::ptr::read_volatile(self.value.get()) }
}
#[inline]
/// Set the raw register value
pub fn set(&self, value: T) {
unsafe { ::core::ptr::write_volatile(self.value.get(), value) }
}
#[inline]
/// Read the value of the given field
pub fn read(&self, field: Field<T, R>) -> T {
field.read(self.get())
}
#[inline]
/// Read value of the given field as an enum member
pub fn read_as_enum<E: TryFromValue<T, EnumType = E>>(&self, field: Field<T, R>) -> Option<E> {
field.read_as_enum(self.get())
}
#[inline]
/// Make a local copy of the register
pub fn extract(&self) -> LocalRegisterCopy<T, R> {
LocalRegisterCopy::new(self.get())
}
#[inline]
/// Write the value of one or more fields, overwriting the other fields with zero
pub fn write(&self, field: FieldValue<T, W>) {
self.set(field.value);
}
#[inline]
/// Check if one or more bits in a field are set
pub fn is_set(&self, field: Field<T, R>) -> bool {
field.is_set(self.get())
}
#[inline]
/// Check if any specified parts of a field match
pub fn matches_any(&self, field: FieldValue<T, R>) -> bool {
field.matches_any(self.get())
}
#[inline]
/// Check if all specified parts of a field match
pub fn matches_all(&self, field: FieldValue<T, R>) -> bool {
field.matches_all(self.get())
}
}
/// A read-write copy of register contents.
///
/// This behaves very similarly to a read-write register, but instead of doing a
/// volatile read to MMIO to get the value for each function call, a copy of the
/// register contents are stored locally in memory. This allows a peripheral
/// to do a single read on a register, and then check which bits are set without
/// having to do a full MMIO read each time. It also allows the value of the
/// register to be "cached" in case the peripheral driver needs to clear the
/// register in hardware yet still be able to check the bits.
/// You can write to a local register, which will modify the stored value.
#[derive(Copy, Clone)]
pub struct LocalRegisterCopy<T: IntLike, R: RegisterLongName = ()> {
value: T,
associated_register: PhantomData<R>,
}
impl<T: IntLike, R: RegisterLongName> LocalRegisterCopy<T, R> {
pub const fn new(value: T) -> Self {
LocalRegisterCopy {
value: value,
associated_register: PhantomData,
}
}
/// Get the raw register value
#[inline]
pub fn get(&self) -> T {
self.value
}
/// Set the raw register value
#[inline]
pub fn set(&mut self, value: T) {
self.value = value;
}
/// Read the value of the given field
#[inline]
pub fn read(&self, field: Field<T, R>) -> T {
field.read(self.get())
}
/// Read value of the given field as an enum member
#[inline]
pub fn read_as_enum<E: TryFromValue<T, EnumType = E>>(&self, field: Field<T, R>) -> Option<E> {
field.read_as_enum(self.get())
}
/// Write the value of one or more fields, overwriting the other fields with zero
#[inline]
pub fn write(&mut self, field: FieldValue<T, R>) {
self.set(field.value);
}
/// Write the value of one or more fields, leaving the other fields unchanged
#[inline]
pub fn modify(&mut self, field: FieldValue<T, R>) {
self.set(field.modify(self.get()));
}
/// Check if one or more bits in a field are set
#[inline]
pub fn is_set(&self, field: Field<T, R>) -> bool {
field.is_set(self.get())
}
/// Check if any specified parts of a field match
#[inline]
pub fn matches_any(&self, field: FieldValue<T, R>) -> bool {
field.matches_any(self.get())
}
/// Check if all specified parts of a field match
#[inline]
pub fn matches_all(&self, field: FieldValue<T, R>) -> bool {
field.matches_all(self.get())
}
/// Do a bitwise AND operation of the stored value and the passed in value
/// and return a new LocalRegisterCopy.
#[inline]
pub fn bitand(&self, rhs: T) -> LocalRegisterCopy<T, R> {
LocalRegisterCopy::new(self.value & rhs)
}
}
impl<T: IntLike + fmt::Debug, R: RegisterLongName> fmt::Debug for LocalRegisterCopy<T, R> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.value)
}
}
macro_rules! From_impl_for {
( $( $type:ty ),+ $(,)? ) => {
$(
impl<R: RegisterLongName> From<LocalRegisterCopy<$type, R>> for $type {
fn from(r: LocalRegisterCopy<$type, R>) -> $type {
r.value
}
}
)*
};
}
From_impl_for!(u8, u16, u32, u64, u128);
/// In memory volatile register.
// To successfully alias this structure onto hardware registers in memory, this
// struct must be exactly the size of the `T`.
#[repr(transparent)]
pub struct InMemoryRegister<T: IntLike, R: RegisterLongName = ()> {
value: UnsafeCell<T>,
associated_register: PhantomData<R>,
}
impl<T: IntLike, R: RegisterLongName> InMemoryRegister<T, R> {
pub const fn new(value: T) -> Self {
InMemoryRegister {
value: UnsafeCell::new(value),
associated_register: PhantomData,
}
}
#[inline]
pub fn get(&self) -> T {
unsafe { ::core::ptr::read_volatile(self.value.get()) }
}
#[inline]
pub fn set(&self, value: T) {
unsafe { ::core::ptr::write_volatile(self.value.get(), value) }
}
#[inline]
pub fn read(&self, field: Field<T, R>) -> T {
field.read(self.get())
}
#[inline]
pub fn read_as_enum<E: TryFromValue<T, EnumType = E>>(&self, field: Field<T, R>) -> Option<E> {
field.read_as_enum(self.get())
}
#[inline]
pub fn extract(&self) -> LocalRegisterCopy<T, R> {
LocalRegisterCopy::new(self.get())
}
#[inline]
pub fn write(&self, field: FieldValue<T, R>) {
self.set(field.value);
}
#[inline]
pub fn modify(&self, field: FieldValue<T, R>) {
self.set(field.modify(self.get()));
}
#[inline]
pub fn modify_no_read(&self, original: LocalRegisterCopy<T, R>, field: FieldValue<T, R>) {
self.set(field.modify(original.get()));
}
#[inline]
pub fn is_set(&self, field: Field<T, R>) -> bool {
field.is_set(self.get())
}
#[inline]
pub fn matches_any(&self, field: FieldValue<T, R>) -> bool {
field.matches_any(self.get())
}
#[inline]
pub fn matches_all(&self, field: FieldValue<T, R>) -> bool {
field.matches_all(self.get())
}
}
/// Specific section of a register.
///
/// For the Field, the mask is unshifted, ie. the LSB should always be set.
#[derive(Copy, Clone)]
pub struct Field<T: IntLike, R: RegisterLongName> {
mask: T,
pub shift: usize,
associated_register: PhantomData<R>,
}
impl<T: IntLike, R: RegisterLongName> Field<T, R> {
pub const fn new(mask: T, shift: usize) -> Field<T, R> {
Field {
mask: mask,
shift: shift,
associated_register: PhantomData,
}
}
/// Get the raw bitmask used by this Field.
#[inline]
pub fn mask(&self) -> T {
(self.mask as T) << self.shift
}
#[inline]
pub fn read(self, val: T) -> T {
(val & (self.mask << self.shift)) >> self.shift
}
#[inline]
/// Check if one or more bits in a field are set
pub fn is_set(self, val: T) -> bool {
val & (self.mask << self.shift) != T::zero()
}
#[inline]
/// Read value of the field as an enum member
pub fn read_as_enum<E: TryFromValue<T, EnumType = E>>(self, val: T) -> Option<E> {
E::try_from(self.read(val))
}
}
macro_rules! Field_impl_for {
( $( $type:ty ),+ $(,)? ) => {
$(
impl<R: RegisterLongName> Field<$type, R> {
pub fn val(&self, value: $type) -> FieldValue<$type, R> {
FieldValue::<$type, R>::new(self.mask, self.shift, value)
}
}
)*
};
}
Field_impl_for!(u8, u16, u32, u64, u128);
/// Values for the specific register fields.
///
/// For the FieldValue, the masks and values are shifted into their actual
/// location in the register.
#[derive(Copy, Clone)]
pub struct FieldValue<T: IntLike, R: RegisterLongName> {
mask: T,
pub value: T,
associated_register: PhantomData<R>,
}
macro_rules! FieldValue_impl_for {
( $( $type:ty ),+ $(,)? ) => {
$(
// Necessary to split the implementation of new() out because the bitwise
// math isn't treated as const when the type is generic.
// Tracking issue: https://github.com/rust-lang/rfcs/pull/2632
impl<R: RegisterLongName> FieldValue<$type, R> {
pub const fn new(mask: $type, shift: usize, value: $type) -> Self {
FieldValue {
mask: mask << shift,
value: (value & mask) << shift,
associated_register: PhantomData,
}
}
}
// Necessary to split the implementation of From<> out because of the orphan rule
// for foreign trait implementation (see [E0210](https://doc.rust-lang.org/error-index.html#E0210)).
impl<R: RegisterLongName> From<FieldValue<$type, R>> for $type {
fn from(val: FieldValue<$type, R>) -> $type {
val.value
}
}
)*
};
}
FieldValue_impl_for!(u8, u16, u32, u64, u128);
impl<T: IntLike, R: RegisterLongName> FieldValue<T, R> {
/// Get the raw bitmask represented by this FieldValue.
#[inline]
pub fn mask(&self) -> T {
self.mask as T
}
#[inline]
pub fn read(&self, field: Field<T, R>) -> T {
field.read(self.value)
}
/// Modify fields in a register value
#[inline]
pub fn modify(self, val: T) -> T {
(val & !self.mask) | self.value
}
/// Check if any specified parts of a field match
#[inline]
pub fn matches_any(self, val: T) -> bool {
val & self.mask != T::zero()
}
/// Check if all specified parts of a field match
#[inline]
pub fn matches_all(self, val: T) -> bool {
val & self.mask == self.value
}
}
// Combine two fields with the addition operator
impl<T: IntLike, R: RegisterLongName> Add for FieldValue<T, R> {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self {
FieldValue {
mask: self.mask | rhs.mask,
value: self.value | rhs.value,
associated_register: PhantomData,
}
}
}
// Combine two fields with the += operator
impl<T: IntLike, R: RegisterLongName> AddAssign for FieldValue<T, R> {
#[inline]
fn add_assign(&mut self, rhs: FieldValue<T, R>) {
self.mask |= rhs.mask;
self.value |= rhs.value;
}
}
#[cfg(not(feature = "no_std_unit_tests"))]
#[cfg(test)]
mod tests {
#[derive(Debug, PartialEq, Eq)]
enum Foo {
Foo0,
Foo1,
Foo2,
Foo3,
Foo4,
Foo5,
Foo6,
Foo7,
}
impl super::TryFromValue<u16> for Foo {
type EnumType = Foo;
fn try_from(v: u16) -> Option<Self::EnumType> {
Self::try_from(v as u32)
}
}
impl super::TryFromValue<u32> for Foo {
type EnumType = Foo;
fn try_from(v: u32) -> Option<Self::EnumType> {
match v {
0 => Some(Foo::Foo0),
1 => Some(Foo::Foo1),
2 => Some(Foo::Foo2),
3 => Some(Foo::Foo3),
4 => Some(Foo::Foo4),
5 => Some(Foo::Foo5),
6 => Some(Foo::Foo6),
7 => Some(Foo::Foo7),
_ => None,
}
}
}
mod field {
use super::super::{Field, TryFromValue};
use super::Foo;
#[test]
fn test_new() {
let field8 = Field::<u8, ()>::new(0x12, 3);
assert_eq!(field8.mask, 0x12_u8);
assert_eq!(field8.shift, 3);
let field16 = Field::<u16, ()>::new(0x1234, 5);
assert_eq!(field16.mask, 0x1234_u16);
assert_eq!(field16.shift, 5);
let field32 = Field::<u32, ()>::new(0x12345678, 9);
assert_eq!(field32.mask, 0x12345678_u32);
assert_eq!(field32.shift, 9);
let field64 = Field::<u64, ()>::new(0x12345678_9abcdef0, 1);
assert_eq!(field64.mask, 0x12345678_9abcdef0_u64);
assert_eq!(field64.shift, 1);
let field128 = Field::<u128, ()>::new(0x12345678_9abcdef0_0fedcba9_87654321, 1);
assert_eq!(field128.mask, 0x12345678_9abcdef0_0fedcba9_87654321_u128);
assert_eq!(field128.shift, 1);
}
#[test]
fn test_read() {
let field = Field::<u32, ()>::new(0xFF, 4);
assert_eq!(field.read(0x123), 0x12);
let field = Field::<u32, ()>::new(0xF0F, 4);
assert_eq!(field.read(0x1234), 0x103);
}
#[test]
fn test_is_set() {
let field = Field::<u16, ()>::new(0xFF, 4);
assert_eq!(field.is_set(0), false);
assert_eq!(field.is_set(0xFFFF), true);
assert_eq!(field.is_set(0x0FF0), true);
assert_eq!(field.is_set(0x1000), false);
assert_eq!(field.is_set(0x0100), true);
assert_eq!(field.is_set(0x0010), true);
assert_eq!(field.is_set(0x0001), false);
for shift in 0..24 {
let field = Field::<u32, ()>::new(0xFF, shift);
for x in 1..=0xFF {
assert_eq!(field.is_set(x << shift), true);
}
assert_eq!(field.is_set(!(0xFF << shift)), false);
}
}
#[test]
fn test_read_as_enum() {
let field = Field::<u16, ()>::new(0x7, 4);
assert_eq!(field.read_as_enum(0x1234), Some(Foo::Foo3));
assert_eq!(field.read_as_enum(0x5678), Some(Foo::Foo7));
assert_eq!(field.read_as_enum(0xFFFF), Some(Foo::Foo7));
assert_eq!(field.read_as_enum(0x0000), Some(Foo::Foo0));
assert_eq!(field.read_as_enum(0x0010), Some(Foo::Foo1));
assert_eq!(field.read_as_enum(0x1204), Some(Foo::Foo0));
for shift in 0..29 {
let field = Field::<u32, ()>::new(0x7, shift);
for x in 0..8 {
assert_eq!(field.read_as_enum(x << shift), Foo::try_from(x));
}
}
}
}
mod field_value {
use super::super::Field;
#[test]
fn test_from() {
let field = Field::<u32, ()>::new(0xFF, 4);
assert_eq!(u32::from(field.val(0)), 0);
assert_eq!(u32::from(field.val(0xFFFFFFFF)), 0xFF0);
assert_eq!(u32::from(field.val(0x12)), 0x120);
assert_eq!(u32::from(field.val(0x123)), 0x230);
for shift in 0..32 {
let field = Field::<u32, ()>::new(0xFF, shift);
for x in 0..=0xFF {
assert_eq!(u32::from(field.val(x)), x << shift);
}
}
}
#[test]
fn test_read_same_field() {
let field = Field::<u32, ()>::new(0xFF, 4);
assert_eq!(field.val(0).read(field), 0);
assert_eq!(field.val(0xFFFFFFFF).read(field), 0xFF);
assert_eq!(field.val(0x12).read(field), 0x12);
assert_eq!(field.val(0x123).read(field), 0x23);
for shift in 0..24 {
let field = Field::<u32, ()>::new(0xFF, shift);
for x in 0..=0xFF {
assert_eq!(field.val(x).read(field), x);
}
}
}
#[test]
fn test_read_disjoint_fields() {
for shift in 0..24 {
let field1 = Field::<u32, ()>::new(0xF0, shift);
let field2 = Field::<u32, ()>::new(0x0F, shift);
for x in 0..=0xFF {
assert_eq!(field1.val(x).read(field2), 0);
assert_eq!(field2.val(x).read(field1), 0);
}
}
for shift in 0..24 {
let field1 = Field::<u32, ()>::new(0xF, shift);
let field2 = Field::<u32, ()>::new(0xF, shift + 4);
for x in 0..=0xFF {
assert_eq!(field1.val(x).read(field2), 0);
assert_eq!(field2.val(x).read(field1), 0);
}
}
}
#[test]
fn test_modify() {
let field = Field::<u32, ()>::new(0xFF, 4);
assert_eq!(field.val(0x23).modify(0x0000), 0x0230);
assert_eq!(field.val(0x23).modify(0xFFFF), 0xF23F);
assert_eq!(field.val(0x23).modify(0x1234), 0x1234);
assert_eq!(field.val(0x23).modify(0x5678), 0x5238);
}
#[test]
fn test_matches_any() {
let field = Field::<u32, ()>::new(0xFF, 4);
assert_eq!(field.val(0x23).matches_any(0x1234), true);
assert_eq!(field.val(0x23).matches_any(0x5678), true);
assert_eq!(field.val(0x23).matches_any(0x5008), false);
for shift in 0..24 {
let field = Field::<u32, ()>::new(0xFF, shift);
for x in 0..=0xFF {
let field_value = field.val(x);
for y in 1..=0xFF {
assert_eq!(field_value.matches_any(y << shift), true);
}
assert_eq!(field_value.matches_any(0), false);
assert_eq!(field_value.matches_any(!(0xFF << shift)), false);
}
}
}
#[test]
fn test_matches_all() {
let field = Field::<u32, ()>::new(0xFF, 4);
assert_eq!(field.val(0x23).matches_all(0x1234), true);
assert_eq!(field.val(0x23).matches_all(0x5678), false);
for shift in 0..24 {
let field = Field::<u32, ()>::new(0xFF, shift);
for x in 0..=0xFF {
assert_eq!(field.val(x).matches_all(x << shift), true);
assert_eq!(field.val(x + 1).matches_all(x << shift), false);
}
}
}
#[test]
fn test_add_disjoint_fields() {
let field1 = Field::<u32, ()>::new(0xFF, 24);
let field2 = Field::<u32, ()>::new(0xFF, 16);
let field3 = Field::<u32, ()>::new(0xFF, 8);
let field4 = Field::<u32, ()>::new(0xFF, 0);
assert_eq!(
u32::from(
field1.val(0x12) + field2.val(0x34) + field3.val(0x56) + field4.val(0x78)
),
0x12345678
);
for shift in 0..24 {
let field1 = Field::<u32, ()>::new(0xF, shift);
let field2 = Field::<u32, ()>::new(0xF, shift + 4);
for x in 0..=0xF {
for y in 0..=0xF {
assert_eq!(
u32::from(field1.val(x) + field2.val(y)),
(x | (y << 4)) << shift
);
}
}
}
}
#[test]
fn test_add_assign_disjoint_fields() {
let field1 = Field::<u32, ()>::new(0xFF, 24);
let field2 = Field::<u32, ()>::new(0xFF, 16);
let field3 = Field::<u32, ()>::new(0xFF, 8);
let field4 = Field::<u32, ()>::new(0xFF, 0);
let mut value = field1.val(0x12);
value += field2.val(0x34);
value += field3.val(0x56);
value += field4.val(0x78);
assert_eq!(u32::from(value), 0x12345678);
for shift in 0..24 {
let field1 = Field::<u32, ()>::new(0xF, shift);
let field2 = Field::<u32, ()>::new(0xF, shift + 4);
for x in 0..=0xF {
for y in 0..=0xF {
let mut value = field1.val(x);
value += field2.val(y);
assert_eq!(u32::from(value), (x | (y << 4)) << shift);
}
}
}
}
}
// TODO: More unit tests here.
}

18
doc/caps.tex Normal file
View File

@ -0,0 +1,18 @@
%! Author = berkus
%! Date = 04.12.2020
% Preamble
\documentclass[11pt]{article}
% Packages
\usepackage{amsmath}
% Document
\begin{document}
\title{Capabilities}
\end{document}

9
doc/error_handling.md Normal file
View File

@ -0,0 +1,9 @@
The different libraries seem to target different use-cases, though. For example, `snafu` with its
strongly typed errors and contexts seems to be a good fit for libraries. On the other hand,
anyhow with its focus on the type-erased Error and on creating string errors and contexts seems
to be more useful for applications. After all, errors produced by libraries need to be understood
by other code, errors produced by executables need to be understood by humans.
-- https://lukaskalbertodt.github.io/2019/11/14/thoughts-on-error-handling-in-rust.html
@see also https://blog.yoshuawuyts.com/error-handling-survey/
@see also https://www.reddit.com/r/rust/comments/dfs1zk/2019_q4_error_patterns_snafu_vs_errderive_anyhow/

View File

@ -4,7 +4,7 @@ version = "0.0.1"
authors = ["Berkus Decker <berkus+vesper@metta.systems>"]
description = "Vesper exokernel"
documentation = "https://docs.metta.systems/vesper"
homepage = "https://github.com/metta-systems/vesper"
homepage = "https://metta.systems/products/vesper"
repository = "https://github.com/metta-systems/vesper"
readme = "README.md"
license = "BlueOak-1.0.0"
@ -16,10 +16,13 @@ edition = "2018"
maintenance = { status = "experimental" }
[features]
# unstable = []
# realtime = []
noserial = []
# Enable JTAG debugging of kernel - enable jtag helpers and
# block waiting for JTAG probe attach at the start of kernel main.
jtag = []
# jlink = [] #'jlink_rtt'
# Build for running under QEMU with semihosting, so various halt/reboot options would for example quit QEMU instead.
qemu = ["qemu-exit"]
@ -34,3 +37,11 @@ bit_field = "0.10.0"
bitflags = "1.2.1"
cfg-if = "1.0"
snafu = { version = "0.6", default-features = false }
paste = "1.0"
vesper-user = { path = "../vesper-user" }
#embedded-serial = "0.5.0"
# jlink_rtt = { version = "0.1.0", optional = true }
# static_assertions = { version = "1.0.0", features = ["nightly"] }
#rcstring = "0.2.1"
# arrayvec = { version = "0.5.1", default-features = false }

View File

@ -15,6 +15,10 @@ args = ["expand", "--target=${TARGET_JSON}", "--release", "--features=${TARGET_F
env = { "TARGET_FEATURES" = "${QEMU_FEATURES}" }
args = ["test", "--target=${TARGET_JSON}", "--features=${TARGET_FEATURES}"]
[tasks.docs]
env = { "TARGET_FEATURES" = "" }
args = ["doc", "--open", "--no-deps", "--target=${TARGET_JSON}", "--features=${TARGET_FEATURES}"]
# These tasks are written in cargo-make's own script to make it portable across platforms (no `basename` on Windows)
[tasks.kernel-binary]
script_runner = "@duckscript"

7
nucleus/README.md Normal file
View File

@ -0,0 +1,7 @@
# Vesper Kernel
This directory contains binary for the vesper kernel.
----
For more information please re-read.

349
nucleus/src/api.rs Normal file
View File

@ -0,0 +1,349 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
//! Syscall API for calling kernel functions.
//!
//! Arch-specific kernel ABI decodes syscall invocations and calls API functions to perform actual
//! operations.
use vesper_user::SysCall as SysCall;
// Syscalls (kernel API)
trait API {
// Three below (send, nb_send, call) are "invocation" syscalls.
fn send(cap: Cap, msg_info: MessageInfo);
fn nb_send(dest: Cap, msg_info: MessageInfo);
fn call(cap: Cap, msg_info: MessageInfo) -> Result<(MessageInfo, Option<&Badge>)>;
// Wait for message, when it is received,
// return object Badge and block caller on `reply`.
fn recv(src: Cap, reply: Cap) -> Result<(MessageInfo, Option<&Badge>)>;
fn reply(msg_info: MessageInfo);
// As Recv but invoke `reply` first.
fn reply_recv(
src: Cap,
reply: Cap,
msg_info: MessageInfo,
) -> Result<(MessageInfo, Option<&Badge>)>;
fn nb_recv(src: Cap) -> Result<(MessageInfo, Option<&Badge>)>;
fn r#yield();
// -- end of default seL4 syscall list --
// As ReplyRecv but invoke `dest` not `reply`.
fn nb_send_recv(
dest: Cap,
msg_info: MessageInfo,
src: Cap,
reply: Cap,
) -> Result<(MessageInfo, Options<&Badge>)>;
// As NBSendRecv, with no reply. Donation is not possible.
fn nb_send_wait(
cap: Cap,
msg_info: MessageInfo,
src: Cap,
) -> Result<(MessageInfo, Option<&Badge>)>;
// As per Recv, but donation not possible.
fn wait(src: Cap) -> Result<(MessageInfo, Option<&Badge>)>;
// Plus some debugging calls...
}
fn handle_syscall(syscall: SysCall) -> Result<()> {
match syscall {
SysCall::Send => {
let result = handle_invocation(false, true);
if result.is_err() {
let irq = get_active_irq();
if irq.is_ok() {
handle_interrupt(irq.unwrap());
}
}
}
SysCall::NBSend => {
let result = handle_invocation(false, false);
if result.is_err() {
let irq = get_active_irq();
if irq.is_ok() {
handle_interrupt(irq.unwrap());
}
}
}
SysCall::Call => {
let result = handle_invocation(true, true);
if result.is_err() {
let irq = get_active_irq();
if irq.is_ok() {
handle_interrupt(irq.unwrap());
}
}
}
SysCall::Recv => handle_receive(true),
SysCall::Reply => handle_reply(),
SysCall::ReplyRecv => {
handle_reply();
handle_receive(true)
}
SysCall::NBRecv => handle_receive(false),
SysCall::Yield => handle_yield(),
}
Scheduler::schedule();
Scheduler::activate_thread();
Ok(())
}
fn handle_invocation(is_call: bool, is_blocking: bool) -> Result<()> {
let thread: &TCB = KernelCurrentThread;
let infoRegister = thread.get_register(msgInfoRegister);
let info: MessageInfo = messageInfoFromWord(infoRegister);
let cap_ptr: CapPath = thread.get_register(capRegister);
result = thread.lookup_cap_and_slot(cap_ptr);
if result.is_err() {
println!(
"<<vesper[T{} \"{}\" @{}]: Invocation of invalid cap {}.>>",
thread,
thread.name,
thread.get_restart_pc(),
cap_ptr,
);
if is_blocking {
handle_fault(thread);
}
return Ok(());
}
let buffer = thread.lookup_ipc_buffer(false);
let status = thread.lookup_extra_caps(buffer, info);
if status.is_err() {
println!(
"<<vesper[T{} \"{}\" @{}]: Lookup of extra caps failed.>>",
thread,
thread.name,
thread.get_restart_pc(),
);
if is_blocking {
handle_fault(thread);
}
return Ok(());
}
let mut length = info.length();
if length > n_MsgRegisters && !buffer {
length = n_MsgRegisters;
}
let status = decode_invocation(
info.label(),
length,
cap_ptr,
result.slot,
result.cap,
current_extra_caps,
is_blocking,
is_call,
buffer,
);
if status.is_err() {
return match status {
Err(Preempted) => status,
Err(SysCallError) => {
if is_call {
thread.replyFromKernel_error();
}
Ok(())
}
};
}
if thread.get_state() == ThreadState::Restart {
if is_call {
thread.replyFromKernel_success_empty();
}
thread.set_state(ThreadState::Running);
}
Ok(())
}
fn handle_receive(is_blocking: bool) {
let endpoint_cap_ptr = KernelCurrentThread.get_register(capRegister);
let result = KernelCurrentThread.lookup_cap(endpoint_cap_ptr);
if result.is_err() {
KernelCurrentFault = Fault_CapFault::new(endpoint_cap_ptr, true);
handle_fault(KernelCurrentThread);
return Ok(());
}
match result.cap.get_type() {
endpoint => ,
notification => ,
_ => fault,
}
}
fn handle_reply() {
let caller_slot = KernelCurrentThread.get_caller_slot();
let caller_cap = caller_slot.capability;
match caller_cap.get_type() {
ReplyCap::Type.value => {
// if (cap_reply_cap_get_capReplyMaster(callerCap)) {
// break;
// }
// caller = ((tcb_t *)(cap_reply_cap_get_capTCBPtr(callerCap)));
// if(!(caller != ksCurThread)) _assert_fail("caller must not be the current thread", "src/api/syscall.c", 313, __FUNCTION__);
// do_reply_transfer(ksCurThread, caller, callerSlot);
},
NullCap::Type.value => {
println!("<<vesper[T{} \"{}\" @{}]: Attempted reply operation when no reply capability present.>>", KernelCurrentThread, KernelCurrentThread.name, KernelCurrentThread.get_restart_pc());
},
_ => {
panic!("<<vesper[T{} \"{}\" @{}]: Invalid caller capability.>>", KernelCurrentThread, KernelCurrentThread.name, KernelCurrentThread.get_restart_pc());
}
}
}
fn do_reply_transfer() {}
fn handle_yield() {
Scheduler::dequeue(KernelCurrentThread);
Scheduler::append(KernelCurrentThread);
Scheduler::reschedule_required();
}
#[derive(Debug, Snafu)]
enum Fault {
#[snafu(display("null fault"))]
Null,
#[snafu(display("capability fault in {} phase at address {:x}", if in_receive_phase { "receive" } else { "send" }, address))]
Capability {
in_receive_phase: bool,
address: PhysAddr,
},
#[snafu(display("vm fault on {} at address {:x} with status {:x}", if is_instruction_fault { "code" } else { "data" }, address, fsr))]
VM {
is_instruction_fault: bool,
address: PhysAddr,
fsr: u64, // status
},
#[snafu(display("unknown syscall {:x}", syscall_number))]
UnknownSyscall {
syscall_number: u64,
},
#[snafu(display("user exception {:x} code {:x}", number, code))]
UserException {
number: u64,
code: u64,
},
}
fn handle_fault(thread: &TCB) {
let fault = KernelCurrentFault;
let result = thread.send_fault_ipc();
if result.is_err() {
handle_double_fault(thread, fault);
}
}
fn handle_double_fault(thread: &TCB, fault1: Fault) {
let fault2 = KernelCurrentFault;
println!("Caught {} while trying to handle {}", fault2, fault1);
println!("in thread T{} \"{}\"", thread, thread.name);
println!("at address {}", thread.get_restart_pc());
println!("with stack trace:");
arch::user_stack_trace(thread);
thread.set_state(ThreadState::Inactive);
}
fn handle_unknown_syscall() {
// handles
// - SysDebugPutChar
// - SysDebugHalt
// - SysDebugSnapshot
// - SysDebugCapIdentify
// - SysDebugNameThread
// - Fault_UnknownSyscall
}
fn handle_interrupt_entry() -> Result<()> {
let irq = get_active_irq();
if irq.is_ok() {
handle_interrupt(irq.unwrap());
} else {
handle_spurious_irq();
}
Scheduler::schedule();
Scheduler::activate_thread();
Ok(())
}
//handleSyscall(syscall) in the slowpath()
// these are expressed in terms of
// handleInvocation(bool isCall, bool isBlocking)
// handleRecv(block)
// handleReply()
// replyRecv: -- handleReply+handleRecv
// handleYield()
// slowpath() called in c_handle_syscall() in abi
// Call and ReplyRecv have fastpath handlers
// the rest goes through slowpath
// c_handle_syscall called directly from SVC vector entry
struct Scheduler;
impl Scheduler {
/* Values of 0 and ~0 encode ResumeCurrentThread and ChooseNewThread
* respectively; other values encode SwitchToThread and must be valid
* tcb pointers */
//KernelSchedulerAction
fn schedule() {
let action = KernelSchedulerAction;
if action == !0 { // all ones..
if KernelCurrentThread.is_runnable() {
Scheduler::enqueue(KernelCurrentThread);
}
if KernelDomainTime == 0 {
next_domain();
}
Scheduler::choose_thread();
KernelSchedulerAction = 0;
} else if action != 0 {
if KernelCurrentThread.is_runnable() {
Scheduler::enqueue(KernelCurrentThread);
}
Scheduler::switch_to_thread(KernelSchedulerAction);
KernelSchedulerAction = 0;
}
}
fn activate_thread() {}
fn dequeue(thread: &mut TCB);
fn append(thread: &mut TCB);
fn reschedule_required();
}
struct Nucleus {}
impl API for Nucleus {
//...
}

View File

@ -0,0 +1,17 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
//! Syscall ABI for calling kernel functions.
//!
//! Principally, there are two syscalls - one does not use capabilities, `Yield` and one is performing
//! a capability invocation, `InvokeCapability`. However internally the invocation is dispatched to
//! multiple available kernel functions, specific to each capability.
/// Parse syscall and invoke API functions.
///
/// Implements C ABI to easily parse passed in parameters.
/// @todo Move this to aarch64-specific part.
#[no_mangle]
extern "C" pub(crate) syscall_entry(syscall_no: u64) {}

View File

@ -1,18 +1,20 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*
* Based on ideas from Jorge Aparicio, Andre Richter, Phil Oppenheimer, Sergio Benitez.
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
//! Low-level boot of the Raspberry's processor
//! http://infocenter.arm.com/help/topic/com.arm.doc.dai0527a/DAI0527A_baremetal_boot_code_for_ARMv8_A_processors.pdf
//! <http://infocenter.arm.com/help/topic/com.arm.doc.dai0527a/DAI0527A_baremetal_boot_code_for_ARMv8_A_processors.pdf>
use {
crate::endless_sleep,
cortex_a::{asm, regs::*},
};
//use crate::arch::caps::{CapNode, Capability};
// Stack placed before first executable instruction
const STACK_START: u64 = 0x0008_0000; // Keep in sync with linker script
@ -218,3 +220,225 @@ pub unsafe extern "C" fn _boot_cores() -> ! {
// if not core0 or not EL3/EL2/EL1, infinitely wait for events
endless_sleep()
}
/*
// caps and mem regions init
enum KernelInitError {}
fn map_kernel_window() {}
/**
* This and only this function initialises the CPU.
* It does NOT initialise any kernel state.
*/
fn init_cpu() -> Result<(), KernelInitError> {
activate_global_pd();
}
/**
* This and only this function initialises the platform.
* It does NOT initialise any kernel state.
*/
fn init_plat() -> Result<(), KernelInitError> {
initIRQController();
initTimer();
initL2Cache();
}
fn arch_init_freemem() -> Result<(), KernelInitError> {
unimplemented!();
}
fn create_domain_cap() -> Result<(), KernelInitError> {
unimplemented!();
}
fn init_irqs() -> Result<(), KernelInitError> {
for (irq_t i = 0; i <= maxIRQ; i++) {
setIRQState(IRQInactive, i);
}
setIRQState(IRQTimer, GPT9_IRQ);
/* provide the IRQ control cap */
write_slot((((slot_ptr_t)((pptr_t)cap_get_capPtr(root_cnode_cap))) + (seL4_CapIRQControl)), cap_irq_control_cap_new());
}
fn create_bootinfo_cap() -> Result<(), KernelInitError> {
unimplemented!();
}
fn create_asid_pool_for_initial_thread() -> Result<(), KernelInitError> {
unimplemented!();
}
fn create_idle_thread() -> Result<(), KernelInitError> {
unimplemented!();
}
fn clean_invalidate_l1_caches() -> Result<(), KernelInitError> {
unimplemented!();
}
fn create_initial_thread() -> Result<(), KernelInitError> {
unimplemented!();
}
fn init_core_state(_: Result<(), KernelInitError>) -> Result<(), KernelInitError> {
unimplemented!();
}
fn create_untypeds() -> Result<(), KernelInitError> {
unimplemented!();
}
fn finalise_bootinfo() -> Result<(), KernelInitError> {
unimplemented!();
}
fn invalidate_local_tlb() -> Result<(), KernelInitError> {
unimplemented!();
}
fn lock_kernel_node() -> Result<(), KernelInitError> {
unimplemented!();
}
fn schedule() {
unimplemented!();
}
fn activate_thread() {
unimplemented!();
}
#[link_section = ".text.boot"]
// #[used]
fn try_init_kernel() -> Result<(), KernelInitError> {
map_kernel_window();
init_cpu()?;
init_plat()?;
println!("Booting kernel");
init_free_memory()?; // arch_init_freemem()
let root_capnode_cap = create_root_capnode()?;
create_domain_cap(root_capnode_cap);
// ...create IRQ CapNode...
init_irqs(root_capnode_cap)?;
//fill in boot info and
// create bootinfo frame
// create initial address space covering init thread
// user image and ipc buffer and bootinfo frame
// create and map bootinfo frame cap
create_bootinfo_cap();
// create initial thread IPC buffer
// create userland image frames
// create initial thread ASID pool
let it_asid_pool_cap = create_asid_pool_for_initial_thread(root_capnode_cap)?;
// create the idle thread
create_idle_thread()?;
/* Before creating the initial thread (which also switches to it)
* we clean the cache so that any page table information written
* as a result of calling create_frames_of_region will be correctly
* read by the hardware page table walker */
clean_invalidate_l1_caches();
let it = create_initial_thread(root_capnode_cap)?;
/* create all of the untypeds. Both devices and kernel window memory */
create_untypeds(root_capnode_cap)?;
finalise_bootinfo();
/* make everything written by the kernel visible to userland. Cleaning to PoC is not
* strictly neccessary, but performance is not critical here so clean and invalidate
* everything to PoC */
clean_invalidate_l1_caches();
invalidate_local_tlb();
/* Export selected CPU features for access by EL0 */
arch_init_user_access();
Ok(())
}
fn try_init_kernel_secondary_core() -> Result<(), KernelInitError>
{
init_cpu();
/* Enable per-CPU timer interrupts */
maskInterrupt(false, KERNEL_TIMER_IRQ);
lock_kernel_node;
ksNumCPUs++; // increase global cpu counter - this should be done differently?
init_core_state(SchedulerAction_ResumeCurrentThread);
Ok(())
}
fn init_kernel() {
try_init_kernel()?;
// or for AP:
// try_init_kernel_secondary_core();
schedule();
activate_thread();
}
const CONFIG_ROOT_CAPNODE_SIZE_BITS: usize = 12;
const wordBits: usize = 64;
fn create_root_capnode() -> Capability // Attr(BOOT_CODE)
{
// write the number of root CNode slots to global state
boot_info.max_slot_pos = 1 << CONFIG_ROOT_CAPNODE_SIZE_BITS; // 12 bits => 4096 slots
// seL4_SlotBits = 32 bytes per entry, 4096 entries =>
// create an empty root CapNode
// this goes into the kernel startup/heap memory (one of the few items that kernel DOES allocate).
let region_size = core::mem::size_of::<Capability> * boot_info.max_slot_pos; // 12 + 5 => 131072 (128Kb)
let pptr = alloc_region(region_size); // GlobalAllocator::alloc_zeroed instead?
if pptr.is_none() {
println!("Kernel init failing: could not create root capnode");
return Capability(NullCap::Type::value);
}
let Some(pptr) = pptr;
memzero(pptr, region_size); // CTE_PTR(pptr) ?
// transmute into a type? (you can use ptr.write() to just write a type into memory location)
let cap = CapNode::new_root(pptr);
// this cnode contains a cap to itself...
/* write the root CNode cap into the root CNode */
// @todo rootCapNode.write_slot(CapInitThreadCNode, cap); -- where cap and rootCapNode are synonyms!
write_slot(SLOT_PTR(pptr, seL4_CapInitThreadCNode), cap);
cap // reference to pptr is here
}
*/
// create initial thread
// - vspace
// - cpace
// - tcb
//
// requires:
// - alloc_region
// - copy_global_mappings
// - create pt/pd caps -- this is arch-specific?
// - root capnode with write_slot()
//
// init thread domain = 0
// init thread asid = 1 (asidInvalid = 0)
//

View File

@ -0,0 +1,33 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use {
crate::{
capdef,
caps::{CapError, Capability},
},
core::convert::TryFrom,
paste::paste,
register::{register_bitfields, LocalRegisterCopy},
};
//=====================
// Cap definition
//=====================
register_bitfields! {
u128,
AsidControlCap [
Type OFFSET(0) NUMBITS(6) [
value = 11
]
]
}
capdef! { AsidControl }
//=====================
// Cap implementation
//=====================

View File

@ -0,0 +1,35 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use {
crate::{
capdef,
caps::{CapError, Capability},
},
core::convert::TryFrom,
paste::paste,
register::{register_bitfields, LocalRegisterCopy},
};
//=====================
// Cap definition
//=====================
register_bitfields! {
u128,
AsidPoolCap [
Type OFFSET(0) NUMBITS(6) [
value = 13
],
ASIDBase OFFSET(64) NUMBITS(16) [],
ASIDPool OFFSET(80) NUMBITS(37) [],
]
}
capdef! { AsidPool }
//=====================
// Cap implementation
//=====================

View File

@ -0,0 +1,39 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use {
crate::{
capdef,
caps::{CapError, Capability},
},
core::convert::TryFrom,
paste::paste,
register::{register_bitfields, LocalRegisterCopy},
};
//=====================
// Cap definition
//=====================
register_bitfields! {
u128,
FrameCap [
Type OFFSET(0) NUMBITS(6) [
value = 1
],
Size OFFSET(6) NUMBITS(2) [],
VMRights OFFSET(8) NUMBITS(2) [],
IsDevice OFFSET(10) NUMBITS(1) [],
BasePtr OFFSET(16) NUMBITS(48) [], // PhysAddr
MappedAddress OFFSET(64) NUMBITS(48) [], // VirtAddr
MappedASID OFFSET(112) NUMBITS(16) [],
]
}
capdef! { Frame }
//=====================
// Cap implementation
//=====================

View File

@ -0,0 +1,14 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
//! AArch64-specific capabilities.
mod asid_control_cap;
mod asid_pool_cap;
mod frame;
mod page_directory_cap;
mod page_global_directory_cap;
mod page_table_cap;
mod page_upper_directory_cap;

View File

@ -0,0 +1,56 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use {
crate::{
arch::memory::{PhysAddr, VirtAddr, ASID},
capdef,
caps::{CapError, Capability},
},
core::convert::TryFrom,
paste::paste,
register::{register_bitfields, LocalRegisterCopy},
};
//=====================
// Cap definition
//=====================
register_bitfields! {
u128,
PageDirectoryCap [
Type OFFSET(0) NUMBITS(6) [
value = 5
],
IsMapped OFFSET(6) NUMBITS(1) [],
BasePtr OFFSET(16) NUMBITS(48) [], // PhysAddr
MappedAddress OFFSET(64) NUMBITS(48) [], // VirtAddr
MappedASID OFFSET(112) NUMBITS(16) [],
]
}
capdef! { PageDirectory }
//=====================
// Cap implementation
//=====================
impl PageDirectoryCapability {
pub(crate) fn base_address(&self) -> PhysAddr {
PhysAddr::new(self.0.read(PageDirectoryCap::BasePtr))
}
pub(crate) fn is_mapped(&self) -> bool {
self.0.read(PageDirectoryCap::IsMapped) == 1
}
pub(crate) fn mapped_address(&self) -> VirtAddr {
VirtAddr::new(self.0.read(PageDirectoryCap::MappedAddress))
}
pub(crate) fn mapped_asid(&self) -> ASID {
self.0.read(PageDirectoryCap::MappedASID)
}
}

View File

@ -0,0 +1,54 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use {
crate::{
arch::memory::{PhysAddr, VirtAddr, ASID},
capdef,
caps::{CapError, Capability},
},
core::convert::TryFrom,
paste::paste,
register::{register_bitfields, LocalRegisterCopy},
};
//=====================
// Cap definition
//=====================
register_bitfields! {
u128,
PageGlobalDirectoryCap [
Type OFFSET(0) NUMBITS(6) [
value = 9
],
IsMapped OFFSET(6) NUMBITS(1) [],
BasePtr OFFSET(16) NUMBITS(48) [], // PhysAddr
MappedASID OFFSET(112) NUMBITS(16) [],
]
}
capdef! { PageGlobalDirectory }
//=====================
// Cap implementation
//=====================
impl PageGlobalDirectoryCapability {
pub(crate) fn base_address(&self) -> PhysAddr {
PhysAddr::new(self.0.read(PageGlobalDirectoryCap::BasePtr))
}
pub(crate) fn is_mapped(&self) -> bool {
self.0.read(PageGlobalDirectoryCap::IsMapped) == 1
}
// Global directory does not give access to mapped addresses,
// instead, it links to lower page directory levels.
pub(crate) fn mapped_asid(&self) -> ASID {
self.0.read(PageGlobalDirectoryCap::MappedASID)
}
}

View File

@ -0,0 +1,56 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use {
crate::{
arch::memory::{PhysAddr, VirtAddr, ASID},
capdef,
caps::{CapError, Capability},
},
core::convert::TryFrom,
paste::paste,
register::{register_bitfields, LocalRegisterCopy},
};
//=====================
// Cap definition
//=====================
register_bitfields! {
u128,
PageTableCap [
Type OFFSET(0) NUMBITS(6) [
value = 3
],
IsMapped OFFSET(6) NUMBITS(1) [],
BasePtr OFFSET(16) NUMBITS(48) [], // PhysAddr
MappedAddress OFFSET(64) NUMBITS(48) [], // VirtAddr
MappedASID OFFSET(112) NUMBITS(16) [],
],
}
capdef! { PageTable }
//=====================
// Cap implementation
//=====================
impl PageTableCapability {
pub(crate) fn base_address(&self) -> PhysAddr {
PhysAddr::new(self.0.read(PageTableCap::BasePtr))
}
pub(crate) fn is_mapped(&self) -> bool {
self.0.read(PageTableCap::IsMapped) == 1
}
pub(crate) fn mapped_address(&self) -> VirtAddr {
VirtAddr::new(self.0.read(PageTableCap::MappedAddress))
}
pub(crate) fn mapped_asid(&self) -> ASID {
self.0.read(PageTableCap::MappedASID)
}
}

View File

@ -0,0 +1,37 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use {
crate::{
capdef,
caps::{CapError, Capability},
},
core::convert::TryFrom,
paste::paste,
register::{register_bitfields, LocalRegisterCopy},
};
//=====================
// Cap definition
//=====================
register_bitfields! {
u128,
PageUpperDirectoryCap [
Type OFFSET(0) NUMBITS(6) [
value = 7
],
IsMapped OFFSET(6) NUMBITS(1) [],
BasePtr OFFSET(16) NUMBITS(48) [], // PhysAddr
MappedAddress OFFSET(64) NUMBITS(48) [], // VirtAddr
MappedASID OFFSET(112) NUMBITS(16) [],
]
}
capdef! { PageUpperDirectory }
//=====================
// Cap implementation
//=====================

View File

@ -1,3 +1,8 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
//! JTAG helper functions.
use cortex_a::asm;

View File

@ -0,0 +1,6 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
pub type ASID = u16;

View File

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

View File

@ -0,0 +1,250 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use {
crate::mm::{align_down, align_up},
bit_field::BitField,
core::{
convert::{From, Into},
fmt,
ops::{Add, AddAssign, Shl, Shr, Sub, SubAssign},
},
usize_conversions::FromUsize,
};
/// A 64-bit physical memory address.
///
/// This is a wrapper type around an `u64`, so it is always 8 bytes, even when compiled
/// on non 64-bit systems. The `UsizeConversions` trait can be used for performing conversions
/// between `u64` and `usize`.
///
/// On `aarch64`, only the 52 lower bits of a physical address can be used. The top 12 bits need
/// to be zero. This type guarantees that it always represents a valid physical address.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
#[repr(transparent)]
pub struct PhysAddr(u64);
/// A passed `u64` was not a valid physical address.
///
/// This means that bits 52 to 64 were not all null.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct PhysAddrNotValid(u64);
impl PhysAddr {
/// Creates a new physical address.
///
/// Panics if any bits in the bit position 52 to 64 is set.
pub fn new(addr: u64) -> PhysAddr {
assert_eq!(
addr.get_bits(52..64),
0,
"physical addresses must not have any set bits in positions 52 to 64"
);
PhysAddr(addr)
}
/// Tries to create a new physical address.
///
/// Fails if any bits in the bit positions 52 to 64 are set.
pub fn try_new(addr: u64) -> Result<PhysAddr, PhysAddrNotValid> {
match addr.get_bits(52..64) {
0 => Ok(PhysAddr(addr)), // address is valid
_ => Err(PhysAddrNotValid(addr)),
}
}
/// Creates a physical address that points to `0`.
pub const fn zero() -> PhysAddr {
PhysAddr(0)
}
/// Converts the address to an `u64`.
pub fn as_u64(self) -> u64 {
self.0
}
/// Convenience method for checking if a physical address is null.
pub fn is_null(&self) -> bool {
self.0 == 0
}
/// Aligns the physical address upwards to the given alignment.
///
/// See the `align_up` function for more information.
pub fn aligned_up<U>(self, align: U) -> Self
where
U: Into<u64>,
{
PhysAddr(align_up(self.0, align.into()))
}
/// Aligns the physical address downwards to the given alignment.
///
/// See the `align_down` function for more information.
pub fn aligned_down<U>(self, align: U) -> Self
where
U: Into<u64>,
{
PhysAddr(align_down(self.0, align.into()))
}
/// Checks whether the physical address has the demanded alignment.
pub fn is_aligned<U>(self, align: U) -> bool
where
U: Into<u64>,
{
self.aligned_down(align) == self
}
}
impl fmt::Debug for PhysAddr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "PhysAddr({:#x})", self.0)
}
}
impl fmt::Binary for PhysAddr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl fmt::LowerHex for PhysAddr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl fmt::Octal for PhysAddr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl fmt::UpperHex for PhysAddr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl From<u64> for PhysAddr {
fn from(value: u64) -> Self {
PhysAddr::new(value)
}
}
impl From<PhysAddr> for u64 {
fn from(value: PhysAddr) -> Self {
value.as_u64()
}
}
impl From<PhysAddr> for u128 {
fn from(value: PhysAddr) -> Self {
value.as_u64() as u128
}
}
impl Add<u64> for PhysAddr {
type Output = Self;
fn add(self, rhs: u64) -> Self::Output {
PhysAddr::new(self.0 + rhs)
}
}
impl AddAssign<u64> for PhysAddr {
fn add_assign(&mut self, rhs: u64) {
*self = *self + rhs;
}
}
impl Add<usize> for PhysAddr
where
u64: FromUsize,
{
type Output = Self;
fn add(self, rhs: usize) -> Self::Output {
self + u64::from_usize(rhs)
}
}
impl AddAssign<usize> for PhysAddr
where
u64: FromUsize,
{
fn add_assign(&mut self, rhs: usize) {
self.add_assign(u64::from_usize(rhs))
}
}
impl Sub<u64> for PhysAddr {
type Output = Self;
fn sub(self, rhs: u64) -> Self::Output {
PhysAddr::new(self.0.checked_sub(rhs).unwrap())
}
}
impl SubAssign<u64> for PhysAddr {
fn sub_assign(&mut self, rhs: u64) {
*self = *self - rhs;
}
}
impl Sub<usize> for PhysAddr
where
u64: FromUsize,
{
type Output = Self;
fn sub(self, rhs: usize) -> Self::Output {
self - u64::from_usize(rhs)
}
}
impl SubAssign<usize> for PhysAddr
where
u64: FromUsize,
{
fn sub_assign(&mut self, rhs: usize) {
self.sub_assign(u64::from_usize(rhs))
}
}
impl Sub<PhysAddr> for PhysAddr {
type Output = u64;
fn sub(self, rhs: PhysAddr) -> Self::Output {
self.as_u64().checked_sub(rhs.as_u64()).unwrap()
}
}
impl Shr<usize> for PhysAddr {
type Output = PhysAddr;
fn shr(self, shift: usize) -> Self::Output {
PhysAddr::new(self.0 >> shift)
}
}
impl Shl<usize> for PhysAddr {
type Output = PhysAddr;
fn shl(self, shift: usize) -> Self::Output {
PhysAddr::new(self.0 << shift)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test_case]
pub fn test_invalid_phys_addr() {
let result = PhysAddr::try_new(0xfafa_0123_3210_3210);
if let Err(e) = result {
assert_eq!(e, PhysAddrNotValid(0xfafa_0123_3210_3210));
} else {
assert!(false)
}
}
}

View File

@ -2,6 +2,7 @@
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use {
crate::mm::{align_down, align_up},
bit_field::BitField,
@ -290,219 +291,3 @@ where
*self = VirtAddr::new(self.0 % u64::from_usize(rhs));
}
}
/// A 64-bit physical memory address.
///
/// This is a wrapper type around an `u64`, so it is always 8 bytes, even when compiled
/// on non 64-bit systems. The `UsizeConversions` trait can be used for performing conversions
/// between `u64` and `usize`.
///
/// On `aarch64`, only the 52 lower bits of a physical address can be used. The top 12 bits need
/// to be zero. This type guarantees that it always represents a valid physical address.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
#[repr(transparent)]
pub struct PhysAddr(u64);
/// A passed `u64` was not a valid physical address.
///
/// This means that bits 52 to 64 were not all null.
#[derive(Debug)]
pub struct PhysAddrNotValid(u64);
impl PhysAddr {
/// Creates a new physical address.
///
/// Panics if any bits in the bit position 52 to 64 is set.
pub fn new(addr: u64) -> PhysAddr {
assert_eq!(
addr.get_bits(52..64),
0,
"physical addresses must not have any set bits in positions 52 to 64"
);
PhysAddr(addr)
}
/// Tries to create a new physical address.
///
/// Fails if any bits in the bit positions 52 to 64 are set.
pub fn try_new(addr: u64) -> Result<PhysAddr, PhysAddrNotValid> {
match addr.get_bits(52..64) {
0 => Ok(PhysAddr(addr)), // address is valid
_ => Err(PhysAddrNotValid(addr)),
}
}
/// Creates a physical address that points to `0`.
pub const fn zero() -> PhysAddr {
PhysAddr(0)
}
/// Converts the address to an `u64`.
pub fn as_u64(self) -> u64 {
self.0
}
/// Convenience method for checking if a physical address is null.
pub fn is_null(&self) -> bool {
self.0 == 0
}
/// Aligns the physical address upwards to the given alignment.
///
/// See the `align_up` function for more information.
pub fn aligned_up<U>(self, align: U) -> Self
where
U: Into<u64>,
{
PhysAddr(align_up(self.0, align.into()))
}
/// Aligns the physical address downwards to the given alignment.
///
/// See the `align_down` function for more information.
pub fn aligned_down<U>(self, align: U) -> Self
where
U: Into<u64>,
{
PhysAddr(align_down(self.0, align.into()))
}
/// Checks whether the physical address has the demanded alignment.
pub fn is_aligned<U>(self, align: U) -> bool
where
U: Into<u64>,
{
self.aligned_down(align) == self
}
}
impl fmt::Debug for PhysAddr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "PhysAddr({:#x})", self.0)
}
}
impl fmt::Binary for PhysAddr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl fmt::LowerHex for PhysAddr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl fmt::Octal for PhysAddr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl fmt::UpperHex for PhysAddr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl From<u64> for PhysAddr {
fn from(value: u64) -> Self {
PhysAddr::new(value)
}
}
impl From<PhysAddr> for u64 {
fn from(value: PhysAddr) -> Self {
value.as_u64()
}
}
impl Add<u64> for PhysAddr {
type Output = Self;
fn add(self, rhs: u64) -> Self::Output {
PhysAddr::new(self.0 + rhs)
}
}
impl AddAssign<u64> for PhysAddr {
fn add_assign(&mut self, rhs: u64) {
*self = *self + rhs;
}
}
impl Add<usize> for PhysAddr
where
u64: FromUsize,
{
type Output = Self;
fn add(self, rhs: usize) -> Self::Output {
self + u64::from_usize(rhs)
}
}
impl AddAssign<usize> for PhysAddr
where
u64: FromUsize,
{
fn add_assign(&mut self, rhs: usize) {
self.add_assign(u64::from_usize(rhs))
}
}
impl Sub<u64> for PhysAddr {
type Output = Self;
fn sub(self, rhs: u64) -> Self::Output {
PhysAddr::new(self.0.checked_sub(rhs).unwrap())
}
}
impl SubAssign<u64> for PhysAddr {
fn sub_assign(&mut self, rhs: u64) {
*self = *self - rhs;
}
}
impl Sub<usize> for PhysAddr
where
u64: FromUsize,
{
type Output = Self;
fn sub(self, rhs: usize) -> Self::Output {
self - u64::from_usize(rhs)
}
}
impl SubAssign<usize> for PhysAddr
where
u64: FromUsize,
{
fn sub_assign(&mut self, rhs: usize) {
self.sub_assign(u64::from_usize(rhs))
}
}
impl Sub<PhysAddr> for PhysAddr {
type Output = u64;
fn sub(self, rhs: PhysAddr) -> Self::Output {
self.as_u64().checked_sub(rhs.as_u64()).unwrap()
}
}
#[cfg(test)]
mod tests {
// use super::*;
// #[test_case]
// pub fn test_invalid_phys_addr() {
// let result = PhysAddr::try_new(0xfafa_0123_0123_0123_3210_3210_3210_3210);
// if let Err(e) = result {
// assert_eq!(
// e,
// PhysAddrNotValid(0xfafa_0123_0123_0123_3210_3210_3210_3210)
// );
// } else {
// assert!(false)
// }
// }
}

View File

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

View File

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

View File

@ -8,11 +8,16 @@
use cortex_a::asm;
mod boot;
pub mod caps;
pub use self::caps::*;
#[cfg(feature = "jtag")]
pub mod jtag;
pub mod memory;
pub mod objects;
pub mod traps;
pub(crate) use objects::thread::user_stack_trace;
/// Loop forever in sleep mode.
#[inline]
pub fn endless_sleep() -> ! {

View File

@ -0,0 +1,9 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
// implemented for x86 and arm
trait ASIDControl {
// fn make_pool(untyped: Untyped, target_cap_space_cap: CapNodeRootedPath) -> Result<()>;
}

View File

@ -0,0 +1,9 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
// implemented for x86 and arm
trait ASIDPool {
// fn assign(virt_space: VirtSpace /*Cap*/) -> Result<()>;
}

View File

@ -0,0 +1,100 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use crate::memory::{PhysAddr, VirtAddr};
mod asid_control;
mod asid_pool;
mod page;
mod page_directory;
mod page_global_directory;
mod page_table;
mod page_upper_directory;
pub(crate) mod thread;
// Allocation details
// 1. should be possible to map non-SAS style
// 2. should be easy to map SAS style
// 3. should not allocate any memory dynamically
// ^ problem with the above API is FrameAllocator
// ^ clients should supply their own memory for frames... from FrameCaps
// https://github.com/seL4/seL4_libs/tree/master/libsel4allocman
// Allocation overview
// Allocation is complex due to the circular dependencies that exist on allocating resources. These dependencies are loosely described as
// Capability slots: Allocated from untypeds, book kept in memory.
// Untypeds / other objects (including frame objects): Allocated from other untypeds, into capability slots, book kept in memory.
// memory: Requires frame object.
//=============================================================================
// ActivePageTable (--> impl VirtSpace for ActivePageTable etc...)
// * translate(VirtAddr)->PhysAddr
// * translate_page(Page)->PhysAddr
// * map_to(Page, PhysFrame, Flags, FrameAllocator)->()
// * map(Page, Flags, FrameAllocator)->()
// * identity_map(PhysFrame, Flags, FrameAllocator)->()
// * unmap(Page, FrameAllocator)->()
trait VirtSpace {
fn map_to(
virt_space: VirtSpace, /*Cap*/
vaddr: VirtAddr,
rights: u32, //CapRights,
attr: u32, //VMAttributes,
) -> Result<()>;
/// ??
fn unmap() -> Result<()>; // ??
fn remap(
virt_space: VirtSpace, /*Cap*/
rights: u32, //CapRights,
attr: u32, //VMAttributes,
) -> Result<()>;
/// ??
fn get_address() -> Result<PhysAddr>; //??
}
// ARM AArch64 processors have a four-level page-table structure, where the
// VirtSpace is realised as a PageGlobalDirectory. All paging structures are
// indexed by 9 bits of the virtual address.
// AArch64 page hierarchy:
//
// PageGlobalDirectory (L0) -- aka VirtSpace
// +--PageUpperDirectory (L1)
// +--Page<Size1GiB> -- aka HugePage
// | or
// +--PageDirectory (L2)
// +--Page<Size2MiB> -- aka LargePage
// | or
// +--PageTable (L3)
// +--Page<Size4KiB> -- aka Page
/// Cache data management.
trait PageCacheManagement {
/// Cleans the data cache out to RAM.
/// The start and end are relative to the page being serviced.
fn clean_data(start_offset: usize, end_offset: usize) -> Result<()>;
/// Clean and invalidates the cache range within the given page.
/// The range will be flushed out to RAM. The start and end are relative
/// to the page being serviced.
fn clean_invalidate_data(start_offset: usize, end_offset: usize) -> Result<()>;
/// Invalidates the cache range within the given page.
/// The start and end are relative to the page being serviced and should
/// be aligned to a cache line boundary where possible. An additional
/// clean is performed on the outer cache lines if the start and end are
/// not aligned, to clean out the bytes between the requested and
/// the cache line boundary.
fn invalidate_data(start_offset: usize, end_offset: usize) -> Result<()>;
/// Cleans data lines to point of unification, invalidates
/// corresponding instruction lines to point of unification, then
/// invalidates branch predictors.
/// The start and end are relative to the page being serviced.
fn unify_instruction_cache(start_offset: usize, end_offset: usize) -> Result<()>;
}

View File

@ -0,0 +1,60 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use crate::arch::{
memory::{PhysAddr, VirtAddr},
objects::PageCacheManagement,
};
struct Page {}
impl Page {
// VirtSpace-like interface.
/// Get the physical address of the underlying frame.
fn get_address() -> Result<PhysAddr> {
todo!()
}
// fn map(
// virt_space: VirtSpace, /*Cap*/
// vaddr: VirtAddr,
// rights: CapRights,
// attr: VMAttributes,
// ) -> Result<()> {
// todo!()
// }
/// Changes the permissions of an existing mapping.
// fn remap(
// virt_space: VirtSpace, /*Cap*/
// rights: CapRights,
// attr: VMAttributes,
// ) -> Result<()> {
// todo!()
// }
fn unmap() -> Result<()> {
todo!()
}
// MMIO space.
// fn map_io(iospace: IoSpace /*Cap*/, rights: CapRights, ioaddr: VirtAddr) -> Result<()> {
// todo!()
// }
}
impl PageCacheManagement for Page {
fn clean_data(start_offset: usize, end_offset: usize) -> _ {
todo!()
}
fn clean_invalidate_data(start_offset: usize, end_offset: usize) -> _ {
todo!()
}
fn invalidate_data(start_offset: usize, end_offset: usize) -> _ {
todo!()
}
fn unify_instruction_cache(start_offset: usize, end_offset: usize) -> _ {
todo!()
}
}

View File

@ -0,0 +1,24 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use crate::memory::{mmu::PageUpperDirectory, VirtAddr};
// probably just impl some Mapping trait for these "structs"?
// L2 table
struct PageDirectory {}
impl PageDirectory {
fn map(
pud: PageUpperDirectory, /*Cap*/
vaddr: VirtAddr,
attr: u32, //VMAttributes,
) -> Result<()> {
todo!()
}
fn unmap() -> Result<()> {
todo!()
}
}

View File

@ -0,0 +1,32 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use crate::arch::{memory::PhysAddr, objects::PageCacheManagement};
// L0 table
struct PageGlobalDirectory {
// @todo should also impl VirtSpace to be able to map shit?
// or the Page's impl will do this?
}
impl PageCacheManagement for PageGlobalDirectory {
fn clean_data(start_offset: usize, end_offset: usize) -> ! {
todo!()
}
fn clean_invalidate_data(start_offset: usize, end_offset: usize) -> ! {
todo!()
}
fn invalidate_data(start_offset: usize, end_offset: usize) -> ! {
todo!()
}
fn unify_instruction_cache(start_offset: usize, end_offset: usize) -> ! {
todo!()
}
}
impl From<PhysAddr> for PageGlobalDirectory {}

View File

@ -0,0 +1,16 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
// L3 tables
struct PageTable {}
impl PageTable {
// fn map_to(virt_space: VirtSpace /*Cap*/, vaddr: VirtAddr, attr: VMAttributes) -> Result<()> {
// todo!()
// }
fn unmap() -> Result<()> {
todo!()
}
}

View File

@ -0,0 +1,22 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use crate::memory::{mmu::PageGlobalDirectory, VirtAddr};
// L1 table
struct PageUpperDirectory {}
impl PageUpperDirectory {
fn map(
pgd: PageGlobalDirectory, /*Cap*/
vaddr: VirtAddr,
attr: u32, //VMAttributes,
) -> Result<()> {
todo!()
}
fn unmap() -> Result<()> {
todo!()
}
}

View File

@ -0,0 +1,11 @@
//! Arch-specific part of the TCB
struct UserContext {
registers: [u64; 32],
}
pub(crate) struct TCB {
register_context: UserContext,
}
pub(crate) fn user_stack_trace(thread: &TCB) {}

View File

@ -126,6 +126,13 @@ unsafe extern "C" fn default_exception_handler() -> ! {
/// Totally unsafe in the land of the hardware.
#[no_mangle]
unsafe extern "C" fn current_el0_synchronous(e: &mut ExceptionContext) {
let cause = ESR_EL1.read(ESR_EL1::EC);
if cause == ESR_EL1::EC::SVC64.read(ESR_EL1::EC) {
let syscall = ESR_EL1.read(ESR_EL1::ISS);
return crate::api::handle_syscall(syscall);
}
println!("[!] USER synchronous exception happened.");
synchronous_common(e)
}
@ -309,8 +316,9 @@ fn iss_dfsc_to_string(iss: IssForDataAbort) -> &'static str {
/// Helper function to 1) display current exception, 2) skip the offending asm instruction.
/// Not for production use!
fn synchronous_common(e: &mut ExceptionContext) {
println!(" ESR_EL1: {:#010x} (syndrome)", ESR_EL1.get());
let cause = ESR_EL1.read(ESR_EL1::EC);
println!(" ESR_EL1: {:#010x} (syndrome)", ESR_EL1.get());
println!(
" EC: {:#06b} (cause) -- {}",
cause,

View File

@ -3,8 +3,10 @@
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
#[cfg(target_arch = "aarch64")]
#[macro_use]
pub mod aarch64;
#[cfg(target_arch = "aarch64")]
pub use self::aarch64::*;
cfg_if::cfg_if! {
if #[cfg(target_arch = "aarch64")] {
#[macro_use]
pub mod aarch64;
pub use self::aarch64::*;
}
}

View File

@ -0,0 +1,93 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use {
super::{
captable::CapTableEntry, derivation_tree::DerivationTreeNode, CapError, Capability, TryFrom,
},
crate::{arch::aarch64::memory::PhysAddr, capdef},
paste::paste,
register::{register_bitfields, LocalRegisterCopy},
ux::u6,
};
/// CapNode capability points to a CapTable containing
/// a number of CapTableEntries.
/// While CapTable is merely a storage, CapNode capability
/// provides information about the guard and the size
/// of that table.
//=====================
// Cap definition
//=====================
register_bitfields! {
u128,
CapNodeCap [
Type OFFSET(0) NUMBITS(6) [
value = 10
],
GuardSize OFFSET(6) NUMBITS(6) [],
Radix OFFSET(12) NUMBITS(6) [],
Ptr OFFSET(16) NUMBITS(48) [],
// Guard is 19 bits in seL4 arm32 (?)
Guard OFFSET(64) NUMBITS(64) [],
]
}
capdef! { CapNode }
//=====================
// Cap implementation
//=====================
impl CapNodeCapability {
/// Create a capability to CapNode.
///
/// CapNode capabilities allow to address a capability node tree entry.
pub fn new(capnode_ptr: PhysAddr, radix: u6, guard_size: u6, guard: u64) -> CapNodeCapability {
CapNodeCapability(LocalRegisterCopy::new(u128::from(
CapNodeCap::Type::value
+ CapNodeCap::Radix.val(radix.into())
+ CapNodeCap::GuardSize.val(guard_size.into())
+ CapNodeCap::Guard.val(guard.into())
+ CapNodeCap::Ptr.val(capnode_ptr.into()),
)))
}
/// Create new root node.
pub fn new_root(capnode_ptr: PhysAddr) -> CapNodeCapability {
const CONFIG_ROOT_CAPNODE_SIZE_BITS: u32 = 12;
const WORD_BITS: u32 = 64;
CapNodeCapability::new(
capnode_ptr,
CONFIG_ROOT_CAPNODE_SIZE_BITS,
WORD_BITS - CONFIG_ROOT_CAPNODE_SIZE_BITS,
0,
)
}
// pub const fn from_capability(cap: dyn Capability) -> CapNodeCapability {
// let reg = LocalRegisterCopy::<_, CapNodeCap::Register>::new(cap.as_u128());
// //assert_eq!(
// // reg.read(CapNodeCap::Type),
// // u128::from(CapNodeCap::Type::value)
// //);
// CapNodeCapability(reg)
// }
/// @internal
pub fn write_slot(&mut self, slot: usize, cap: &dyn Capability) {
let ptr = self.0.read(CapNodeCap::Ptr);
let size =
(1usize << self.0.read(CapNodeCap::Radix)) * core::mem::size_of::<CapTableEntry>();
let slice = unsafe { core::slice::from_raw_parts_mut(ptr as *mut CapTableEntry, size) };
slice[slot].capability = cap.as_u128();
slice[slot].derivation = DerivationTreeNode::empty()
.set_revocable(true)
.set_first_badged(true);
}
}

View File

@ -0,0 +1,273 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use crate::caps::Capability;
use snafu::Snafu;
use {super::derivation_tree::DerivationTreeNode, /*crate::memory::PhysAddr,*/ core::fmt};
// * Capability slots: 16 bytes of memory per slot (exactly one capability). --?
// CapNode describes `a given number of capability slots` with `a given guard`
// of `a given guard size` bits.
// @todo const generic on number of capabilities contained in the node? currently only contains a Cap
// capnode_cap has a pptr, guard_size, guard and radix
// this is enough to address a cap in the capnode contents
// by having a root capnode cap we can traverse the whole tree.
// -- cte_t from seL4
// structures.h:140
// /* Capability table entry (CTE) */
// struct cte {
// cap_t cap; // two words
// mdb_node_t cteMDBNode; // two words
// }; // -- four words: u256, 32 bytes.
// typedef struct cte cte_t;
/// Each entry in capability tree contains capability value and its position in the derivation tree.
#[derive(Clone)]
pub(crate) struct CapTableEntry {
pub(crate) capability: u128,
pub(crate) derivation: DerivationTreeNode,
}
impl fmt::Debug for CapTableEntry {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:x}", self.capability) // @todo
}
}
impl Default for CapTableEntry {
fn default() -> Self {
Self::empty()
}
}
impl CapTableEntry {
/// Temporary for testing:
fn empty() -> CapTableEntry {
CapTableEntry {
capability: 0,
derivation: DerivationTreeNode::empty(),
}
}
// We need to pass reference to the parent entry so that we can set up derivation pointers.
// @todo should be &mut since we need to set up Next pointer in parent also.
// @fixme this cannot work well unless we modify already allocated cap table entry in the table.
// (otherwise Next pointer will be invalid)
// sel4: cteInsert()
fn derived_from(&mut self, _parent: &mut CapTableEntry) {
// self.derivation
// .set_prev(parent as *mut CapTableEntry as PhysAddr);
// parent
// .derivation
// .set_next(self as *mut CapTableEntry as PhysAddr);
}
}
/*
struct CapNodePath {
/// Index contains `depth` lowermost bits of the path.
index: u64,
/// Depth specifies the remaining amount of bits left to traverse in the path.
/// Once depth reaches zero, the selected CapNode slot is the final target.
depth: usize,
}
struct CapNodeRootedPath {
root: CapNode,
path: CapNodePath,
}
// sel4: cnode_capdata_t
// @todo just use CapNodeCap
//struct CapNodeConfig { <-- for each CapTable we would need these..
// guard: u64,
// guard_bits: usize,
//}
// @note src and dest are swapped here, compared to seL4 api
impl CapNode { // actually an impl CapPtr
// Derives a capability into a new, less powerful one, with potentially added Badge.
fn mint(
src: CapNodeRootedPath, // can be just CapNodePath since it's relative (is it?) to this CapNode.
dest: CapNodePath,
rights: CapRights,
badge: Badge,
) -> Result<(), CapError> {
unimplemented!();
}
// [wip] is copy a derivation too? - yes it is - kernel_final.c:15769
fn copy(src: CapNodeRootedPath, dest: CapNodePath, rights: CapRights) -> Result<(), CapError> {
unimplemented!();
}
fn r#move(src: CapNodeRootedPath, dest: CapNodePath) -> Result<(), CapError> {
unimplemented!();
}
fn mutate(src: CapNodeRootedPath, dest: CapNodePath, badge: Badge) -> Result<(), CapError> {
unimplemented!();
}
fn rotate(
src: CapNodeRootedPath,
dest: CapNodePath,
dest_badge: Badge,
pivot: CapNodeRootedPath,
pivot_badge: Badge,
) -> Result<(), CapError> {
unimplemented!();
}
fn delete(path: CapNodePath) -> Result<(), CapError> {
unimplemented!();
}
fn revoke(path: CapNodePath) -> Result<(), CapError> {
unimplemented!();
}
fn save_caller(r#where: CapNodePath) -> Result<(), CapError> { // save_reply_cap() in sel4
unimplemented!();
}
fn cancel_badged_sends(path: CapNodePath) -> Result<(), CapError> {
unimplemented!();
}
}*/
/// Structure holding a number of capabilities.
// In seL4 the capnode is capability to an object called CapTable btw:
// case seL4_CapTableObject:
// return cap_cnode_cap_new(userSize, 0, 0, CTE_REF(regionBase));
struct CapTable<const SIZE_BITS: usize>
where
[CapTableEntry; 1 << SIZE_BITS]: Sized,
{
items: [CapTableEntry; 1 << SIZE_BITS],
}
/// Conceptually a threads CapSpace is the portion of the directed graph that is reachable
/// starting with the CapNode capability that is its CapSpace root.
struct CapSpace {
// cap_space_root: CapNodePath, -- probably not a path but direct CapNode pointer??
}
//impl CapNode for CapSpace {} -- ?
type CapPath = u64;
#[derive(Debug, Snafu)]
pub(crate) enum LookupFault {
InvalidRoot,
GuardMismatch,
DepthMismatch { expected: usize, actual: usize },
NoResolvedBits,
}
type Slot = u64; // @temp
type BitsRemaining = usize; // @temp
// seL4: resolveAddressBits(nodeCap, capptr, n_bits)
pub(crate) fn resolve_address_bits(
node_cap: &dyn Capability,
capptr: CapPath, // CapPtr = u64, aka CapPath
n_bits: usize,
) -> Result<(Slot, BitsRemaining), LookupFault> {
if node_cap.get_type() != CapNodeCapability {
return Err(LookupFault::InvalidRoot);
}
let mut n_bits = n_bits;
let mut node_cap = node_cap;
loop {
let radix_bits = node_cap.radixBits();
let guard_bits = node_cap.guardBits();
let level_bits = radix_bits + guard_bits;
if level_bits == 0 {
return Err(LookupFault::NoResolvedBits);
}
let cap_guard = node_cap.guard();
// @todo common code to extract guard_bits from an int?
let guard = (capptr >> core::cmp::min(n_bits - guard_bits, 63)) & ((1 << guard_bits) - 1);
if guard_bits > n_bits || guard != cap_guard {
return Err(LookupFault::GuardMismatch);
}
if level_bits > n_bits {
return Err(LookupFault::DepthMismatch {
expected: level_bits,
actual: n_bits,
});
}
let offset = (capptr >> (n_bits - level_bits)) & ((1 << radix_bits) - 1);
let slot = node_cap.getPtr() + offset; // @todo Need to turn this into CapTableEntry ptr
// actually == here since > case has errored above
if level_bits == n_bits {
return Ok((slot, 0));
}
n_bits -= level_bits;
node_cap = slot.capability;
if node_cap.get_type() != CapNodeCapability {
return Ok((slot, n_bits));
}
}
}
#[cfg(test)]
mod tests {
use super::{
super::{derivation_tree::DerivationTreeError, null_cap::NullCapability},
*,
};
#[test_case]
fn create_empty_cap_table() {
let table = CapTable::<5> {
items: Default::default(),
};
assert_eq!(table.items[0].capability, NullCapability::new().into());
assert_eq!(table.items[31].capability, NullCapability::new().into());
// Doesn't even compile:
// assert_eq!(table.items[32].capability, NullCapability::new().into());
}
#[test_case]
fn first_capability_derivation_has_no_prev_link() {
let entry = CapTableEntry::empty();
assert!(entry
.derivation
.try_get_prev()
.contains_err(&DerivationTreeError::InvalidPrev));
}
// Impl strategy
// 1. Make capabilities list
// 2. Fill it with capabilities
// 3. Test capability manipulation functions - mint/clone/revoke
// 4. Validate capability path, capability contents and capability derivation chain at each step
// 5. Start with Untyped capabilities and implement Retype()
// typedef enum api_object { -- basic list of API types of objects:
// seL4_UntypedObject,
// seL4_TCBObject,
// seL4_EndpointObject,
// seL4_NotificationObject,
// seL4_CapTableObject,
// 6. Retype to TCB and implement Thread capability to run threads (in priv mode first?)
}
/* caps with fixed slot positions in the root (boot) CNode */
// enum {
// seL4_CapNull = 0, /* null cap */
// seL4_CapInitThreadTCB = 1, /* initial thread's TCB cap */
// seL4_CapInitThreadCNode = 2, /* initial thread's root CNode cap */
// seL4_CapInitThreadVSpace = 3, /* initial thread's VSpace cap */
// seL4_CapIRQControl = 4, /* global IRQ controller cap */
// seL4_CapASIDControl = 5, /* global ASID controller cap */
// seL4_CapInitThreadASIDPool = 6, /* initial thread's ASID pool cap */
// seL4_CapIOPort = 7, /* global IO port cap (null cap if not supported) */
// seL4_CapIOSpace = 8, /* global IO space cap (null cap if no IOMMU support) */
// seL4_CapBootInfoFrame = 9, /* bootinfo frame cap */
// seL4_CapInitThreadIPCBuffer = 10, /* initial thread's IPC buffer frame cap */
// seL4_CapDomain = 11, /* global domain controller cap */
// seL4_NumInitialCaps = 12
// };

View File

@ -0,0 +1,161 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
//! DerivationTree nodes record the tree of inheritance for caps:
//! See the picture on derivation from seL4 manual for how this works: each cap contains a ref to
//! DerivationTree node, which records the previous cap and the following cap(s).
use {
super::captable::CapTableEntry,
crate::memory::PhysAddr,
register::{register_bitfields, LocalRegisterCopy},
snafu::Snafu,
};
//-- Mapping database (MDB) node: size = 16 bytes
//block mdb_node {
//padding 16 -- highest in word[1]
//field_high mdbNext 46 <-- field_high means "will need sign-extension", also value has 2 lower bits just dropped when setting
//field mdbRevocable 1 -- second bit in word[1]
//field mdbFirstBadged 1 -- lowest in word[1]
//field mdbPrev 64 -- enter lowest word (word[0]) in sel4
//}
register_bitfields! {
u128,
CapDerivationNode [
FirstBadged OFFSET(0) NUMBITS(1) [
Disable = 0,
Enable = 1
],
Revocable OFFSET(1) NUMBITS(1) [
Disable = 0,
Enable = 1
],
// -- 2 bits still free here --
// Next CTE node address -- per cteInsert this is address of the entire CTE slot
// cap derivation slots are supposedly aligned in u128 boundary (16 bytes) this means we can
// drop bottom 4 bits from it in these fields.
Next OFFSET(4) NUMBITS(44) [], // 16-bytes-aligned, size of canonical phys address is 48 bits
// -- 16 bits still free here --
// -- New word boundary --
// -- 4 bits still free here --
// Prev CTE node address -- per cteInsert this is address of the entire CTE slot
Prev OFFSET(68) NUMBITS(44) []
// -- 16 bits still free here --
]
}
/// Wrapper for CapDerivationNode
#[derive(Clone, Debug, Copy)]
pub struct DerivationTreeNode(LocalRegisterCopy<u128, CapDerivationNode::Register>);
/// Errors that may happen in capability derivation tree operations.
#[derive(Debug, PartialEq, Snafu)]
pub enum DerivationTreeError {
/// Previous link is invalid.
InvalidPrev,
/// Next link is invalid.
InvalidNext,
}
// In seL4, the MDB is stored as a doubly-linked list, representing the **preorder-DFS** through
// the hierarchy of capabilities. This data structure allows easy insertion of a capability
// given its immediate ancestor or a copy, and easy checking for existence of copies and descendants.
// But when no relations are known beforehand, finding the position to place a new capability
// requires a O(n) linear scan through the list, as does finding ancestors and descendants
// of a capability given just the capabilitys value. This operation is performed in
// the non-preemptable kernel, creating a scheduling hole that is problematic for real-time applications.
// To reduce the complexity of operations described above, we replace the MDBs linked list with
// a more suitable search data structure.
// -- nevill-master-thesis Using Capabilities for OS Resource Management
// sel4: mdb_node_t
impl DerivationTreeNode {
const ADDR_BIT_SHIFT: usize = 4;
pub(crate) fn empty() -> Self {
Self(LocalRegisterCopy::new(0))
}
// Unlike mdb_node_new we do not pass revocable and firstBadged flags here, they are enabled
// using builder interface set_first_badged() and set_revocable().
pub(crate) fn new(prev_ptr: PhysAddr, next_ptr: PhysAddr) -> Self {
Self::empty().set_prev(prev_ptr).set_next(next_ptr)
}
/// Get previous link in derivation tree.
/// Previous link exists if this is a derived capability.
///
/// SAFETY: it is UB to get prev reference from a null Prev pointer.
pub(crate) unsafe fn get_prev(&self) -> CapTableEntry {
let ptr =
(self.0.read(CapDerivationNode::Prev) << Self::ADDR_BIT_SHIFT) as *const CapTableEntry;
(*ptr).clone()
}
/// Try to get previous link in derivation tree.
/// Previous link exists if this is a derived capability.
pub(crate) fn try_get_prev(&self) -> Result<CapTableEntry, DerivationTreeError> {
if self.0.read(CapDerivationNode::Prev) == 0 {
Err(DerivationTreeError::InvalidPrev)
} else {
Ok(unsafe { self.get_prev() })
}
}
pub(crate) fn set_prev(&mut self, prev_ptr: PhysAddr) -> Self {
self.0
.write(CapDerivationNode::Prev.val((prev_ptr >> Self::ADDR_BIT_SHIFT).into()));
*self
}
/// Get next link in derivation tree.
/// Next link exists if this capability has derived capabilities or siblings.
///
/// SAFETY: it is UB to get next reference from a null Next pointer.
pub(crate) unsafe fn get_next(&self) -> CapTableEntry {
let ptr =
(self.0.read(CapDerivationNode::Next) << Self::ADDR_BIT_SHIFT) as *const CapTableEntry;
(*ptr).clone()
}
/// Try to get next link in derivation tree.
/// Next link exists if this capability has derived capabilities or siblings.
pub(crate) fn try_get_next(&self) -> Result<CapTableEntry, DerivationTreeError> {
if self.0.read(CapDerivationNode::Next) == 0 {
Err(DerivationTreeError::InvalidNext)
} else {
Ok(unsafe { self.get_next() })
}
}
pub(crate) fn set_next(&mut self, next_ptr: PhysAddr) -> Self {
self.0
.write(CapDerivationNode::Next.val((next_ptr >> Self::ADDR_BIT_SHIFT).into()));
*self
}
/// Builder interface to modify firstBadged flag
/// @todo Describe the firstBadged flag and what it does.
pub(crate) fn set_first_badged(mut self, enable: bool) -> Self {
self.0.modify(if enable {
CapDerivationNode::FirstBadged::Enable
} else {
CapDerivationNode::FirstBadged::Disable
});
self
}
/// Builder interface to modify revocable flag
/// @todo Describe the revocable flag and what it does.
pub(crate) fn set_revocable(mut self, enable: bool) -> Self {
self.0.modify(if enable {
CapDerivationNode::Revocable::Enable
} else {
CapDerivationNode::Revocable::Disable
});
self
}
}

View File

@ -0,0 +1,30 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use {
super::{CapError, Capability, TryFrom},
crate::capdef,
paste::paste,
register::{register_bitfields, LocalRegisterCopy},
};
//=====================
// Cap definition
//=====================
register_bitfields! {
u128,
DomainCap [
Type OFFSET(0) NUMBITS(6) [
value = 20
]
],
}
capdef! { Domain }
//=====================
// Cap implementation
//=====================

View File

@ -0,0 +1,40 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use {
super::{CapError, Capability, TryFrom},
crate::capdef,
paste::paste,
register::{register_bitfields, LocalRegisterCopy},
};
//=====================
// Cap definition
//=====================
register_bitfields! {
u128,
EndpointCap [
Type OFFSET(0) NUMBITS(6) [
value = 4
],
// @todo Badge has 4 lower bits all-zero - why?
Badge OFFSET(0) NUMBITS(64) [],
CanGrantReply OFFSET(69) NUMBITS(1) [],
CanGrant OFFSET(70) NUMBITS(1) [],
CanReceive OFFSET(71) NUMBITS(1) [],
CanSend OFFSET(72) NUMBITS(1) [],
Ptr OFFSET(80) NUMBITS(48) [],
]
}
capdef! { Endpoint }
//=====================
// Cap implementation
//=====================
// Endpoints support all 10 IPC variants (see COMP9242 slides by Gernot)
impl EndpointCapability {}

View File

@ -0,0 +1,32 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use {
super::{CapError, Capability, TryFrom},
crate::capdef,
paste::paste,
register::{register_bitfields, LocalRegisterCopy},
};
//=====================
// Cap definition
//=====================
register_bitfields! {
u128,
IrqControlCap [
Type OFFSET(0) NUMBITS(6) [
value = 14
]
]
}
capdef! { IrqControl }
//=====================
// Cap implementation
//=====================
impl IrqControlCapability {}

View File

@ -0,0 +1,33 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use {
super::{CapError, Capability, TryFrom},
crate::capdef,
paste::paste,
register::{register_bitfields, LocalRegisterCopy},
};
//=====================
// Cap definition
//=====================
register_bitfields! {
u128,
IrqHandlerCap [
Type OFFSET(0) NUMBITS(6) [
value = 16
],
Irq OFFSET(52) NUMBITS(12) [],
]
}
capdef! { IrqHandler }
//=====================
// Cap implementation
//=====================
impl IrqHandlerCapability {}

109
nucleus/src/caps/mod.rs Normal file
View File

@ -0,0 +1,109 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
//! Implementation of system capabilities.
// ☐ Rust implementation of capabilities - ?
// ☐ Need to implement in kernel entries storage and lookup
// ☐ cte = cap table entry (a cap_t plus mdb_node_t)
// ☐ mdb = ? (mdb_node_new)
// ☐ sameObjectAs()
// cap_get_capType();//generated
// lookupCapAndSlot();
// cap_domain_cap_new() etc //generated
// create_mapped_it_frame_cap(); //vspace.c
// pptr_of_cap(); -- extracts cap.pptr from cnode_cap
// deriveCap();
// @todo Use bitmatch over cap Type field?
// Could be interesting if usable. See https://github.com/porglezomp/bitmatch
// Maybe look at https://lib.rs/crates/enumflags2 too
use {crate::memory::PhysAddr, core::convert::TryFrom, snafu::Snafu};
mod capnode_cap;
pub(crate) mod captable;
mod derivation_tree;
mod domain_cap;
mod endpoint_cap;
mod irq_control_cap;
mod irq_handler_cap;
mod notification_cap;
pub(crate) mod null_cap;
pub(crate) mod reply_cap;
mod resume_cap;
mod thread_cap;
mod untyped_cap;
mod zombie_cap;
pub use null_cap::NullCapability;
pub use reply_cap::ReplyCapability;
/// Opaque capability object, manipulated by the kernel.
pub trait Capability {
///
/// Is this capability arch-specific?
///
fn is_arch(&self) -> bool;
///
/// Retrieve this capability as scalar value.
///
fn as_u128(&self) -> u128;
}
/// Errors in capability operations.
#[derive(Debug, Snafu)]
pub enum CapError {
/// Unable to create capability, exact reason TBD.
CannotCreate,
/// Capability has a type incompatible with the requested operation.
InvalidCapabilityType,
}
/// Implement default fns and traits for the capability.
#[macro_export]
macro_rules! capdef {
($name:ident) => {
paste! {
#[doc = "Wrapper representing `" $name "Capability`."]
pub struct [<$name Capability>](LocalRegisterCopy<u128, [<$name Cap>]::Register>);
impl [<$name Capability>] {
//@todo must be part of trait impl then? See rust-lang/rust#8995
// type Type = [<$name Cap>]::Register;
}
impl Capability for [<$name Capability>] {
#[inline]
fn as_u128(&self) -> u128 {
self.0.into()
}
#[inline]
fn is_arch(&self) -> bool {
([<$name Cap>]::Type::Value::value as u8) % 2 != 0
}
}
impl TryFrom<u128> for [<$name Capability>] {
type Error = CapError;
fn try_from(v: u128) -> Result<[<$name Capability>], Self::Error> {
let reg = LocalRegisterCopy::<_, [<$name Cap>]::Register>::new(v);
if reg.read([<$name Cap>]::Type) == u128::from([<$name Cap>]::Type::value) {
Ok([<$name Capability>](LocalRegisterCopy::new(v)))
} else {
Err(Self::Error::InvalidCapabilityType)
}
}
}
impl From<[<$name Capability>]> for u128 {
#[inline]
fn from(v: [<$name Capability>]) -> u128 {
v.as_u128()
}
}
}
};
}

View File

@ -0,0 +1,44 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
//! @todo replace with Event
use {
super::{CapError, Capability, TryFrom},
crate::capdef,
paste::paste,
register::{register_bitfields, LocalRegisterCopy},
};
//=====================
// Cap definition
//=====================
register_bitfields! {
u128,
NotificationCap [
Type OFFSET(0) NUMBITS(6) [
value = 6
],
Badge OFFSET(0) NUMBITS(64) [],
CanReceive OFFSET(69) NUMBITS(1) [],
CanSend OFFSET(70) NUMBITS(1) [],
Ptr OFFSET(80) NUMBITS(48) [],
]
}
capdef! { Notification }
//=====================
// Cap implementation
//=====================
// Notifications support NBSend (Signal), Wait and NBWait (Poll) (see COMP9242 slides by Gernot)
// Other objects support only Call() (see COMP9242 slides by Gernot)
// Appear as (kernel-implemented) servers
// • Each has a kernel-defined protocol
// • operations encoded in message tag
// • parameters passed in message words
// • Mostly hidden behind “syscall” wrappers

View File

@ -0,0 +1,39 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use {
super::{CapError, Capability, TryFrom},
crate::capdef,
paste::paste,
register::{register_bitfields, LocalRegisterCopy},
};
//=====================
// Cap definition
//=====================
register_bitfields! {
u128,
NullCap [
Type OFFSET(0) NUMBITS(6) [
value = 0
]
]
}
capdef! { Null }
//=====================
// Cap implementation
//=====================
impl NullCapability {
/// Create a Null capability.
///
/// Such capabilities are invalid and can not be used for anything.
pub fn new() -> NullCapability {
NullCapability(LocalRegisterCopy::new(u128::from(NullCap::Type::value)))
}
}

View File

@ -0,0 +1,33 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use {
super::{CapError, Capability, TryFrom},
crate::capdef,
paste::paste,
register::{register_bitfields, LocalRegisterCopy},
};
//=====================
// Cap definition
//=====================
register_bitfields! {
u128,
ReplyCap [
Type OFFSET(0) NUMBITS(6) [
value = 8
],
ReplyCanGrant OFFSET(62) NUMBITS(1) [],
ReplyMaster OFFSET(63) NUMBITS(1) [],
TCBPtr OFFSET(64) NUMBITS(64) [],
]
}
capdef! { Reply }
//=====================
// Cap implementation
//=====================

View File

@ -0,0 +1,50 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use {
super::{CapError, Capability, TryFrom},
crate::capdef,
paste::paste,
register::{register_bitfields, LocalRegisterCopy},
};
//=====================
// Cap definition
//=====================
register_bitfields! {
u128,
// https://ts.data61.csiro.au/publications/csiro_full_text/Lyons_MAH_18.pdf
// Resume objects, modelled after KeyKOS [Bomberger et al.1992], are a new object type
// that generalise the “reply capabilities” of baseline seL4. These were capabilities
// to virtual objects created by the kernel on-the-fly in seL4s RPC-style call() operation,
// which sends a message to an endpoint and blocks on a reply. The receiver of the message
// (i.e. the server) receives the reply capability in a magic “reply slot” in its
// capability space. The server replies by invoking that capability. Resume objects
// remove the magic by explicitly representing the reply channel (and the SC-donation chain).
// They also provide more efficient support for stateful servers that handle concurrent client
// sessions.
// The introduction of Resume objects requires some changes to the IPC system-call API.
// The client-style call() operation is unchanged, but server-side equivalent, ReplyRecv
// (previously ReplyWait) replies to a previous request and then blocks on the next one.
// It now must provide an explicit Resume capability; on the send phase, that capability
// identifies the client and returns the SC if appropriate, on the receive phase it is
// populated with new values. The new API makes stateful server implementation more efficient.
// In baseline seL4, the server would have to use at least two extra system calls to save the
// reply cap and later move it back into its magic slot, removing the magic also removes
// the need for the extra system calls.
ResumeCap [
Type OFFSET(0) NUMBITS(6) [
value = 22
]
]
}
capdef! { Resume }
//=====================
// Cap implementation
//=====================

View File

@ -0,0 +1,37 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use {
super::{CapError, Capability, TryFrom},
crate::{arch::memory::PhysAddr, capdef},
paste::paste,
register::{register_bitfields, LocalRegisterCopy},
};
//=====================
// Cap definition
//=====================
register_bitfields! {
u128,
ThreadCap [
Type OFFSET(0) NUMBITS(6) [
value = 12
],
TCBPtr OFFSET(64) NUMBITS(48) [],
]
}
capdef! { Thread }
//=====================
// Cap implementation
//=====================
impl ThreadCapability {
pub(crate) fn ptr(&self) -> PhysAddr {
0.into() // @todo
}
}

View File

@ -0,0 +1,157 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use {
super::{CapError, Capability, PhysAddr, TryFrom},
crate::capdef,
paste::paste,
register::{register_bitfields, LocalRegisterCopy},
};
//=====================
// Cap definition
//=====================
register_bitfields! {
u128,
// The combination of freeIndex and blockSize must match up with the
// definitions of MIN_SIZE_BITS and MAX_SIZE_BITS
// -- https://github.com/seL4/seL4/blob/master/include/object/structures_32.bf#L18
//
// /* It is assumed that every untyped is within seL4_MinUntypedBits and seL4_MaxUntypedBits
// * (inclusive). This means that every untyped stored as seL4_MinUntypedBits
// * subtracted from its size before it is stored in capBlockSize, and
// * capFreeIndex counts in chunks of size 2^seL4_MinUntypedBits. The seL4_MaxUntypedBits
// * is the minimal untyped that can be stored when considering both how
// * many bits of capBlockSize there are, and the largest offset that can
// * be stored in capFreeIndex */
// +#define MAX_FREE_INDEX(sizeBits) (BIT( (sizeBits) - seL4_MinUntypedBits ))
// +#define FREE_INDEX_TO_OFFSET(freeIndex) ((freeIndex)<<seL4_MinUntypedBits)
// #define GET_FREE_REF(base,freeIndex) ((word_t)(((word_t)(base)) + FREE_INDEX_TO_OFFSET(freeIndex)))
// #define GET_FREE_INDEX(base,free) (((word_t)(free) - (word_t)(base))>>seL4_MinUntypedBits)
// #define GET_OFFSET_FREE_PTR(base, offset) ((void *)(((word_t)(base)) + (offset)))
// +#define OFFSET_TO_FREE_INDEX(offset) ((offset)>>seL4_MinUntypedBits)
//
// exception_t decodeUntypedInvocation(word_t invLabel, word_t length,
// cte_t *slot, cap_t cap,
// extra_caps_t excaps, bool_t call,
// word_t *buffer);
// exception_t invokeUntyped_Retype(cte_t *srcSlot, bool_t reset,
// void *retypeBase, object_t newType,
// word_t userSize, slot_range_t destSlots,
// bool_t deviceMemory);
// // -- https://github.com/seL4/seL4/blob/master/src/object/untyped.c#L276
// -- https://github.com/seL4/seL4/blob/master/include/object/untyped.h
//
// /* Untyped size limits */
// #define seL4_MinUntypedBits 4
// #define seL4_MaxUntypedBits 47
// -- https://github.com/seL4/seL4/blob/master/libsel4/sel4_arch_include/aarch64/sel4/sel4_arch/constants.h#L234
//
// /*
// * Determine where in the Untyped region we should start allocating new
// * objects.
// *
// * If we have no children, we can start allocating from the beginning of
// * our untyped, regardless of what the "free" value in the cap states.
// * (This may happen if all of the objects beneath us got deleted).
// *
// * If we have children, we just keep allocating from the "free" value
// * recorded in the cap.
// */
// -- https://github.com/seL4/seL4/blob/master/src/object/untyped.c#L175
// /*
// * Determine the maximum number of objects we can create, and return an
// * error if we don't have enough space.
// *
// * We don't need to worry about alignment in this case, because if anything
// * fits, it will also fit aligned up (by packing it on the right hand side
// * of the untyped).
// */
// -- https://github.com/seL4/seL4/blob/master/src/object/untyped.c#L196
UntypedCap [
Type OFFSET(0) NUMBITS(6) [
value = 2
],
/// Index of the first unoccupied byte within this Untyped.
/// This index is limited between MIN_UNTYPED_BITS and max bits number in BlockSizePower.
/// To occupy less bits, the free index is shifted right by MIN_UNTYPED_BITS.
///
/// Free index is used only if this untyped has children, which may be occupying only
/// part of its space.
/// This means an Untyped can be retyped multiple times as long as there is
/// free space left in it.
FreeIndexShifted OFFSET(0) NUMBITS(48) [],
/// Device mapped untypeds cannot be touched by the kernel.
IsDevice OFFSET(57) NUMBITS(1) [],
/// Untyped is 2**BlockSizePower bytes in size
BlockSizePower OFFSET(58) NUMBITS(6) [],
/// Physical address of untyped.
Ptr OFFSET(80) NUMBITS(48) [],
]
}
capdef! { Untyped }
//=====================
// Cap implementation
//=====================
// @todo retyping a device capability requires specifying memory base exactly, can't just pick next frame?
/// Capability to a block of untyped memory.
/// Can be retyped into more usable types.
impl UntypedCapability {
const MIN_BITS: usize = 4;
const MAX_BITS: usize = 47;
/// This untyped belongs to device memory (will not be zeroed on allocation).
pub fn is_device(&self) -> bool {
self.0.read(UntypedCap::IsDevice) == 1
}
/// Return untyped block size in bytes.
pub fn block_size(&self) -> usize {
1 << self.0.read(UntypedCap::BlockSizePower)
}
// FreeIndex OFFSET(0) NUMBITS(48) [],
/// Return free area offset in this block in bytes.
pub fn free_area_offset(&self) -> usize {
use core::convert::TryInto;
Self::free_index_to_offset(
self.0
.read(UntypedCap::FreeIndexShifted)
.try_into()
.unwrap(),
)
}
/// Return start address of this untyped block.
pub fn base(&self) -> PhysAddr {
(self.0.read(UntypedCap::Ptr) as u64).into() // @todo implement TryFrom<u128> for PhysAddr
}
// #define MAX_FREE_INDEX(sizeBits) (BIT( (sizeBits) - seL4_MinUntypedBits ))
/// Calculate maximum free index value based on allowed size bits.
pub fn max_free_index_from_bits(size_bits: usize) -> usize {
assert!(size_bits >= Self::MIN_BITS);
assert!(size_bits <= Self::MAX_BITS);
1 << (size_bits - Self::MIN_BITS)
}
// #define FREE_INDEX_TO_OFFSET(freeIndex) ((freeIndex)<<seL4_MinUntypedBits)
/// Convert free index to byte offset.
fn free_index_to_offset(index: usize) -> usize {
index << Self::MIN_BITS
}
// #define OFFSET_TO_FREE_INDEX(offset) ((offset)>>seL4_MinUntypedBits)
/// Convert byte offset to free index.
/// @todo Check proper offset alignment!
fn offset_to_free_index(offset: usize) -> usize {
offset >> Self::MIN_BITS
}
}

View File

@ -0,0 +1,32 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use {
super::{CapError, Capability, TryFrom},
crate::capdef,
paste::paste,
register::{register_bitfields, LocalRegisterCopy},
};
//=====================
// Cap definition
//=====================
register_bitfields! {
u128,
ZombieCap [
Type OFFSET(0) NUMBITS(6) [
value = 18
],
ZombieType OFFSET(58) NUMBITS(6) [],
ZombieID OFFSET(64) NUMBITS(64) [],
]
}
capdef! { Zombie }
//=====================
// Cap implementation
//=====================

View File

@ -1,5 +1,6 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
#![allow(dead_code)]

View File

@ -1,6 +1,8 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
pub mod console;
pub use console::{Console, ConsoleOps};

View File

@ -15,12 +15,19 @@
#![feature(allocator_api)]
#![feature(ptr_internals)]
#![feature(format_args_nl)]
#![feature(result_contains_err)]
#![feature(nonnull_slice_from_raw_parts)]
#![feature(custom_test_frameworks)]
#![test_runner(crate::tests::test_runner)]
#![reexport_test_harness_main = "test_main"]
#![deny(missing_docs)]
#![deny(warnings)]
#![allow(dead_code)] // While working on features, some code may remain unused
// repr128 is not yet stable
#![feature(repr128)]
#![feature(const_generics)]
#![feature(const_evaluatable_checked)] // see https://github.com/rust-lang/rust/issues/68436
#![allow(incomplete_features)]
#[cfg(not(target_arch = "aarch64"))]
use architecture_not_supported_sorry;
@ -29,9 +36,12 @@ use architecture_not_supported_sorry;
#[macro_use]
pub mod arch;
pub use arch::*;
mod api;
mod caps;
mod devices;
mod macros;
mod mm;
mod objects;
mod panic;
mod platform;
#[cfg(feature = "qemu")]
@ -161,6 +171,11 @@ pub fn kmain() -> ! {
#[cfg(test)]
test_main();
/*
try_init_kernel().expect("Failed to init kernel");*/
// schedule();
// activate_thread();
command_prompt();
reboot()
@ -231,6 +246,70 @@ fn reboot() -> ! {
}
}
#[derive(Debug)]
enum KernelInitError {
CapabilityCreationFailed,
}
// #[link_section = ".text.boot"]
// fn try_init_kernel() -> Result<(), KernelInitError> {
// let root_capnode_cap = create_root_capnode();
// if root_capnode_cap.is_err() {
// return Err(KernelInitError::CapabilityCreationFailed);
// }
// Ok(())
// }
const CONFIG_ROOT_CAPNODE_SIZE_BITS: usize = 12;
const WORD_BITS: usize = 64;
enum BootInfoCaps {
InitThreadCapNode = 1,
}
impl From<BootInfoCaps> for usize {
fn from(val: BootInfoCaps) -> usize {
val as usize
}
}
/*
#[link_section = ".text.boot"]
fn create_root_capnode() -> Result<CapNodeCapability, caps::CapError> {
// write the number of root CNode slots to global state
BOOT_INFO.lock(|bi| bi.max_slot_pos = 1 << CONFIG_ROOT_CAPNODE_SIZE_BITS); // 12 bits => 4096 slots
// seL4_SlotBits = 32 bytes per entry, 4096 entries =>
// create an empty root CapNode
// this goes into the kernel startup/heap memory (one of the few items that kernel DOES allocate).
let region_size = core::mem::size_of::<CapTableEntry>() * BOOT_INFO.lock(|bi| bi.max_slot_pos); // 12 + 5 => 131072 (128Kb)
let pptr = BOOT_INFO.lock(|bi| bi.alloc_region(region_size)); // GlobalAllocator::alloc_zeroed instead?
let pptr = match pptr {
Ok(pptr) => pptr,
Err(_) => {
println!("Kernel init failed: could not create root capnode");
return Err(caps::CapError::CannotCreate);
}
};
// @todo lifetime of slice -- slice here only to zero out memory
let slice =
unsafe { core::slice::from_raw_parts_mut(pptr.as_u64() as *mut u8, region_size as usize) };
for byte in slice.iter_mut() {
*byte = 0;
} // @todo wtf
// transmute into a type? (you can use ptr.write() to just write a type into memory location)
let cap = CapNodeCapability::new_root(pptr.as_u64());
use core::convert::TryFrom;
let mut cap_node = CapNodeCapability::try_from(cap.as_u128()).unwrap();
// this cnode contains a cap to itself...
/* write the root CNode cap into the root CNode */
cap_node.write_slot(usize::from(BootInfoCaps::InitThreadCapNode), &cap);
Ok(cap_node) // reference to pptr is here
}*/
fn check_display_init() {
display_graphics()
.map_err(|e| {
@ -285,4 +364,9 @@ mod main_tests {
fn test_data_abort_trap() {
check_data_abort_trap()
}
#[test_case]
fn test_user_thread_syscall() {
// To test syscall from user-space we need to construct a user-space thread and switch to it
}
}

View File

@ -45,15 +45,15 @@ mod tests {
// align 1
assert_eq!(align_up(0, 1), 0);
assert_eq!(align_up(1234, 1), 1234);
assert_eq!(align_up(0xffffffffffffffff, 1), 0xffffffffffffffff);
assert_eq!(align_up(0xffff_ffff_ffff_ffff, 1), 0xffff_ffff_ffff_ffff);
// align 2
assert_eq!(align_up(0, 2), 0);
assert_eq!(align_up(1233, 2), 1234);
assert_eq!(align_up(0xfffffffffffffffe, 2), 0xfffffffffffffffe);
assert_eq!(align_up(0xffff_ffff_ffff_fffe, 2), 0xffff_ffff_ffff_fffe);
// address 0
assert_eq!(align_up(0, 128), 0);
assert_eq!(align_up(0, 1), 0);
assert_eq!(align_up(0, 2), 0);
assert_eq!(align_up(0, 0x8000000000000000), 0);
assert_eq!(align_up(0, 0x8000_0000_0000_0000), 0);
}
}

View File

@ -0,0 +1,16 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
struct CapNode {}
impl super::KernelObject for CapNode {
fn size_bits() -> usize {
unimplemented!()
}
fn invoke() {
unimplemented!()
}
}

View File

@ -0,0 +1,17 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
register_bitfields! {
u128,
Endpoint [
QueueHead OFFSET(0) NUMBITS(64) [],
QueueTail OFFSET(80) NUMBITS(46) [],
State OFFSET(126) NUMBITS(2) [
Idle = 00b,
Send = 01b,
Recv = 10b,
],
],
}

View File

@ -0,0 +1,11 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
trait IRQControl {
fn get(irq: u32, dest: CapNodeRootedPath) -> Result<()>;
// ARM specific?
fn get_trigger();
fn get_trigger_core();
}

View File

@ -0,0 +1,10 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
trait IRQHandler {
fn set_notification(notification: CapNode) -> Result<()>;
fn ack() -> Result<()>;
fn clear() -> Result<()>;
}

View File

@ -0,0 +1,23 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
// The basic services Vesper provides are as follows:
//
// * _Threads_ are an abstraction of CPU execution that supports running software;
// * _Address spaces_ are virtual memory spaces that each contain an application.
// Applications are limited to accessing memory in their address space;
// * _Inter-process communication (IPC)_ via endpoints allows threads to communicate using
// message passing;
// * _Events_ provide a non-blocking signalling mechanism similar to counting semaphores;
// * _Device primitives_ allow device drivers to be implemented as unprivileged applications.
// The kernel exports hardware device interrupts via IPC messages; and
// * _Capability spaces_ store capabilities (i.e., access rights) to kernel services along with
// their book-keeping information.
pub mod nucleus_object;
pub mod thread;
pub mod untyped;
pub(crate) use nucleus_object::NucleusObject;

View File

@ -0,0 +1,28 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
// @todo replace with Event
register_bitfields! {
u128,
Notification [
BoundTCB OFFSET(16) NUMBITS(48) [],
MsgId OFFSET(64) NUMBITS(64) [],
],
NotificationQueue [
QueueHead OFFSET(16) NUMBITS(48) [],
QueueTail OFFSET(64) NUMBITS(48) [],
State OFFSET(126) NUMBITS(2) [
Idle = 00b,
Waiting = 01b,
Active = 10b,
],
]
}
trait Notification {
fn signal(dest: Cap);
fn wait(src: Cap) -> Result<Option<&Badge>>;
fn poll(cap: Cap) -> Result<(MessageInfo, Option<&Badge>)>;
}

View File

@ -0,0 +1,9 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
pub(crate) trait NucleusObject {
fn size_bits() -> usize;
fn invoke();
}

View File

@ -0,0 +1,137 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use crate::{api::Fault, arch, arch::memory::VirtAddr, caps::captable::LookupFault};
// trait Thread {
// // Configuration
// // Effectively, SetSpace followed by SetIPCBuffer.
// fn configure(fault_endpoint: Cap, cap_space_root: Cap, cap_space_root_data: CapNodeConfig, virt_space_root: Cap, virt_space_root_data: (), ipc_buffer_frame: Cap, ipc_buffer_offset: usize) -> Result<()>;
// fn set_space(fault_endpoint: Cap, cap_space_root: Cap, cap_space_root_data: CapNodeConfig, virt_space_root: Cap, virt_space_root_data: ()) -> Result<()>;
// fn set_ipc_buffer(ipc_buffer_frame: CapNode, ipc_buffer_offset: usize) -> Result<()>;
// // Debugging tools
// fn configure_single_stepping(bp_num: u16, num_insns): Result<SingleStepping>;
// fn get_breakpoint(bp_num: u16) -> Result<BreakpointInfo>;
// fn set_breakpoint(bp_num: u16, bp: BreakpointInfo) -> Result<()>;
// fn unset_breakpoint(bp_num: u16) -> Result<()>;
// // Scheduling
// fn suspend() -> Result<()>;
// fn resume() -> Result<()>;
// fn set_priority(authority: TCB/*Cap*/, priority: u32) -> Result<()>;
// fn set_mc_priority(authority: TCB/*Cap*/, mcp: u32) -> Result<()>;
// fn set_sched_params(authority: TCB/*Cap*/, mcp: u32, priority: u32) -> Result<()>;
// fn set_affinity(affinity: u64) -> Result<()>;
// // TCB configuration
// fn copy_registers(source: TCB/*Cap*/, suspend_source: bool, resume_target: bool, transfer_frame_regs: bool, transfer_integer_regs: bool, arch_flags: u8) -> Result<()>;
// fn read_registers(suspend_source: bool, arch_flags: u8, num_regs: u16, register_context: &mut ArchRegisterContext) -> Result<()>;
// fn write_registers(resume_target: bool, arch_flags: u8, num_regs: u16, register_context: &ArchRegisterContext) -> Result<()>;
// // Notifications
// fn bind_notification(notification: CapNode) -> Result<()>;
// fn unbind_notification() -> Result<()>;
//
// // Arch-specific
// // fn set_tls_base(tls_base: usize) -> Result<()>;
// // virtualized - x86-specific
// // fn set_ept_root(eptpml: X86::EPTPML4) -> Result<()>;
// }
//
// // @todo <<SchedContext>>
//
// // struct Thread {}
// struct TCB {
// capability: u128, // should actually be a CapPath here - this is the argument to
// // Thread.read_registers(cap, ... call for example.
// }
//
// impl Thread for TCB {
// // ...
// }
// impl super::KernelObject for Thread {}
impl super::NucleusObject for TCB {
const SIZE_BITS: usize = 12;
}
// -- from actual code parts in api.rs
/* TCB: size 64 bytes + sizeof(arch_tcb_t) (aligned to nearest power of 2) */
pub(crate) struct TCB<'a> {
arch_specific: arch::objects::thread::TCB,
state: ThreadState, // 12 bytes?
/* Notification that this TCB is bound to. If this is set, when this TCB waits on
* any sync endpoint, it may receive a signal from a Notification object.
* 4 bytes*/
// notification_t *tcbBoundNotification;
fault: Fault, // 8 bytes?
lookup_failure: LookupFault, // 8 bytes
/* Domain, 1 byte (packed to 4) */
domain: Domain,
/* maximum controlled priorioty, 1 byte (packed to 4) */
mcp: Priority,
/* Priority, 1 byte (packed to 4) */
priority: Priority,
/* Timeslice remaining, 4 bytes */
time_slice: u32,
/* Capability pointer to thread fault handler, 8 bytes */
fault_handler: CapPath,
/* userland virtual address of thread IPC buffer, 8 bytes */
ipc_buffer: VirtAddr,
/* Previous and next pointers for scheduler queues , 8+8 bytes */
sched_next: *mut TCB,
sched_prev: *mut TCB,
/* Previous and next pointers for endpoint and notification queues, 8+8 bytes */
ep_next: *mut TCB,
ep_prev: *mut TCB,
/* Use any remaining space for a thread name */
name: &'a str,
// name_storage: [u8],// add SIZE_BITS calculations for length of storage in here somewhere
}
pub(crate) enum ThreadState {
Inactive,
Restart,
}
impl TCB {
fn get_register(&self, register_index: usize) {
self.arch_tcb.register_context.registers[register_index]
}
fn lookup_cap_and_slot() -> Result<()> {}
fn get_restart_pc() {}
fn lookup_ipc_buffer(some: bool) {}
fn lookup_extra_caps() -> Result<()> {}
fn get_state() -> ThreadState {}
fn set_state(state: ThreadState) {}
fn get_caller_slot() -> Slot {}
fn send_fault_ipc(&self) {}
fn replyFromKernel_success_empty() {}
fn replyFromKernel_error() {}
// // Configuration
// // Effectively, SetSpace followed by SetIPCBuffer.
// fn configure(fault_endpoint: Cap, cap_space_root: Cap, cap_space_root_data: CapNodeConfig, virt_space_root: Cap, virt_space_root_data: (), ipc_buffer_frame: Cap, ipc_buffer_offset: usize) -> Result<()>;
// fn set_space(fault_endpoint: Cap, cap_space_root: Cap, cap_space_root_data: CapNodeConfig, virt_space_root: Cap, virt_space_root_data: ()) -> Result<()>;
// fn set_ipc_buffer(ipc_buffer_frame: CapNode, ipc_buffer_offset: usize) -> Result<()>;
// // Debugging tools
// fn configure_single_stepping(bp_num: u16, num_insns): Result<SingleStepping>;
// fn get_breakpoint(bp_num: u16) -> Result<BreakpointInfo>;
// fn set_breakpoint(bp_num: u16, bp: BreakpointInfo) -> Result<()>;
// fn unset_breakpoint(bp_num: u16) -> Result<()>;
// // Scheduling
// fn suspend() -> Result<()>;
// fn resume() -> Result<()>;
// fn set_priority(authority: TCB/*Cap*/, priority: u32) -> Result<()>;
// fn set_mc_priority(authority: TCB/*Cap*/, mcp: u32) -> Result<()>;
// fn set_sched_params(authority: TCB/*Cap*/, mcp: u32, priority: u32) -> Result<()>;
// fn set_affinity(affinity: u64) -> Result<()>;
// // TCB configuration
// fn copy_registers(source: TCB/*Cap*/, suspend_source: bool, resume_target: bool, transfer_frame_regs: bool, transfer_integer_regs: bool, arch_flags: u8) -> Result<()>;
// fn read_registers(suspend_source: bool, arch_flags: u8, num_regs: u16, register_context: &mut ArchRegisterContext) -> Result<()>;
// fn write_registers(resume_target: bool, arch_flags: u8, num_regs: u16, register_context: &ArchRegisterContext) -> Result<()>;
// // Notifications
// fn bind_notification(notification: CapNode) -> Result<()>;
// fn unbind_notification() -> Result<()>;
}

View File

@ -0,0 +1,50 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use snafu::Snafu;
// The source of all available memory, device or general.
// Boot code reserves kernel memory and initial mapping allocations (4 pages probably - on rpi3? should be platform-dependent).
// The rest is converted to untypeds with appropriate kind and given away to start thread.
// Untyped.retype() derives cap to a typed cap (derivation tree must be maintained)
pub(crate) struct Untyped {}
impl super::NucleusObject for Untyped {
fn size_bits() -> usize {
unimplemented!()
}
fn invoke() {
unimplemented!()
}
}
#[derive(Debug, Snafu)]
enum RetypeError {
Whatever,
}
impl Untyped {
// Uses T::SIZE_BITS to properly size the resulting object
// in some cases size_bits must be passed as argument though...
// @todo return an array of caps?
fn retype<T: NucleusObject>(
target_cap: CapNodeRootedPath,
target_cap_offset: usize,
num_objects: usize,
) -> Result<CapSlice, RetypeError> {
Err(RetypeError::Whatever)
}
}
enum MemoryKind {
General,
Device,
}
// with GATs
// trait Retyped { type Result = CapTable::<T> .. }

View File

@ -1,3 +1,8 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
#[cfg(not(test))]
#[panic_handler]
fn panicked(info: &core::panic::PanicInfo) -> ! {

View File

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

View File

@ -2,6 +2,7 @@
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use snafu::Snafu;
/* Character cells are 8x8 */

View File

@ -1,3 +1,8 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use {
super::{
mailbox::{channel, read, write, MailboxOps, RegisterBlock, Result},
@ -89,7 +94,7 @@ impl MailboxOps for FrameBuffer {
self.base_addr as *const _
}
/// https://github.com/raspberrypi/firmware/wiki/Accessing-mailboxes says:
/// <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.**

View File

@ -0,0 +1,58 @@
/*
rpi-3-b-plus FDT:
interrupt-controller@7e00b200 {
compatible = "brcm,bcm2836-armctrl-ic";
reg = <0x7e00b200 0x00000200>;
interrupt-controller;
#interrupt-cells = <0x00000002>;
interrupt-parent = <0x00000018>;
interrupts = <0x00000008 0x00000004>;
phandle = <0x00000001>;
};
local_intc@40000000 {
compatible = "brcm,bcm2836-l1-intc";
reg = <0x40000000 0x00000100>;
interrupt-controller;
#interrupt-cells = <0x00000002>;
interrupt-parent = <0x00000018>;
phandle = <0x00000018>;
};
timer {
compatible = "arm,armv7-timer";
interrupt-parent = <0x00000018>;
interrupts = <0x00000000 0x00000004 0x00000001 0x00000004 0x00000003 0x00000004 0x00000002 0x00000004>;
always-on;
};
rpi-400 FDT:
interrupt-controller@40041000 {
interrupt-controller;
#interrupt-cells = <0x00000003>;
compatible = "arm,gic-400";
reg = <0x40041000 0x00001000 0x40042000 0x00002000 0x40044000 0x00002000 0x40046000 0x00002000>;
interrupts = <0x00000001 0x00000009 0x00000f04>;
phandle = <0x00000001>;
};
local_intc@40000000 {
compatible = "brcm,bcm2836-l1-intc";
reg = <0x40000000 0x00000100>;
phandle = <0x000000b2>;
};
timer@7e003000 {
compatible = "brcm,bcm2835-system-timer";
reg = <0x7e003000 0x00001000>;
interrupts = <0x00000000 0x00000040 0x00000004 0x00000000 0x00000041 0x00000004 0x00000000 0x00000042 0x00000004 0x00000000 0x00000043 0x00000004>;
clock-frequency = <0x000f4240>;
phandle = <0x00000044>;
};
*/
pub fn get_active_irq() -> core::Result<u16> {}

View File

@ -54,8 +54,8 @@ states! {
pub struct Reserved<T>(T);
/// The offsets for reach register.
/// From https://wiki.osdev.org/Raspberry_Pi_Bare_Bones and
/// https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf
/// 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 {

View File

@ -478,7 +478,7 @@ impl Mailbox {
}
/// NB: Do not intermix Get/Set and Test tags in one request!
/// See https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface
/// See <https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface>
/// * It is not valid to mix Test tags with Get/Set tags in the same operation
/// and no tags will be returned.
#[inline]
@ -492,7 +492,7 @@ impl Mailbox {
}
/// NB: Do not intermix Get/Set and Test tags in one request!
/// See https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface
/// See <https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface>
/// * It is not valid to mix Test tags with Get/Set tags in the same operation
/// and no tags will be returned.
#[inline]

View File

@ -7,6 +7,7 @@
pub mod display;
pub mod fb;
pub mod gic;
pub mod gpio;
pub mod mailbox;
pub mod mini_uart;
@ -15,14 +16,14 @@ pub mod power;
pub mod vc;
/// See BCM2835-ARM-Peripherals.pdf
/// See https://www.raspberrypi.org/forums/viewtopic.php?t=186090 for more details.
/// 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
/// 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
@ -38,7 +39,7 @@ impl BcmHost {
0xc000_0000 // uncached
}
/// As per https://www.raspberrypi.org/forums/viewtopic.php?p=1170522#p1170522
/// As per <https://www.raspberrypi.org/forums/viewtopic.php?p=1170522#p1170522>
///
pub fn bus2phys(bus: usize) -> usize {
bus & !0xc000_0000

View File

@ -2,6 +2,7 @@
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use {
super::{
display::{Display, PixelOrder, CHARSIZE_X, CHARSIZE_Y},

View File

@ -2,6 +2,7 @@
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
pub mod semihosting {
pub fn exit_success() -> ! {
use qemu_exit::QEMUExit;

View File

@ -1,6 +1,7 @@
/*
* SPDX-License-Identifier: MIT OR BlueOak-1.0.0
* Copyright (c) 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
*/
@ -19,8 +20,8 @@ pub struct NullLock<T> {
/// threads don't exist yet in our code.
///
/// Literature:
/// https://doc.rust-lang.org/beta/nomicon/send-and-sync.html
/// https://doc.rust-lang.org/book/ch16-04-extensible-concurrency-sync-and-send.html
/// * <https://doc.rust-lang.org/beta/nomicon/send-and-sync.html>
/// * <https://doc.rust-lang.org/book/ch16-04-extensible-concurrency-sync-and-send.html>
unsafe impl<T> Sync for NullLock<T> {}
impl<T> NullLock<T> {

View File

@ -2,17 +2,33 @@
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
//============================================================================
// Testing environment
//============================================================================
use crate::{println, qemu};
use crate::{print, println, qemu};
pub trait TestFn {
fn run(&self) -> ();
}
impl<T> TestFn for T
where
T: Fn(),
{
fn run(&self) {
print!("*TEST* {}...\t", core::any::type_name::<T>());
self();
println!("[ok]\n");
}
}
#[cfg(test)]
pub fn test_runner(tests: &[&dyn Fn()]) {
pub fn test_runner(tests: &[&dyn TestFn]) {
println!("Running {} tests", tests.len());
for test in tests {
test();
println!("\n[ok]\n");
test.run();
}
println!("\n[success]\n");
qemu::semihosting::exit_success();

View File

@ -1 +1,4 @@
nightly
[toolchain]
channel = "nightly"
components = ["clippy", "llvm-tools-preview", "rust-src", "rustfmt"]
targets = ["aarch64-unknown-none-softfloat"]

18
vesper-user/Cargo.toml Normal file
View File

@ -0,0 +1,18 @@
[package]
name = "vesper-user"
version = "0.0.1"
authors = ["Berkus Decker <berkus+vesper@metta.systems>"]
description = "Vesper user-space interface"
documentation = "https://docs.metta.systems/vesper-user"
homepage = "https://metta.systems/products/vesper"
repository = "https://github.com/metta-systems/vesper"
readme = "README.md"
license = "BlueOak-1.0.0"
categories = ["no-std", "embedded", "os"]
publish = false
edition = "2018"
[badges]
maintenance = { status = "experimental" }
[dependencies]

8
vesper-user/README.md Normal file
View File

@ -0,0 +1,8 @@
# User-space Kernel Interface
This directory contains library for interfacing with the kernel through syscalls.
This library also defines constants and types shared between kernel- and user-space.
----
For more information please re-read.

View File

@ -0,0 +1 @@
pub mod syscall;

View File

@ -0,0 +1,3 @@
pub fn syscall(_number: u64) {
unsafe { asm!("svc #1234") }
}

View File

@ -0,0 +1,10 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
#[cfg(target_arch = "aarch64")]
#[macro_use]
pub mod aarch64;
#[cfg(target_arch = "aarch64")]
pub use self::aarch64::*;

36
vesper-user/src/lib.rs Normal file
View File

@ -0,0 +1,36 @@
#![no_std]
#![feature(asm)]
pub mod arch;
pub use arch::syscall;
// @todo make this use numeric constants for ABI compat
// but to keep this interface simpler, enum-to-numeric remapping will be implemented inside of the
// syscall() fn.
pub enum SysCall {
Send,
NBSend,
Call,
Recv,
Reply,
ReplyRecv,
NBRecv,
Yield,
#[cfg(debug)]
DebugPutChar,
#[cfg(debug)]
DebugHalt,
#[cfg(debug)]
DebugSnapshot,
#[cfg(debug)]
DebugCapIdentify,
#[cfg(debug)]
DebugNameThread,
}
#[cfg(test)]
mod tests {
#[test_case]
fn test_debug_output_syscall() {}
}

View File

@ -0,0 +1,46 @@
// This is probably what a user-space look of the capabilities should look like? Like direct access
// to some kernel objects via possible invocations.
// Some of these functions are probably kernel-space only and so should be removed from user-side interface.
trait Thread {
// Configuration
// Effectively, SetSpace followed by SetIPCBuffer.
fn configure(fault_endpoint: Cap, cap_space_root: Cap, cap_space_root_data: CapNodeConfig, virt_space_root: Cap, virt_space_root_data: (), ipc_buffer_frame: Cap, ipc_buffer_offset: usize) -> Result<()>;
fn set_space(fault_endpoint: Cap, cap_space_root: Cap, cap_space_root_data: CapNodeConfig, virt_space_root: Cap, virt_space_root_data: ()) -> Result<()>;
fn set_ipc_buffer(ipc_buffer_frame: CapNode, ipc_buffer_offset: usize) -> Result<()>;
// Debugging tools
fn configure_single_stepping(bp_num: u16, num_insns): Result<SingleStepping>;
fn get_breakpoint(bp_num: u16) -> Result<BreakpointInfo>;
fn set_breakpoint(bp_num: u16, bp: BreakpointInfo) -> Result<()>;
fn unset_breakpoint(bp_num: u16) -> Result<()>;
// Scheduling
fn suspend() -> Result<()>;
fn resume() -> Result<()>;
fn set_priority(authority: TCB/*Cap*/, priority: u32) -> Result<()>;
fn set_mc_priority(authority: TCB/*Cap*/, mcp: u32) -> Result<()>;
fn set_sched_params(authority: TCB/*Cap*/, mcp: u32, priority: u32) -> Result<()>;
fn set_affinity(affinity: u64) -> Result<()>;
// TCB configuration
fn copy_registers(source: TCB/*Cap*/, suspend_source: bool, resume_target: bool, transfer_frame_regs: bool, transfer_integer_regs: bool, arch_flags: u8) -> Result<()>;
fn read_registers(suspend_source: bool, arch_flags: u8, num_regs: u16, register_context: &mut ArchRegisterContext) -> Result<()>;
fn write_registers(resume_target: bool, arch_flags: u8, num_regs: u16, register_context: &ArchRegisterContext) -> Result<()>;
// Notifications
fn bind_notification(notification: CapNode) -> Result<()>;
fn unbind_notification() -> Result<()>;
// Arch-specific
// fn set_tls_base(tls_base: usize) -> Result<()>;
// virtualized - x86-specific
// fn set_ept_root(eptpml: X86::EPTPML4) -> Result<()>;
}
// @todo <<SchedContext>>
// struct Thread {}
struct TCB {
capability: u128, // should actually be a CapPath here - this is the argument to
// Thread.read_registers(cap, ... call for example.
}
impl Thread for TCB {
// ...
}