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_config
とdevice
オブジェクトを配置していることがわかります。
この時のオブジェクト名は、それぞれ、__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 };
次は、実際に試してみましょう。