Zephyrがおもしろい～OSSのRTOS～

## はじめに

[Zephyr](https://www.zephyrproject.org/)は、2015年から開発されている組込み向けRTOSです。The Linux Foundationのプロジェクトでもあります。
ソースコードは[zephyrproject-rtos/zephyr](https://github.com/zephyrproject-rtos/zephyr)、ドキュメントは[Zephyr Documentation](https://docs.zephyrproject.org/latest/introduction/introducing_zephyr.html)にあります。

ライセンスはApache 2.0で、Intel, Nordic, NXPがプラチナスポンサー、SiFive, TI, Synopsysなどがシルバースポンサーです。

OSのソースコードリーディングする分には、Linuxよりかなりハードルが低く感じました。

### 起源

VxWorksのmicrokernelからforkしたようです。

> The Zephyr kernel is derived from Wind River’s commercial VxWorks Microkernel Profile for VxWorks.

[インテルの枷が外れたウインドリバー、組み込みOSの老舗はIoTで本気を出せるか](http://monoist.atmarkit.co.jp/mn/articles/1804/27/news017.html)、を見ると、Wind River Rocketというプロジェクトをやっていて、それがZephyr Projectに統合されたようですね。

first commitを見ると、intelからコミットされています (Wind Riverは2009年にintelに買収され、2018年に売却しています)。

```
commit 8ddf82cf70dc6f951ab477f325dee0efde3ec589
Author: Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
Date:   Fri Apr 10 16:44:37 2015 -0700

    First commit
    
    Signed-off-by:  <inaky.perez-gonzalez@intel.com>
```

### アーキテクチャ、ボードサポート

様々なプロセッサアーキテクチャがサポートされています。

> ARM Cortex-M, Intel x86, ARC, Nios II, Tensilica Xtensa, and RISC-V

サポートされているボードのリストは、下のページで見ることができます。

[Zephyr Supported Boards](https://docs.zephyrproject.org/latest/boards/boards.html)

よく見ると、[Zedborad Pulpino](https://docs.zephyrproject.org/latest/boards/riscv32/zedboard_pulpino/doc/zedboard_pulpino.html)のサポートがあります。
[Pulpino](https://github.com/pulp-platform/pulpino)は、[PULP](https://www.pulp-platform.org/)が開発しているRISC-Vの実装で、OSSとして公開されています。
[Zedboard](http://akizukidenshi.com/catalog/g/gM-12085/)は、Zynqの評価ボードの1つで、FPGAを積んでいます。PulpinoはZedboard上で動作させることができます。
つまり、Zedboard (6万円くらい)があれば、FPGAにRISC-V構築して、Zephyr動かすことができます。
**CPUからペリフェラル、OSまで全てOSSで、好きにカスタマイズできる環境の完成です！**

### ビルドツール

モダンです。

- cmake + ninja
- テストがあって、カバレッジ測定しています。
  - マージ時にカバレッジレポートが上がってくるようになっています。GitHubで適当なpull requestを覗いてみて下さい。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/204676/50457bf5-a2ad-d493-5b2a-19bbbafbcb35.png)
- 一部設定ファイルはyaml
  - コンパイル時にpythonで解析して、ヘッダファイル作ったりしています
- ドキュメントはSphinx

### デバイス記述方法

Linuxと同様にdevice treeを使用します。
ただし、内部での使い方はかなり異なります。

### 特徴

[Zephyr Doc Introduction](https://docs.zephyrproject.org/latest/introduction/introducing_zephyr.html)から、気になった機能を紹介します。

> Device Tree Support
> Use of Device Tree (DTS) to describe hardware and configuration information for boards. The DTS information will be used only during compile time. Information about the system is extracted from the compiled DTS and used to create the application image.

Linuxと違って、コンパイル時にだけ情報を抜き出して、使用するみたいです。
driverのロードを動的にやったりしないでしょうから、妥当な感じがします。

> Native Linux, macOS, and Windows Development
> A command-line CMake build environment runs on popular developer OS systems. A native POSIX port, lets you build and run Zephyr as a native application on Linux and other OSes, aiding development and testing.

POSIX互換のOSがホストの場合、Zephyrをネイティブアプリケーションとして動作させることができるようです。
これは、かなり便利そうですね。

## 軽く動かしてみる

環境はUbuntu 16.04です。
とりあえず、ARM Cortex-M3 Emulation (QEMU)をターゲットにhello worldしてみます。

[Getting Started Guide](https://docs.zephyrproject.org/latest/getting_started/getting_started.html)をもとに軽く動かしていきます。

ツールチェイン、cmake、ninjaはインストールしておいてください。
QEMUのインストールも必要です。

```
sudo apt install qemu-system-arm
```

`~/.zephyrrc`に、ビルドツールチェイン環境変数を設定します。
例えば、次のような感じです。

```
$ cat .zephyrrc
export ZEPHYR_TOOLCHAIN_VARIANT=gnuarmemb
export GNUARMEMB_TOOLCHAIN_PATH="/opt/gnuarmemb"
```

zephyrのソースコードを入手して、環境設定用のシェルスクリプトを実行します。

```
git clone https://github.com/zephyrproject-rtos/zephyr
cd zephyr
source zephyr-env.sh
```

zephyrレポジトリ内にサンプルが提供されているので、hello_worldのサンプルをビルドして、実行します。

```
cd $ZEPHYR_BASE/samples/hello_world
mkdir build && cd build

# Use cmake to configure a Ninja-based build system:
cmake -GNinja -DBOARD=qemu_cortex_m3 ..

# Now run ninja on the generated build system:
ninja run
```

下のように、無事、Hello Worldが表示されました。
超お手軽です！

```
***** Booting Zephyr OS v1.13.99-ncs2 *****
Hello World! qemu_cortex_m3
```

## 軽くバイナリハック

動くバイナリが手に入ったので、軽く見てみます。
buildディレクトリ下の`zephyr`ディレクトリにビルド生成物があります。

```
$ cd zephyr
$ ls
arch                 kernel                     qemu_cortex_m3.dts_compiled
boards               lib                        qemu_cortex_m3.dts.pre.tmp
cmake                liboffsets.a               soc
CMakeFiles           libzephyr.a                subsys
cmake_install.cmake  linker.cmd                 tests
drivers              linker.cmd.dep             zephyr.elf
ext                  linker_pass_final.cmd      zephyr.lst
include              linker_pass_final.cmd.dep  zephyr.map
isrList.bin          misc                       zephyr_prebuilt.elf
isr_tables.c         nrf                        zephyr.stat
kconfig              nrfxlib
```

`zephyr.elf`が最終成果物です。
確かに、ARMのバイナリができています。

```
$ file zephyr.elf
zephyr.elf: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, not stripped
```

最終的なバイナリサイズとしては、11KBくらいのようです。超コンパクトです。

```
$ arm-none-eabi-size zephyr.elf 
   text	   data	    bss	    dec	    hex	filename
   6962	    588	   3872	  11422	   2c9e	zephyr.elf
```

セクション情報を見てみると、コードと初期値ありのデータは、Flash領域(0x0000_0000)に、初期値なしのデータはRAM領域(0x2000_0000)に配置されています。
エントリポイントは`0xc11`です。

```
$ arm-none-eabi-readelf -l zephyr.elf 

Elf file type is EXEC (Executable file)
Entry point 0xc11
There are 3 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000094 0x00000000 0x00000000 0x01cec 0x01cec RWE 0x4
  LOAD           0x001d80 0x20000f20 0x00001cec 0x00094 0x00094 RW  0x4
  LOAD           0x001e18 0x20000000 0x20000000 0x00000 0x00f20 RW  0x8

 Section to Segment mapping:
  Segment Sections...
   00     text sw_isr_table devconfig rodata 
   01     datas initlevel _k_mutex_area 
   02     bss noinit
```

`qemu_cortex_m3.dts_compiled`があります。最終的にzephyrに組み込まれたdevice treeが簡単に見れるのは、ありがたいですね。

```
$ cat qemu_cortex_m3.dts_com
piled
/dts-v1/;

/ {
	#address-cells = < 0x01 >;
	#size-cells = < 0x01 >;
	model = "QEMU Cortex-M3";
	compatible = "ti,lm3s6965evb-qemu", "ti,lm3s6965";
...
	sram0: memory@20000000 {
		device_type = "memory";
		compatible = "mmio-sram";
		reg = < 0x20000000 0x10000 >;
	};

	flash0: flash@0 {
		compatible = "soc-nv-flash";
		reg = < 0x00 0x40000 >;
	};
};
```

`linker.cmd`の中身はリンカスクリプトです。

```
$ head -n 10 linker.cmd
 OUTPUT_FORMAT("elf32-littlearm")
_region_min_align = 4;
MEMORY
    {
    FLASH (rx) : ORIGIN = (0x0 + 0), LENGTH = (256*1K - 0)
    SRAM (wx) : ORIGIN = 0x20000000, LENGTH = (64 * 1K)
    IDT_LIST (wx) : ORIGIN = (0x20000000 + (64 * 1K)), LENGTH = 2K
    }
ENTRY("__start")
SECTIONS
```

`zephyr.lst`, `zephyr.map`, `zephyr.stat`というファイルがあって、何者か気になります。
`zephyr.lst`は、バイナリをディスアセンブリしたファイルでした。

```
zephyr.elf:     file format elf32-littlearm


Disassembly of section text:

00000000 <_vector_table>: // 0x0000_0000番地はベクタテーブルのアドレス

	return fd_entry->obj;
}
...
        // 0x0000_000x番地はエントリポイントのアドレス
       4:       00000c11        .word   0x00000c11
...
000000ec <main>:
#include <zephyr.h>
#include <misc/printk.h>

void main(void)
{
        printk("Hello World! %s\n", CONFIG_BOARD);
      ec:       4901            ldr     r1, [pc, #4]    ; (f4 <main+0x8>)
      ee:       4802            ldr     r0, [pc, #8]    ; (f8 <main+0xc>)
      f0:       f000 ba0a       b.w     508 <printk>
      f4:       0000184c        .word   0x0000184c
      f8:       0000185b        .word   0x0000185b
```

親切です。11KBくらいなら、頑張ってハックしよう、という気になりますね。

`zephyr.map`は、リンカが生成するマップファイルです。
シンボル情報などが出力されています。

```
Archive member included to satisfy reference by file (symbol)

libapp.a(main.c.obj)          (--whole-archive)
zephyr/libzephyr.a(isr_tables.c.obj)
                              (--whole-archive)
...
                0x0000000000000000                _image_rom_start = 0x0

text            0x0000000000000000     0x1692
                0x0000000000000000                . = 0x0
                0x0000000000000000                _vector_start = .
 *(.exc_vector_table)
 *(.exc_vector_table.*)
 .exc_vector_table._vector_table_section
                0x0000000000000000       0x40 zephyr/arch/arm/core/cortex_m/libarch__arm__core__cortex_m.a(vector_table.S.obj)
                0x0000000000000000                _vector_table
 *(.gnu.linkonce.irq_vector_table)
 .gnu.linkonce.irq_vector_table
                0x0000000000000040       0xac zephyr/CMakeFiles/kernel_elf.dir/isr_tables.c.obj
                0x0000000000000040                _irq_vector_table
...
```

`zephyr.stat`は、readelfの実行結果のようですね。

```
$ cat zephyr.stat 
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
...
Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000094 0x00000000 0x00000000 0x01cec 0x01cec RWE 0x4
  LOAD           0x001d80 0x20000f20 0x00001cec 0x00094 0x00094 RW  0x4
  LOAD           0x001e18 0x20000000 0x20000000 0x00000 0x00f20 RW  0x8

 Section to Segment mapping:
  Segment Sections...
   00     text sw_isr_table devconfig rodata 
   01     datas initlevel _k_mutex_area 
   02     bss noinit 
```

実は、自分でreadelfする必要はありませんでした。

## 軽くソース解析

masterの`0906a51`を解析します。
1.13と比較するとトップレベルのディレクトリ構造が変化していました。

### 全体像

ディレクトリ構造は、Linuxと比較するとシンプルです。
`boards`とか`soc`とか`dts`とかがトップレベルにあるのが、組込みっぽさを醸し出しています。

```
tree -L 1
.
├── arch
├── boards
├── cmake
├── CMakeLists.txt
├── CODEOWNERS
├── CONTRIBUTING.rst
├── doc
├── drivers
├── dts
├── ext
├── include
├── Kconfig
├── Kconfig.zephyr
├── kernel
├── lib
├── LICENSE
├── Makefile
├── misc
├── README.rst
├── samples
├── scripts
├── soc
├── subsys
├── tests
├── VERSION
├── version.h.in
├── zephyr-env.cmd
└── zephyr-env.sh
```

Kconfigあたりを見ていると、Wind RiverとIntelのcopyright表記があります。

```
$ cat Kconfig
# Kconfig - general configuration options

#
# Copyright (c) 2014-2015 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
mainmenu "Zephyr Kernel Configuration"

source "Kconfig.zephyr"
```

scripts下を見ると大量のpythonファイルがあります。

```
$ ls scripts/
checkpatch         coccicheck               gen_relocate_app.py
ci                 dir_is_writeable.py      gen_syscall_header.py
coccinelle         elf_helper.py            gen_syscalls.py
cross_compiler     file2hex.py              mergehex.py
dts                filter-known-issues.py   parse_syscalls.py
footprint          gen_alignment_script.py  process_gperf.py
gitlint            gen_app_partitions.py    qemu-machine-hack.py
kconfig            gen_cfb_font_header.py   requirements.txt
meta               gen_gcov_files.py        sanitycheck
__pycache__        gen_gdt.py               spelling.txt
sanity_chk         gen_idt.py               subfolder_list.py
support            gen_kobject_list.py      tags.sh
check_link_map.py  gen_mmu_x86.py           valgrind.supp
checkpatch.pl      gen_offset_header.py     waitpid
checkstack.pl      gen_priv_stacks.py       west
```

### Sample application解析

`zephyr/samples/hello_world/src/main.c`

```c
/*
 * Copyright (c) 2012-2014 Wind River Systems, Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr.h>
#include <misc/printk.h>

void main(void)
{
	printk("Hello World! %s\n", CONFIG_ARCH);
}
```

printkしているだけで、めっちゃシンプルです。
設定ファイルっぽいyamlです。

```yaml
sample:
  description: Hello World sample, the simplest Zephyr
    application
  name: hello world
  platforms: all
common:
    tags: samples
    harness: console
    harness_config:
      type: one_line
      regex:
        - "Hello World! (.*)"
tests:
  sample.helloworld.multithread:
    tags: samples
```

platformsで、ビルド可能なプラットフォームを指定できるようです。`harness`とかこのあたりは、何でしょうね？
まだ、謎は多いです。

## Device Driver

やはり、device driverがどうなっているのか気になります。

[Zephyr Doc Device and Driver Support](https://docs.zephyrproject.org/latest/devices/index.html)

特に、Device Treeをどのように利用しているか、を見ていきます。

### Zephyr Device Tree

[Zephyr Doc Device Tree](https://docs.zephyrproject.org/latest/devices/dts/device_tree.html)

> After compilation, a python script extracts information from the compiled device tree file using a set of rules specified in YAML files. The extracted information is placed in a header file that is used by the rest of the code as the project is compiled.

device tree sourceをコンパイルした後、pythonスクリプトでyamlファイルのルールに則って情報を抜き出し、ヘッダファイルを作るようです。

Zephyrのdevice tree bindingは、yamlファイルで定義されており、ドキュメントを兼ねているようです。地味に良いです。
UARTの例を示します。

`zephyr/dts/bindings/serial/ti,stellaris-uart.yaml`

```yaml
---
title: TI Stellaris UART
version: 0.1

description: >
    This binding gives a base representation of the TI Stellaris UART

inherits:
    !include uart.yaml

properties:
    compatible:
      constraint: "ti,stellaris-uart"

    reg:
      type: array
      description: mmio register space
      generation: define
      category: required

    interrupts:
      type: array
      category: required
      description: required interrupts
      generation: define
...
```

`uart.yaml`をinheritしています。
`uart.yaml`はuartデバイスに共通のプロパティが定義されています。

```yaml
---
title: Uart Base Structure
version: 0.1

description: >
    This binding gives the base structures for all UART devices

child:
    bus: uart

properties:
    compatible:
      type: string
      category: required
      description: compatible strings
      generation: define
...
    current-speed:
      type: int
      category: required
      description: Initial baud rate setting for UART
      generation: define
...
```

`current-speed`はUARTのボーレートですね。この後順番に見ていきます。

device tree sourceは、2つに分割されています。複数のボードで共通して使えるものは、`dts`下に置きます。
`zephyr/dts/arm/ti/lm3s6965.dtsi`

```
#include <arm/armv7-m.dtsi>

/ {
	cpus {
		#address-cells = <1>;
		#size-cells = <0>;

		cpu@0 {
			device_type = "cpu";
			compatible = "arm,cortex-m3";
			reg = <0>;
		};
	};
...
	soc {
		uart0: uart@4000c000 {
			compatible = "ti,stellaris-uart";
			reg = <0x4000c000 0x4c>;
			interrupts = <5 3>;
			status = "disabled";
			label = "UART_0";
		};
...
```

ボード固有の設定は、`board`下に置きます。
`boards/arm/qemu_cortex_m3/qemu_cortex_m3.dts`

```
/dts-v1/;

#include <ti/lm3s6965.dtsi>

/ {
	model = "QEMU Cortex-M3";
	compatible = "ti,lm3s6965evb-qemu", "ti,lm3s6965";
...
&uart0 {
	status = "ok";
	current-speed = <115200>;
};
...
```

uart0を有効にして、ボーレートは115200に設定しています。

driverでどのようにプロパティが使われているか、見てみます。
`zephyr/drivers/serial/uart_stellaris.c`

```c
static struct uart_stellaris_dev_data_t uart_stellaris_dev_data_0 = {
	.baud_rate = DT_TI_STELLARIS_UART_4000C000_CURRENT_SPEED,
};
```

一見、知っていないと分からないのですが、上のマクロが、device treeで設定したボーレートです。
device tree sourceからマクロの設定値を生成します。

ビルドディレクトリにdtsから生成されたヘッダファイルがあります。一度アプリケーションをビルドすると入手できます。
`build/zephyr/include/generated/generated_dts_board.h`

`DT_`のプレフィックスが付いているものを、device treeから生成しているようです。
上のdriverで使用されていた、`DT_TI_STELLARIS_UART_4000C000_CURRENT_SPEED`も定義されていますね。

```c
/**************************************************
 * Generated include file for ti,lm3s6965evb-qemu
 *               DO NOT MODIFY
 */

#ifndef DEVICE_TREE_BOARD_H
#define DEVICE_TREE_BOARD_H
...
/* uart@4000c000 */
#define CONFIG_UART_CONSOLE_ON_DEV_NAME                 "UART_0"
#define CONFIG_UART_SHELL_ON_DEV_NAME                   "UART_0"
#define DT_TI_STELLARIS_UART_0                          1
#define DT_TI_STELLARIS_UART_4000C000_BASE_ADDRESS      0x4000c000
#define DT_TI_STELLARIS_UART_4000C000_CURRENT_SPEED     115200  // これ
#define DT_TI_STELLARIS_UART_4000C000_IRQ_0             5
#define DT_TI_STELLARIS_UART_4000C000_IRQ_0_PRIORITY    3
#define DT_TI_STELLARIS_UART_4000C000_LABEL             "UART_0"
#define DT_TI_STELLARIS_UART_4000C000_SIZE              76
...
```

また、device tree sourceから生成したヘッダファイルの設定値を取り込むために、fixupファイルが用意されています。

> Fixup files contain mappings from existing Kconfig options to the actual underlying DTS derived configuration #defines. Fixup files are temporary artifacts until additional DTS changes are made to make them unnecessary.

fixupファイルは、Kconfigのオプション値をdevice tree sourceから生成したマクロにマッピングしたり、マクロ名をリネームするのに使うようです。
リネームしている使用方法しか見つかりませんでした。

`zephyr/soc/arm/ti_lm3s6965/dts_fixup.h`

```c
/* SoC level DTS fixup file */
...
#define DT_GPIO_A_BASE_ADDRESS  DT_TI_STELLARIS_GPIO_40004000_BASE_ADDRESS
#define DT_GPIO_A_LABEL         DT_TI_STELLARIS_GPIO_40004000_LABEL
#define DT_GPIO_A_IRQ           DT_TI_STELLARIS_GPIO_40004000_IRQ_0
#define DT_GPIO_A_IRQ_PRIO      DT_TI_STELLARIS_GPIO_40004000_IRQ_0_PRIORITY
...
```

このように、Device Treeをコンパイル時に解析して、kernelに埋め込むことで、実行時の負荷を減らすような使い方をしています。

## さいごに

OSSのRTOS、Zephyrについて簡単に紹介しました。
私も触り始めたばかりなので、もっと触ってみて、情報を共有していこうと思います。

## 参考

- [Zephyr](https://www.zephyrproject.org/)
- [Zephyr Project Documentation](https://docs.zephyrproject.org/latest/index.html)
- [GitHub zephyrproject-rtos/zephyr](https://github.com/zephyrproject-rtos/zephyr)