Zephyr×Rustのインテグレーションにチャレンジ!③~bindgen~

はじめに

ZephyrのアプリケーションをRustで実装するインテグレーションに挑戦しています。

tomo-wait-for-it-yuki.hatenablog.com

これまでに、Rustをライブラリとしてインテグレーションすることに成功しました。 ただし、Rustから呼ぶC言語FFIは、手動で作成していました。

下記は、前回のHello worldプログラムです。ここで呼び出しているputs_c()は、自分でFFIを用意した関数です。

#[no_mangle]
pub extern "C" fn rust_main() {
    const HELLO: &[u8] = b"Hello from Rust.\0";
    unsafe { puts_c(HELLO.as_ptr()) };
}

今回は、Zephyr APIFFIを自動生成します。 ZephyrのprintkをRustから呼び出して、Hello worldします。

準備

Rustのbindgenを使います。

github.com

bindgenをインストールしておきます。

cargo install bindgen

後は、Zephyrのビルド環境があればOKです。

Let's bindgen!

まず、うまく行く手順を書きます。

  1. 一度、対象とする設定でZephyrをビルドする
  2. bindgenでbindingsを生成する
  3. Rustプロジェクトを含んだZephyrプロジェクトをビルドする

1. 一度、対象とする設定でZephyrをビルドする

この手順が必要な理由は、Zephyrのビルドプロセス中で、自動生成されるヘッダファイルが必要なためです。 自動生成されるヘッダファイルは、ビルドを実行したディレクトリの、下記パスに置かれます。bindgenでは、次のディレクトリをインクルードパスに加えます。

|> build/zephyr/include/generated/

特に、Zephyrのkernel configurationからC言語マクロを生成するヘッダファイルが重要になります。

|> build/zephyr/include/generated/autoconf.h

/* Generated by Kconfiglib (https://github.com/ulfalizer/Kconfiglib) */
#define CONFIG_BOARD "qemu_cortex_m3"
#define CONFIG_SOC "ti_lm3s6965"
#define CONFIG_NUM_IRQS 43
#define CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC 12000000
#define CONFIG_ISR_STACK_SIZE 2048
#define CONFIG_SYS_CLOCK_TICKS_PER_SEC 100
...

2. bindgenでbindingsを生成する

続いて、bindingを生成する対象となるヘッダファイルを用意します。

$ cat bindings.h
#include <autoconf.h>  // autoconf.hを最初にincludeします
#include <zephyr.h>

次のようなディレクトリ構造にしておきます。

# アプリケーションプロジェクトトップディレクトリ
$ tree -L 2
.
├── bindings
│   ├── headers
│   │   └── bindings.h
│   └── src
├── build
...
│   └── zephyr
...

bindgenを実行します。今回は、ARMがターゲットであるため、ARM固有のインクルードパスも追加します。

# アプリケーションプロジェクトのトップディレクトリで実行します
bindgen --use-core --ctypes-prefix cty bindings/headers/bindings.h -o bindings/src/kernel.rs -- -I${ZEPHYR_BASE}/include -I${ZEPHYR_BASE}/arch/arm/include -I./build/zephyr/include/generated -m32

デフォルトでは、stdクレートのC言語FFI用の型が使われてしまうため、no_std環境でも使える、ctyクレートを使うようにします。

これで、printkのbindingが生成されます。

$ grep -w -1 printk bindings/src/bindings.rs 
extern "C" {
    pub fn printk(fmt: *const cty::c_char, ...);
}

3. Rustプロジェクトを含んだZephyrプロジェクトをビルドする

自動生成したファイルには手を加えたくないため、no_stdアトリビュートを指定するためのlib.rsを用意します。

|> bindings/src/lib.rs

$ cat bindings/src/lib.rs 
#![no_std]
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(dead_code)]

pub mod bindings;
pub use bindings as zephyr;

Cargo.tomlは次の通りです。

$ tail -n 2 bindings/Cargo.toml 
[dependencies]
cty = "0.2.0"

自動生成したbindingsクレートに依存するRustアプリケーションクレートを用意します。

|> Cargo.toml

$ tail -n 6 hello/Cargo.toml 
cty = "0.2.0"
bindings = { path = "../bindings" }

[lib]
name = "rust_echo_server"
crate-type = ["staticlib"]

ソースコードは次の通りです。printkの引数がダサい感じですが、今後改善していきます。

$ cat hello/src/lib.rs 
#![no_std]

use bindings as zephyr;
use cty;

#[no_mangle]
pub extern "C" fn rust_main() {
    unsafe {
        zephyr::printk(b"Hello from %s\0".as_ptr() as *const cty::c_char,
                       b"Rust\0".as_ptr());
    }
}

動作確認

C言語のmainは次の通りです。

void main(void)
{
        printk("Hello World! %s\n", CONFIG_BOARD);
        rust_main();
}

ビルドして実行します。

$ mkdir build && cd $_
$ cmake -GNinja -DBOARD=qemu_cortex_m3 ..
$ ninja run
***** Booting Zephyr OS v1.13.99-ncs2-5-g13a04b1 *****
Hello World! qemu_cortex_m3
Hello from Rust

無事、Rustからprintk経由で出力できました!