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 APIのFFIを自動生成します。
Zephyrのprintk
をRustから呼び出して、Hello worldします。
準備
Rustのbindgenを使います。
bindgenをインストールしておきます。
cargo install bindgen
後は、Zephyrのビルド環境があればOKです。
Let's bindgen!
まず、うまく行く手順を書きます。
- 一度、対象とする設定でZephyrをビルドする
- bindgenでbindingsを生成する
- 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経由で出力できました!