# rusTkey ("_rusty key_")
A library for developing bare-bones applications for the [tillitis TKey][tillitis-tkey] in rust.
__warning__ this library is still in development. There may be API-breaking changes in the future.
The crate provides:
- basic set-up for a new rust-based TKey application,
- functions to interact with the TKey device (encapsulating `unsafe` operations),
This library and the proposed set-up is chosen to keep things simple. The set-up itself requires no external dependencies and the linker-script and initial assembly-code are almost exactly as provided by [tillitis].
__note__ a few points of attention:
- `random(out, seed)` is not yet sufficiently mature. It is based on a persistent buffer of random bytes and 4-byte input from TRNG. Until new entropy is available, currently sampled entropy is used to derive new unpredictable bits. There is undoubtedly room for improvement.
- `rustTkey` is provided to enable development in Rust, as opposed to a statement against C. Although Rust is praised for safer, more secure applications, I do not think the difference is always as big as proclaimed. Applications written in C, e.g. with a stack-only development approach, already avoid some issues. It may prove beneficial to consider the larger ecosystem before choosing either one.
- `rusTkey` _will panic_ in case of improper use: there is no point in obscuring or working around incorrect uses. There is more risk in making things seem okay. Instead, _assertions_ and _panics_ will signal improper use (or a bug in _rusTkey_).
## Changes
Changelog:
__0.4.2__
- Change license to _2-Clause BSD License_, from _GPL-2.0 only_. This follows the [recent license change]( "Change of Source Code License (October 2, 2024)") by Tillitis.
- `hash_firmware_rom(key)` for calculating a hash of the TKey device's firmware-ROM. It computes a hash over the complete available ROM-space. (Experimental)
__0.4.1__
- `cpumonitor::monitor_application_memory`: fix address of "last" in monitoring range.
__0.4.0__
- Module `io`:
- Added functions for controlled reading: `read_available`, `read_into_checked`, `read_into_timed`, `read_into_limited`, `read_checked`, `read_timed`.
- Change `available()` to return `usize`, which is the more appropriate data-type for what it represents.
- Added `configuration()`, `configure(bitrate, data_bits, stop_bits)` for configuring UART I/O operation. See documentation for parameter specifics. `bitrate` is expressed in the representation used by the device.
- `rustkey::Error`: added variants `Underrun` and `Timeout` used by controlled-read functions.
- `timer::PRESCALE_MILLISECONDS`, also used for timed-read functions.
- `rustkey::TK1_CPU_FREQUENCY` as the CPU frequency of the TKey1.
__0.3.1__
- `rustkey::random`: proper unsafe access to buffer without using `&mut` ([`static_mut_refs`]( "rustc book: static_mut_refs (will be forbidden in rust edition 2024)"))
__0.3.0__
- `rustkey::random`: remix existing TRNG entropy if TRNG is not yet ready. Given TRNG's rate of approx. 66.6 times per second, this improves performance.
- Renamed `bench` from `bench_trng`, as it tests a few more aspects of TKey.
- Moved changelog into `README.md` from `CHANGELOG.md`, to make it available wherever the README is readable.
__0.2.0__
- Module `cpumonitor`: `monitor_range` allow `first` and `last` to be same address.
- Module `timer`:
- provide `PRESCALE_SECONDS` prescaler constant.
- provide `sleep` for timer-based blocking sleep in seconds.
- Module `touch`: tweak, improve `request`.
- Module `trng`:
- comment on production-rate of entropy
- add `read_next`, rename `read_next_bytes` to `read_bytes_next`.
- Module `frame` provides `LENGTH_MAX` for maximum length of frame according to the protocol.
- `Error` derives `Debug` trait.
- `README.md`: comments documenting some configuration options, ref for minimizing rust binaries, clean-up.
- `bench_trng`: application used to perform repeated sampling of TRNG entropy for purpose of testing performance. Seems to be stable at approximately 66.6 samples per second.
__0.1.0__
Initial release.
## Open issues
These are known open issues.
- `cargo build` warns of an unstable feature. Maybe we should file an issue for this.
```
cargo build --release --bins
warning: unknown and unstable feature specified for `-Ctarget-feature`: `zmmul`
|
= note: it is still passed through to the codegen backend, but use of this feature might be unsound and the behavior of this feature can change in the future
= help: consider filing a feature request
```
Apart from items listed here, I leave `TODO` comments for things that need to be considered, even if they turn out to be irrelevant.
## Decisions
- No heap/dynamic memory allocation (for now).
- use [crate 'heapless'][crate-heapless] for some data structures implementation on stack.
- Not thread-safe (not needed until `std` or customized threading).
- No use of dependencies w.r.t. bootstrapping the execution (such as [rust-embedded/riscv]):
1. adds a layer of intransparency, non-straightforward logic.
1. more comprehensive initialization which addresses features that are not supported, performing initialization that is not needed.
1. instead, when initializing in global assembly, execution from entry-point (linker-script) to initialization to `main` is obvious.
- Incorrect use of the functions may result in panic.
- Implementations (in rare cases) may change.
For example, if `random` can be improved within reasonable constraints.
## Getting started
The following describes the application set-up needed to build app binaries for loading onto the TKey.
The following steps are required:
- Configure the build for `riscv32i-unknown-none-elf`, with additional CPU features '`c`' (compressed instructions) and '`Zmmul`' (multiplication instructions only, i.e. not division). Other build-options contribute to a smaller binary such that it fits in 128 KiB of memory.
- Apply a suitable linker-script.
- Create an entry-point that performs basic initialization of the TKey device for the application.
- Use `llvm-objcopy` to create the raw bare-bones application binary for the [tillitis TKey][tillitis-tkey].
- The application: [`src/main.rs`]( "main.rs - a basic example of an application") is an example that uses this set-up and demonstrates a working TKey-app.
### Configure the build
The build target `riscv32i-unknown-none-elf` must be available in the rust ecosystem.
```sh
rustup target add riscv32i-unknown-none-elf
```
The following build configuration enables the necessary features and options.
`.cargo/config.toml`:
```toml
[build]
target = "riscv32i-unknown-none-elf"
[profile.dev]
panic = "abort"
[profile.release]
codegen-units = 1 # No parallelism, may increase ability to optimize.
debug = false # Do not produce a debug-build.
debug-assertions = false # Drop debug-assertions.
opt-level = 3 # Perform a high level of optimization, also 'z' to specifically target size.
overflow-checks = false
strip = true # Currently translates to 'strip = "symbols"', i.e. strip all excess information.
lto = true # Perform link-time optimization, to further optimize and reduce binary size.
panic = "abort"
[target.riscv32i-unknown-none-elf]
rustflags = [
"-Ctarget-feature=+c,+zmmul",
"-Ccode-model=medium",
"-Cpanic=abort",
"-Crelocation-model=pic",
"-Clink-arg=-Tapp.lds",
]
```
This configuration applies to `--release` builds.
Note: the `panic` settings _should_ obviate the need for `.eh_frame` section in the linker-script, but this is not currently the case.
### Linker-script
The following linker-script defines the memory region for the TKey.
Changes to the original linker-script:
- defining `_stack_start` symbol such that this address can be modified outside of assembly-code.
- `_stack_start` is assigned a different value: '`ORIGIN(RAM)+LENGTH(RAM)`' from the original value '`0x4001fff0`'. The original value is slightly lower, which I suspect is unnecessary given that the _stack-pointer_ is manipulated before use. ([issue]( "Why not set stack-pointer to end of stack?"))
- added `*(.eh_frame)` to the `.text` region.
This should not be necessary, because `panic=abort` should obviate the need for `.eh_frame` section according to many references and from my own understanding, as it disables stack-unwinding. There should be no need to keep extra data. This changed, however, quite suddenly with no clear explanation. Adding the `.eh_frame` section produces a memory-layout that accomodates for this, although it would be better if we could drop it completely.
```lds
/*
* SPDX-FileCopyrightText: 2022 Tillitis AB
* SPDX-License-Identifier: BSD-2-Clause
*
* Modified for rusTkey. (See README.md for details.)
*/
OUTPUT_ARCH( "riscv" )
ENTRY(_start)
MEMORY
{
RAM (rwx) : ORIGIN = 0x40000000, LENGTH = 0x20000 /* 128 KB */
}
SECTIONS
{
.text.init :
{
*(.text.init)
} >RAM
.text :
{
. = ALIGN(4);
*(.text) /* .text sections (code) */
*(.text*) /* .text* sections (code) */
*(.rodata) /* .rodata sections (constants, strings, etc.) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
*(.srodata) /* .rodata sections (constants, strings, etc.) */
*(.srodata*) /* .rodata* sections (constants, strings, etc.) */
*(.eh_frame)
. = ALIGN(4);
_etext = .;
_sidata = _etext;
} >RAM
.data : AT (_etext)
{
. = ALIGN(4);
_sdata = .;
. = ALIGN(4);
*(.data) /* .data sections */
*(.data*) /* .data* sections */
*(.sdata) /* .sdata sections */
*(.sdata*) /* .sdata* sections */
. = ALIGN(4);
_edata = .;
} >RAM
/* Uninitialized data section */
.bss :
{
. = ALIGN(4);
_sbss = .;
*(.bss)
*(.bss*)
*(.sbss)
*(.sbss*)
*(COMMON)
. = ALIGN(4);
_ebss = .;
} >RAM
}
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
```
### Create an entry-point
The following (global) assembly-code create the entry-point `_start` at which the device is initialized for the loaded application, after which function `main` is called.
The response of the device on returning from '`main`' seems to deviate based on the optimization-level of the application binary. It is best to define main as '`#[no_mangle] extern "C" fn main() -> !`', i.e. non-returning as to avoid running into this situation.
Changes to the original assembly-code:
- to expect symbol `_stack_start` to be defined in the linker-script for initialization of the stack-pointer.
```rs
// Note: this assembly provides the initialization procedure that clears memory and finally calls
// `main` to start execution of the app-binary.
global_asm!(
".section \".text.init\"",
".global _start",
"_start:",
"li x1, 0",
"li x2, 0",
"li x3, 0",
"li x4, 0",
"li x5, 0",
"li x6, 0",
"li x7, 0",
"li x8, 0",
"li x9, 0",
"li x10,0",
"li x11,0",
"li x12,0",
"li x13,0",
"li x14,0",
"li x15,0",
"li x16,0",
"li x17,0",
"li x18,0",
"li x19,0",
"li x20,0",
"li x21,0",
"li x22,0",
"li x23,0",
"li x24,0",
"li x25,0",
"li x26,0",
"li x27,0",
"li x28,0",
"li x29,0",
"li x30,0",
"li x31,0",
/* init stack below 0x40020000 (TK1_RAM_BASE+TK1_RAM_SIZE) */
"la sp, _stack_start",
/* zero-init bss section */
"la a0, _sbss",
"la a1, _ebss",
"bge a0, a1, end_init_bss",
"loop_init_bss:",
"sw zero, 0(a0)",
"addi a0, a0, 4",
"blt a0, a1, loop_init_bss",
"end_init_bss:",
"call main",
options(raw)
);
```
### Create the raw application-binary
Use '`llvm-objcopy --input-target=elf32-littleriscv --output-target=binary target/riscv32i-unknown-none-elf/release/example example.bin`' to produce the raw binary data for loading onto the TKey.
### The application
At this point, the set-up is done up to entering the application at function `main`.
There are a few recommendations:
- '`#![no_std]`' to prevent standard library from being included. This also means that `std::*` is unavailable, although most primitive functions can also be found at `core::*`.
- '`#![no_main]`' to indicate that we provide our own entry-point into the application
- '`#[no_mangle] extern "C" fn main() -> !`' as our definition for main, such that:
- `main` can be found after initialization,
- `#[no_mangle] extern "C"` to ensure availability under the expected, unchanged function-name,
- _non-returning_, to avoid running into undefined behavior and/or making the device unavailable.
- need to define a `#[panic_handler]`, given that the run-time is minimized.
- [heapless][crate-heapless] provides static-friendly data-structures that do not require dynamic memory allocation.
An basic example [`src/main.rs`]( "main.rs - a basic example of an application") can be found in this code-base.
## Alternative
An alternative approach, is to use a `#![feature(start)]` macro with `#[start]` annotated `fn main()`. This is currently only possible if Rust _nightly_ features are activated. This, together with appropriate build configuration to ensure that `main` as a function is available in unmangled form, is sufficient get the application to run. Unfortunately, this does not resolve the issue with `.eh_frame` described above.
For now, this crate will keep using the `global_asm!` macro in an application. This creates a straight-forward path from linker-script (`app.lds`) to global assembly (`_start`) to calling `main()`. This also leaves open possibilities for a call to a set-up of _stack-protector_ and other security-features that need early initialization, with everything concentrated within the application.
## LICENSE
```txt
Copyright 2024 D. van Heumen
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
```
See [`LICENSE`][LICENSE] for the full license.
## References
- [Minimizing Rust Binary Size]( "Minimizing Rust Binary Size (github.com/johnthagen/min-sized-rust)")
- Note to self: [Last evaluated: 66a1fd90eead93d9e0472a3a1a88e185ae8c1761]( "Last time I checked the advice for rust binary minimization.")
- `TODO support for heap memory (dynamic allocation)?`
- [Embedded RiscV][rust-embedded/riscv] (riscv, riscv-rt, etc.) code-base
- [Heap Allocation]( "Heap Allocation - writing an OS in Rust")
[LICENSE]: "2-Clause BSD License"
[tillitis]: "tillitis"
[tillitis-tkey]: "TKey - tillitis"
[crate-heapless]: "Crate 'heapless', on docs.rs (github.com/rust-embedded/heapless)"
[rust-embedded/riscv]: "RiscV support in the Rust-Embedded project (github)"