Zephyr×Rustのインテグレーションにチャレンジ!⑩~UART Driver 送信編~
はじめに
ZephyrとRustのインテグレーションに挑戦しています。
RustでデバイスDriverを作っていきます。
まずは、QEMUのUARTをターゲットにします。
QEMUのUARTデバイスはガバガバで、初期化を行わなくてもデータ送受信ができるので、敷居が低いです。
やることのおさらい
前回、Rustで次のようにDriver登録に必要な情報を作成しました。
#[link_section = ".init_POST_KERNEL40"] static __DEVICE_MY_DEVICE: Device = Device { config: &__CONFIG_MY_DEVICE, driver_api: 0, driver_data: 0 }; #[link_section = ".devconfig.init"] static __CONFIG_MY_DEVICE: DeviceConfig = DeviceConfig { name: 0, init: my_init, config_info: 0 };
やることは2つです。
poll_out
を実装して、driver_api
に登録するname
にUART_0
を登録する
bindingから使えるものを探す
UART_0
は、下の参照を引っ張ってくれば良さそうです。
pub const CONFIG_UART_CONSOLE_ON_DEV_NAME: &'static [u8; 7usize] = b"UART_0\0";
UART0のベースアドレスは、下記の定義を使いましょう。
pub const UART_0_BASE_ADDRESS: u32 = 1073790976;
uart_driver_api
のbindingは次の通り生成されています。
#[doc = " @brief Driver API structure."] #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct uart_driver_api { #[doc = " Console I/O function"] pub poll_in: ::core::option::Option< unsafe extern "C" fn(dev: *mut device, p_char: *mut cty::c_uchar) -> cty::c_int, >, pub poll_out: ::core::option::Option<unsafe extern "C" fn(dev: *mut device, out_char: cty::c_uchar)>, #[doc = " Console I/O function"] pub err_check: ::core::option::Option<unsafe extern "C" fn(dev: *mut device) -> cty::c_int>, #[doc = " UART configuration functions"] pub configure: ::core::option::Option< unsafe extern "C" fn(dev: *mut device, cfg: *const uart_config) -> cty::c_int, >, pub config_get: ::core::option::Option< unsafe extern "C" fn(dev: *mut device, cfg: *mut uart_config) -> cty::c_int, >, }
いらないAPIはNone
にしておけば良さそうです。
パーツは揃っていそうなので、早速やっていきましょう。
uart_driver_api
use bindings::zephyr::{self, device, uart_driver_api}; unsafe extern "C" fn rust_poll_out(_dev: *mut device, _out_char: cty::c_uchar) { () } static UART_API: uart_driver_api = uart_driver_api { poll_out: Some(rust_poll_out), poll_in: None, err_check: None, configure: None, config_get: None, };
このような実装でビルドが通ります。
deviceとdevice_config
ひとまず、具体的な型を愚直に書きます。トレイト、ジェネリクス、関連型を使えば、もっと良くできると思います。が、それは今後の課題、ということで。
pub struct Device { pub config: &'static DeviceConfig, - pub driver_api: usize, + pub driver_api: &'static zephyr::uart_driver_api, pub driver_data: usize, } pub struct DeviceConfig { - pub name: usize, + pub name: &'static [u8], pub init: unsafe extern "C" fn(device: *mut Device) -> cty::c_int, pub config_info: usize, }
#[link_section = ".init_POST_KERNEL40"] static __DEVICE_MY_DEVICE: Device = Device { config: &__CONFIG_MY_DEVICE, - driver_api: 0, + driver_api: &UART_API, driver_data: 0 }; #[link_section = ".devconfig.init"] static __CONFIG_MY_DEVICE: DeviceConfig = DeviceConfig { - name: 0, + name: zephyr::CONFIG_UART_CONSOLE_ON_DEV_NAME, init: my_init, config_info: 0 };
一度動作確認
さて、ここまでで空の実装ができました。 ビルドして実行しても、HardFaultにはならないはずです。
$ ninja run [0/1] To exit from QEMU enter: 'CTRL+a, x'[QEMU] CPU: cortex-m3 qemu-system-arm: warning: nic stellaris_enet.0 has no peer # 予想通りここでブロックします
rust_poll_outの実装
ターゲットのUART (stellaris-uart) では、ベースアドレスに送信用レジスタがあります。 お行儀が悪いですが、次のように実装して、本当に動くかどうか、確かめてみましょう。
unsafe extern "C" fn rust_poll_out(_dev: *mut device, out_char: cty::c_uchar) { *(zephyr::UART_0_BASE_ADDRESS as *mut u32) = out_char as u32; }
動作確認
$ ninja run [0/1] To exit from QEMU enter: 'CTRL+a, x'[QEMU] CPU: cortex-m3 qemu-system-arm: warning: nic stellaris_enet.0 has no peer ***** Booting Zephyr OS 1.13.99 ***** Hello from Rust!
動いちゃいました!