Zephyr×Rustのインテグレーションにチャレンジ!⑥~RustでDriverを書く!~

はじめに

ZephyrとRustのインテグレーションに挑戦しています。

これまでで、アプリケーションをRustで書いてきました。 ここからは、Driverを書く方法を調査していきます。

前回までのあらすじ

RustでZephyrのDriverを書くためには、INIT_DEVICEマクロ相当のことをすればよいことがわかりました。 具体的には、.devconfig.init.init_<priority level>に、device_configdeviceオブジェクトを配置します。

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!が出力されました!