Rustで組み込み Lチカ
RustでRaspberry Pi4を対象に組み込みの開発環境を整えていきます。
環境構築
-
- cargo-binutilsのインストール
- toolchainの追加
cargo-binutilsのインストール
rust-lld や、rust-objcopy, rust-objdumpといった組み込みに必須のコマンドが含まれています。インストールするには
$ cargo install cargo-binutils
$ rustup component add llvm-tools-preview
を実行したらいいです。ここに詳細載ってます。
toolchainの追加
今回Raspberry Pi4で使うtoolchainはaarch64-unknown-noneです。target-tripleについてはここを参照ください。toolchainの追加は
rustup target add <target-triple>
で行えます。今回は rustup target add aarch64-unknown-none です。
環境構築は以上です。stable を使ってる方はnightlyへ変更を。。。
Lチカ
環境も整ったところで、早速Lチカコード書いていきます。
Makefileの作成
必須ではないのですが、コマンドが長く面倒なので、作成をおすすめします。cargo new blink_rustを実行した後、ディレクトリ移動して以下のMakefileを作成してください。
##project info#############################################################
PROJECT=blink_rust
TARGET=aarch64-unknown-none
ver ?= debug
QEMU_DIST=stdio
##files####################################################################
ELF=target/$(TARGET)/$(ver)/$(PROJECT)
IMG=kernel8.img
DUMP=$(PROJECT).dump
SRCS=$(wildcard src/*.rs)
###########################################################################
all: build $(DUMP)
build : $(IMG)
dump: $(DUMP)
less $<
check: $(SRC)
cargo check
###########################################################################
$(ELF): $(SRCS)
cargo rustc
$(IMG): $(ELF)
rust-objcopy -O binary $< $@
clean:
$(RM) src/*~ *~
distclean: clean
cargo clean
$(RM) *.lock $(DUMP) $(DOC_LN) $(IMG)
$(DUMP): $(ELF)
@date > $(PROJECT).dump
rust-objdump -d $< >> $(PROJECT).dump
カーネルロードのためのアセンブリコードとlinker script
Makefile作成を終えたらlink用のファイルとブート用のアセンブリを作成します。ここを参考にしています。アセンブリについては、Rustのコード内で、bssセクションの初期化を行っているため、それに当たる部分を削除しています。
/* cite from https://isometimes.github.io/rpi4-osdev/part1-bootstrapping */
SECTIONS
{
. = 0x80000; /* Kernel load address for AArch64 */
.text : { KEEP(*(.text.boot)) *(.text .text.* .gnu.linkonce.t*) }
.rodata : { *(.rodata .rodata.* .gnu.linkonce.r*) }
PROVIDE(_data = .);
.data : { *(.data .data.* .gnu.linkonce.d*) }
.bss (NOLOAD) : {
. = ALIGN(16);
__bss_start = .;
*(.bss .bss.*)
*(COMMON)
__bss_end = .;
}
_end = .;
/DISCARD/ : { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) }
}
__bss_size = (__bss_end - __bss_start)>>3;
.section ".text.boot"
.global _start
_start:
mrs x1, mpidr_el1
and x1, x1, #3
cbz x1, 2f
1:
wfe
b 1b
2: // setting stack pointer
ldr x1, =_start
mov sp, x1
// load _rust_start
bl _rust_start
b 1b
Rustコード
ここまで作成したらRustのコードを作成していきます。
#![no_std]
#![no_main]
#![feature(asm, global_asm)]
mod boot;
mod gpio;
use gpio::*;
const MMIO_BASE: usize = 0xFE00_0000;
/// # This is the function loaded by _rust_start in boot.rs
/// see the code in boot.rs
fn main() -> ! {
// set gpio pin
let mut led = gpio::GpioPin::new(16);
led.set_function(GpioFunction::OUTPUT);
let mut dur;
loop {
dur = 30000;
while dur > 5000 {
led.set_high();
busy_wait(dur);
led.set_low();
busy_wait(dur);
dur -= 500;
}
led.set_low();
}
}
/// busy wait function
fn busy_wait(time: usize) {
for _ in 0..time {
unsafe {
asm!("");
}
}
}
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
use core::convert::{From, Into};
use core::ptr::{read_volatile, write_volatile};
pub const GPBASE: usize = crate::MMIO_BASE + 0x20_0000;
const GPFSEL: usize = GPBASE + 0x0;
const GPSET: usize = GPBASE + 0x1c;
const GPCLR: usize = GPBASE + 0x28;
pub enum PullMode {
PullNone,
PullUp,
PullDown,
}
impl From<PullMode> for u32 {
fn from(val: PullMode) -> u32 {
match val {
PullMode::PullDown => 0b10,
PullMode::PullNone => 0b0,
PullMode::PullUp => 1,
}
}
}
impl core::clone::Clone for PullMode {
fn clone(&self) -> Self {
*self
}
}
impl core::marker::Copy for PullMode {}
pub enum GpioFunction {
INPUT,
OUTPUT,
ALTF0,
ALTF1,
ALTF2,
ALTF3,
ALTF4,
ALTF5,
}
impl From<GpioFunction> for u32 {
fn from(val: GpioFunction) -> u32 {
match val {
GpioFunction::INPUT => 0,
GpioFunction::OUTPUT => 1,
GpioFunction::ALTF0 => 0b100,
GpioFunction::ALTF1 => 0b101,
GpioFunction::ALTF2 => 0b110,
GpioFunction::ALTF3 => 0b111,
GpioFunction::ALTF4 => 0b011,
GpioFunction::ALTF5 => 0b010,
}
}
}
impl core::clone::Clone for GpioFunction {
fn clone(&self) -> Self {
*self
}
}
impl core::marker::Copy for GpioFunction {}
/// struct for gpio pin
pub struct GpioPin {
pin: u32,
}
impl GpioPin {
/// make new instance witout init
pub fn new(pin: u32) -> Self {
Self { pin }
}
pub fn set_function(&self, func: GpioFunction) {
gpio_ctrl(self.pin, func.into(), GPFSEL, 3);
}
pub fn set_high(&mut self) {
gpio_ctrl(self.pin, 1, GPSET, 1);
}
pub fn set_low(&mut self) {
gpio_ctrl(self.pin, 1, GPCLR, 1);
}
}
fn gpio_ctrl(pin_num: u32, value: u32, base: usize, width: usize) {
let frame = 32 / width;
let reg = (base + (pin_num as usize / frame) * 4) as *mut u32;
let shift = ((pin_num as usize % frame) * width) as u32;
let val = value << shift;
let mask = ((1 << width as u32) - 1) << shift;
unsafe {
let tmp = read_volatile(reg); // read the previous value
write_volatile(reg, (tmp & !mask) | val);
}
}
use crate::main;
global_asm!(include_str!("boot.S"));
#[no_mangle]
fn _rust_start() {
init_bss();
main();
}
extern "C" {
static __bss_start: core::cell::UnsafeCell<u64>;
static __bss_end: core::cell::UnsafeCell<u64>;
}
fn init_bss() {
let mut ptr = unsafe { __bss_start.get() };
let end = unsafe { __bss_end.get() };
while ptr <= end {
unsafe {
core::ptr::write_volatile(ptr, 0);
ptr = ptr.offset(1);
}
}
}
以上です!
最後に、.cargoというディレクトリを作って、
[build]
target="aarch64-unknown-none"
rustflags = ["-C","link-arg=-Tsrc/link.ld"]
と入れておいてください。環境変数RUST_FLAGSでエクスポートしてもいいはずです。あと、Cargo.tomlに
[profile.dev]
panic = "abort"
を付け加えてください。
ここまで終わったらmake buildを実行すると、imgファイルが作成されるので、SDカードにコピーしてください。ラズパイの電源をいれれば、Lチカするはず。。。
ちなみに、かんたんなので回路は紹介していませんが、GPIO16につないでます。
objdump 実行するとわかると思うのですが、Cに比べてめっちゃ命令数が多い。。。