Zephyr×Rustのインテグレーションにチャレンジ!⑥~RustでDriverを書く!~
はじめに
ZephyrとRustのインテグレーションに挑戦しています。
これまでで、アプリケーションをRustで書いてきました。 ここからは、Driverを書く方法を調査していきます。
前回までのあらすじ
RustでZephyrのDriverを書くためには、INIT_DEVICE
マクロ相当のことをすればよいことがわかりました。
具体的には、.devconfig.init
と.init_<priority level>
に、device_config
とdevice
オブジェクトを配置します。
bindgenで自動されたbindingは次の通りでした。これをグローバル領域に置いていきます。
#[repr(C)] #[derive(Debug, Copy, Clone)] pub struct device_config { pub name: *const cty::c_char, pub init: ::core::option::Option<unsafe extern "C" fn(device: *mut device) -> cty::c_int>, pub config_info: *const cty::c_void, } #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct device { pub config: *mut device_config, pub driver_api: *const cty::c_void, pub driver_data: *mut cty::c_void, }
Syncの洗礼
とりあえず、雑に試してみます。
#[link_section = ".init_POST_KERNEL40"] static __DEVICE_MY_DEVICE: zephyr::device = zephyr::device { config: core::ptr::null_mut(), driver_api: core::ptr::null_mut(), driver_data: core::ptr::null_mut() };
ビルドしてみます。
error[E0277]: `*mut bindings::device_config` cannot be shared between threads safely --> src/lib.rs:31:1 | 31 | / static __DEVICE_MY_DEVICE: zephyr::device = zephyr::device { 32 | | config: core::ptr::null_mut(), 33 | | driver_api: core::ptr::null_mut(), 34 | | driver_data: core::ptr::null_mut() 35 | | }; | |__^ `*mut bindings::device_config` cannot be shared between threads safely | = help: within `bindings::device`, the trait `core::marker::Sync` is not implemented for `*mut bindings::device_config` = note: required because it appears within the type `bindings::device` = note: shared static variables must have a type that implements `Sync`
あー、生ポインタ型なので、Syncトレイトが実装されていない、と…。 どちらにしても、bindgenから自動生成した構造体を使うのは無理そうですね。
真面目にやるのであれば、生ポインタを包むNewtype型を作って、Syncトレイトを実装する方法があります。
pub struct MY_PTR(*mut bindings::device_config); unsafe impl Sync for MY_PTR {}
こうなると、自分で構造体を定義する必要があるので、一回動くまでは、楽をします。
device_config
およびdevice
と等価になる構造体を用意します。
#[repr(C)] #[derive(Debug, Copy, Clone)] pub struct DeviceConfig { pub name: usize, // 初期化関数の関数ポインタ pub init: unsafe extern "C" fn(device: *mut Device) -> cty::c_int, pub config_info: usize, } #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct Device { pub config: &'static DeviceConfig, pub driver_api: usize, pub driver_data: usize, } unsafe impl Sync for DeviceConfig {} unsafe impl Sync for Device {}
これでバイナリ上の辻褄は合うはずです。
実装
かなり力技で、褒められた実装ではないですが、次のように実装します。
unsafe extern "C" fn my_init(_device: *mut Device) -> cty::c_int { println!("Hello from My Driver!\n"); 0 } #[link_section = ".devconfig.init"] static __CONFIG_MY_DEVICE: DeviceConfig = DeviceConfig { name: 0, init: my_init, config_info: 0 }; #[link_section = ".init_POST_KERNEL40"] static __DEVICE_MY_DEVICE: Device = Device { config: &__CONFIG_MY_DEVICE, driver_api: 0, driver_data: 0 };
結果
$ ninja run Recompacting log... [1/1] To exit from QEMU enter: 'CTRL+a, x'[QEMU] CPU: cortex-m3 qemu-system-arm: warning: nic stellaris_enet.0 has no peer Hello from My Driver! ***** Booting Zephyr OS v1.13.99-ncs2-10-gdad14f4 ***** Hello from Rust!
Rustで書いたDriverの初期化関数から、Hello from My Driver!
が出力されました!