Rustで組み込み Lチカ

# Rustで組み込み Lチカ

RustでRaspberry Pi4を対象に組み込みの開発環境を整えていきます。

##  環境構築

+ cargo-binutilsのインストール
+ toolchainの追加

### cargo-binutilsのインストール

rust-lld や、rust-objcopy, rust-objdumpといった組み込みに必須のコマンドが含まれています。インストールするには

```shell-session
$ cargo install cargo-binutils
$ rustup component add llvm-tools-preview
```

を実行したらいいです。[ここ](https://github.com/rust-embedded/cargo-binutils)に詳細載ってます。

### toolchainの追加

今回Raspberry Pi4で使うtoolchainは`aarch64-unknown-none`です。target-tripleについては[ここ](https://doc.rust-lang.org/nightly/rustc/platform-support.html)を参照ください。toolchainの追加は

```shell-session
rustup target add <target-triple>
```

で行えます。今回は `rustup target add aarch64-unknown-none` です。

環境構築は以上です。stable を使ってる方はnightlyへ変更を。。。

## Lチカ

環境も整ったところで、早速Lチカコード書いていきます。

### Makefileの作成

必須ではないのですが、コマンドが長く面倒なので、作成をおすすめします。`cargo new blink_rust`を実行した後、ディレクトリ移動して以下のMakefileを作成してください。

```Makefile:blink/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用のファイルとブート用のアセンブリを作成します。[ここ](https://isometimes.github.io/rpi4-osdev/part1-bootstrapping/)を参考にしています。アセンブリについては、Rustのコード内で、bssセクションの初期化を行っているため、それに当たる部分を削除しています。

```:blink/src/link.ld
/* 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;
```

```assembly:blink/src/boot.S
    .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のコードを作成していきます。

```Rust:blink/src/main.rs
#![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 {}
}
```

```Rust:blink/src/gpio.rs
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);
    }
}
```

```rust:blink/src/boot.rs
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というディレクトリを作って、

```toml:.cargo/config.toml
[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に比べてめっちゃ命令数が多い。。。