From e414c87414d3aed353719212c6057504b71af4c2 Mon Sep 17 00:00:00 2001 From: bunnie Date: Mon, 12 Oct 2020 03:21:20 +0800 Subject: [PATCH] add doc notes on the UTRA --- svd2utra/README.md | 177 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 svd2utra/README.md diff --git a/svd2utra/README.md b/svd2utra/README.md new file mode 100644 index 000000000..b86f61f32 --- /dev/null +++ b/svd2utra/README.md @@ -0,0 +1,177 @@ +# 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); +} + +``` \ No newline at end of file