Zephyrのdriverを作ってみよう①~写経開始~

はじめに

Zephyrのdriverを作る方法を学びます。 まずは、qemu_cortex_m3をターゲットとした場合の、UART device driverを写経します。

|> zephyr/drivers/serial/uart_stellaris.c

全体で700行くらいなので、全てを単純に写経する、というよりポイントを押さえて行きたいと思います。

進め方

qemu_cortex_m3のボード定義をコピーして、UARTだけ、自作driverに差し替えます。

テスト用アプリケーションプロジェクトの準備

まずは、テスト用のアプリケーションプロジェクトとして、hello_worldをベースとします。

| zephyr/samples/hello_world

レポジトリのboardファイルを直接編集したくないため、プロジェクト内にboardファイルを作成します。

# at ${ZEPHYR_BASE}/samples directory
cp -r hello_world hello_from_my_uart
cd hello_from_my_uart
mkdir -p boards/arm
cp -r ${ZEPHYR_BASE}/boards/arm/qemu_cortex_m3 boards/arm/

CMakeLists.txtを編集して、プロジェクト下のボードファイルを見に行くようにします。

$ head -n 5 CMakeLists.txt 
cmake_minimum_required(VERSION 3.8.2)
# Re-direct the directory where the 'boards' directory is found from
# $ZEPHYR_BASE to this directory.
set(BOARD_ROOT ${CMAKE_CURRENT_LIST_DIR})
set(BOARD qemu_cortex_m3)

defconfigの修正

qemu_cortex_m3をターゲットにすると、UARTのdriverは、stellarisというデバイスのdriverを使います。 これを、自作のmy_uart driverに変更していきます。

|> ${ZEPHYR_BASE}/samples/hello_from_my_uart/boards/arm/qemu_cortex_m3_defconfig

# CONFIG_UART_STELLARIS=y
CONFIG_UART_MY_UART=y

これで、存在しない機能を有効化したため、ビルドが通らなくなるはず。一応試しておきます。

$ mkdir build && cd $_
$ cmake -GNinja -DBOARD=qemu_cortex_m3 ..
-- Selected BOARD qemu_cortex_m3
...
${ZEPHYR_BASE}/samples/hello_from_my_uart/boards/arm/qemu_cortex_m3/qemu_cortex_m3_defconfig:10: warning: attempt to assign the value "y" to the undefined symbol UART_MY_UART
...

期待通り、UART_MY_UARTがundefined symbolとなり、ビルドが通りません。

Kconfigの追加

ビルドが通るように、自作UARTのKconfigを追加します。まずは、シリアルデバイスのroot Kconfigです。

|> ${ZEPHYR_BASE}/drivers/serial/Kconfig

# Kconfig - serial driver configuration options
...
source "drivers/serial/Kconfig.imx"

source "drivers/serial/Kconfig.stellaris"

source "drivers/serial/Kconfig.native_posix"

...
# 追加
source "drivers/serial/Kconfig.my_uart"

ベースとするstellarisのKconfigをコピーします。

# at ${ZEPHYR_BASE}/drivers/serial/
cp Kconfig.stellaris Kconfig.my_uart

Kconfig.my_uartを編集します。STELLARISとなっている部分を、MY_UARTに置換します。

- menuconfig UART_STELLARIS
+ menuconfig UART_MY_UART
...

これで、UART_MY_UARTのundefined symbolが解決されます。ビルドプロセスが少し先に進み、次のエラーが発生します。

CMake Error at ../../CMakeLists.txt:678 (message):
  The Zephyr library 'drivers__serial' was created without source files.
  Empty (non-imported) libraries are not supported.  Either make sure that
  the library has the sources it should have, or make sure it is not created
  when it has no source files.

my_uartに、何らかのソースファイルを紐づける必要があるようです。とりあえず、stellarisのソースファイルを紐づけるとして、どこでソースファイルの紐づけを行っているか調べます。

|> ${ZEPHYR_BASE}/drivers/serial/CMakeLists.txt

zephyr_library()
zephyr_library_sources_ifdef(CONFIG_UART_ALTERA_JTAG uart_altera_jtag_hal.c)
zephyr_library_sources_if_kconfig(uart_imx.c)
zephyr_library_sources_if_kconfig(uart_cc32xx.c)
zephyr_library_sources_if_kconfig(uart_cmsdk_apb.c)
...
zephyr_library_sources_if_kconfig(usart_sam.c)
zephyr_library_sources_if_kconfig(uart_stellaris.c)
zephyr_library_sources_if_kconfig(uart_stm32.c)
...

CONFIG_XXXXXXXX部分と対応した名前のソースコードを指定します。 uart_stellaris.cをコピーしてuart_my_uart.cを作成し、次の1行を追加します。

zephyr_library_sources_if_kconfig(uart_my_uart.c)

これで、cmakeが通るようになります。

$ cmake -GNinja -DBOARD=qemu_cortex_m3 ..
...
-- Configuring done
-- Generating done
-- Build files have been written to: ${ZEPHYR_BASE}/samples/hello_from_my_uart/build

寄り道 (zephyr_library_sources_if_kconfig )

|> ${ZEPHYR_BASE}/cmake/extensions.cmake

function(zephyr_library_sources_if_kconfig item)
  get_filename_component(item_basename ${item} NAME_WE)
  string(TOUPPER CONFIG_${item_basename} UPPER_CASE_CONFIG)
  zephyr_library_sources_ifdef(${UPPER_CASE_CONFIG} ${item})
endfunction()

zephyr_library_sources_if_kconfig(uart_my_uart.c)と書いた場合、CONFIG_UART_MY_UARTが定義されていれば、ビルド対象になるようです。 難しい…。

動作確認

ビルドできるようになったので、動作確認してみます。

# at ${ZEPHYR_BASE}/samples/hello_from_my_uart
$ ninja run
[70/101] Building C object zephyr/driv...drivers__serial.dir/uart_my_uart.c.obj
${ZEPHYR_BASE}/drivers/serial/uart_my_uart.c:263:12: warning: ‘uart_stellaris_init’ defined but not used [-Wunused-function]
 static int uart_stellaris_init(struct device *dev)
            ^~~~~~~~~~~~~~~~~~~
${ZEPHYR_BASE}/drivers/serial/uart_my_uart.c:604:37: warning: ‘uart_stellaris_driver_api’ defined but not used [-Wunused-const-variable=]
 static const struct uart_driver_api uart_stellaris_driver_api = {
                                     ^~~~~~~~~~~~~~~~~~~~~~~~~
[95/101] Linking C executable zephyr/zephyr_prebuilt.elf
Memory region         Used Size  Region Size  %age Used
           FLASH:        7288 B       256 KB      2.78%
            SRAM:        3952 B        64 KB      6.03%
        IDT_LIST:           8 B         2 KB      0.39%
[101/101] To exit from QEMU enter: 'CTRL+a, x'[QEMU] CPU: cortex-m3
qemu-system-arm: warning: nic stellaris_enet.0 has no peer
qemu: fatal: Lockup: can't escalate 3 to HardFault (current priority -1)

R00=00000000 R01=0000002a R02=0000189b R03=4f480688
R04=0000002a R05=20000000 R06=000001fd R07=20000e10
R08=00000000 R09=ffffffff R10=00000000 R11=00000000
R12=00000000 R13=20000db4 R14=0000056f R15=4f480688
XPSR=20000003 --C- A handler
FPSCR: 00000000
Aborted (core dumped)

むむむ…。driverのソースコードを確認します。

|> ${ZEPHYR_BASE}/drivers/serial/uart_my_uart.c

# line 626
#ifdef CONFIG_UART_STELLARIS_PORT_0  // Kconfigで名前を変えた

#ifdef CONFIG_UART_INTERRUPT_DRIVEN
static void irq_config_func_0(struct device *port);
#endif

...

DEVICE_AND_API_INIT(uart_stellaris0, DT_TI_STELLARIS_UART_4000C000_LABEL,
            &uart_stellaris_init,
            &uart_stellaris_dev_data_0, &uart_stellaris_dev_cfg_0,
            PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
            &uart_stellaris_driver_api);

...

#endif /* CONFIG_UART_STELLARIS_PORT_0 */

Kconfigで変更したシンボルが、ソースコードで反映されていないので、初期化が走っていないようです。

- #ifdef CONFIG_UART_STELLARIS_PORT_0
+ #ifdef CONFIG_UART_MY_UART_PORT_0

修正して、これでもダメ! 元々、CONFIG_UART_STELLARIS_PORT_0を有効化している場所を探してみると、socディレクトリにあるdefconfigでした。

|> soc/arm/ti_lm3s6965/Kconfig.defconfig

...
if UART_STELLARIS

config UART_STELLARIS_PORT_0
        default y
...

ということで、少しアドホックな対応ですが、boardファイルを少し修正します。

|> ${ZEPHYR_BASE}/samples/hello_from_my_uart/boards/arm/qemu_cortex_m3/Kconfig.defconfig

if UART_MY_UART

config UART_MY_UART_PORT_0
        default y

endif # UART_MY_UART

さて、気を取り直して

$ ninja
${ZEPHYR_BASE}/drivers/serial/uart_my_uart.c:636:18: error: ‘DT_UART_STELLARIS_CLK_FREQ’ undeclared here (not in a function)
  .sys_clk_freq = DT_UART_STELLARIS_CLK_FREQ,
                  ^~~~~~~~~~~~~~~~~~~~~~~~~~
...

うげぇ~。

# at ${ZEPHYR_BASE}
$ grep DT_UART_STELLARIS_CLK_FREQ soc/arm/ti_lm3s6965/*
soc/arm/ti_lm3s6965/soc.h:#define DT_UART_STELLARIS_CLK_FREQ            SYSCLK_DEFAULT_IOSC_HZ

なんかありますねぇ…。

/* uart configuration settings */
#if defined(CONFIG_UART_STELLARIS)

#define DT_UART_STELLARIS_CLK_FREQ              SYSCLK_DEFAULT_IOSC_HZ

#endif /* CONFIG_UART_STELLARIS */

ああ、そういう…。泣く泣く、上記ヘッダファイルに追加します。

+ #if defined(CONFIG_UART_MY_UART)
+ 
+ #define DT_UART_STELLARIS_CLK_FREQ              SYSCLK_DEFAULT_IOSC_HZ
+ 
+ #endif /* CONFIG_UART_MY_UART */
$ ninja run
[2/6] Linking C executable zephyr/zephyr_prebuilt.elf
Memory region         Used Size  Region Size  %age Used
           FLASH:        7464 B       256 KB      2.85%
            SRAM:        3968 B        64 KB      6.05%
        IDT_LIST:           8 B         2 KB      0.39%
[6/6] 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

動きました!