Zephyr×Rustのインテグレーションにチャレンジ!⑤~続RustでDriverを書くための調査~

はじめに

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

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

DEVICE_INITマクロ

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

前回記事で、DEVICE_INITマクロさえ呼べれば、アプリケーションプロジェクト内でdriverが書けることがわかりました。 そこで、DEVICE_INITマクロを少し深堀りします。

DEVICE_INITは、DEVICE_AND_API_INITのラッパーです。

|> zephyr/include/device.h

#define DEVICE_INIT(dev_name, drv_name, init_fn, data, cfg_info, level, prio) \
   DEVICE_AND_API_INIT(dev_name, drv_name, init_fn, data, cfg_info,      \
               level, prio, NULL)

DEVICE_AND_API_INITは、CONFIG_DEVICE_POWER_MANAGEMENTが有効か無効か、で定義が異なります。 今回は、無効な場合を見ます。

#define DEVICE_AND_API_INIT(dev_name, drv_name, init_fn, data, cfg_info,  \
               level, prio, api)                 \
   static struct device_config _CONCAT(__config_, dev_name) __used     \
   __attribute__((__section__(".devconfig.init"))) = {        \
       .name = drv_name, .init = (init_fn),              \
       .config_info = (cfg_info)                 \
   };                                \
   static struct device _CONCAT(__device_, dev_name) __used    \
   __attribute__((__section__(".init_" #level STRINGIFY(prio)))) = { \
       .config = &_CONCAT(__config_, dev_name),          \
       .driver_api = api,                    \
       .driver_data = data                   \
   }

パッと見た感じ、特定のセクション (.devconfig.init.init_<priority level>) に、device_configdeviceオブジェクトを配置していることがわかります。 この時のオブジェクト名は、それぞれ、__config_<dev_name>__device_<dev_name>になります。 (わかりにくい…)

バイナリを確認してみましょう。前回、次のようにデバイスを定義しました。

DEVICE_INIT(my_device,
            "MY_DRIVER",
            my_init,
            NULL,
            NULL,
            POST_KERNEL,
            CONFIG_KERNEL_INIT_PRIORITY_DEFAULT );

zephyr.elfを覗いてみると、該当するシンボルがありません。あれ??

予想を外してしまったので、中間生成物のlibapp.aを覗いてみます。

Disassembly of section .devconfig.init:

00000000 <__config_my_device>:
        ...

Disassembly of section .init_POST_KERNEL40:

00000000 <__device_my_device>:
        ...

ありますね。最終リンク時にシンボル情報が消えるのかもしれません。 リンカのマップ情報を見ます。

devconfig       0x000000000000183c       0x6c
                0x000000000000183c                __devconfig_start = .
 *(.devconfig.*)
 .devconfig.init
                0x000000000000183c        0xc libapp.a(main.c.obj)
 .devconfig.init
                0x0000000000001848        0xc zephyr/libzephyr.a(soc.c.obj)

居ますね。サイズも12バイト (name, init, config_info) なので良さそうです。 deviceの方も、ちゃんといます。

 .init_POST_KERNEL40
                0x0000000020000fa0        0xc libapp.a(main.c.obj)
                0x0000000020000fac                __device_APPLICATION_start = .

Rustでやることは?

まとめると、device_config構造体とdevice構造体のオブジェクトを、ユニークな名前で、特定セクション (.devconfig.init.init_<priority level>) に置けば良いわけです。

ZephyrのRust bindingを作ったときに、上記の構造体は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,
}

ということで、恐らく、次のようにRustでdriverを書けるはずです。

#[no_mangle]
pub unsafe extern "C" fn my_init(device: *mut device) -> cty::c_int {
...
}

#[link_section = ".devconfig.init"]
#[no_mangle]
pub static __CONFIG_MY_DEVICE: unsafe extern "C" zephyr::device_config {
    .drv_name = b"my_driver",
    .init = Some(my_init),
    .config_info = core::ptr::null
};

#[link_section = ".init_POST_KERNEL40"]
#[no_mangle]
pub static __DEVICE_MY_DEVICE: unsafe extern "C" zephyr::device {
    .config = core::ptr::null,
    .driver_api = core::ptr::null,
    .driver_data = core::ptr::null
};

次は、実際に試してみましょう。