ARM TrustZone CryptoCell 310という名称の疑問
はじめに
nRF52840は、Cortex-M4Fが搭載されたSoCです。 また、CryptoCell 310という暗号化アクセラレータが搭載されています。
Nordic Documentation LibraryのCryptoCellのページを見ると、「ARM TrustZone CryptoCell 310」と書かれています。
しかし、Cortex-M4自体は、TrustZoneをサポートしていませんし、これはどういう意味なんだろう、と思っていました。 先日、他人にも突っ込まれたので、自分のために整理しておこうと思います。
TrustZoneサポートのあるCortex-Mシリーズ
Armv8-M命令セットのシリーズに、オプションでTrustZoneを実装することができます。 下記ページの下の方に一覧表があります。
Cortex-M23, M33, M35PがOptionのFeatureとして、TrustZone for Armv8-M
をサポートしています。
CryptoCell
再度、CryptoCellの話に戻ります。 ARMのWebページでの紹介をみると、次のように書いてあります。
CryptoCell complements TrustZone for Armv8-M and together these solutions form a Trusted Execution Environment (TEE).
ということで、TrustZone for Armv8-M
を補完するのが、CryptoCellの役割のようです。
下図が一番最初に掲載されていますが、TrustZoneからアクセスすることもできるし、そうでない場合でも、専用インタフェースを通してしかアクセスできないので、(多少は)安全です、ということなのでしょう。
結局のところ
nRF52840でARM TrustZone CryptoCell 310、という文言は、少しミスリーディングな気がしますね。
Zephyr×Rustのインテグレーションにチャレンジ!⑥~RustでDriverを書く!~
はじめに
ZephyrとRustのインテグレーションに挑戦しています。
これまでで、アプリケーションをRustで書いてきました。 ここからは、Driverを書く方法を調査していきます。
前回までのあらすじ
RustでZephyrのDriverを書くためには、INIT_DEVICE
マクロ相当のことをすればよいことがわかりました。
具体的には、.devconfig.init
と.init_<priority level>
に、device_config
とdevice
オブジェクトを配置します。
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!
が出力されました!
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 };
次は、実際に試してみましょう。
Zephyr×Rustのインテグレーションにチャレンジ!④~RustでDriverを書くための調査~
はじめに
ZephyrとRustのインテグレーションに挑戦しています。
tomo-wait-for-it-yuki.hatenablog.com
これまでで、アプリケーションをRustで書いてきました。 ここからは、Driverを書く方法を調査していきます。
最低限必要なことは何か?
まずは、ここからです。 Zephyrのビルドプロセスを解析し、device driverのライブラリを作るプロセスがあることが分かっています。
tomo-wait-for-it-yuki.hatenablog.com
理想的には、このプロセスにRustで書いたdriverがビルドできると良いです。 ですが、いきなりこれをやるのはハードルが高そうです。
そこで、まずアプリケーションプロジェクト内device driverが作れるかどうか、試してみます。
external driver project
hello_world
サンプルアプリケーションをコピーして、device driverを作ってみます。
driverを登録するマクロは、DEVICE_INIT
です。
DEVICE_INIT(dev_name, drv_name, init_fn, data, cfg_info, level, prio)
このマクロ(と引数に使うマクロ)を使うために、init.h
とdevice.h
とをincludeします(init.h内でdevice.hをincludeしているので、device.hは明示的にincludeしなくても大丈夫です)。
#include <init.h> #include <device.h>
最低限、初期化関数は必要なので、初期化関数内で文字を表示するようにしておきます。
static int my_init(struct device *dev) { printk("Hello from MY_DRIVER.\n"); return 0; } DEVICE_INIT(my_device, "MY_DRIVER", my_init, NULL, NULL, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT );
ビルドしてみます。
$ mkdir build && cd $_ $ cmake -GNinja -DBOARD=qemu_cortex_m3 .. $ ninja [3/100] Preparing syscall dependency handling [95/100] Linking C executable zephyr/zephyr_prebuilt.elf Memory region Used Size Region Size %age Used FLASH: 7680 B 256 KB 2.93% SRAM: 4032 B 64 KB 6.15% IDT_LIST: 8 B 2 KB 0.39% [100/100] Linking C executable zephyr/zephyr.elf
いけるやん。実行してみましょう。
$ ninja run [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 1.13.99 ***** Hello World! qemu_cortex_m3
いけるやん。
Discovery翻訳の宣伝
はじめに
Discoveryを翻訳したので、マイコンでRustを動かす体験をお楽しみください。
tomoyuki-nakabayashi.github.io
Discoveryでは、STM32F3DISCOVERY開発ボードを使って、次のようなアプリケーションを実装します。
- LEDルーレット
- LEDコンパス
- シリアル通信
- パンチングマシーン
F3DISCOVERYは、チップワンストップなどで購入できます。 いつの間にか秋月では品切れ状態ですね…。 秋葉原をうろついても見つからなかったので、最初から通販するのが安定かと思います。
レポジトリは、↓です。誤植や改善案があれば、気軽にフィードバック頂けると嬉しいです。
他愛ない翻訳裏話
と言っても大した裏話はありません。
コミットログを見てみると、翻訳を開始したのが、2月9日でした。
3月25日に一通り翻訳が完了したので、1ヶ月半くらいで翻訳が完了したことになります。 The Embedded Rust Bookと違って、普段から組込みとRustをやっている人間からすると、高度な内容はありません。 割と、粛々とやるだけ!という感じでした。
組込みの入門書として、丁寧に書かれているので、あまり組込み開発の経験がない方でも、読み進められると思います。 Discoveryでマイコンを動かしてみて、より深いことを知りたい場合は、The Embedded Rust BookやEmbedonomiconへ進むと良いです。
予告
これで、ずっと翻訳したかったEmbedonomiconの和訳を始める準備が整いました。
tomoyuki-nakabayashi.github.io
まだ、翻訳開始したばかりですが、ウォッチして頂けると嬉しいです。
Zephyrのdriverを作ってみよう③~単純なシリアル受信~
はじめに
Zephyrのdriverを作る方法を学びます。 まずは、qemu_cortex_m3をターゲットとした場合の、UART device driverを写経します。
|> zephyr/drivers/serial/uart_stellaris.c
全体で700行くらいなので、全てを単純に写経する、というよりポイントを押さえて行きたいと思います。
今回は、割込みなしのUART (polling) で、データを受信する部分を写経していきます。
実装するAPI
前回、poll_out
を実装しました。今回は、poll_in
を実装します。
|> ${ZEPHYR_BASE}/include/uart.h
/** @brief Driver API structure. */ struct uart_driver_api { /** Console I/O function */ int (*poll_in)(struct device *dev, unsigned char *p_char); void (*poll_out)(struct device *dev, unsigned char out_char); ... }
シリアル受信
uart_driver_api
構造体のpoll_in
APIを実装します。
static const struct uart_driver_api uart_stellaris_driver_api = { .poll_in = uart_stellaris_poll_in, // これから実装 .poll_out = uart_stellaris_poll_out, // 実装済み ... }
UARTのpoll_in
APIでは、UARTデバイスの受信バッファにデータが入っているかどうかを確認します。
データがない場合は-1
を返し、データがある場合はパラメータのc
に読み込んだ文字を詰めて0
を返します。
static int uart_my_uart_poll_in(struct device *dev, unsigned char *c) { volatile struct _uart *uart = UART_STRUCT(dev); if (uart->fr & UARTFR_RXFE) return (-1); /* got a character */ *c = (unsigned char)uart->dr; return 0; }
動作確認
受信処理を実装する前に、Zephyrのshellを有効にすると、poll_in
API呼び出しで例外が発生してしまいます。
$ ninja run [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 ***** Booting Zephyr OS 1.13.99 ***** Hello World! qemu_cortex_m3 uart:~$ ***** USAGE FAULT ***** Illegal use of the EPSR ***** Hardware exception ***** Current thread ID = 0x20000400 Faulting instruction address = 0x0 Fatal fault in essential thread! Spinning... QEMU 3.0.50 monitor - type 'help' for more information (qemu) QEMU: Terminated
受信処理を実装した後、同じ設定でZephyrを起動すると、無事shellが動きます。
$ ninja run [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 ***** Booting Zephyr OS 1.13.99 ***** Hello World! qemu_cortex_m3 uart:~$ help Please press the <Tab> button to see all available commands. You can also use the <Tab> button to prompt or auto-complete all commands or its subcommands. You can try to call commands with <-h> or <--help> parameter for more information.
おまけ
UART poll_in
APIの実装と、shell subsystemから利用する方法を見てみましょう。
poll_in
はsystem callとして提供され、shell subsystemのどこかからタイマ割り込みで、処理が開始されているはずです。
system call
まずは、UARTのAPIを深堀りしていきます。
|> zephyr/include/uart.h
/** * @brief Poll the device for input. * * @param dev UART device structure. * @param p_char Pointer to character. * * @retval 0 If a character arrived. * @retval -1 If no character was available to read (i.e., the UART * input buffer was empty). * @retval -ENOTSUP If the operation is not supported. */ __syscall int uart_poll_in(struct device *dev, unsigned char *p_char); static inline int _impl_uart_poll_in(struct device *dev, unsigned char *p_char) { const struct uart_driver_api *api = (const struct uart_driver_api *)dev->driver_api; return api->poll_in(dev, p_char); }
uart_poll_in()
がシステムコールとして宣言されています。
ここでは宣言だけですが、uart_poll_in()
の定義で、_impl_uart_poll_in()
が呼び出されると推測できます。
_impl_uart_poll_in()
では、driverのpoll_in()
(さきほど実装したAPI) が呼ばれています。
uart_poll_in()
の定義は、下記です (多分) 。
|> zephyr/drivers/serial/uart_handlers.c
Z_SYSCALL_HANDLER(uart_poll_in, dev, p_char) { Z_OOPS(Z_SYSCALL_DRIVER_UART(dev, poll_in)); Z_OOPS(Z_SYSCALL_MEMORY_WRITE(p_char, sizeof(unsigned char))); return _impl_uart_poll_in((struct device *)dev, (unsigned char *)p_char); }
Z_SYSCALL_HANDLER
は、可変長引数のハンドラ登録マクロです。引数に応じたハンドラ登録マクロが呼ばれる仕組みになっています。
|> zephyr/kernel/include/syscall_handler.h
#define Z_SYSCALL_HANDLER(...) \ _SYSCALL_CONCAT(__SYSCALL_HANDLER, \ _SYSCALL_NARG(__VA_ARGS__))(__VA_ARGS__)
subsys/shell
次にsubsystemのshellを見ていきます。
今回は割込み駆動の設定を行っていないため、定期的にpoll_in
を呼び出すような実装になっているはずです。
|> zephyr/subsys/shell/shell_uart.c
早速、それっぽいタイマハンドラが見つかりました。
static void timer_handler(struct k_timer *timer) { u8_t c; const struct shell_uart *sh_uart = k_timer_user_data_get(timer); while (uart_poll_in(sh_uart->ctrl_blk->dev, &c) == 0) { // ★ if (ring_buf_put(sh_uart->rx_ringbuf, &c, 1) == 0) { /* ring buffer full. */ LOG_WRN("RX ring buffer full."); } sh_uart->ctrl_blk->handler(SHELL_TRANSPORT_EVT_RX_RDY, sh_uart->ctrl_blk->context); } }
上記ソースコードの★の部分で、uart_poll_in()
システムコールを行っています。
uart_poll_in()
では、受信バッファにデータがあった場合に、0
を返す実装になっていました。
UARTに受信データがあった場合、shellのリングバッファに受信したデータを詰めています。
タイマの初期化は、同ファイル内のinit()
で行われています。
static int init(const struct shell_transport *transport, const void *config, shell_transport_handler_t evt_handler, void *context) { const struct shell_uart *sh_uart = (struct shell_uart *)transport->ctx; ... if (IS_ENABLED(CONFIG_SHELL_BACKEND_SERIAL_INTERRUPT_DRIVEN)) { uart_irq_init(sh_uart); } else { // 今回はこちら k_timer_init(sh_uart->timer, timer_handler, NULL); k_timer_user_data_set(sh_uart->timer, (void *)sh_uart); k_timer_start(sh_uart->timer, RX_POLL_PERIOD, RX_POLL_PERIOD); } return 0; }
参考
ZephyrのIRQ_CONNECTメモ
はじめに
Zephyrのdriverを書いています。IRQハンドラ登録の処理を調査したので、メモを残しておきます。
IRQ_CONNECT
Zephyrで割り込みハンドラを登録する場合、IRQ_CONNECT
というマクロを使用します。
まず、Zephyrのドキュメントに掲載されている利用例です。
#define MY_DEV_IRQ 24 /* device uses IRQ 24 */ #define MY_DEV_PRIO 2 /* device uses interrupt priority 2 */ /* argument passed to my_isr(), in this case a pointer to the device */ #define MY_ISR_ARG DEVICE_GET(my_device) #define MY_IRQ_FLAGS 0 /* IRQ flags. Unused on non-x86 */ void my_isr(void *arg) { ... /* ISR code */ } void my_isr_installer(void) { ... IRQ_CONNECT(MY_DEV_IRQ, MY_DEV_PRIO, my_isr, MY_ISR_ARG, MY_IRQ_FLAGS); irq_enable(MY_DEV_IRQ); ... }
素直な作りで、IRQ番号、プライオリティ、IRQハンドラといった情報を指定します。 このマクロは次のように定義されています。
|> zephyr/include/irq.h
#define IRQ_CONNECT(irq_p, priority_p, isr_p, isr_param_p, flags_p) \ _ARCH_IRQ_CONNECT(irq_p, priority_p, isr_p, isr_param_p, flags_p)
ターゲットアーキテクチャごとに実装が変わります。ここでは、Cortex-Mの実装を見ていきます。
#define _ARCH_IRQ_CONNECT(irq_p, priority_p, isr_p, isr_param_p, flags_p) \ ({ \ _ISR_DECLARE(irq_p, 0, isr_p, isr_param_p); \ _irq_priority_set(irq_p, priority_p, flags_p); \ irq_p; \ })
コメントで次のように書かれている通り、Cortex-Mでは、静的に割り込みが設定できます。
/** * Configure a static interrupt. * * All arguments must be computable by the compiler at build time. * ... */
Zephyrで面白いのは、ここからです。
* _ISR_DECLARE will populate the .intList section with the interrupt's * parameters, which will then be used by gen_irq_tables.py to create * the vector table and the software ISR table. This is all done at * build-time.
_ISR_DECLARE
マクロは、.intList
セクションに割り込みパラメータを置きます。これを、pythonスクリプトで解析して、ISRテーブルを作ります。
_ISR_DECLARE
マクロは、次のように定義されています。
|> zephyr/include/sw_isr_table.h
/* Create an instance of struct _isr_list which gets put in the .intList * section. This gets consumed by gen_isr_tables.py which creates the vector * and/or SW ISR tables. */ #define _ISR_DECLARE(irq, flags, func, param) \ static struct _isr_list _GENERIC_SECTION(.intList) __used \ _MK_ISR_NAME(func, __COUNTER__) = \ {irq, flags, &func, (void *)param}
さて、別言語から、静的にIRQを登録したいのですが、どうしましょうかね…。