Zephyr×Rustのインテグレーションにチャレンジ!⑩~UART Driver 送信編~

はじめに

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

RustでデバイスDriverを作っていきます。 まずは、QEMUのUARTをターゲットにします。 QEMUのUARTデバイスガバガバで、初期化を行わなくてもデータ送受信ができるので、敷居が低いです。

やることのおさらい

前回、Rustで次のようにDriver登録に必要な情報を作成しました。

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

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

やることは2つです。

  • poll_outを実装して、driver_apiに登録する
  • nameUART_0を登録する

bindingから使えるものを探す

UART_0は、下の参照を引っ張ってくれば良さそうです。

pub const CONFIG_UART_CONSOLE_ON_DEV_NAME: &'static [u8; 7usize] = b"UART_0\0";

UART0のベースアドレスは、下記の定義を使いましょう。

pub const UART_0_BASE_ADDRESS: u32 = 1073790976;

uart_driver_apiのbindingは次の通り生成されています。

#[doc = " @brief Driver API structure."]
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct uart_driver_api {
    #[doc = " Console I/O function"]
    pub poll_in: ::core::option::Option<
        unsafe extern "C" fn(dev: *mut device, p_char: *mut cty::c_uchar) -> cty::c_int,
    >,
    pub poll_out:
        ::core::option::Option<unsafe extern "C" fn(dev: *mut device, out_char: cty::c_uchar)>,
    #[doc = " Console I/O function"]
    pub err_check: ::core::option::Option<unsafe extern "C" fn(dev: *mut device) -> cty::c_int>,
    #[doc = " UART configuration functions"]
    pub configure: ::core::option::Option<
        unsafe extern "C" fn(dev: *mut device, cfg: *const uart_config) -> cty::c_int,
    >,
    pub config_get: ::core::option::Option<
        unsafe extern "C" fn(dev: *mut device, cfg: *mut uart_config) -> cty::c_int,
    >,
}

いらないAPINoneにしておけば良さそうです。 パーツは揃っていそうなので、早速やっていきましょう。

uart_driver_api

use bindings::zephyr::{self, device, uart_driver_api};

unsafe extern "C" fn rust_poll_out(_dev: *mut device, _out_char: cty::c_uchar)
{
    ()
}

static UART_API: uart_driver_api = uart_driver_api {
    poll_out: Some(rust_poll_out),
    poll_in: None,
    err_check: None,
    configure: None,
    config_get: None,
};

このような実装でビルドが通ります。

deviceとdevice_config

ひとまず、具体的な型を愚直に書きます。トレイト、ジェネリクス、関連型を使えば、もっと良くできると思います。が、それは今後の課題、ということで。

pub struct Device {
    pub config: &'static DeviceConfig,
-    pub driver_api: usize,
+    pub driver_api: &'static zephyr::uart_driver_api,
    pub driver_data: usize,
}

pub struct DeviceConfig {
-    pub name: usize,
+    pub name: &'static [u8],
    pub init: unsafe extern "C" fn(device: *mut Device) -> cty::c_int,
    pub config_info: usize,
}
#[link_section = ".init_POST_KERNEL40"]
static __DEVICE_MY_DEVICE: Device = Device {
    config: &__CONFIG_MY_DEVICE,
-    driver_api: 0,
+    driver_api: &UART_API,
    driver_data: 0
};

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

一度動作確認

さて、ここまでで空の実装ができました。 ビルドして実行しても、HardFaultにはならないはずです。

$ ninja run
[0/1] To exit from QEMU enter: 'CTRL+a, x'[QEMU] CPU: cortex-m3
qemu-system-arm: warning: nic stellaris_enet.0 has no peer
# 予想通りここでブロックします

rust_poll_outの実装

ターゲットのUART (stellaris-uart) では、ベースアドレスに送信用レジスタがあります。 お行儀が悪いですが、次のように実装して、本当に動くかどうか、確かめてみましょう。

unsafe extern "C" fn rust_poll_out(_dev: *mut device, out_char: cty::c_uchar) {
    *(zephyr::UART_0_BASE_ADDRESS as *mut u32) = out_char as u32;
}

動作確認

$ ninja run
[0/1] To exit from QEMU enter: 'CTRL+a, x'[QEMU] CPU: cortex-m3
qemu-system-arm: warning: nic stellaris_enet.0 has no peer
***** Booting Zephyr OS 1.13.99 *****
Hello from Rust!

動いちゃいました!