# 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)"