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-cpu=cortex-a53",
|
||||
"-C", "embed-bitcode=yes",
|
||||
"-Z", "macro-backtrace",
|
||||
]
|
||||
runner = "cargo make test-runner"
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
.idea/
|
||||
*.iml
|
||||
.nova/
|
||||
.vscode/
|
||||
target/
|
||||
kernel8*
|
||||
.gdb_history
|
||||
|
|
|
@ -33,6 +33,12 @@ version = "0.3.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7151b083b0664ed58ed669fcdd92f01c3d2fdbf10af4931a301474950b52bfa9"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.24"
|
||||
|
@ -95,9 +101,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.48"
|
||||
version = "1.0.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac"
|
||||
checksum = "8833e20724c24de12bbaba5ad230ea61c3eafb05b881c7c9d3cfe8638b187e68"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -107,8 +113,6 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "tock-registers"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f521a79accce68c417c9c77ce22108056b626126da1932f7e2e9b5bbffee0cea"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
|
@ -136,10 +140,16 @@ dependencies = [
|
|||
"bitflags",
|
||||
"cfg-if",
|
||||
"cortex-a",
|
||||
"paste",
|
||||
"qemu-exit",
|
||||
"r0",
|
||||
"register",
|
||||
"snafu",
|
||||
"usize_conversions",
|
||||
"ux",
|
||||
"vesper-user",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vesper-user"
|
||||
version = "0.0.1"
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"nucleus"
|
||||
"nucleus",
|
||||
"vesper-user",
|
||||
"crates/tock-registers"
|
||||
]
|
||||
|
||||
[profile.dev]
|
||||
|
@ -18,3 +20,7 @@ lto = true
|
|||
[profile.test]
|
||||
opt-level = 's'
|
||||
debug = true
|
||||
|
||||
[patch.crates-io]
|
||||
tock-registers = { path = 'crates/tock-registers' }
|
||||
# register = { git = 'https://github.com/metta-systems/register-rs', branch = 'update-deps-1' }
|
||||
|
|
5
Justfile
5
Justfile
|
@ -50,3 +50,8 @@ nm:
|
|||
expand:
|
||||
# Run `cargo expand` on modules
|
||||
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>"]
|
||||
description = "Vesper exokernel"
|
||||
documentation = "https://docs.metta.systems/vesper"
|
||||
homepage = "https://github.com/metta-systems/vesper"
|
||||
homepage = "https://metta.systems/products/vesper"
|
||||
repository = "https://github.com/metta-systems/vesper"
|
||||
readme = "README.md"
|
||||
license = "BlueOak-1.0.0"
|
||||
|
@ -16,10 +16,13 @@ edition = "2018"
|
|||
maintenance = { status = "experimental" }
|
||||
|
||||
[features]
|
||||
# unstable = []
|
||||
# realtime = []
|
||||
noserial = []
|
||||
# Enable JTAG debugging of kernel - enable jtag helpers and
|
||||
# block waiting for JTAG probe attach at the start of kernel main.
|
||||
jtag = []
|
||||
# jlink = [] #'jlink_rtt'
|
||||
# Build for running under QEMU with semihosting, so various halt/reboot options would for example quit QEMU instead.
|
||||
qemu = ["qemu-exit"]
|
||||
|
||||
|
@ -34,3 +37,11 @@ bit_field = "0.10.0"
|
|||
bitflags = "1.2.1"
|
||||
cfg-if = "1.0"
|
||||
snafu = { version = "0.6", default-features = false }
|
||||
paste = "1.0"
|
||||
vesper-user = { path = "../vesper-user" }
|
||||
|
||||
#embedded-serial = "0.5.0"
|
||||
# jlink_rtt = { version = "0.1.0", optional = true }
|
||||
# static_assertions = { version = "1.0.0", features = ["nightly"] }
|
||||
#rcstring = "0.2.1"
|
||||
# arrayvec = { version = "0.5.1", default-features = false }
|
||||
|
|
|
@ -15,6 +15,10 @@ args = ["expand", "--target=${TARGET_JSON}", "--release", "--features=${TARGET_F
|
|||
env = { "TARGET_FEATURES" = "${QEMU_FEATURES}" }
|
||||
args = ["test", "--target=${TARGET_JSON}", "--features=${TARGET_FEATURES}"]
|
||||
|
||||
[tasks.docs]
|
||||
env = { "TARGET_FEATURES" = "" }
|
||||
args = ["doc", "--open", "--no-deps", "--target=${TARGET_JSON}", "--features=${TARGET_FEATURES}"]
|
||||
|
||||
# These tasks are written in cargo-make's own script to make it portable across platforms (no `basename` on Windows)
|
||||
[tasks.kernel-binary]
|
||||
script_runner = "@duckscript"
|
||||
|
|
|
@ -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
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*
|
||||
* Based on ideas from Jorge Aparicio, Andre Richter, Phil Oppenheimer, Sergio Benitez.
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
//! Low-level boot of the Raspberry's processor
|
||||
//! http://infocenter.arm.com/help/topic/com.arm.doc.dai0527a/DAI0527A_baremetal_boot_code_for_ARMv8_A_processors.pdf
|
||||
//! <http://infocenter.arm.com/help/topic/com.arm.doc.dai0527a/DAI0527A_baremetal_boot_code_for_ARMv8_A_processors.pdf>
|
||||
|
||||
use {
|
||||
crate::endless_sleep,
|
||||
cortex_a::{asm, regs::*},
|
||||
};
|
||||
|
||||
//use crate::arch::caps::{CapNode, Capability};
|
||||
|
||||
// Stack placed before first executable instruction
|
||||
const STACK_START: u64 = 0x0008_0000; // Keep in sync with linker script
|
||||
|
||||
|
@ -218,3 +220,225 @@ pub unsafe extern "C" fn _boot_cores() -> ! {
|
|||
// if not core0 or not EL3/EL2/EL1, infinitely wait for events
|
||||
endless_sleep()
|
||||
}
|
||||
|
||||
/*
|
||||
// caps and mem regions init
|
||||
|
||||
enum KernelInitError {}
|
||||
|
||||
fn map_kernel_window() {}
|
||||
|
||||
/**
|
||||
* This and only this function initialises the CPU.
|
||||
* It does NOT initialise any kernel state.
|
||||
*/
|
||||
fn init_cpu() -> Result<(), KernelInitError> {
|
||||
activate_global_pd();
|
||||
}
|
||||
|
||||
/**
|
||||
* This and only this function initialises the platform.
|
||||
* It does NOT initialise any kernel state.
|
||||
*/
|
||||
fn init_plat() -> Result<(), KernelInitError> {
|
||||
initIRQController();
|
||||
initTimer();
|
||||
initL2Cache();
|
||||
}
|
||||
|
||||
fn arch_init_freemem() -> Result<(), KernelInitError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn create_domain_cap() -> Result<(), KernelInitError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn init_irqs() -> Result<(), KernelInitError> {
|
||||
for (irq_t i = 0; i <= maxIRQ; i++) {
|
||||
setIRQState(IRQInactive, i);
|
||||
}
|
||||
setIRQState(IRQTimer, GPT9_IRQ);
|
||||
/* provide the IRQ control cap */
|
||||
write_slot((((slot_ptr_t)((pptr_t)cap_get_capPtr(root_cnode_cap))) + (seL4_CapIRQControl)), cap_irq_control_cap_new());
|
||||
}
|
||||
|
||||
fn create_bootinfo_cap() -> Result<(), KernelInitError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn create_asid_pool_for_initial_thread() -> Result<(), KernelInitError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn create_idle_thread() -> Result<(), KernelInitError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn clean_invalidate_l1_caches() -> Result<(), KernelInitError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn create_initial_thread() -> Result<(), KernelInitError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn init_core_state(_: Result<(), KernelInitError>) -> Result<(), KernelInitError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn create_untypeds() -> Result<(), KernelInitError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn finalise_bootinfo() -> Result<(), KernelInitError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn invalidate_local_tlb() -> Result<(), KernelInitError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn lock_kernel_node() -> Result<(), KernelInitError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn schedule() {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn activate_thread() {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
#[link_section = ".text.boot"]
|
||||
// #[used]
|
||||
fn try_init_kernel() -> Result<(), KernelInitError> {
|
||||
map_kernel_window();
|
||||
init_cpu()?;
|
||||
init_plat()?;
|
||||
|
||||
println!("Booting kernel");
|
||||
|
||||
init_free_memory()?; // arch_init_freemem()
|
||||
|
||||
let root_capnode_cap = create_root_capnode()?;
|
||||
create_domain_cap(root_capnode_cap);
|
||||
// ...create IRQ CapNode...
|
||||
init_irqs(root_capnode_cap)?;
|
||||
|
||||
//fill in boot info and
|
||||
// create bootinfo frame
|
||||
|
||||
// create initial address space covering init thread
|
||||
// user image and ipc buffer and bootinfo frame
|
||||
|
||||
// create and map bootinfo frame cap
|
||||
create_bootinfo_cap();
|
||||
|
||||
// create initial thread IPC buffer
|
||||
|
||||
// create userland image frames
|
||||
|
||||
// create initial thread ASID pool
|
||||
let it_asid_pool_cap = create_asid_pool_for_initial_thread(root_capnode_cap)?;
|
||||
|
||||
// create the idle thread
|
||||
create_idle_thread()?;
|
||||
|
||||
/* Before creating the initial thread (which also switches to it)
|
||||
* we clean the cache so that any page table information written
|
||||
* as a result of calling create_frames_of_region will be correctly
|
||||
* read by the hardware page table walker */
|
||||
clean_invalidate_l1_caches();
|
||||
|
||||
let it = create_initial_thread(root_capnode_cap)?;
|
||||
|
||||
/* create all of the untypeds. Both devices and kernel window memory */
|
||||
create_untypeds(root_capnode_cap)?;
|
||||
|
||||
finalise_bootinfo();
|
||||
|
||||
/* make everything written by the kernel visible to userland. Cleaning to PoC is not
|
||||
* strictly neccessary, but performance is not critical here so clean and invalidate
|
||||
* everything to PoC */
|
||||
clean_invalidate_l1_caches();
|
||||
invalidate_local_tlb();
|
||||
|
||||
/* Export selected CPU features for access by EL0 */
|
||||
arch_init_user_access();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn try_init_kernel_secondary_core() -> Result<(), KernelInitError>
|
||||
{
|
||||
init_cpu();
|
||||
|
||||
/* Enable per-CPU timer interrupts */
|
||||
maskInterrupt(false, KERNEL_TIMER_IRQ);
|
||||
|
||||
lock_kernel_node;
|
||||
|
||||
ksNumCPUs++; // increase global cpu counter - this should be done differently?
|
||||
|
||||
init_core_state(SchedulerAction_ResumeCurrentThread);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_kernel() {
|
||||
try_init_kernel()?;
|
||||
// or for AP:
|
||||
// try_init_kernel_secondary_core();
|
||||
schedule();
|
||||
activate_thread();
|
||||
}
|
||||
|
||||
const CONFIG_ROOT_CAPNODE_SIZE_BITS: usize = 12;
|
||||
const wordBits: usize = 64;
|
||||
|
||||
fn create_root_capnode() -> Capability // Attr(BOOT_CODE)
|
||||
{
|
||||
// write the number of root CNode slots to global state
|
||||
boot_info.max_slot_pos = 1 << CONFIG_ROOT_CAPNODE_SIZE_BITS; // 12 bits => 4096 slots
|
||||
|
||||
// seL4_SlotBits = 32 bytes per entry, 4096 entries =>
|
||||
// create an empty root CapNode
|
||||
// this goes into the kernel startup/heap memory (one of the few items that kernel DOES allocate).
|
||||
let region_size = core::mem::size_of::<Capability> * boot_info.max_slot_pos; // 12 + 5 => 131072 (128Kb)
|
||||
let pptr = alloc_region(region_size); // GlobalAllocator::alloc_zeroed instead?
|
||||
if pptr.is_none() {
|
||||
println!("Kernel init failing: could not create root capnode");
|
||||
return Capability(NullCap::Type::value);
|
||||
}
|
||||
let Some(pptr) = pptr;
|
||||
memzero(pptr, region_size); // CTE_PTR(pptr) ?
|
||||
|
||||
// transmute into a type? (you can use ptr.write() to just write a type into memory location)
|
||||
|
||||
let cap = CapNode::new_root(pptr);
|
||||
|
||||
// this cnode contains a cap to itself...
|
||||
/* write the root CNode cap into the root CNode */
|
||||
// @todo rootCapNode.write_slot(CapInitThreadCNode, cap); -- where cap and rootCapNode are synonyms!
|
||||
write_slot(SLOT_PTR(pptr, seL4_CapInitThreadCNode), cap);
|
||||
|
||||
cap // reference to pptr is here
|
||||
}
|
||||
*/
|
||||
|
||||
// create initial thread
|
||||
// - vspace
|
||||
// - cpace
|
||||
// - tcb
|
||||
//
|
||||
// requires:
|
||||
// - alloc_region
|
||||
// - copy_global_mappings
|
||||
// - create pt/pd caps -- this is arch-specific?
|
||||
// - root capnode with write_slot()
|
||||
//
|
||||
// init thread domain = 0
|
||||
// init thread asid = 1 (asidInvalid = 0)
|
||||
//
|
||||
|
|
|
@ -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.
|
||||
|
||||
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
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
use {
|
||||
crate::mm::{align_down, align_up},
|
||||
bit_field::BitField,
|
||||
|
@ -290,219 +291,3 @@ where
|
|||
*self = VirtAddr::new(self.0 % u64::from_usize(rhs));
|
||||
}
|
||||
}
|
||||
|
||||
/// A 64-bit physical memory address.
|
||||
///
|
||||
/// This is a wrapper type around an `u64`, so it is always 8 bytes, even when compiled
|
||||
/// on non 64-bit systems. The `UsizeConversions` trait can be used for performing conversions
|
||||
/// between `u64` and `usize`.
|
||||
///
|
||||
/// On `aarch64`, only the 52 lower bits of a physical address can be used. The top 12 bits need
|
||||
/// to be zero. This type guarantees that it always represents a valid physical address.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
|
||||
#[repr(transparent)]
|
||||
pub struct PhysAddr(u64);
|
||||
|
||||
/// A passed `u64` was not a valid physical address.
|
||||
///
|
||||
/// This means that bits 52 to 64 were not all null.
|
||||
#[derive(Debug)]
|
||||
pub struct PhysAddrNotValid(u64);
|
||||
|
||||
impl PhysAddr {
|
||||
/// Creates a new physical address.
|
||||
///
|
||||
/// Panics if any bits in the bit position 52 to 64 is set.
|
||||
pub fn new(addr: u64) -> PhysAddr {
|
||||
assert_eq!(
|
||||
addr.get_bits(52..64),
|
||||
0,
|
||||
"physical addresses must not have any set bits in positions 52 to 64"
|
||||
);
|
||||
PhysAddr(addr)
|
||||
}
|
||||
|
||||
/// Tries to create a new physical address.
|
||||
///
|
||||
/// Fails if any bits in the bit positions 52 to 64 are set.
|
||||
pub fn try_new(addr: u64) -> Result<PhysAddr, PhysAddrNotValid> {
|
||||
match addr.get_bits(52..64) {
|
||||
0 => Ok(PhysAddr(addr)), // address is valid
|
||||
_ => Err(PhysAddrNotValid(addr)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a physical address that points to `0`.
|
||||
pub const fn zero() -> PhysAddr {
|
||||
PhysAddr(0)
|
||||
}
|
||||
|
||||
/// Converts the address to an `u64`.
|
||||
pub fn as_u64(self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Convenience method for checking if a physical address is null.
|
||||
pub fn is_null(&self) -> bool {
|
||||
self.0 == 0
|
||||
}
|
||||
|
||||
/// Aligns the physical address upwards to the given alignment.
|
||||
///
|
||||
/// See the `align_up` function for more information.
|
||||
pub fn aligned_up<U>(self, align: U) -> Self
|
||||
where
|
||||
U: Into<u64>,
|
||||
{
|
||||
PhysAddr(align_up(self.0, align.into()))
|
||||
}
|
||||
|
||||
/// Aligns the physical address downwards to the given alignment.
|
||||
///
|
||||
/// See the `align_down` function for more information.
|
||||
pub fn aligned_down<U>(self, align: U) -> Self
|
||||
where
|
||||
U: Into<u64>,
|
||||
{
|
||||
PhysAddr(align_down(self.0, align.into()))
|
||||
}
|
||||
|
||||
/// Checks whether the physical address has the demanded alignment.
|
||||
pub fn is_aligned<U>(self, align: U) -> bool
|
||||
where
|
||||
U: Into<u64>,
|
||||
{
|
||||
self.aligned_down(align) == self
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for PhysAddr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "PhysAddr({:#x})", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Binary for PhysAddr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::LowerHex for PhysAddr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Octal for PhysAddr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::UpperHex for PhysAddr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for PhysAddr {
|
||||
fn from(value: u64) -> Self {
|
||||
PhysAddr::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PhysAddr> for u64 {
|
||||
fn from(value: PhysAddr) -> Self {
|
||||
value.as_u64()
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<u64> for PhysAddr {
|
||||
type Output = Self;
|
||||
fn add(self, rhs: u64) -> Self::Output {
|
||||
PhysAddr::new(self.0 + rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<u64> for PhysAddr {
|
||||
fn add_assign(&mut self, rhs: u64) {
|
||||
*self = *self + rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<usize> for PhysAddr
|
||||
where
|
||||
u64: FromUsize,
|
||||
{
|
||||
type Output = Self;
|
||||
fn add(self, rhs: usize) -> Self::Output {
|
||||
self + u64::from_usize(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<usize> for PhysAddr
|
||||
where
|
||||
u64: FromUsize,
|
||||
{
|
||||
fn add_assign(&mut self, rhs: usize) {
|
||||
self.add_assign(u64::from_usize(rhs))
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<u64> for PhysAddr {
|
||||
type Output = Self;
|
||||
fn sub(self, rhs: u64) -> Self::Output {
|
||||
PhysAddr::new(self.0.checked_sub(rhs).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign<u64> for PhysAddr {
|
||||
fn sub_assign(&mut self, rhs: u64) {
|
||||
*self = *self - rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<usize> for PhysAddr
|
||||
where
|
||||
u64: FromUsize,
|
||||
{
|
||||
type Output = Self;
|
||||
fn sub(self, rhs: usize) -> Self::Output {
|
||||
self - u64::from_usize(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign<usize> for PhysAddr
|
||||
where
|
||||
u64: FromUsize,
|
||||
{
|
||||
fn sub_assign(&mut self, rhs: usize) {
|
||||
self.sub_assign(u64::from_usize(rhs))
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<PhysAddr> for PhysAddr {
|
||||
type Output = u64;
|
||||
fn sub(self, rhs: PhysAddr) -> Self::Output {
|
||||
self.as_u64().checked_sub(rhs.as_u64()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// use super::*;
|
||||
|
||||
// #[test_case]
|
||||
// pub fn test_invalid_phys_addr() {
|
||||
// let result = PhysAddr::try_new(0xfafa_0123_0123_0123_3210_3210_3210_3210);
|
||||
// if let Err(e) = result {
|
||||
// assert_eq!(
|
||||
// e,
|
||||
// PhysAddrNotValid(0xfafa_0123_0123_0123_3210_3210_3210_3210)
|
||||
// );
|
||||
// } else {
|
||||
// assert!(false)
|
||||
// }
|
||||
// }
|
||||
}
|
|
@ -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 cache;
|
||||
pub mod mmu;
|
||||
|
||||
pub use addr::PhysAddr;
|
||||
pub use addr::VirtAddr;
|
||||
pub use addr::ASID;
|
||||
|
||||
// aarch64 granules and page sizes howto:
|
||||
// https://stackoverflow.com/questions/34269185/simultaneous-existence-of-different-sized-pages-on-aarch64
|
||||
|
|
|
@ -8,11 +8,16 @@
|
|||
use cortex_a::asm;
|
||||
|
||||
mod boot;
|
||||
pub mod caps;
|
||||
pub use self::caps::*;
|
||||
#[cfg(feature = "jtag")]
|
||||
pub mod jtag;
|
||||
pub mod memory;
|
||||
pub mod objects;
|
||||
pub mod traps;
|
||||
|
||||
pub(crate) use objects::thread::user_stack_trace;
|
||||
|
||||
/// Loop forever in sleep mode.
|
||||
#[inline]
|
||||
pub fn endless_sleep() -> ! {
|
||||
|
|
|
@ -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.
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn current_el0_synchronous(e: &mut ExceptionContext) {
|
||||
let cause = ESR_EL1.read(ESR_EL1::EC);
|
||||
|
||||
if cause == ESR_EL1::EC::SVC64.read(ESR_EL1::EC) {
|
||||
let syscall = ESR_EL1.read(ESR_EL1::ISS);
|
||||
return crate::api::handle_syscall(syscall);
|
||||
}
|
||||
|
||||
println!("[!] USER synchronous exception happened.");
|
||||
synchronous_common(e)
|
||||
}
|
||||
|
@ -309,8 +316,9 @@ fn iss_dfsc_to_string(iss: IssForDataAbort) -> &'static str {
|
|||
/// Helper function to 1) display current exception, 2) skip the offending asm instruction.
|
||||
/// Not for production use!
|
||||
fn synchronous_common(e: &mut ExceptionContext) {
|
||||
println!(" ESR_EL1: {:#010x} (syndrome)", ESR_EL1.get());
|
||||
let cause = ESR_EL1.read(ESR_EL1::EC);
|
||||
|
||||
println!(" ESR_EL1: {:#010x} (syndrome)", ESR_EL1.get());
|
||||
println!(
|
||||
" EC: {:#06b} (cause) -- {}",
|
||||
cause,
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[macro_use]
|
||||
pub mod aarch64;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub use self::aarch64::*;
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_arch = "aarch64")] {
|
||||
#[macro_use]
|
||||
pub mod aarch64;
|
||||
pub use self::aarch64::*;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
pub mod console;
|
||||
|
||||
pub use console::{Console, ConsoleOps};
|
||||
|
|
|
@ -15,12 +15,19 @@
|
|||
#![feature(allocator_api)]
|
||||
#![feature(ptr_internals)]
|
||||
#![feature(format_args_nl)]
|
||||
#![feature(result_contains_err)]
|
||||
#![feature(nonnull_slice_from_raw_parts)]
|
||||
#![feature(custom_test_frameworks)]
|
||||
#![test_runner(crate::tests::test_runner)]
|
||||
#![reexport_test_harness_main = "test_main"]
|
||||
#![deny(missing_docs)]
|
||||
#![deny(warnings)]
|
||||
#![allow(dead_code)] // While working on features, some code may remain unused
|
||||
// repr128 is not yet stable
|
||||
#![feature(repr128)]
|
||||
#![feature(const_generics)]
|
||||
#![feature(const_evaluatable_checked)] // see https://github.com/rust-lang/rust/issues/68436
|
||||
#![allow(incomplete_features)]
|
||||
|
||||
#[cfg(not(target_arch = "aarch64"))]
|
||||
use architecture_not_supported_sorry;
|
||||
|
@ -29,9 +36,12 @@ use architecture_not_supported_sorry;
|
|||
#[macro_use]
|
||||
pub mod arch;
|
||||
pub use arch::*;
|
||||
mod api;
|
||||
mod caps;
|
||||
mod devices;
|
||||
mod macros;
|
||||
mod mm;
|
||||
mod objects;
|
||||
mod panic;
|
||||
mod platform;
|
||||
#[cfg(feature = "qemu")]
|
||||
|
@ -161,6 +171,11 @@ pub fn kmain() -> ! {
|
|||
#[cfg(test)]
|
||||
test_main();
|
||||
|
||||
/*
|
||||
try_init_kernel().expect("Failed to init kernel");*/
|
||||
// schedule();
|
||||
// activate_thread();
|
||||
|
||||
command_prompt();
|
||||
|
||||
reboot()
|
||||
|
@ -231,6 +246,70 @@ fn reboot() -> ! {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum KernelInitError {
|
||||
CapabilityCreationFailed,
|
||||
}
|
||||
|
||||
// #[link_section = ".text.boot"]
|
||||
// fn try_init_kernel() -> Result<(), KernelInitError> {
|
||||
// let root_capnode_cap = create_root_capnode();
|
||||
// if root_capnode_cap.is_err() {
|
||||
// return Err(KernelInitError::CapabilityCreationFailed);
|
||||
// }
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
const CONFIG_ROOT_CAPNODE_SIZE_BITS: usize = 12;
|
||||
const WORD_BITS: usize = 64;
|
||||
|
||||
enum BootInfoCaps {
|
||||
InitThreadCapNode = 1,
|
||||
}
|
||||
|
||||
impl From<BootInfoCaps> for usize {
|
||||
fn from(val: BootInfoCaps) -> usize {
|
||||
val as usize
|
||||
}
|
||||
}
|
||||
/*
|
||||
#[link_section = ".text.boot"]
|
||||
fn create_root_capnode() -> Result<CapNodeCapability, caps::CapError> {
|
||||
// write the number of root CNode slots to global state
|
||||
BOOT_INFO.lock(|bi| bi.max_slot_pos = 1 << CONFIG_ROOT_CAPNODE_SIZE_BITS); // 12 bits => 4096 slots
|
||||
|
||||
// seL4_SlotBits = 32 bytes per entry, 4096 entries =>
|
||||
// create an empty root CapNode
|
||||
// this goes into the kernel startup/heap memory (one of the few items that kernel DOES allocate).
|
||||
let region_size = core::mem::size_of::<CapTableEntry>() * BOOT_INFO.lock(|bi| bi.max_slot_pos); // 12 + 5 => 131072 (128Kb)
|
||||
let pptr = BOOT_INFO.lock(|bi| bi.alloc_region(region_size)); // GlobalAllocator::alloc_zeroed instead?
|
||||
let pptr = match pptr {
|
||||
Ok(pptr) => pptr,
|
||||
Err(_) => {
|
||||
println!("Kernel init failed: could not create root capnode");
|
||||
return Err(caps::CapError::CannotCreate);
|
||||
}
|
||||
};
|
||||
// @todo lifetime of slice -- slice here only to zero out memory
|
||||
let slice =
|
||||
unsafe { core::slice::from_raw_parts_mut(pptr.as_u64() as *mut u8, region_size as usize) };
|
||||
for byte in slice.iter_mut() {
|
||||
*byte = 0;
|
||||
} // @todo wtf
|
||||
|
||||
// transmute into a type? (you can use ptr.write() to just write a type into memory location)
|
||||
|
||||
let cap = CapNodeCapability::new_root(pptr.as_u64());
|
||||
use core::convert::TryFrom;
|
||||
let mut cap_node = CapNodeCapability::try_from(cap.as_u128()).unwrap();
|
||||
|
||||
// this cnode contains a cap to itself...
|
||||
/* write the root CNode cap into the root CNode */
|
||||
cap_node.write_slot(usize::from(BootInfoCaps::InitThreadCapNode), &cap);
|
||||
|
||||
Ok(cap_node) // reference to pptr is here
|
||||
}*/
|
||||
|
||||
fn check_display_init() {
|
||||
display_graphics()
|
||||
.map_err(|e| {
|
||||
|
@ -285,4 +364,9 @@ mod main_tests {
|
|||
fn test_data_abort_trap() {
|
||||
check_data_abort_trap()
|
||||
}
|
||||
|
||||
#[test_case]
|
||||
fn test_user_thread_syscall() {
|
||||
// To test syscall from user-space we need to construct a user-space thread and switch to it
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,15 +45,15 @@ mod tests {
|
|||
// align 1
|
||||
assert_eq!(align_up(0, 1), 0);
|
||||
assert_eq!(align_up(1234, 1), 1234);
|
||||
assert_eq!(align_up(0xffffffffffffffff, 1), 0xffffffffffffffff);
|
||||
assert_eq!(align_up(0xffff_ffff_ffff_ffff, 1), 0xffff_ffff_ffff_ffff);
|
||||
// align 2
|
||||
assert_eq!(align_up(0, 2), 0);
|
||||
assert_eq!(align_up(1233, 2), 1234);
|
||||
assert_eq!(align_up(0xfffffffffffffffe, 2), 0xfffffffffffffffe);
|
||||
assert_eq!(align_up(0xffff_ffff_ffff_fffe, 2), 0xffff_ffff_ffff_fffe);
|
||||
// address 0
|
||||
assert_eq!(align_up(0, 128), 0);
|
||||
assert_eq!(align_up(0, 1), 0);
|
||||
assert_eq!(align_up(0, 2), 0);
|
||||
assert_eq!(align_up(0, 0x8000000000000000), 0);
|
||||
assert_eq!(align_up(0, 0x8000_0000_0000_0000), 0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))]
|
||||
#[panic_handler]
|
||||
fn panicked(info: &core::panic::PanicInfo) -> ! {
|
||||
|
|
|
@ -2,4 +2,5 @@
|
|||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
pub mod rpi3;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
use snafu::Snafu;
|
||||
|
||||
/* Character cells are 8x8 */
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
use {
|
||||
super::{
|
||||
mailbox::{channel, read, write, MailboxOps, RegisterBlock, Result},
|
||||
|
@ -89,7 +94,7 @@ impl MailboxOps for FrameBuffer {
|
|||
self.base_addr as *const _
|
||||
}
|
||||
|
||||
/// https://github.com/raspberrypi/firmware/wiki/Accessing-mailboxes says:
|
||||
/// <https://github.com/raspberrypi/firmware/wiki/Accessing-mailboxes> says:
|
||||
/// **With the exception of the property tags mailbox channel,**
|
||||
/// when passing memory addresses as the data part of a mailbox message,
|
||||
/// the addresses should be **bus addresses as seen from the VC.**
|
||||
|
|
|
@ -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);
|
||||
|
||||
/// The offsets for reach register.
|
||||
/// From https://wiki.osdev.org/Raspberry_Pi_Bare_Bones and
|
||||
/// https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf
|
||||
/// From <https://wiki.osdev.org/Raspberry_Pi_Bare_Bones> and
|
||||
/// <https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf>
|
||||
#[allow(non_snake_case)]
|
||||
#[repr(C)]
|
||||
pub struct RegisterBlock {
|
||||
|
|
|
@ -478,7 +478,7 @@ impl Mailbox {
|
|||
}
|
||||
|
||||
/// NB: Do not intermix Get/Set and Test tags in one request!
|
||||
/// See https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface
|
||||
/// See <https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface>
|
||||
/// * It is not valid to mix Test tags with Get/Set tags in the same operation
|
||||
/// and no tags will be returned.
|
||||
#[inline]
|
||||
|
@ -492,7 +492,7 @@ impl Mailbox {
|
|||
}
|
||||
|
||||
/// NB: Do not intermix Get/Set and Test tags in one request!
|
||||
/// See https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface
|
||||
/// See <https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface>
|
||||
/// * It is not valid to mix Test tags with Get/Set tags in the same operation
|
||||
/// and no tags will be returned.
|
||||
#[inline]
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
pub mod display;
|
||||
pub mod fb;
|
||||
pub mod gic;
|
||||
pub mod gpio;
|
||||
pub mod mailbox;
|
||||
pub mod mini_uart;
|
||||
|
@ -15,14 +16,14 @@ pub mod power;
|
|||
pub mod vc;
|
||||
|
||||
/// See BCM2835-ARM-Peripherals.pdf
|
||||
/// See https://www.raspberrypi.org/forums/viewtopic.php?t=186090 for more details.
|
||||
/// See <https://www.raspberrypi.org/forums/viewtopic.php?t=186090> for more details.
|
||||
|
||||
pub struct BcmHost;
|
||||
|
||||
impl BcmHost {
|
||||
/// This returns the ARM-side physical address where peripherals are mapped.
|
||||
///
|
||||
/// As per https://www.raspberrypi.org/documentation/hardware/raspberrypi/peripheral_addresses.md
|
||||
/// As per <https://www.raspberrypi.org/documentation/hardware/raspberrypi/peripheral_addresses.md>
|
||||
/// BCM SOC could address only 1Gb of memory, so 0x4000_0000 is the high watermark.
|
||||
pub const fn get_peripheral_address() -> usize {
|
||||
0x3f00_0000
|
||||
|
@ -38,7 +39,7 @@ impl BcmHost {
|
|||
0xc000_0000 // uncached
|
||||
}
|
||||
|
||||
/// As per https://www.raspberrypi.org/forums/viewtopic.php?p=1170522#p1170522
|
||||
/// As per <https://www.raspberrypi.org/forums/viewtopic.php?p=1170522#p1170522>
|
||||
///
|
||||
pub fn bus2phys(bus: usize) -> usize {
|
||||
bus & !0xc000_0000
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
use {
|
||||
super::{
|
||||
display::{Display, PixelOrder, CHARSIZE_X, CHARSIZE_Y},
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
pub mod semihosting {
|
||||
pub fn exit_success() -> ! {
|
||||
use qemu_exit::QEMUExit;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: MIT OR BlueOak-1.0.0
|
||||
* Copyright (c) 2019 Andre Richter <andre.o.richter@gmail.com>
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
* Original code distributed under MIT, additional changes are under BlueOak-1.0.0
|
||||
*/
|
||||
|
||||
|
@ -19,8 +20,8 @@ pub struct NullLock<T> {
|
|||
/// threads don't exist yet in our code.
|
||||
///
|
||||
/// Literature:
|
||||
/// https://doc.rust-lang.org/beta/nomicon/send-and-sync.html
|
||||
/// https://doc.rust-lang.org/book/ch16-04-extensible-concurrency-sync-and-send.html
|
||||
/// * <https://doc.rust-lang.org/beta/nomicon/send-and-sync.html>
|
||||
/// * <https://doc.rust-lang.org/book/ch16-04-extensible-concurrency-sync-and-send.html>
|
||||
unsafe impl<T> Sync for NullLock<T> {}
|
||||
|
||||
impl<T> NullLock<T> {
|
||||
|
|
|
@ -2,17 +2,33 @@
|
|||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
|
||||
//============================================================================
|
||||
// Testing environment
|
||||
//============================================================================
|
||||
use crate::{println, qemu};
|
||||
|
||||
use crate::{print, println, qemu};
|
||||
|
||||
pub trait TestFn {
|
||||
fn run(&self) -> ();
|
||||
}
|
||||
|
||||
impl<T> TestFn for T
|
||||
where
|
||||
T: Fn(),
|
||||
{
|
||||
fn run(&self) {
|
||||
print!("*TEST* {}...\t", core::any::type_name::<T>());
|
||||
self();
|
||||
println!("[ok]\n");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn test_runner(tests: &[&dyn Fn()]) {
|
||||
pub fn test_runner(tests: &[&dyn TestFn]) {
|
||||
println!("Running {} tests", tests.len());
|
||||
for test in tests {
|
||||
test();
|
||||
println!("\n[ok]\n");
|
||||
test.run();
|
||||
}
|
||||
println!("\n[success]\n");
|
||||
qemu::semihosting::exit_success();
|
||||
|
|
|
@ -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