5 个版本 (3 个重大更新)
0.4.0 | 2024 年 5 月 2 日 |
---|---|
0.3.1 | 2024 年 4 月 12 日 |
0.3.0 | 2024 年 4 月 8 日 |
0.2.0 | 2024 年 4 月 2 日 |
0.1.0 | 2024 年 3 月 12 日 |
#204 in 嵌入式开发
63KB
847 行
rusTkey ("rusty key")
一个用于在 rust 中开发 tillitis TKey 的裸机应用程序的库。
警告 该库仍在开发中。未来可能会有破坏 API 的更改。
FIXME 考虑使用 GPLv3 许可证,因为与 GPLv2 以及 Apache 2.0 等知名许可证存在兼容性问题。
该crate提供
- 为新的基于 rust 的 TKey 应用程序的基本设置
- 与 TKey 设备交互的函数(封装
unsafe
操作)
该库和所提议的设置旨在保持简单。该设置本身不需要外部依赖,链接器脚本和初始汇编代码几乎与 tillitis 提供的完全一致。
注意 几点注意事项
rustTkey
提供,以支持 Rust 中的开发,而不是针对 C 的声明。虽然 Rust 因其更安全、更安全的程序而受到赞誉,但我认为这种差异并不总是像宣称的那么大。用 C 编写的应用程序,例如采用仅栈开发方法的应用程序,已经避免了某些问题。在选择之前考虑更大的生态系统可能是有益的。rusTkey
在不正确使用的情况下会 panic:没有必要掩盖或规避不正确的使用。这样做风险更大。相反,断言 和 panic 将表明不正确的使用(或 rusTkey 中的错误)。
更改
变更日志
0.4.0
- 模块
io
- 添加了以下受控读取函数:
read_available
、read_into_checked
、read_into_timed
、read_into_limited
、read_checked
、read_timed
。 - 将
available()
的返回类型更改为usize
,这是它所表示的更合适的数据类型。 - 添加了配置,用于配置 UART I/O 操作:
configuration()
,configure(bitrate, data_bits, stop_bits)
。有关参数详细信息,请参阅文档。《code>bitrate 以设备使用的表示形式表示。
- 添加了以下受控读取函数:
rustkey::Error
:添加了由受控读取函数使用的变体Underrun
和Timeout
。timer::PRESCALE_MILLISECONDS
,也用于定时读取函数。rustkey::TK1_CPU_FREQUENCY
作为 TKey1 的 CPU 频率。
0.3.1
rustkey::random
:在不使用&mut
的情况下(static_mut_refs
)对缓冲区进行适当的不安全访问。
0.3.0
rustkey::random
:如果 TRNG 尚未准备好,则重新混合现有的 TRNG 混沌熵。鉴于 TRNG 的速率约为每秒 66.6 次,这提高了性能。- 将
bench
从bench_trng
重命名为,因为它测试了 TKey 的更多方面。 - 将更改日志从
CHANGELOG.md
移动到README.md
,以便在可以读取 README 的任何地方都可以访问。
0.2.0
- 模块
cpumonitor
:允许first
和last
使用相同的地址。 - 模块
timer
- 提供
PRESCALE_SECONDS
预分频常量。 - 提供基于定时器的阻塞睡眠的
sleep
。
- 提供
- 模块
touch
:调整并改进request
。 - 模块
trng
- 关于熵的生产速率的注释
- 添加
read_next
,将read_next_bytes
重命名为read_bytes_next
。
- 模块
frame
提供了根据协议的最大帧长度的LENGTH_MAX
。 Error
继承了Debug
特性。README.md
:注释记录了一些配置选项,关于最小化 rust 二进制文件的参考,清理。bench_trng
:用于重复采样 TRNG 混沌熵以进行性能测试的应用程序。似乎每秒稳定在约 66.6 个样本。
0.1.0
初始发布。
开放问题
这些都是已知的开放问题。
rustkey::random
表示一个应(某种程度上)具有密码学安全的随机字节生成器。这尚未得到验证。
它使用固件中的blake2s
来生成随机数,使用持久性缓冲区来维护使用之间的某些状态,并使用来自 TRNG 的字节将新的熵引入混合。使用seed
参数可以手动注入额外的熵。cargo build
警告不稳定功能。也许我们应该为此创建一个问题。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
除了这里列出的项目外,我还为需要考虑的事情留下了 TODO
注释,即使它们最终被证明是不相关的。
决策
- 不使用堆/动态内存分配(目前)。
- 使用crate 'heapless'在栈上实现某些数据结构。
- 不线程安全(直到使用
std
或自定义线程时才需要)。 - 在启动执行方面不使用依赖项(例如rust-embedded/riscv)
- 增加了一层不透明性,非直接的逻辑。
- 更全面的初始化,解决了不支持的功能,执行了不必要的初始化。
- 相反,在全局汇编中初始化时,从入口点(链接脚本)到初始化再到
main
的执行是明显的。
- 不正确使用函数可能会导致panic。
- 实现(在罕见情况下)可能会更改。
例如,如果random
在合理约束下可以改进。
入门
以下描述了构建加载到TKey的应用程序二进制文件所需的应用程序设置。
以下步骤是必须的
- 为
riscv32i-unknown-none-elf
配置构建,并添加额外的CPU功能 'c
'(压缩指令)和'Zmmul
'(仅乘法指令,即不除法)。其他构建选项有助于生成更小的二进制文件,以便它可以装入128 KiB的内存。 - 应用合适的链接脚本。
- 创建一个入口点,用于执行应用程序的TKey设备的基本初始化。
- 使用
llvm-objcopy
为tillitis TKey创建原始裸机应用程序二进制文件。 - 应用程序:
src/main.rs
是一个使用此设置并演示工作TKey-app的示例。
配置构建
构建目标riscv32i-unknown-none-elf
必须在rust生态系统中可用。
rustup target add riscv32i-unknown-none-elf
以下构建配置启用了必要的功能和选项。
.cargo/config.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",
]
此配置适用于--release
构建。
注意:panic
设置应该消除在链接脚本中需要.eh_frame
部分的需求,但当前并非如此。
链接脚本
以下链接脚本定义了TKey的内存区域。
对原始链接脚本的变化
- 定义
_stack_start
符号,以便可以在汇编代码之外修改此地址。 _stack_start
被分配了不同的值:'ORIGIN(RAM)+LENGTH(RAM)
'从原始值'0x4001fff0
。原始值略低,我怀疑这是不必要的,因为在使用之前已经操纵了堆栈指针。(问题)- 向
.text
区域添加了*(.eh_frame)
。
这不应该有必要,因为panic=abort
应该会消除对.eh_frame
节的需要,根据许多参考资料和我的理解,因为它禁用了堆栈展开。不应该需要保留额外数据。然而,这种改变却非常突然,没有明确的解释。添加.eh_frame
节会产生一个适应这种情况的内存布局,尽管如果我们能完全去掉它会更好。
/*
* 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);
创建入口点
以下(全局)汇编代码创建入口点 _start
,在此之后设备被初始化用于加载的应用程序,之后调用函数 main
。
设备从 'main
' 返回时的响应似乎取决于应用程序二进制文件的优化级别。最好将 main 定义为 '#[no_mangle] extern "C" fn main() -> !
',即非返回,以避免遇到这种情况。
对原始汇编代码的修改
- 以期望符号
_stack_start
在连接器脚本中定义,用于初始化堆栈指针。
// 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)
);
创建原始应用程序二进制文件
使用 'llvm-objcopy --input-target=elf32-littleriscv --output-target=binary target/riscv32i-unknown-none-elf/release/example example.bin
' 生成加载到 TKey 上的原始二进制数据。
应用程序
此时,设置已经完成,直到进入函数 main
的应用程序。
有一些推荐
- '
#![no_std]
' 以防止包含标准库。这也意味着std::*
不可用,尽管大多数基本函数也可以在core::*
中找到。 - '
#![no_main]
' 以指示我们提供了进入应用程序的自己的入口点 - 定义 '
#[no_mangle]extern"C"fnmain()->!
' 作为我们 main 的定义,以便main
可以在初始化后找到,#[no_mangle]extern"C"
确保在预期的未更改的函数名下可用,- 不返回,以避免遇到未定义的行为和/或使设备不可用。
- 因为运行时间最小化,所以需要定义一个
#[panic_handler]
。 - heapless 提供了无需动态内存分配的静态友好数据结构。
在此代码库中可以找到基本示例 src/main.rs
。
替代方案
另一种方法,是使用 #![feature(start)]
宏与 #[start]
注释的 fnmain()
。这目前仅在激活 Rust nightly 功能时才可行。这,加上适当的构建配置,以确保 main
作为函数以未混淆的形式可用,足以让应用程序运行。不幸的是,这并不能解决上面描述的 .eh_frame
问题。
目前,此 crate 将在应用程序中使用 global_asm!
宏。这从链接脚本 (app.lds
) 到全局汇编 (_start
) 到调用 main()
创建了一条直接路径。这也为调用需要早期初始化的 堆栈保护 和其他安全功能留下了可能性,所有这些都集中在应用程序中。
许可
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.
有关完整许可信息,请参阅 LICENSE
。
参考
- 最小化 Rust 二进制大小
- 备注:上次检查 rust 二进制最小化建议的时间:66a1fd90eead93d9e0472a3a1a88e185ae8c1761
待办事项支持for堆内存(动态分配)?