mirror of
https://github.com/saymrwulf/xous-core.git
synced 2026-11-08 04:56:43 +00:00
177 lines
No EOL
7.7 KiB
Markdown
177 lines
No EOL
7.7 KiB
Markdown
# Unambiguous Thin Register Abstraction (UTRA)
|
|
|
|
## Motivation
|
|
|
|
UTRA is a register abstraction for accessing hardware resources. It tries to be:
|
|
|
|
* Unambiguous -- the access rules should be concise and unambiguous to a systems programmer with a C background
|
|
* Thin -- it should hide constants, but not bury them so they become difficult to verify
|
|
|
|
Here is an example of an ambiguous style of register access, from a
|
|
PAC generated using [svd2rust](https://crates.io/crates/svd2rust):
|
|
|
|
```
|
|
// this seems clear -- as long as all the bit fields are specified
|
|
// (they actually aren't, so some non-obvious things are happening)
|
|
p.POWER.power.write(|w|
|
|
w.discharge().bit(true)
|
|
.soc_on().bit(false)
|
|
.kbddrive().bit(true)
|
|
.kbdscan().bits(3)
|
|
);
|
|
|
|
// what should this do?
|
|
// 1. just set the discharge bit to true and everything else to zero?
|
|
// 2. read the register first, change only the discharge bit to true, leaving the rest unchanged?
|
|
p.POWER.power.write(|w|
|
|
w.discharge().bit(true)
|
|
);
|
|
|
|
// answer: it does (1). You need to use the `modify()` function to have (2) happen.
|
|
|
|
```
|
|
|
|
While the closure-chaining is clever syntax, it's also ambiguous.
|
|
First, does the chaining imply an order of writes happening in
|
|
sequence, or do they all happen at once? The answer depends on Rust's
|
|
optimizer, which is very good and one can expect the behavior to be
|
|
the latter, but it is still write-ordering behavior that depends upon
|
|
the outcome of an optimizer and not a linguistic guarantee. Second,
|
|
the term `write` itself is ambiguous when it comes to bitfields: do we
|
|
write just the bitfield, or do we write the entire register, assuming
|
|
the rest of the contents are zero? These types of ambiguity make it
|
|
hard to audit code, especially for experts in systems programming
|
|
who are not also experts in Rust.
|
|
|
|
The primary trade-off for achieving unambiguity and thinness is less
|
|
type checking and type hardening, because we are not fully taking
|
|
advantage of the advanced syntax features of Rust.
|
|
|
|
That being said, a certain degree of deliberate malleability in the
|
|
register abstraction is desired to assist with security-oriented
|
|
audits: for a security audit, it is often just as important to ask
|
|
what the undefined bits do, as it is to check the settings of the
|
|
defined bits. Malleabilty allows an auditor to quickly create targeted
|
|
tests that exercise undefined bits. Existing Rust-based access crates
|
|
create strict types that eliminate the class of errors where constants
|
|
defined for one register are used in an incorrect type of register,
|
|
but they also make it very hard to modify in an ad-hoc manner.
|
|
|
|
## API
|
|
|
|
This crate is designed to serve as an alternative to `svd2rust`. It generates
|
|
a crate which consists of:
|
|
|
|
1. A library which is used to perform register accesses
|
|
2. A "header file" (library) that is auto-generated from a given `soc.svd` file
|
|
|
|
The library provides the a function template for `CSR` that provides the following
|
|
methods:
|
|
|
|
* `.r(reg: Register) -> T` - Read. Reads the entire contents of a CSR
|
|
* `.rf(field: Field) -> T` - Read Field. Read a CSR and return only the masked and shifted value of a sub-field
|
|
* `.wo(reg: Register, value:T)` - Write only. Write `value` into a register, replacing its entire contents
|
|
* `.wfo(field: Field, value:T)` - Write field only. Write `value` into a field of a register, zeroizing all the other fields and replacing its entire contents
|
|
* `.rmwf(field: Field, value:T)` - Read-modify-write a register. Replace just the contents of `field` while leaving the other fields intact. The current implementation makes no guarantees about atomicity.
|
|
|
|
`Register` and `Field` are generated by the library; `Field` refers to
|
|
the `Register` to which it belongs, and thus it is not necessary to
|
|
specify it explicitly. Furthermore, the base address of the `CSR` is
|
|
bound when the object is created, which allows the crate to work both
|
|
with physical and virtual addresses by replacing the base address with
|
|
the desired value depending upon the active addressing mode.
|
|
|
|
In addition to the `CSR` function template, convenience constants for
|
|
the CSR base, as well as any memory bases and interrupts, are also
|
|
generated by this crate.
|
|
|
|
This set of API calls supports the most common set of use cases, which
|
|
is reading, writing, and updating single fields of a register, or
|
|
entire registers all at once.
|
|
|
|
The API does not natively support setting two fields
|
|
simultaneously. This is because there can be nuances to this that
|
|
depend upon the hardware implementation, such as bit fields that are
|
|
self-resetting, registers that self-clear on read, or registers that
|
|
have other automatic and implicit side effects.
|
|
|
|
Users that require multiple bit fields to be set simultaneously must
|
|
explicitly read the CSR value, bind it to a temporary variable, mask
|
|
out the fields they want to replace, and combine in the values before
|
|
writing it back to the CSR.
|
|
|
|
To aid with this, the following helper functions are also available:
|
|
|
|
* `zf(field:Field, value:T) -> T` - Zeroize field. Take bits corresponding to `field` and set it to zero in `value`, leaving other bits unchanged
|
|
* `ms(field:Field, value:T) -> T` - Mask and shift. Take value, mask it to the field width, and then shift to its final position.
|
|
|
|
The idea here is that the `.r(register)` method is used to read the
|
|
entire register; then successive `.zf(field, value)` calls are made to
|
|
clear the fields prior to setting. Field values are OR'd with the
|
|
result of `.ms(field, value)` to create the final composite register
|
|
value. Finally, `.wo(value)` is used to overwrite the entire
|
|
register with the final composite register value.
|
|
|
|
The `.ms(field,value)` can also be used to synthesize initial register
|
|
values that need to be committed all at once to a hardware register,
|
|
before a `.wo(value)` call.
|
|
|
|
## Example Usage
|
|
|
|
Let's assume you've used svd2utra.py to create a `utra` crate in the
|
|
same directory as svd2utra.py, and you've added this to your `Cargo.toml` file.
|
|
Now, inside your `lib.rs` file, you might have something like this:
|
|
|
|
```Rust
|
|
use utra
|
|
|
|
fn test_fn() {
|
|
// Audio tests
|
|
|
|
// The audio block is a pointer to *mut 32.
|
|
let mut audio = CSR::new(HW_AUDIO_BASE as *mut u32);
|
|
|
|
// Read the entire contents of the RX_CTL register
|
|
audio.r(utra::audio::RX_CTL);
|
|
|
|
// Or read just one field
|
|
audio.rf(utra::audio::RX_CTL_ENABLE);
|
|
|
|
// Do a read-modify-write of the specified field
|
|
audio.rmwf(utra::audio::RX_CTL_RESET, 1);
|
|
|
|
// Do a multi-field operation where all fields are updated in a single write.
|
|
// First read the field into a temp variable.
|
|
let mut stat = audio.r(utra::audio::RX_STAT);
|
|
// in read replica, zero EMPTY and RDCOUNT
|
|
stat = audio.zf(utra::audio::RX_STAT_EMPTY, stat);
|
|
stat = audio.zf(utra::audio::RX_STAT_RDCOUNT, stat);
|
|
// in read replica, now set RDCOUNT to 0x123
|
|
stat |= audio.ms(utra::audio::RX_STAT_RDCOUNT, 0x123);
|
|
// commit read replica to register, updating both EMPTY and RDCOUNT in a single write
|
|
audio.wo(utra::audio::RX_STAT, stat);
|
|
|
|
// UART tests
|
|
|
|
// Create the UART register as a pointer to *mut u8
|
|
let mut uart = CSR::new(HW_UART_BASE as *mut u8);
|
|
|
|
// Write the RXTX field of the RXTX register
|
|
uart.wfo(utra::uart::RXTX_RXTX, b'a');
|
|
|
|
// Or you can write the whole UART register
|
|
uart.wo(utra::uart::RXTX, b'a');
|
|
assert_ne!(uart.rf(pac::uart::TXFULL_TXFULL), 1);
|
|
|
|
// Anomalies
|
|
|
|
// This compiles but requires a cast since `audio` is a pointer to
|
|
// u32, whereas `uart` is a pointer to u8.
|
|
audio.wfo(utra::uart::RXTX_RXTX, b'a' as _);
|
|
|
|
// This also compiles, despite the fact that the register offset is
|
|
// mismatched and nonsensical
|
|
audio.wfo(utra::uart::TXFULL_TXFULL, 1);
|
|
}
|
|
|
|
``` |