Zephyr×Rustのインテグレーションにチャレンジ!⑫~Driver APIリファクタリングの旅1~

はじめに

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

Rustで最低限動作するZephyrのUART Driverを作りました。 力技で無理矢理実装したところも多いです。負債を返済するときが来ました。

バイス登録

まずは、デバイスおよびデバイスドライバをZephyr kernelに認識させるために、次のように実装していました。

#[link_section = ".init_POST_KERNEL40"]
static __DEVICE_MY_DEVICE: Device = Device {
    config: &__CONFIG_MY_DEVICE,
    driver_api: &UART_API,
    driver_data: 0
};

#[link_section = ".devconfig.init"]
static __CONFIG_MY_DEVICE: DeviceConfig = DeviceConfig {
    name: zephyr::CONFIG_UART_CONSOLE_ON_DEV_NAME,
    init: my_init,
    config_info: 0
};

特定のセクションに、Device / DeviceConfig構造体のオブジェクトを配置しています。

とりあえずで解決したい問題は2つです。

  • 使い方が面倒
  • 型がUART Driver固定

使い方が面倒

Device / DeviceConfig構造体を知っていなければならないのと、それ以上にリンカセクションを意識しなければならないのがダメダメです。

マクロで簡素化

雑にマクロを書いて、もう少し簡単に書けるようにします。 次のように、必要な情報をマクロの引数に渡すと、上述したものと同等になるようにします。

device_config!(
    __CONFIG_MY_DEVICE,
    zephyr::CONFIG_UART_CONSOLE_ON_DEV_NAME,
    my_init,
    0
);
device_init!(__DEVICE_MY_DEVICE, &__CONFIG_MY_DEVICE, &UART_API, 0);

少しAPIを簡単にするだけなので、マクロもそれほど複雑になりません。 与えられた引数から、Device / DeviceConfig構造体の静的なオブジェクトを作成し、特定のリンカセクションを指定します。

#[macro_export]
macro_rules! device_config {
    ($dev_name:ident, $name:expr, $init:expr, $info:expr) => {
        #[link_section = ".devconfig.init"]
        static $dev_name: DeviceConfig = DeviceConfig {
            name: $name,
            init: $init,
            config_info: $info
        };
    }
}

#[macro_export]
macro_rules! device_init {
    ($dev_name:ident, $config:expr, $api:expr, $data:expr) => {
        #[link_section = ".init_POST_KERNEL40"]
        static $dev_name: Device = Device {
            config: $config,
            driver_api: $api,
            driver_data: $data
        };
    }
}

現状、device_init!の方は、.init_POST_KERNEL40と固定になっています。 おそらく、これ以上は手続きマクロが必要なので、これから勉強します。

いずれにせよ、これで少し使うのが楽になりました。

型がUART Driver固定

Device構造体のdriver_apiの型がuart_driver_api固定になっています。これではUARTでしか使えません。

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct Device {
    pub config: &'static DeviceConfig,
    pub driver_api: &'static zephyr::uart_driver_api,
    pub driver_data: usize,
}

ジェネリクスで汎化!

driver_apiの型をジェネリックにします。

pub struct Device<T: 'static + DriverApi> {
    pub config: &'static DeviceConfig<T>,
    pub driver_api: &'static T,
    pub driver_data: usize,
}

単純にジェネリックにするだけでは、どんな型も指定できてしまうため、DriverApi traitの実装を制約にします。

DriverApi traitは、ただのマーカーです。何か必要になったら追加しましょう。

pub trait DriverApi {}
impl DriverApi for bindings::uart_driver_api {}

これで、少しはマシになりました。 が、まだまだ負債はたくさんあるので、少しずつ解消していきます。