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