# 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.
> FIXME consider GPLv3 license given compatibility issues with GPLv2 and well-known licenses such as Apache 2.0
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:
- `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.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.
- `rustkey::random` represents a random-bytes generator that should be (somewhat) cryptographically secure. This has not been verified.
It makes use of `blake2s` from firmware to produce randomness, with a persistent buffer to maintain some state between uses, and bytes from the TRNG to introduce new entropy into the mix. The `seed` parameter allows injecting additional entropy manually.
- `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
/*
* Copyright (C) 2022, 2023 - Tillitis AB
* SPDX-License-Identifier: GPL-2.0-only
*/
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
rusTkey, a rust crate/library that provides a development API for the
tillitis TKey.
Copyright (C) 2024 Danny van Heumen
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
```
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]: "GPL v2.0 only 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)"