Zephyr を qemu_riscv32 ボード向けにコンフィグし、ビルドディレクトリ下で ninja run を実行すると、最終的に下記のコマンドが実行され、QMEU が起動します。
$ /usr/bin/qemu-system-riscv32 -nographic -machine sifive_e -net none -chardev stdio,id=con,mux=on -serial chardev:con -mon chardev=con,mode=readline -kernel zephyr/build/zephyr/zephyr.elf -s -S
GDB で追うとわかりますが、QEMU の実行開始アドレスは 0x1000 です。0x1000 には 0x20400000 にジャンプするコードだけが置かれています。
0x1000: lui t0,0x20400
=> 0x1004: jr t0
0x1008: unimp
0x100a: unimp
HiFive1 実機であれば 0x20400000 は QSPI Flash がマッピングされているアドレスです(SiFive FE310-G000 Manual v2p3 を参照)。QEMU の場合は ELF ファイル build/zephyr/zephyr.elf のセクション情報通りに配置されます。
$ riscv64-zephyr-elf-readelf -a build/zephyr/zephyr.elf | less ... Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] vector PROGBITS 20400000 000094 000010 00 AX 0 0 4 [ 2] exceptions PROGBITS 20400010 0000a4 000258 00 AX 0 0 4 [ 3] text PROGBITS 20400268 0002fc 002ad4 00 AX 0 0 4 [ 4] sw_isr_table PROGBITS 20402d3c 002dd0 000200 00 WA 0 0 4 [ 5] devconfig PROGBITS 20402f3c 002fd0 000060 00 A 0 0 4 [ 6] rodata PROGBITS 20402f9c 003030 000215 00 A 0 0 4 [ 7] datas PROGBITS 80000000 003248 000018 00 WA 0 0 4 [ 8] initlevel PROGBITS 80000018 003260 000060 00 WA 0 0 4 [ 9] _k_mutex_area PROGBITS 80000078 0032c0 000014 00 WA 0 0 4 [10] bss NOBITS 80000090 0032e0 00013c 00 WA 0 0 8 [11] noinit NOBITS 800001d0 0032e0 000e00 00 WA 0 0 16 ...
ジャンプ先の 0x20400000 には vector というセクションが対応していることがわかります。ELF ファイルを逆アセンブルしてみると、
zephyr/zephyr.elf: file format elf32-littleriscv
Disassembly of section vector:
20400000 <__start>:
/*
* Set mtvec (Machine Trap-Vector Base-Address Register)
* to __irq_wrapper.
*/
la t0, __irq_wrapper
20400000: 00000297 auipc t0,0x0
20400004: 01028293 addi t0,t0,16 # 20400010 <__irq_wrapper>
csrw mtvec, t0
20400008: 30529073 csrw mtvec,t0
/* Jump to __initialize */
tail __initialize
2040000c: 4b40106f j 204014c0 <__initialize>
以上のように __start という関数が居るようです。実装は zephyr/soc/riscv/riscv-privilege/common/vector.S にあります。
このエントリポイントアドレスはどのように決まるのでしょう?Zephyr では、ボード用のデバイスツリー zephyr/boards/riscv/qemu_riscv32/qemu_riscv32.dts にある Flash の先頭アドレスを変えると ELF のエントリポイント(0x20400000)も追従します。
chosen {
zephyr,console = &uart0;
zephyr,shell-uart = &uart0;
zephyr,sram = &dtim;
zephyr,flash = &flash0;
};
この chosen 以下を検知しているようです。スクリプト scripts/dts/gen_defines.py を見ると、SPI のときだけ特殊処理になっていて、それ以外は reg の値を先頭アドレスとみなしています。
# zephyr/scripts/dts/gen_defines.py
...
def write_flash_node(edt):
# Writes output for the top-level flash node pointed at by
# zephyr,flash in /chosen
node = edt.chosen_node("zephyr,flash")
out_comment(f"/chosen/zephyr,flash ({node.path if node else 'missing'})")
if not node:
# No flash node. Write dummy values.
out("FLASH_BASE_ADDRESS", 0)
out("FLASH_SIZE", 0)
return
if len(node.regs) != 1:
err("expected zephyr,flash to have a single register, has "
f"{len(node.regs)}")
if node.on_bus == "spi" and len(node.bus_node.regs) == 2:
reg = node.bus_node.regs[1] # QSPI flash
else:
reg = node.regs[0]
out("FLASH_BASE_ADDRESS", hex(reg.addr))
if reg.size:
out("FLASH_SIZE", reg.size//1024)
...
Flash のアドレスは DT_FLASH_BASE_ADDRESS というマクロで参照できます。エントリーポイントを最終的に決めるのはリンカースクリプトです。SoC で独自のリンカースクリプトを実装することもできるようになっていますが、RISC-V の多くの SoC は、下記のスクリプトを使っています。
// zephyr/include/arch/riscv/common/linker.ld
MEMORY
{
#ifdef CONFIG_XIP
ROM (rx) : ORIGIN = DT_FLASH_BASE_ADDRESS, LENGTH = KB(DT_FLASH_SIZE)
#endif
RAM (rwx) : ORIGIN = CONFIG_SRAM_BASE_ADDRESS, LENGTH = KB(CONFIG_SRAM_SIZE)
/* Used by and documented in include/linker/intlist.ld */
IDT_LIST (wx) : ORIGIN = 0xFFFFF7FF, LENGTH = 2K
}
Zephyr のエントリポイントがどうやって決まるのか、少しわかった気がします。
先日(2020年 1月 31日の日記参照)RISC-V 32bit 版の Zephyr OS が動作しました。気分を変えて、別のボードへの移植に挑戦してみたいと思います。
名前は何でも良いのですが、とりあえず hoge ボードということにします。-DBOARD=hoge を指定して cmake を実行すると、そんなものはないと怒られ、猛烈な勢いでヘルプメッセージが出るとともに、サポートされているボードの一覧が出てきます。
$ cmake -G Ninja -DBOARD=hoge ../samples/hello_world/ -- Zephyr version: 2.1.99 -- Found PythonInterp: /usr/bin/python3 (found suitable version "3.7.6", minimum required is "3.6") -- Selected BOARD hoge No board named 'hoge' found see usage: ... Supported Boards: ... riscv: hifive1 hifive1_revb litex_vexriscv m2gl025_miv qemu_riscv32 qemu_riscv64 rv32m1_vega_ri5cy rv32m1_vega_zero_riscy x86: acrn gpmrb minnowboard qemu_x86_64 qemu_x86_coverage qemu_x86 qemu_x86_nommu up_squared ...
まず、この一覧に hoge ボードを載せるのが第一段階です。ボードの一覧を調べている cmake のスクリプトを見ます。
# zephyr/cmake/app/boilerplate.cmake
...
foreach(root ${BOARD_ROOT})
# NB: find_path will return immediately if the output variable is
# already set
find_path(BOARD_DIR
NAMES ${BOARD}_defconfig
PATHS ${root}/boards/*/*
NO_DEFAULT_PATH
)
if(BOARD_DIR AND NOT (${root} STREQUAL ${ZEPHYR_BASE}))
set(USING_OUT_OF_TREE_BOARD 1)
endif()
...
これで探しているように見えます。boards/*/* ディレクトリに「ボード名_defconfig」という名前のファイルがあれば良さそうです。足してから cmake を再度、存在しないボード名(hoge2 とかで良いです)で実行しましょう。
$ mkdir boards/riscv/hoge $ touch boards/riscv/hoge/hoge_defconfig $ cmake -G Ninja -DBOARD=hoge2 ../samples/hello_world/ ... riscv: hifive1 hifive1_revb hoge ★hoge が出てきた★ litex_vexriscv m2gl025_miv qemu_riscv32 qemu_riscv64 rv32m1_vega_ri5cy rv32m1_vega_zero_riscy ...
やりました。ボードの一覧に hoge が出ました。BOARD=hoge にして cmake を実行しましょう。
$ cmake -G Ninja -DBOARD=hoge ../samples/hello_world/ -- Zephyr version: 2.1.99 -- Selected BOARD hoge Parsing zephyr/Kconfig zephyr/scripts/kconfig/kconfig.py: Kconfig.zephyr:26: 'zephyr/boards/riscv/hoge/Kconfig.defconfig' not found (in 'source "$(BOARD_DIR)/Kconfig.defconfig"'). Check that environment variables are set correctly (e.g. $srctree, which is set to 'zephyr'). Also note that unset environment variables expand to the empty string. CMake Error at zephyr/cmake/kconfig.cmake:214 (message): command failed with return code: 1 Call Stack (most recent call first): zephyr/cmake/app/boilerplate.cmake:461 (include) CMakeLists.txt:5 (include) -- Configuring incomplete, errors occurred!
まだ色々とエラーが出ています。エラーメッセージは Kconfig.defconfig がないと言っています。このファイルを足すと、今度は Kconfig.board がないと言われますので、両方とも足します。
$ touch boards/riscv/hoge/Kconfig.defconfig $ touch boards/riscv/hoge/Kconfig.board $ cmake -G Ninja -DBOARD=hoge ../samples/hello_world/ -- Zephyr version: 2.1.99 -- Found PythonInterp: /usr/bin/python3 (found suitable version "3.7.6", minimum required is "3.6") -- Selected BOARD hoge Parsing zephyr/Kconfig Loaded configuration 'zephyr/boards/riscv/hoge/hoge_defconfig' Merged configuration 'zephyr/samples/hello_world/prj.conf' warning: <choice> (defined at boards/Kconfig:19) defined with type unknown error: Aborting due to non-whitelisted Kconfig warning 'warning: <choice> (defined at boards/Kconfig:19) defined with type unknown'. If this warning doesn't point to an actual problem, you can add it to the whitelist at the top of zephyr/scripts/kconfig/kconfig.py. CMake Error at zephyr/cmake/kconfig.cmake:214 (message): command failed with return code: 1 Call Stack (most recent call first): zephyr/cmake/app/boilerplate.cmake:461 (include) CMakeLists.txt:5 (include) -- Configuring incomplete, errors occurred!
今度は choice が無いと言っています。エラーを出しているのは下記の cmake ファイルです。
# zephyr/cmake/kconfig.cmake execute_process( COMMAND ${PYTHON_EXECUTABLE} ${ZEPHYR_BASE}/scripts/kconfig/kconfig.py ${input_configs_are_handwritten} ${KCONFIG_ROOT} ${DOTCONFIG} ${AUTOCONF_H} ${PARSED_KCONFIG_SOURCES_TXT} ${input_configs} WORKING_DIRECTORY ${APPLICATION_SOURCE_DIR} # The working directory is set to the app dir such that the user # can use relative paths in CONF_FILE, e.g. CONF_FILE=nrf5.conf RESULT_VARIABLE ret ) if(NOT "${ret}" STREQUAL "0") message(FATAL_ERROR "command failed with return code: ${ret}") endif()
このまま kconfig.py を見ても良いですが、おそらく時間のムダです。親切なエラーメッセージが出ているからです。メッセージの言うとおり boards/Kconfig を見ます。
# boards/Kconfig ... # Note: $BOARD_DIR might be a glob pattern choice prompt "Board Selection" source "$(BOARD_DIR)/Kconfig.board" ★このファイルが空★ endchoice
先ほど作成した Kconfig.board が空っぽだったため、choice の選択肢が存在せず怒られているようです。書く内容については、別のボード(例えば zephyr/boards/riscv/qemu_riscv32/Kconfig.board)を参照すると良いと思います。今回はこんな内容にしました。
# SPDX-License-Identifier: Apache-2.0
config BOARD_HOGE
bool "Hoge target"
depends on SOC_RISCV_SIFIVE_FREEDOM
この定義だと SiFive の Freedom が搭載されていることになりますが、depends on の先に手を出すには、SoC の定義を加えなければなりません。一度に紹介しても訳がわからないので、今回はボードに焦点を絞ります。
書き換えた後にもう一度 cmake を実行すると、成功します。
$ cmake -G Ninja -DBOARD=hoge ../samples/hello_world/ -- Zephyr version: 2.1.99 -- Found PythonInterp: /usr/bin/python3 (found suitable version "3.7.6", minimum required is "3.6") -- Selected BOARD hoge Parsing zephyr/Kconfig Loaded configuration 'zephyr/boards/riscv/hoge/hoge_defconfig' Merged configuration 'zephyr/samples/hello_world/prj.conf' Configuration saved to 'zephyr/build/zephyr/.config' -- The C compiler identification is GNU 8.3.0 -- The CXX compiler identification is GNU 8.3.0 -- The ASM compiler identification is GNU -- Found assembler: /home/katsuhiro/x-tools/riscv64-zephyr-elf/bin/riscv64-zephyr-elf-gcc -- Cache files will be written to: /home/katsuhiro/.cache/zephyr -- Configuring done -- Generating done -- Build files have been written to: zephyr/build $ ninja [0/1] Re-running CMake... -- Zephyr version: 2.1.99 -- Selected BOARD hoge Parsing zephyr/Kconfig Loaded configuration 'zephyr/build/zephyr/.config' No change to 'zephyr/build/zephyr/.config' -- Cache files will be written to: /home/katsuhiro/.cache/zephyr -- Configuring done -- Generating done -- Build files have been written to: zephyr/build [5/83] Building C object zephyr/CMakeFiles/offsets.dir/arch/riscv/core/offsets/offsets.c.obj FAILED: zephyr/CMakeFiles/offsets.dir/arch/riscv/core/offsets/offsets.c.obj ccache /home/katsuhiro/x-tools/riscv64-zephyr-elf/bin/riscv64-zephyr-elf-gcc -DBUILD_VERSION=zephyr-v2.1.0-1471-g7e7a4426d835 -DKERNEL -D_FORTIFY_SOURCE=2 -D__ZEPHYR__=1 -I../kernel/include -I../arch/riscv/include -I../include -Izephyr/include/generated -I../soc/riscv/litex-vexriscv -isystem ../lib/libc/minimal/include -isystem /home/katsuhiro/x-tools/riscv64-zephyr-elf/lib/gcc/riscv64-zephyr-elf/8.3.0/include -isystem /home/katsuhiro/x-tools/riscv64-zephyr-elf/lib/gcc/riscv64-zephyr-elf/8.3.0/include-fixed -Os -imacroszephyr/build/zephyr/include/generated/autoconf.h -ffreestanding -fno-common -g -mabi=ilp32 -march=rv32ima -imacroszephyr/include/toolchain/zephyr_stdint.h -Wall -Wformat -Wformat-security -Wno-format-zero-length -Wno-main -Wno-pointer-sign -Wpointer-arith -Wno-unused-but-set-variable -Werror=implicit-int -fno-asynchronous-unwind-tables -fno-pie -fno-pic -fno-strict-overflow -fno-reorder-functions -fno-defer-pop -fmacro-prefix-map=zephyr/samples/hello_world=CMAKE_SOURCE_DIR -fmacro-prefix-map=zephyr=ZEPHYR_BASE -ffunction-sections -fdata-sections -std=c99 -nostdinc -MD -MT zephyr/CMakeFiles/offsets.dir/arch/riscv/core/offsets/offsets.c.obj -MF zephyr/CMakeFiles/offsets.dir/arch/riscv/core/offsets/offsets.c.obj.d -o zephyr/CMakeFiles/offsets.dir/arch/riscv/core/offsets/offsets.c.obj -c zephyr/arch/riscv/core/offsets/offsets.c In file included from ../include/sys/atomic.h:468, from ../include/kernel_includes.h:21, from ../include/kernel.h:17, from zephyr/arch/riscv/core/offsets/offsets.c:16: zephyr/include/generated/syscalls/atomic.h:25:19: error: conflicting types for 'atomic_cas' static inline int atomic_cas(atomic_t * target, atomic_val_t old_value, atomic_val_t new_value) ^~~~~~~~~~ In file included from ../include/kernel_includes.h:21, from ../include/kernel.h:17, from zephyr/arch/riscv/core/offsets/offsets.c:16: ../include/sys/atomic.h:44:20: note: previous definition of 'atomic_cas' was here static inline bool atomic_cas(atomic_t *target, atomic_val_t old_value,
せっかく cmake がうまくいった、と思ったのも束の間で、ninja は見るのが嫌になるくらい大量のエラーを表示して失敗します。続きはまた今度。
前回 ninja を実行したときに嫌というほどエラーが出ました。エラーを追う前に、コンフィグがあっているかチェックしたいと思います。
Zephyr は Linux Kernel と似たコンフィグのシステム(Kconfig)を持っています。使い方も Linux と似ており、ninja menuconfig とすると、ケバケバしい色(私の環境だと真っ白+黄色になる……)の画面が表示されます。
なぜか Architecture が x86 となっていたり、SoC が LiteX になっていたり、色々おかしいです。なぜこうなってしまったかは後にして、手当たり次第でそれらしい値に直すと、Board Selection に今回追加した Hoge target が出現します。
$ ninja menuconfig Architecture (x86 architecture) ---> ( ) RISCV architecture を選択 SoC/CPU/Configuration Selection (LiteX VexRiscv system implementation) ---> ( ) SiFive Freedom SOC implementation を選択
コンフィグがそれらしくなったら ninja でビルドします。
$ ninja zephyr/samples/hello_world/src/main.c:12:30: error: 'CONFIG_BOARD' undeclared (first use in this function); did you mean 'CONFIG_ARCH'? printk("Hello World! %s\n", CONFIG_BOARD); ^~~~~~~~~~~~ CONFIG_ARCH ... In file included from ../include/devicetree.h:12, from ../soc/riscv/riscv-privilege/sifive-freedom/soc.h:15, from ../include/arch/riscv/arch.h:25, from ../include/arch/cpu.h:23, from ../include/kernel_includes.h:34, from ../include/kernel.h:17, from zephyr/drivers/interrupt_controller/intc_plic.c:13: zephyr/drivers/interrupt_controller/intc_plic.c: In function 'riscv_plic_irq_enable': zephyr/include/generated/devicetree_fixups.h:8:25: error: 'DT_INST_0_SIFIVE_PLIC_1_0_0_IRQ_EN_BASE_ADDRESS' undeclared (first use in this function) #define DT_PLIC_IRQ_EN DT_INST_0_SIFIVE_PLIC_1_0_0_IRQ_EN_BASE_ADDRESS ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ...
先程よりはマシになりましたが、まだエラーが山のように出ます。エラーの種類を大別すると CONFIG_BOARD の定義がない、DT_INST_ なんちゃらの定義がない、に分けられます。
最初の CONFIG_BOARD については、いかにもボード側で定義しなければならなさそうな名前です。おなじみ他のボードを参照(boards/riscv/qemu_riscv32/Kconfig.defconfig など)すると、Kconfig.board で定義した config BOARD_HOGE が定義されているときに限り、CONFIG_BOARD にボード名のデフォルト値を設定していました。
つまり、このように書けば良いみたいです。
# SPDX-License-Identifier: Apache-2.0
if BOARD_HOGE
config BOARD
default "hoge"
endif
それ以外の DT_INST_ なんちゃらの定義については、少し複雑です。
Linux ではデバイスツリーファイル(*.dts)をデバイスツリーコンパイラ dtc でコンパイルして、Flattened Device Tree(fdt)という形式のバイナリにします。fdt はカーネル内部に組み込んだり、ブートローダが起動時にカーネルに渡すなどして利用されます。
しかし Zephyr はそもそも dtc を使わず、Python で処理します。デバイスツリーを書き間違えると Python スクリプトが怒ってくるのですが、これが理由みたいです。なんだと?と思う方は Zephyr のドキュメント(Devicetree - Zephyr Project Documentation)をご参照ください。下記に引用します。
Note: In addition to the Python code above, the standard dtc DTS compiler is also run on the devicetree. This is just to catch any errors or warnings it generates. The output is unused.
ドキュメントの Generated macros の章を見ると、デバイスツリーから DT_INST_ なんちゃら、というマクロを生成する、という説明があり、ビルドエラーの原因となったマクロの仲間のようです。
推測するにデバイスツリーを何も定義していないことが、DT_INST_ なんちゃらマクロが存在しないことの原因ではないでしょうか?他のボード(boards/riscv/qemu_riscv32/qemu_riscv32.dts など)がデバイスツリーをどうしているか、見ながら真似してみます。
/* SPDX-License-Identifier: Apache-2.0 */
/dts-v1/;
#include <riscv32-fe310.dtsi>
/ {
model = "Hoge Board";
compatible = "hoge,hoge";
chosen {
zephyr,console = &uart0;
zephyr,shell-uart = &uart0;
zephyr,sram = &dtim;
zephyr,flash = &flash0;
};
};
&gpio0 {
status = "okay";
};
&uart0 {
status = "okay";
current-speed = <115200>;
clock-frequency = <16000000>;
};
&spi0 {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
reg = <0x10014000 0x1000 0x20400000 0xc00000>;
flash0: flash@0 {
compatible = "issi,is25lp128", "jedec,spi-nor";
size = <134217728>;
label = "FLASH0";
jedec-id = [96 60 18];
reg = <0>;
// Dummy entry
spi-max-frequency = <0>;
};
};
ほぼ全てコピーしただけです。ビルドしてみると、最後の方まで行きますが、いくつかシンボルがないと言われます。ログはフルパスが出ていて鬱陶しいので意味が残る程度に削っています。
$ ninja ... riscv64-zephyr-elf/bin/ld: zephyr/arch/arch/riscv/core/libarch__riscv__core.a(isr.S.obj): in function `.L0 ': zephyr/arch/riscv/core/isr.S:244: undefined reference to `_sw_isr_table' riscv64-zephyr-elf/bin/ld: zephyr/kernel/libkernel.a(sched.c.obj): in function `z_reset_time_slice': zephyr/kernel/sched.c:276: undefined reference to `z_clock_elapsed' riscv64-zephyr-elf/bin/ld: zephyr/kernel/libkernel.a(timeout.c.obj): in function `elapsed': zephyr/kernel/timeout.c:69: undefined reference to `z_clock_elapsed' collect2: error: ld returned 1 exit status % ninja: build stopped: subcommand failed.
これらのシンボルはドライバが提供するもののようですので、コンフィグでそれらしきものを ON にしていきます。
General Architecture Options ---> Interrupt Configuration ---> [ ] Use generated IRQ tables を選択 General Kernel Options ---> [ ] Execute in place Device Drivers ---> [ ] Serial Drivers ---- [ ] Enable UART Interrupt support を選択 [ ] SiFive Freedom serial driver (NEW) ---- [ ] Enable SIFIVE Port 0 (NEW) ---- を選択 [ ] Console drivers --- [ ] Use UART for console (NEW) を選択 Timer Drivers ---> [ ] RISCV Machine Timer を選択 [ ] GPIO Drivers ---- [ ] SiFive Freedom Processor GPIO driver (NEW) ---- を選択 [ ] Enable board pinmux driver ---- [ ] SiFive Freedom SOC pinmux driver (NEW) ----
このように全て手動で設定する必要はない(後々不要となる)ので、あまり詳しくなる必要はないですが、
コンフィグがそれらしくなったら ninja でビルドして、実行します。
$ ninja ... Memory region Used Size Region Size %age Used ROM: 13165 B 12 MB 0.10% RAM: 3808 B 16 KB 23.24% IDT_LIST: 569 B 2 KB 27.78% [98/98] Linking C executable zephyr/zephyr.elf $ /usr/bin/qemu-system-riscv32 -nographic -machine sifive_e -net none -chardev stdio,id=con,mux=on -serial chardev:con -mon chardev=con,mode=readline -kernel /home/katsuhiro/share/projects/oss/zephyr/build/zephyr/zephyr.elf *** Booting Zephyr OS build zephyr-v2.1.0-1471-g7e7a4426d835 *** Hello World! hoge
実行できました。Hello World の後ろがボード名(CONFIG_BOARD)です。無事、アプリが hoge ボード上で動いたということですね。次回は面倒くさかった手動コンフィグの撲滅に挑みます。
前回はデフォルトコンフィグが x86 になっていたり、ドライバを有効にしないとリンクエラーになったり、おかしなことが起きていました。対策として手動でコンフィグを変えまくりましたが、本来は必要ありません。Zephyr のボード定義では、ボードごとにデフォルトコンフィグを持つことができるからです。
他のボード(zephyr/boards/riscv/qemu_riscv32/qemu_riscv32_defconfig など)を見ると、ボード名_defconfig というファイルの中に CONFIG_ABCD=y という文がたくさんあります。このファイルに追加すると反映されそうです。
(2/19 訂正: CONFIG_SYS_CLOCK_TICKS_PER_SEC=100 を追加しないとタイマー割り込みが入りすぎて動かなくなるため、修正しました)
# SPDX-License-Identifier: Apache-2.0
CONFIG_RISCV=y
CONFIG_SOC_SERIES_RISCV_SIFIVE_FREEDOM=y
CONFIG_SOC_RISCV_SIFIVE_FREEDOM=y
CONFIG_BOARD_HOGE=y
CONFIG_CONSOLE=y
CONFIG_PRINTK=y
CONFIG_SERIAL=y
CONFIG_UART_SIFIVE=y
CONFIG_UART_SIFIVE_PORT_0=y
CONFIG_UART_CONSOLE=y
CONFIG_PLIC=y
CONFIG_PINMUX=y
CONFIG_PINMUX_SIFIVE=y
CONFIG_RISCV_MACHINE_TIMER=y
CONFIG_GPIO=y
CONFIG_GPIO_SIFIVE=y
CONFIG_SYS_CLOCK_TICKS_PER_SEC=100
CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC=10000000
CONFIG_BOARD_HOGE を除けば、ほぼ qemu_riscv32_defconfig と同じです。
$ cd build $ rm -r * $ cmake -G Ninja -DBOARD=hoge ../samples/hello_world/ -- Zephyr version: 2.1.99 -- Found PythonInterp: /usr/bin/python3 (found suitable version "3.7.6", minimum required is "3.6") -- Selected BOARD hoge -- Loading zephyr/boards/riscv/hoge/hoge.dts as base Devicetree header saved to 'zephyr/build/zephyr/include/generated/devicetree_unfixed.h' Parsing zephyr/Kconfig Loaded configuration 'zephyr/boards/riscv/hoge/hoge_defconfig' Merged configuration 'zephyr/samples/hello_world/prj.conf' Configuration saved to 'zephyr/build/zephyr/.config' Kconfig header saved to 'zephyr/build/zephyr/include/generated/autoconf.h' -- The C compiler identification is GNU 8.3.0 -- The CXX compiler identification is GNU 8.3.0 -- The ASM compiler identification is GNU -- Found assembler: /home/katsuhiro/x-tools/riscv64-zephyr-elf/bin/riscv64-zephyr-elf-gcc -- Cache files will be written to: /home/katsuhiro/.cache/zephyr -- Configuring done -- Generating done -- Build files have been written to: zephyr/build $ ninja [0/1] Re-running CMake... -- Zephyr version: 2.1.99 -- Selected BOARD hoge -- Loading zephyr/boards/riscv/hoge/hoge.dts as base Devicetree header saved to 'zephyr/build/zephyr/include/generated/devicetree_unfixed.h' Parsing zephyr/Kconfig Loaded configuration 'zephyr/build/zephyr/.config' No change to configuration in 'zephyr/build/zephyr/.config' No change to Kconfig header in 'zephyr/build/zephyr/include/generated/autoconf.h' -- Cache files will be written to: /home/katsuhiro/.cache/zephyr -- Configuring done -- Generating done -- Build files have been written to: zephyr/build [97/102] Linking C executable zephyr/zephyr_prebuilt.elf Memory region Used Size Region Size %age Used ROM: 12761 B 12 MB 0.10% RAM: 4048 B 16 KB 24.71% IDT_LIST: 553 B 2 KB 27.00% [102/102] Linking C executable zephyr/zephyr.elf
この状態で cmake から実行すると、ビルド時に文句を言われなくなります。build ディレクトリには、以前 cmake を実行したときの設定ファイルが残っていますので、一度全て削除してから再度 cmake を実行した方が良いでしょう。特に -DBOARD を変更するときは削除したほうが良いです(cmake にも同様の内容を注意されるはず)。
次回以降は、独自の SoC の追加に挑もうと思います。
前回は説明を省きましたが、デバイスツリーから DT_INST_ なんとかマクロへの変換ルールは下記にあります。
# zephyr/cmake/dts.cmake
...
#
# Run gen_defines.py to create a .conf file and a header file
#
set(CMD_NEW_EXTRACT ${PYTHON_EXECUTABLE} ${ZEPHYR_BASE}/scripts/dts/gen_defines.py
--dts ${BOARD}.dts.pre.tmp
--bindings-dirs ${DTS_ROOT_BINDINGS}
--conf-out ${DEVICETREE_CONF}
--header-out ${DEVICETREE_UNFIXED_H}
--dts-out ${PROJECT_BINARY_DIR}/zephyr.dts # As a debugging aid
)
...
# zephyr/scripts/dts/gen_defines.py
def main():
global conf_file
global header_file
global flash_area_num
...
for node in sorted(edt.nodes, key=lambda node: node.dep_ordinal):
write_node_comment(node)
# Flash partition nodes are handled as a special case. It
# would be nicer if we had bindings that would let us
# avoid that, but this will do for now.
if node.name.startswith("partition@"):
write_flash_partition(node, flash_area_num)
flash_area_num += 1
if node.enabled and node.matching_compat:
write_regs(node)
write_irqs(node)
write_props(node)
write_clocks(node)
write_spi_dev(node)
write_bus(node)
write_existence_flags(node)
...
def write_existence_flags(node):
# Generate #defines of the form
#
# #define DT_INST_<instance no.>_<compatible string> 1
#
# for enabled nodes. These are flags for which devices exist.
for compat in node.compats:
instance_no = node.edt.compat2enabled[compat].index(node)
out(f"INST_{instance_no}_{str2ident(compat)}", 1)
...
def str2ident(s):
# Converts 's' to a form suitable for (part of) an identifier
return s.replace("-", "_") \
.replace(",", "_") \
.replace("@", "_") \
.replace("/", "_") \
.replace(".", "_") \
.replace("+", "PLUS") \
.upper()
基本的にノードの compatible がそのまま使われますが、C 言語のマクロ名として使用できない文字はアンダースコアに置換されます。compatible は小文字で書くことが多いですが、マクロ名は大文字に変換しています。
Hoge ボードの定義を一通り実装しました。Hoge ボードの SoC は qemu_riscv32 と同じ SiFive FE310 にしたことを覚えているでしょうか?Zephyr の移植に興味のある方からすると「ボード名を変えただけで、qemu_riscv32 のコピーでは?」と感じるはずです。そんな悲しみの声にお答えすべく、次は SoC の変更を行います。
今回、ターゲットとする SoC は QEMU の Spike モードです。このモードは、特別なハードウェアが必要なく手軽、偶然にも現状の Zephyr が対応していない、簡単なドライバの実装が 1つだけ必要、など学習にピッタリです。
Spike はシミュレータのみで、物理的に存在する SoC ではありません。ですが名前がないと説明しづらいので、以降の説明では Spike SoC と呼びます。
将来複雑になるかもしれませんが、今は簡単な構造です。zephyr/soc/riscv の下に SoC が定義されており、litex-vexriscv(LiteX VexRiscv), openisa_rv32m1(OpenISA RV32M1), riscv-privilege(RISC-V Priviledged Architecture 対応 SoC)の 3つのディレクトリが存在しています。このうち riscv-privilege は、さらに内部にいくつかの SoC の定義を抱えています。
Spike は RISC-V Priviledged に対応しているので、今回は riscv-privilege シリーズの 1つとして、新たな SoC を定義すると良さそうです。
Hoge ボードの定義を見直してみると、Kconfig.board に "depends on SOC_RISCV_SIFIVE_FREEDOM" の記述があり、hoge.dts に #include <riscv32-fe310.dtsi> の記述があります。とりあえずこれを下記のように変更します。
diff --git a/boards/riscv/hoge/Kconfig.board b/boards/riscv/hoge/Kconfig.board
index 7ece0d6dee..c90614a391 100644
--- a/boards/riscv/hoge/Kconfig.board
+++ b/boards/riscv/hoge/Kconfig.board
@@ -2,4 +2,4 @@
config BOARD_HOGE
bool "Hoge target"
- depends on SOC_RISCV_SIFIVE_FREEDOM
+ depends on SOC_RISCV_SPIKE
diff --git a/boards/riscv/hoge/hoge.dts b/boards/riscv/hoge/hoge.dts
index 21678f49ea..ee0c6589c4 100644
--- a/boards/riscv/hoge/hoge.dts
+++ b/boards/riscv/hoge/hoge.dts
@@ -2,7 +2,7 @@
/dts-v1/;
-#include <riscv32-fe310.dtsi>
+#include <riscv32-spike.dtsi>
/ {
model = "Hoge";
この状態で cmake をやり直します。念のため build ディレクトリのファイルを全て削除してから、実行したほうが良いでしょう。
$ cmake -G Ninja -DBOARD=hoge ../samples/hello_world/ -- Zephyr version: 2.1.99 -- Found PythonInterp: /usr/bin/python3 (found suitable version "3.7.6", minimum required is "3.6") -- Selected BOARD hoge -- Loading zephyr/boards/riscv/hoge/hoge.dts as base In file included from <command-line>: zephyr/boards/riscv/hoge/hoge.dts:5:10: fatal error: riscv32-spike.dtsi: No such file or directory #include <riscv32-spike.dtsi> ^~~~~~~~~~~~~~~~~~~~ compilation terminated. CMake Error at zephyr/cmake/dts.cmake:140 (message): command failed with return code: 1 Call Stack (most recent call first): zephyr/cmake/app/boilerplate.cmake:460 (include) CMakeLists.txt:5 (include) -- Configuring incomplete, errors occurred!
エラーをみるに riscv32-spike.dtsi がないと言って怒られているようですから、追加しましょう。
Zephyr のデバイスツリーは zephyr/dts 以下にあります。今回は RISC-V 一派の SoC を追加しますから、zephyr/dts/riscv の下に riscv32-spike.dtsi を作りましょう。
/* SPDX-License-Identifier: Apache-2.0 */
/ {
#address-cells = <1>;
#size-cells = <1>;
compatible = "spike-dev";
model = "spike";
soc {
#address-cells = <1>;
#size-cells = <1>;
compatible = "spike-soc", "simple-bus";
ranges;
clint: clint@2000000 {
compatible = "riscv,clint0";
reg = <0x02000000 0x10000>;
reg-names = "control";
};
mem: mem@80000000 {
compatible = "spike,mem";
reg = <0x80000000 0x4000>;
reg-names = "mem";
};
uart0: serial {
compatible = "spike,uart-spike";
label = "uart_0";
};
};
};
最低限のデバイスだけ定義しています。
このファイルを加えてもまだ cmake に怒られます。Spike SoC 用のデバイスツリーに出てこないノード(gpio, spi など)を参照している箇所があるためです。ボード側の定義からばっさり削除します。
diff --git a/boards/riscv/hoge/hoge.dts b/boards/riscv/hoge/hoge.dts
index 21678f49ea..e572d5a769 100644
--- a/boards/riscv/hoge/hoge.dts
+++ b/boards/riscv/hoge/hoge.dts
@@ -11,34 +11,10 @@
chosen {
zephyr,console = &uart0;
zephyr,shell-uart = &uart0;
- zephyr,sram = &dtim;
- zephyr,flash = &flash0;
+ zephyr,sram = &mem;
};
};
-&gpio0 {
- status = "okay";
-};
-
&uart0 {
status = "okay";
- current-speed = <115200>;
- clock-frequency = <16000000>;
-};
-
-&spi0 {
- status = "okay";
-
- #address-cells = <1>;
- #size-cells = <0>;
- reg = <0x10014000 0x1000 0x20400000 0xc00000>;
- flash0: flash@0 {
- compatible = "issi,is25lp128", "jedec,spi-nor";
- size = <134217728>;
- label = "FLASH0";
- jedec-id = [96 60 18];
- reg = <0>;
- // Dummy entry
- spi-max-frequency = <0>;
- };
};
これでもまだ cmake は通過できません。長くなってきましたので、続きはまた今度。
今回は cmake をパスするまで頑張ります。
今後の方針のため cmake のエラーを見ます。どうやら SOC_FAMILY_RISCV_PRIVILEGE がないとか、SOC_SERIES_RISCV_SPIKE とは何だ?とか、Kconfig 周りの設定で怒っているようです。何も定義していないので当然ですね。
warning: UART_CONSOLE (defined at drivers/console/Kconfig:47) was assigned the value 'y' but got the value 'n'. Check these unsatisfied dependencies: SERIAL_HAS_DRIVER (=n). See http://docs.zephyrproject.org/latest/reference/kconfig/CONFIG_UART_CONSOLE.html and/or look up UART_CONSOLE in the menuconfig/guiconfig interface. The Application Development Primer, Setting Configuration Values, and Kconfig - Tips and Best Practices sections of the manual might be helpful too. warning: RISCV_MACHINE_TIMER (defined at drivers/timer/Kconfig:153) was assigned the value 'y' but got the value 'n'. Check these unsatisfied dependencies: SOC_FAMILY_RISCV_PRIVILEGE (=n). See http://docs.zephyrproject.org/latest/reference/kconfig/CONFIG_RISCV_MACHINE_TIMER.html and/or look up RISCV_MACHINE_TIMER in the menuconfig/guiconfig interface. The Application Development Primer, Setting Configuration Values, and Kconfig - Tips and Best Practices sections of the manual might be helpful too. warning: The choice symbol BOARD_HOGE (defined at boards/riscv/hoge/Kconfig.board:3) was selected (set =y), but no symbol ended up as the choice selection. See http://docs.zephyrproject.org/latest/reference/kconfig/CONFIG_BOARD_HOGE.html and/or look up BOARD_HOGE in the menuconfig/guiconfig interface. The Application Development Primer, Setting Configuration Values, and Kconfig - Tips and Best Practices sections of the manual might be helpful too. zephyr/boards/riscv/hoge/hoge_defconfig:4: warning: attempt to assign the value 'y' to the undefined symbol SOC_SERIES_RISCV_SPIKE zephyr/boards/riscv/hoge/hoge_defconfig:5: warning: attempt to assign the value 'y' to the undefined symbol SOC_RISCV_SPIKE error: Aborting due to Kconfig warnings CMake Error at zephyr/cmake/kconfig.cmake:216 (message): command failed with return code: 1 Call Stack (most recent call first): zephyr/cmake/app/boilerplate.cmake:461 (include) CMakeLists.txt:5 (include)
すぐに Kconfig を追加と行きたいところですが、その前に少しやることがあります。
Hoge ボードの defconfig で余計なもの(SiFive FE310 SoC 用の設定)を定義しているので、削りましょう。SiFive 由来のドライバの設定、前回のデバイスツリーで定義していない PINMUX や GPIO の設定を削ります。
diff --git a/boards/riscv/hoge/hoge_defconfig b/boards/riscv/hoge/hoge_defconfig
index 3f9626815f..32c4e9e90d 100644
--- a/boards/riscv/hoge/hoge_defconfig
+++ b/boards/riscv/hoge/hoge_defconfig
@@ -1,19 +1,12 @@
# SPDX-License-Identifier: Apache-2.0
CONFIG_RISCV=y
-CONFIG_SOC_SERIES_RISCV_SIFIVE_FREEDOM=y
-CONFIG_SOC_RISCV_SIFIVE_FREEDOM=y
+CONFIG_SOC_SERIES_RISCV_SPIKE=y
+CONFIG_SOC_RISCV_SPIKE=y
CONFIG_BOARD_HOGE=y
CONFIG_CONSOLE=y
CONFIG_PRINTK=y
CONFIG_SERIAL=y
-CONFIG_UART_SIFIVE=y
-CONFIG_UART_SIFIVE_PORT_0=y
CONFIG_UART_CONSOLE=y
-CONFIG_PLIC=y
-CONFIG_PINMUX=y
-CONFIG_PINMUX_SIFIVE=y
CONFIG_RISCV_MACHINE_TIMER=y
-CONFIG_GPIO=y
-CONFIG_GPIO_SIFIVE=y
CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC=10000000
これで余計なコンフィグが原因で怒られることはないはずです。
基本的に SoC の Kconfig を追加する場合、zephyr/soc/アーキテクチャ名/SoC 名/Kconfig.soc を足せば良いですが、RISC-V Privileged 系の SoC は少し様子が違い、Kconfig が 2段構成になります。
# zephyr/soc/Kconfig source "$(SOC_DIR)/$(ARCH)/*/Kconfig.soc" # zephyr/soc/riscv/riscv-privilege/Kconfig.soc source "soc/riscv/riscv-privilege/*/Kconfig.series"
ファイルを足すべき場所がわかりました。早速 Kconfig.series を足します。内容は隣にある SiFive のディレクトリ(sifive-freedom)などを参考にします。
# RISCV_SPIKE SOC implementation
# SPDX-License-Identifier: Apache-2.0
config SOC_SERIES_RISCV_SPIKE
bool "SPIKE series SOC implementation"
depends on RISCV
select SOC_FAMILY_RISCV_PRIVILEGE
help
Enable support for Spike series SOC
再度 cmake を実行すると SOC_SERIES_RISCV_SPIKE のエラーが消えるはずです。残りの SOC_RISCV_SPIKE はどこで定義すべきでしょうか?
RISC-V Privileged や他のディレクトリを見ると気づくと思いますが、SoC の Kconfig には 2つ系列があります。
riscv-privilege/Kconfig.soc -> riscv-privilege/spike/Kconfig.series のラインと、
riscv-privilege/Kconfig -> riscv-privilege/spike/Kconfig.soc のラインです。先程、追加したのは前者です。名前が紛らわしいのは RISC-V Privileged 系列の実装の良くないところですね……。
# zephyr/Kconfig
source "Kconfig.zephyr"
# zephyr/Kconfig.zephyr
source "$(SOC_DIR)/Kconfig" # SOC_DIR = soc
# zephyr/soc/Kconfig
choice
prompt "SoC/CPU/Configuration Selection"
source "$(SOC_DIR)/$(ARCH)/*/Kconfig.soc" # soc/riscv/riscv-privilege/Kconfig.soc
endchoice
menu "Hardware Configuration"
osource "$(SOC_DIR)/$(ARCH)/Kconfig"
osource "$(SOC_DIR)/$(ARCH)/*/Kconfig" # soc/riscv/riscv-privilege/Kconfig
# zephyr/soc/riscv/riscv-privilege/Kconfig.soc
source "soc/riscv/riscv-privilege/*/Kconfig.series"
# zephyr/soc/riscv/riscv-privilege/Kconfig
config SOC_FAMILY_RISCV_PRIVILEGE # riscv-privilege/spike/Kconfig.series の select で有効にする
config SOC_FAMILY
config RISCV_HAS_PLIC
...
source "soc/riscv/riscv-privilege/*/Kconfig.soc"
仕組みがわかったところで、後者の Kconfig.soc も追加しましょう。内容はやはり SiFive の Kconfig を参考にします。ありがとう SiFive さん。
# RISCV_SPIKE SOC configuration options
# SPDX-License-Identifier: Apache-2.0
choice
prompt "Spike series SOC implementation"
depends on SOC_SERIES_RISCV_SPIKE
config SOC_RISCV_SPIKE
bool "Spike SOC implementation"
select ATOMIC_OPERATIONS_C
endchoice
SOC_SERIES_RISCV_SPIKE は、先程 Kconfig.series にて定義しました。ATOMIC_OPERATIONS_C はアトミック演算を行う関数を定義するためのコンフィグです。これを指定しないと、後ほどリンクする際に atomic_cas() がないと言われます。
//zephyr/include/sys/atomic.h
#ifdef CONFIG_ATOMIC_OPERATIONS_BUILTIN
static inline bool atomic_cas(atomic_t *target, atomic_val_t old_value,
atomic_val_t new_value)
{
return __atomic_compare_exchange_n(target, &old_value, new_value,
0, __ATOMIC_SEQ_CST,
__ATOMIC_SEQ_CST);
}
#elif defined(CONFIG_ATOMIC_OPERATIONS_C)
__syscall int atomic_cas(atomic_t *target, atomic_val_t old_value,
atomic_val_t new_value);
#else
extern int atomic_cas(atomic_t *target, atomic_val_t old_value,
atomic_val_t new_value);
#endif
これでもまだ cmake に怒られます。linker.ld がどうのこうのと言っています。パスもなにやらおかしいです。
CMake Error at ../../CMakeLists.txt:372 (message): Could not find linker script: 'zephyr/soc/riscv//linker.ld'. Corrupted configuration?
この linker.ld を探しているのは cmake です。下記の部分で探しています。
# zephyr/CMakeLists.txt
...
if(CONFIG_HAVE_CUSTOM_LINKER_SCRIPT)
set(LINKER_SCRIPT ${APPLICATION_SOURCE_DIR}/${CONFIG_CUSTOM_LINKER_SCRIPT})
if(NOT EXISTS ${LINKER_SCRIPT})
set(LINKER_SCRIPT ${CONFIG_CUSTOM_LINKER_SCRIPT})
assert_exists(CONFIG_CUSTOM_LINKER_SCRIPT)
endif()
else()
# Try a board specific linker file
set(LINKER_SCRIPT ${BOARD_DIR}/linker.ld) # ★★★これ、もしくは★★★
if(NOT EXISTS ${LINKER_SCRIPT})
# If not available, try an SoC specific linker file
set(LINKER_SCRIPT ${SOC_DIR}/${ARCH}/${SOC_PATH}/linker.ld) # ★★★これ★★★
endif()
endif()
if(NOT EXISTS ${LINKER_SCRIPT})
message(FATAL_ERROR "Could not find linker script: '${LINKER_SCRIPT}'. Corrupted configuration?")
endif()
デバッグメッセージ(message(SOC_PATH ${SOC_PATH}) を書き足すなど)を出して確認すると、SOC_PATH が空になっています。まだ何か足りないようですので、調べます。SOC_PATH を定義しているのは下記の部分です。
# zephyr/cmake/app/boilerplate.cmake
...
set(SOC_NAME ${CONFIG_SOC})
set(SOC_SERIES ${CONFIG_SOC_SERIES})
set(SOC_FAMILY ${CONFIG_SOC_FAMILY})
if("${SOC_SERIES}" STREQUAL "")
set(SOC_PATH ${SOC_NAME})
else()
set(SOC_PATH ${SOC_FAMILY}/${SOC_SERIES})
endif()
CONFIG_SOC もしくは CONFIG_SOC_FAMILY と CONFIG_SOC_SERIES を定義する必要があるようです。探してみると CONFIG_SOC_FAMILY は zephyr/soc/riscv/riscv-privilege/Kconfig で定義されています。
もう一方の CONFIG_SOC_SERIES は Kconfig.defconfig.series というとても変な名前の Kconfig ファイルで定義するようです。
$ cd zephyr/soc/riscv/riscv-privilege $ grep -r 'config SOC_SERIES' miv/Kconfig.defconfig.series:config SOC_SERIES miv/Kconfig.series:config SOC_SERIES_RISCV32_MIV sifive-freedom/Kconfig.defconfig.series:config SOC_SERIES sifive-freedom/Kconfig.series:config SOC_SERIES_RISCV_SIFIVE_FREEDOM
この Kconfig はどこから参照されているのでしょうか?これも一応、調べておきましょう。
# zephyr/Kconfig
source "Kconfig.zephyr"
# zephyr/Kconfig.zephyr
source "$(SOC_DIR)/$(ARCH)/*/Kconfig.defconfig" # SOC_DIR = soc, ARCH = riscv
# zephyr/soc/riscv/riscv-privilege/Kconfig.defconfig
source "soc/riscv/riscv-privilege/*/Kconfig.defconfig.series"
Kconfig.defconfig.series も他の SoC に習って追加しましょう。Spike に PLIC は実装されていないので、RISCV_HAS_PLIC は n にしています。また Flash ROM もないので、XIP(Execution In Place)を無効にしています。
# SPDX-License-Identifier: Apache-2.0
if SOC_SERIES_RISCV_SPIKE
config SOC_SERIES
default "spike"
config RISCV_HAS_PLIC
default n
config NUM_IRQS
default 16
config XIP
default n
endif #SOC_SERIES_RISCV_SPIKE
肝心の linker.ld を追加することも忘れないようにします。これも他の SoC を見ればわかるはず。
/*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @brief Linker script for the Spike series processor
*/
#include <arch/riscv/common/linker.ld>
これで Kconfig 周りが整ったはずです。
これでクリアかと思いきや、まだ cmake に怒られます。もう一手だけ必要です。
CMake Error at ../../soc/riscv/riscv-privilege/CMakeLists.txt:4 (add_subdirectory): The source directory /home/katsuhiro/share/projects/oss/zephyr/soc/riscv/riscv-privilege/spike does not contain a CMakeLists.txt file. -- Configuring incomplete, errors occurred!
CMakeLists.txt がないと言っています。このファイルには何を書くのが正解なのか、私もイマイチ理解できないですが、他の SoC を見ると、下記の一行を書いておけば良さそうです。
# SPDX-License-Identifier: Apache-2.0
zephyr_sources()
ここまで変更すると cmake のエラーが解消します。良く見ると何やら UART_CONSOLE がどうのこうのと文句を言っていますが、後ほど対応します。
$ cmake -G Ninja -DBOARD=hoge ../samples/hello_world/
-- Zephyr version: 2.1.99
-- Found PythonInterp: /usr/bin/python3 (found suitable version "3.7.6", minimum required is "3.6")
-- Selected BOARD hoge
-- Loading zephyr/boards/riscv/hoge/hoge.dts as base
hoge.dts.pre.tmp:22.17-25.5: Warning (simple_bus_reg): /soc/serial: missing or empty reg/ranges property
also defined at hoge.dts.pre.tmp:37.8-39.3
Devicetree header saved to 'zephyr/build/zephyr/include/generated/devicetree_unfixed.h'
Parsing zephyr/Kconfig
Loaded configuration 'zephyr/boards/riscv/hoge/hoge_defconfig'
Merged configuration 'zephyr/samples/hello_world/prj.conf'
Configuration saved to 'zephyr/build/zephyr/.config'
Kconfig header saved to 'zephyr/build/zephyr/include/generated/autoconf.h'
warning: UART_CONSOLE (defined at drivers/console/Kconfig:47) was assigned the value 'y' but got the
value 'n'. Check these unsatisfied dependencies: SERIAL_HAS_DRIVER (=n). See
http://docs.zephyrproject.org/latest/reference/kconfig/CONFIG_UART_CONSOLE.html and/or look up
UART_CONSOLE in the menuconfig/guiconfig interface. The Application Development Primer, Setting
Configuration Values, and Kconfig - Tips and Best Practices sections of the manual might be helpful
too.
-- The C compiler identification is GNU 8.3.0
-- The CXX compiler identification is GNU 8.3.0
-- The ASM compiler identification is GNU
-- Found assembler: /home/katsuhiro/x-tools/riscv64-zephyr-elf/bin/riscv64-zephyr-elf-gcc
-- Cache files will be written to: /home/katsuhiro/.cache/zephyr
-- Configuring done
-- Generating done
-- Build files have been written to: zephyr/build
この後は ninja のビルドエラーがたくさん出るので、1つずつ解消していくことになります。続きはまた次回です。
やっと cmake を通過したので、いよいよビルドに挑みます。
In file included from ../include/arch/cpu.h:25, from ../include/kernel_includes.h:34, from ../include/kernel.h:17, from zephyr/arch/riscv/core/offsets/offsets.c:16: ../include/arch/riscv/arch.h:25:10: fatal error: soc.h: No such file or directory #include <soc.h> ^~~~~~~ compilation terminated. ninja: build stopped: subcommand failed.
他の soc.h を見ると、soc_common.h, devicetree.h というヘッダをインクルードしているようです。それに習っておきます。
/*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file SoC configuration macros for the Spike series processor
*/
#ifndef __RISCV_SPIKE_SOC_H_
#define __RISCV_SPIKE_SOC_H_
#include <soc_common.h>
#include <devicetree.h>
#endif /* __RISCV_SPIKE_SOC_H_ */
再びビルドすると、今度は PLIC_* がないと怒られます。
zephyr/drivers/interrupt_controller/intc_plic.c: In function 'riscv_plic_irq_enable': zephyr/drivers/interrupt_controller/intc_plic.c:45:41: error: 'DT_PLIC_IRQ_EN' undeclared (first use in this function); did you mean 'PLIC_IRQS'? volatile u32_t *en = (volatile u32_t *)DT_PLIC_IRQ_EN; ^~~~~~~~~~~~~~ PLIC_IRQS
RISC-V Privileged 系の SoC では PLIC という割り込みコントローラのドライバが自動的に有効になります。今回、動作させるに当たって PLIC のサポートは不要なので、zephyr/boards/riscv/hoge/hoge_defconfig で無効にします。具体的には CONFIG_PLIC=n を足します。
この後 cmake からやり直してみると、
zephyr/drivers/timer/riscv_machine_timer.c: In function 'set_mtimecmp': zephyr/drivers/timer/riscv_machine_timer.c:28:31: error: 'RISCV_MTIMECMP_BASE' undeclared (first use in this function) volatile u32_t *r = (u32_t *)RISCV_MTIMECMP_BASE; ^~~~~~~~~~~~~~~~~~~ zephyr/drivers/timer/riscv_machine_timer.c:28:31: note: each undeclared identifier is reported only once for each function it appears in zephyr/drivers/timer/riscv_machine_timer.c: In function 'mtime': zephyr/drivers/timer/riscv_machine_timer.c:47:31: error: 'RISCV_MTIME_BASE' undeclared (first use in this function) volatile u32_t *r = (u32_t *)RISCV_MTIME_BASE; ^~~~~~~~~~~~~~~~ [87/94] Linking C static library zephyr/kernel/libkernel.a ninja: build stopped: subcommand failed.
CLINT のレジスタ mtime, mtimecmp のアドレスを必要としているようです。
他の SoC を眺めてみると、CLINT のレジスタアドレスを定義する場所は soc.h のようです。しかし肝心のアドレスがわかりません。QEMU のドキュメントに載っていれば簡単でしたが、見当たらなかったので QEMU のソースコードを見ます。
// https://github.com/qemu/qemu/blob/master/hw/riscv/spike.c
static const struct MemmapEntry {
hwaddr base;
hwaddr size;
} spike_memmap[] = {
[SPIKE_MROM] = { 0x1000, 0x11000 },
[SPIKE_CLINT] = { 0x2000000, 0x10000 },
[SPIKE_DRAM] = { 0x80000000, 0x0 },
};
// https://github.com/qemu/qemu/blob/master/include/hw/riscv/sifive_clint.h
enum {
SIFIVE_SIP_BASE = 0x0,
SIFIVE_TIMECMP_BASE = 0x4000,
SIFIVE_TIME_BASE = 0xBFF8
};
ベースアドレスが 0x02000000 で、レジスタのオフセットアドレスもわかりました。soc.h に追記します。
(2/19 訂正: MTIMECMP_BASE と MTIME_BASE のアドレスが逆だったので、修正しました)
/* Timer configuration */
#define RISCV_MTIMECMP_BASE 0x02004000
#define RISCV_MTIME_BASE 0x0200bff8
再びビルドします。やっとビルドをパスしました。
$ ninja [0/1] Re-running CMake... -- Zephyr version: 2.1.99 -- Selected BOARD hoge -- Loading zephyr/boards/riscv/hoge/hoge.dts as base hoge.dts.pre.tmp:22.17-25.5: Warning (simple_bus_reg): /soc/serial: missing or empty reg/ranges property also defined at hoge.dts.pre.tmp:37.8-39.3 Devicetree header saved to 'zephyr/build/zephyr/include/generated/devicetree_unfixed.h' Parsing zephyr/Kconfig Loaded configuration 'zephyr/build/zephyr/.config' No change to configuration in 'zephyr/build/zephyr/.config' No change to Kconfig header in 'zephyr/build/zephyr/include/generated/autoconf.h' -- Cache files will be written to: /home/katsuhiro/.cache/zephyr -- Configuring done -- Generating done -- Build files have been written to: zephyr/build [89/94] Linking C executable zephyr/zephyr_prebuilt.elf Memory region Used Size Region Size %age Used RAM: 12752 B 16 KB 77.83% IDT_LIST: 25 B 2 KB 1.22% [94/94] Linking C executable zephyr/zephyr.elf
まだめでたしめでたし、ではありません。
生成されたバイナリを実行すると何かエラーが表示されるものの、Hello world は表示されません。残念です。
$ /usr/bin/qemu-system-riscv32 -nographic -machine spike -net none -chardev stdio,id=con,mux=on -serial chardev:con -mon chardev=con,mode=readline -kernel zephyr/build/zephyr/zephyr.elf qemu-system-riscv32: clint: time_hi write not implemented qemu-system-riscv32: clint: time_lo write not implemented qemu-system-riscv32: clint: time_hi write not implemented
このままでは全く何も実行されていないのか、惜しいところまで実行されたのか判別が付きません。以前ご紹介した(2020年 1月 31日の日記参照)デバッグオプション -s と -S を使い、gdb で追跡します。
復習ですが、オプション -s は GDB の接続を localhost:1234 で受け付け、-S はエミュレータを Halted 状態で起動し gdb から再開要求をするまで動かないで待っている、という意味です。
$ riscv64-zephyr-elf-gdb zephyr/build/zephyr/zephyr.elf GNU gdb (crosstool-NG 1.24.0.60-a152d61) 8.3.1 Copyright (C) 2019 Free Software Foundation, Inc. ... (gdb) target remote localhost:1234 Remote debugging using localhost:1234 0x00001000 in ?? () (gdb) b main Breakpoint 1 at 0x800006d4: file zephyr/samples/hello_world/src/main.c, line 12. (gdb) c Continuing. Breakpoint 1, main () at zephyr/samples/hello_world/src/main.c:12 12 printk("Hello World! %s
", CONFIG_BOARD);
ターゲット(QEMU)に接続し、main にブレークを設定して、実行を継続しています。Hello world の main 関数に到達していることがわかります。素晴らしいですね。printk は呼ばれているものの、文字が出ないことが問題だとわかりました。
Hello world を拝むには、シリアルのドライバを追加する必要があります。続きはまた次回。
Zephyr のビルドに成功し、gdb で追跡したところ Hello world の main 関数まで到達していることもわかりました。ここまでくればあとは文字出力だけです。
まず Hoge ボードの defconfig に CONFIG_UART_SPIKE=y を加えます。cmake に「そんなコンフィグはない」と怒られるようになりますが、これから作るので OK です。
シリアルドライバのディレクトリは zephyr/drivers/serial にあります。ディレクトリの構造を見ると、CMakeLists.txt, Kconfig.*, ソースコード uart_*.c が並んでいます。この 3つの組を追加もしくは変更すれば良さそうです。
# SPDX-License-Identifier: Apache-2.0
menuconfig UART_SPIKE
bool "Spike Simulator serial driver"
depends on SOC_RISCV_SPIKE
select SERIAL_HAS_DRIVER
help
This option enables the Spike Simulator serial driver.
名前は何でも良いですが、Kconfig.spike という名前にしました。Kconfig.soc とは違って、ファイルを足すだけではダメで、シリアルドライバの Kconfig から読んでもらう必要があります。
...
source "drivers/serial/Kconfig.litex"
source "drivers/serial/Kconfig.rtt"
source "drivers/serial/Kconfig.xlnx"
source "drivers/serial/Kconfig.spike" # ★この行を足す★
endif # SERIAL
Kconfig を追加して cmake をするとまだ怒られます。CMakeList.txt も変更する必要があるとのことです。
-- Configuring done CMake Error at ../../cmake/extensions.cmake:372 (add_library): No SOURCES given to target: drivers__serial Call Stack (most recent call first): ../../cmake/extensions.cmake:349 (zephyr_library_named) ../../drivers/serial/CMakeLists.txt:3 (zephyr_library) CMake Generate step failed. Build files cannot be regenerated correctly. FAILED: build.ninja
CMakeLists.txt は他の行に習って追加します。CMakeLists.txt を変更しただけだと uart_spike.c が無いと言われるので、touch uart_spike.c で空のファイルを作成します。
...
zephyr_library_sources_if_kconfig(uart_liteuart.c)
zephyr_library_sources_ifdef(CONFIG_UART_RTT_DRIVER uart_rtt.c)
zephyr_library_sources_if_kconfig(uart_xlnx_ps.c)
zephyr_library_sources_if_kconfig(uart_spike.c) # ★この行を足す★
...
他の行に習って追加するだけではつまらないので、CMake のスクリプトも眺めます。今回使用した zephyr_library_sources_if_kconfig() と zephyr_library_sources_ifdef() との違いは、ソースコードのコメントにある説明がわかりやすいです。
ファイル名を大文字にして、CONFIG_ と連結させた名前(今回のケースだと CONFIG_UART_SPIKE)を生成して、コンフィグが有効ならソースコードをビルド対象に加えるという意味です。Kconfig のコンフィグは y, n, m の 3値を取りますが、Zephyr のスクリプトでは、コンフィグが有効だと y という値が入り、無効だと未定義になるようです。
# zephyr/cmake/extensions.cmake
...
# zephyr_library_sources_if_kconfig(fft.c)
# is the same as
# zephyr_library_sources_ifdef(CONFIG_FFT fft.c)
...
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()
...
function(zephyr_library_sources_ifdef feature_toggle source)
if(${${feature_toggle}})
zephyr_library_sources(${source} ${ARGN})
endif()
endfunction()
# zephyr/cmake/extensions.cmake
...
function(zephyr_library_sources source)
target_sources(${ZEPHYR_CURRENT_LIBRARY} PRIVATE ${source} ${ARGN})
endfunction()
...
以上の変更でビルドが通り、シリアルドライバを実装する枠が完成しました。続きは次回。
前回はシリアルドライバの枠組み、というか、何も実装されていないドライバを追加しました。今回はシリアルドライバを実装します。
Zephyr のドライバについてはドキュメント(Device Driver Model - Zephyr Project Documentation)があります。ドライバの定義や初期化を司る API が Driver API の節に書いてあります。
今回作成する UART ドライバでは初期化は要りませんが、ドライバの API をいくつか実装したいので DEVICE_AND_API_INIT() を使います。
UART のドライバで実装できる API の一覧はドキュメント(UART - Zephyr Project Documentation)に書いてあります。API はたくさんありますが、poll_in, poll_out があれば動作するようなので、この 2つを作ります。
コードは下記のようにしました。
/*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @brief UART driver for the Spike Simulator
*/
#include <kernel.h>
#include <arch/cpu.h>
#include <drivers/uart.h>
volatile u64_t tohost;
volatile u64_t fromhost;
void uart_spike_poll_out(struct device *dev, unsigned char c)
{
u64_t cmd;
// QEMU spike mode
u8_t dev_no = 1;
u8_t cmd_no = 1;
u64_t payload = c;
cmd = ((u64_t)dev_no << 56) |
((u64_t)cmd_no << 48) |
(payload & 0xffffffffffffULL);
tohost = cmd;
while (fromhost == 0)
;
fromhost = 0;
}
static int uart_spike_poll_in(struct device *dev, unsigned char *c)
{
return -1;
}
static int uart_spike_init(struct device *dev)
{
return 0;
}
static const struct uart_driver_api uart_spike_driver_api = {
.poll_in = uart_spike_poll_in,
.poll_out = uart_spike_poll_out,
};
DEVICE_AND_API_INIT(uart_spike_0, DT_INST_0_SPIKE_UART_SPIKE_LABEL,
uart_spike_init, NULL, NULL,
PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
(void *)&uart_spike_driver_api);
このままビルドすると DEVICE_AND_API_INIT() に渡しているラベル DT_INST_0_SPIKE_UART_SPIKE_LABEL が未定義だと怒られます。これについては後ほど作り方を説明します。
zephyr/drivers/serial/uart_spike.c:73:35: error: 'DT_INST_0_SPIKE_UART_SPIKE_LABEL' undeclared here (not in a function); did you mean 'DT_COMPAT_SPIKE_UART_SPIKE'? DEVICE_AND_API_INIT(uart_spike_0, DT_INST_0_SPIKE_UART_SPIKE_LABEL, ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ../include/device.h:106:11: note: in definition of macro 'DEVICE_AND_API_INIT' .name = drv_name, .init = (init_fn), \ ^~~~~~~~ [89/97] Linking C static library zephyr/libzephyr.a ninja: build stopped: subcommand failed.
初期化は DEVICE_AND_API_INIT() で指定します。引数の意味は DEVICE_AND_API_INIT() のドキュメントを見たほうが良いでしょう。
引っかかりそうなところは dev_name にダブルクォートを「付けない」こと、一番に初期化されるように PRE_KERNEL_1 にすることくらいでしょうか。
文字出力 poll_out の実装は、グローバル変数に謎の数字を書くだけの不思議実装になっていますが、これは QEMU Spike モードの Host, Guest 間インタフェースの仕様に従っているためです。
ちなみに QEMU 側はこんなコードになっています。
// https://github.com/qemu/qemu/blob/master/hw/riscv/riscv_htif.c
...
static void htif_handle_tohost_write(HTIFState *htifstate, uint64_t val_written)
{
uint8_t device = val_written >> 56;
uint8_t cmd = val_written >> 48;
uint64_t payload = val_written & 0xFFFFFFFFFFFFULL;
int resp = 0;
...
} else if (likely(device == 0x1)) {
/* HTIF Console */
if (cmd == 0x0) {
/* this should be a queue, but not yet implemented as such */
htifstate->pending_read = val_written;
htifstate->env->mtohost = 0; /* clear to indicate we read */
return;
} else if (cmd == 0x1) {
qemu_chr_fe_write(&htifstate->chr, (uint8_t *)&payload, 1);
resp = 0x100 | (uint8_t)payload;
} else {
qemu_log("HTIF device %d: unknown command\n", device);
}
} else {
...
htifstate->env->mfromhost = (val_written >> 48 << 48) | (resp << 16 >> 16);
htifstate->env->mtohost = 0; /* clear to indicate we read */
}
途中の if 文を見るとわかるように、device=1, cmd=1 にすると文字が出力できます。また QEMU が文字出力を終えたときに fromhost が 0 以外の値に書き換わります(なので、Zephyr のシリアルドライバ側では fromhost をポーリングで監視する実装にしています)。
DT_INST_0_SPIKE_UART_SPIKE_LABEL のようなラベルはユーザが定義するのではなく、zephyr/dts/bindings 以下のファイルに存在する compatible と、デバイスツリーを突き合わせて自動生成するみたいです。命名規則については、以前 Zephyr OS で遊ぼう その 3(2020年 2月 4日の日記参照)で紹介したとおりです。
# SPDX-License-Identifier: Apache-2.0
description: UART for Spike Simulator
compatible: "spike,uart-spike"
include: uart-controller.yaml
デバイスツリーの compatible の突き合わせとラベルの自動生成は、zephyr/scripts/dts/gen_defines.py と edtlib.py 辺りでやっていることは間違いなさそうなのですが、詳細な仕組みはまだ追えていません。
若干もやっとしますが YAML ファイルを作成して、compatible をデバイスツリーで使用したものと合わせると、目的のラベル DT_INST_0_SPIKE_UART_SPIKE_LABEL が定義され、ビルドが通ります。
ビルドが通ったら実行します。
$ /usr/bin/qemu-system-riscv32 -nographic -machine spike -net none -chardev stdio,id=con,mux=on -serial chardev:con -mon chardev=con,mode=readline -kernel zephyr/build/zephyr/zephyr.elf qemu-system-riscv32: clint: time_hi write not implemented qemu-system-riscv32: clint: time_lo write not implemented qemu-system-riscv32: clint: time_hi write not implemented *** Booting Zephyr OS build zephyr-v2.1.0-1699-gbb40304f2531 *** Hello World! hoge
やっと Hello world が拝めました。長かった。 (2/19 追記: qemu-system-riscv32: clint: time_hi write not implemented のエラーメッセージは mtime と mtimecmp のアドレスが逆だったために発生していたエラーです。アドレス修正後は表示されません)
今まで説明してきた変更をパッチとしてまとめておきます。
Zephyr は開発中なので、ときおり内部構造が変わり、過去と互換性がなくなってしまうことがあります。ボードや SoC の定義も例外ではありません。
前回までの説明では Zephyr 2.1.0 を使用していましたが、2.1.0 から 2.2.0 の間で、ボードの defconfig の書き方が変更されました。具体的に言うと、
下記のパッチにあるような変更をすると、2.2.0 でも Hoge ボードと Spike SoC の対応が適用できます。
それぞれ 1行ずつしか変更していませんので、すぐわかると思います。
Zephyr は Linux と同じ Kconfig を採用していますが、コンフィグの起点に対する考え方がだいぶ違います。Zephyr は最初に cmake で「ボード名(-DBOARD オプション)」を指定します。すなわちコンフィグの起点はボードです。一方の Linux は export ARCH=x86 のようにアーキテクチャを設定の起点にします。
Zephyr 2.1.0 では、アーキテクチャ(CONFIG_RISCV など)、SoC(CONFIG_SOC_RISCV_SIFIVE_FREEDOM など)、ボードの依存関係は下記のようになっていました。
アーキテクチャ(ボードの defconfig が y にする)← アーキテクチャが依存の起点 ↑depends on SoC(ボードの defconfig が y にする) ↑depends on ボード(ボードの defconfig が y にする)← Zephyr はなぜかボードがコンフィグの起点
依存関係とコンフィグの起点が矛盾しているため menuconfig が異常な動きをします。おそらく ninja menuconfig を弄れば矛盾に気づくと思いますが、Zephyr はなぜかボードが未対応のアーキテクチャや SoC(例えば qemu_riscv32 で CONFIG_ARM を選択できる)を有効にできます。Zephyr 2.1.0 だと 2つ、おかしな設定ができます。
実際に ninja menuconfig で、qemu_riscv32 が未対応のアーキテクチャを選ぶと下記のようになります。
# qemu_riscv32 のデフォルト Modules ---> Board Selection (QEMU RISCV32 target) ---> Board Options ---- SoC/CPU/Configuration Selection (SiFive Freedom SOC implementation) ---> Hardware Configuration ---> RISCV Options ---> Architecture (RISCV architecture) ---> General Architecture Options ---> General Kernel Options ---> ... # ボードが対応していないアーキテクチャを選択すると、SoC が勝手に変わる、ボードは何も選べなくなる Modules ---> Board Selection ---- Board Options ---- SoC/CPU/Configuration Selection (LiteX VexRiscv system implementation) ---> ★なぜか RISC-V 用の SoC が出てくる Hardware Configuration ---- Architecture (x86 architecture) ---> ★x86 に変えた General Architecture Options ---> General Kernel Options ---> ...
そもそも x86 が選択可能な時点でおかしいですが、x86 を選択したのに x86 じゃない SoC が選択肢に出てくるなど、かなりおかしな動きです。
Zephyr 2.2.0 では依存関係は下記のように、少しだけ整理されました。
アーキテクチャ(SoC が強制的に y にする、menuconfig では触れない) ↑select SoC(ボードの defconfig が y にする) ↑depends on ボード(ボードの defconfig が y にする)← ここが起点
アーキテクチャを変えることが出来なくなりました。前よりマシですが、やはり変なところは残っていて、未対応の SoC を選択するとボードの選択肢が消えてしまいます。
# qemu_riscv32 のデフォルト Modules ---> Board Selection (QEMU RISCV32 target) ---> Board Options ---- SoC/CPU/Configuration Selection (SiFive Freedom SOC implementation) ---> Hardware Configuration ---> RISCV Options ---> ... # SoC を無理やり未対応のものに変えると、ボードが選べなくなる Modules ---> Board Selection ---- Board Options ---- SoC/CPU/Configuration Selection (LiteX VexRiscv system implementation) ---> Hardware Configuration ---- RISCV Options ---> ...
個人的には Zephyr の Kconfig の使い方は変だな、と思います。依存関係があることはわかっているのだから、依存の根本の方から設定(要は、アーキテクチャ → SoC → ボード、の順)したら良いのに、Zephyr はなぜかボードの設定から固定していて、変な動きになっています。
Linux はどうしているかというと、最初に export ARCH=x86 とします。すなわち、アーキテクチャを設定の起点にしています。自然です。特に変な動きもしません。
Zephyr の方が Linux より後発ですが、ビルド周りは退化したように感じます。不思議。
以前(2020年 2月 1日の日記参照)Zephyr のエントリアドレスを調べましたが、もう少し先まで Zephyr のブート処理を調べます。復習となりますが、起動の仕方は下記のとおりです。
$ qemu-system-riscv32 -nographic -machine spike -net none -chardev stdio,id=con,mux=on -serial chardev:con -mon chardev=con,mode=readline -kernel zephyr/build/zephyr/zephyr.elf -s -S $ riscv64-zephyr-elf-gdb zephyr/build/zephyr/zephyr.elf ... (gdb) target remote localhost:1234 Remote debugging using localhost:1234 0x00001000 in ?? ()
ブート処理を追うにはデバッガが必須です(ブート部分でシリアル出力はできません)。これも前回の復習となりますが、QEMU は GDB でデバッグするためのオプションを持っています。オプション -s は GDB の接続を localhost:1234 で受け付けます、という意味で、オプション -S はエミュレータを Halted 状態で起動するという意味です。
GDB を接続すると、PC は 0x1000 を指しています。QEMU Spike モードはリセット後 0x1000 から実行を開始するようです。何度かステップ実行 si を行うと、0x1010 から 0x80000000 にジャンプするはずです。
0x1000: auipc t0,0x0
0x1004: addi a1,t0,32
0x1008: csrr a0,mhartid
0x100c: lw t0,24(t0)
=> 0x1010: jr t0 # 0x80000000 にジャンプ
0x1000: 0x00000297 0x02028593 0xf1402573 0x0182a283
0x1010: 0x00028067 0x00000000 0x80000000 0x00000000
↑この値をロードする
特に難しい話ではなく 0x1000 + 24 = 0x1018 からロード(0x80000000 が書かれている)して、ジャンプするだけです。ジャンプ先には Zephyr のエントリポイント __start がいます。
// zephyr/soc/riscv/riscv-privilege/common/vector.S
SECTION_FUNC(vectors, __start)
.option norvc;
/*
* Set mtvec (Machine Trap-Vector Base-Address Register)
* to __irq_wrapper.
*/
la t0, __irq_wrapper
csrw mtvec, t0
/* Jump to __initialize */
tail __initialize
// zephyr/arch/riscv/core/isr.S
/*
* Handler called upon each exception/interrupt/fault
* In this architecture, system call (ECALL) is used to perform context
* switching or IRQ offloading (when enabled).
*/
SECTION_FUNC(exception.entry, __irq_wrapper)
/* Allocate space on thread stack to save registers */
addi sp, sp, -__z_arch_esf_t_SIZEOF
/*
* Save caller-saved registers on current thread stack.
* NOTE: need to be updated to account for floating-point registers
* floating-point registers should be accounted for when corresponding
* config variable is set
*/
RV_OP_STOREREG ra, __z_arch_esf_t_ra_OFFSET(sp)
RV_OP_STOREREG gp, __z_arch_esf_t_gp_OFFSET(sp)
RV_OP_STOREREG tp, __z_arch_esf_t_tp_OFFSET(sp)
RV_OP_STOREREG t0, __z_arch_esf_t_t0_OFFSET(sp)
...
__start は __irq_wrapper 関数のアドレス(C 言語の関数ではないので、関数と呼ぶのは変ですが)を割り込みベクタに設定したあと、__initialize にジャンプします。
// zephyr/arch/riscv/core/reset.S
/*
* Remainder of asm-land initialization code before we can jump into
* the C domain
*/
SECTION_FUNC(TEXT, __initialize)
/*
* This will boot master core, just halt other cores.
* Note: need to be updated for complete SMP support
*/
csrr a0, mhartid
beqz a0, boot_master_core
loop_slave_core:
wfi
j loop_slave_core
boot_master_core:
#ifdef CONFIG_INIT_STACKS
...
#endif
/*
* Initially, setup stack pointer to
* _interrupt_stack + CONFIG_ISR_STACK_SIZE
*/
la sp, _interrupt_stack
li t0, CONFIG_ISR_STACK_SIZE
add sp, sp, t0
#ifdef CONFIG_WDOG_INIT
call _WdogInit
#endif
/*
* Jump into C domain. _PrepC zeroes BSS, copies rw data into RAM,
* and then enters kernel z_cstart
*/
call _PrepC
最初に mhartid という CPU コアの ID を読み取ります。0 番をマスターコアとして、0 番以外のコアは割り込み待ち(wfi)の無限ループに入ります。マスターコアはスタックポインタの初期化を行って、_PrepC() 関数を呼び出します。
ここからやっと C 言語の世界です。
前回はリセット直後から C 言語の関数と出会うところまでを紹介しました。今回はドライバの初期化までを紹介します。
// zephyr/arch/riscv/core/prep_c.c
void _PrepC(void)
{
z_bss_zero();
#ifdef CONFIG_XIP
z_data_copy();
#endif
#if defined(CONFIG_RISCV_SOC_INTERRUPT_INIT)
soc_interrupt_init();
#endif
z_cstart();
CODE_UNREACHABLE;
}
ブート後に最初に出会う C 言語の関数 _PrepC() ですが、実はまだグローバル変数の初期化が終わっていませんので、この関数で初期化しています。z_bss_zero() は、ゼロ初期化される変数の領域(BSS)を 0 で初期化します。z_data_copy() は、変数領域を ROM から RAM にコピーします。
XIP(Execution In Place)が有効な場合、コード領域は Flash ROM などに配置され、CPU は ROM から直接コードを読み出して実行します。コード領域はそれで良いですが、変数の初期値も ROM におかれていて、そのままでは変数に書き込みできません。そのため ROM から RAM にコピーする必要があります。
最後の z_start() まで来ると、やっと普通の C 言語の世界です。
// zephyr/kernel/init.c
FUNC_NORETURN void z_cstart(void)
{
...
/* perform basic hardware initialization */
z_sys_device_do_config_level(_SYS_INIT_LEVEL_PRE_KERNEL_1); //★ level = 0
z_sys_device_do_config_level(_SYS_INIT_LEVEL_PRE_KERNEL_2);
// zephyr/include/init.h
#define _SYS_INIT_LEVEL_PRE_KERNEL_1 0
#define _SYS_INIT_LEVEL_PRE_KERNEL_2 1
#define _SYS_INIT_LEVEL_POST_KERNEL 2
#define _SYS_INIT_LEVEL_APPLICATION 3
初期化関数 z_cstart() は、色々ごちゃごちゃやっているのですが、真ん中あたりでドライバの初期化が行われます。レベルを PRE_KERNEL_1 (level 0), PRE_KERNEL_2 (level 1) の順に指定して、z_sys_device_do_config_level() という関数を呼びます。ちなみに POST_KERNEL (level 2), APPLICATION (level 3) はもっと後で、カーネルの初期化が終わった後に使われます。
初期化される対象はデバイスドライバ以外(後述の、メモリ割り当てシステムドライバなど)もありますけど、仕組みは同じみたいです。
// zephyr/kernel/device.c
extern struct device __device_init_start[];
extern struct device __device_PRE_KERNEL_1_start[];
extern struct device __device_PRE_KERNEL_2_start[];
extern struct device __device_POST_KERNEL_start[];
extern struct device __device_APPLICATION_start[];
extern struct device __device_init_end[];
...
void z_sys_device_do_config_level(s32_t level)
{
struct device *info;
static struct device *config_levels[] = {
__device_PRE_KERNEL_1_start,
__device_PRE_KERNEL_2_start,
__device_POST_KERNEL_start,
__device_APPLICATION_start,
/* End marker */
__device_init_end,
};
for (info = config_levels[level]; info < config_levels[level+1];
info++) {
int retval;
const struct device_config *device_conf = info->config;
...
実装は上記のようになっています。level 0 の場合は __device_PRE_KERNEL_1_start[] という配列を先頭から処理します。他のレベルも同様ですね。
======== <-- config_levels[0] __device_PRE_KERNEL_1_start[0] __device_PRE_KERNEL_1_start[1] ... __device_PRE_KERNEL_1_start[n] ======== <-- config_levels[1] __device_PRE_KERNEL_2_start[0] __device_PRE_KERNEL_2_start[1] ... __device_PRE_KERNEL_2_start[n] ======== <-- config_levels[2] __device_POST_KERNEL_start[0] __device_POST_KERNEL_start[1] ... __device_POST_KERNEL_start[n] ======== <-- config_levels[3] __device_APPLICATION_start[0] __device_APPLICATION_start[1] ... __device_APPLICATION_start[n] ======== <-- __device_init_end
このようにメモリ上に struct device がレベル順に並んでいることを期待しているようです。しかし配列の実体は C のソースコード上には存在しないように見えます。これは一体何者なんでしょう?
続きはまた今度。
ドライバの初期化に重要な役割を果たしている __device_PRE_KERNEL_1_start[] の謎を追っていきます。謎について復習すると「実体はバイナリにあるもののソースコードには見当たらない」という点でした。
ソースコードでわからなければバイナリを見るのも一つの手です。シンボルの一覧を調べます。
$ riscv64-zephyr-elf-nm -n zephyr/build/zephyr/zephyr.elf ... 80002c38 D __device_PRE_KERNEL_1_start 80002c38 D __device_init_start 80002c38 d __device_sys_init_init_static_pools3 80002c44 d __device_uart_spike 80002c50 d __device_sys_init_uart_console_init0 80002c5c D __device_PRE_KERNEL_2_start 80002c5c d __device_sys_init_z_clock_driver_init0 80002c68 D __device_APPLICATION_start 80002c68 D __device_POST_KERNEL_start 80002c68 D __device_init_end ...
メモリイメージを図示すると下記のような感じで、前回のドライバの初期化処理が期待していると予想した並びになっています。違っていたら動きませんから、当たり前ですけども。
======== 80002c38 <-- __device_PRE_KERNEL_1_start, __device_init_start __device_sys_init_init_static_pools3 ======== 80002c44 __device_uart_spike ======== 80002c50 __device_sys_init_uart_console_init0 ======== 80002c5c <-- __device_PRE_KERNEL_2_start __device_sys_init_z_clock_driver_init0 ======== 80002c68 <-- __device_POST_KERNEL_start, __device_APPLICATION_start, __device_init_end
POST_KERNEL と APPLICATION は何も要素を持っておらず、同じアドレスになってしまっているものの、確かにレベル順(PRE_KERNEL_1, PRE_KERNEL_2, POST_KERNEL, APPLICATION)に並んでいます。
$ riscv64-zephyr-elf-readelf -a zephyr/build/zephyr/zephyr.elf ... Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] vector PROGBITS 80000000 000060 000010 00 AX 0 0 4 [ 2] exceptions PROGBITS 80000010 000070 000258 00 AX 0 0 4 [ 3] text PROGBITS 80000268 0002c8 002604 00 AX 0 0 4 [ 4] sw_isr_table PROGBITS 8000286c 0028cc 000080 00 WA 0 0 4 [ 5] devconfig PROGBITS 800028ec 00294c 000030 00 A 0 0 4 [ 6] rodata PROGBITS 8000291c 00297c 000305 00 A 0 0 4 [ 7] datas PROGBITS 80002c24 002c84 000014 00 WA 0 0 4 [ 8] initlevel PROGBITS 80002c38 002c98 000030 00 WA 0 0 4 ★ここに配置される★ [ 9] _k_mutex_area PROGBITS 80002c68 002cc8 000014 00 WA 0 0 4 [10] bss NOBITS 80002c80 002cdc 000140 00 WA 0 0 8 [11] noinit NOBITS 80002dc0 002cdc 000e00 00 WA 0 0 16 ...
実体はあるのにソースコードに見当たらず、initlevel という変わった名前のセクションに置かれています。どうやら通常のグローバル変数や、static 変数ではなさそうです。
一番最初に出てくる __device_sys_init_init_static_pools3 がどうやって作られるのか、追いかけたらわかるかもしれません。部分一致でも良いので、それっぽい名前を grep すると kernel/mempool.c で見つかります。
// zephyr/kernel/mempool.c
SYS_INIT(init_static_pools, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_OBJECTS);
// zephyr/include/init.h
/* A counter is used to avoid issues when two or more system devices
* are declared in the same C file with the same init function.
*/
#define Z_SYS_NAME(init_fn) _CONCAT(_CONCAT(sys_init_, init_fn), __COUNTER__)
/**
* @def SYS_INIT
*
* @brief Run an initialization function at boot at specified priority
*
* @details This macro lets you run a function at system boot.
*
* @param init_fn Pointer to the boot function to run
*
* @param level The initialization level, See DEVICE_AND_API_INIT for details.
*
* @param prio Priority within the selected initialization level. See
* DEVICE_AND_API_INIT for details.
*/
#define SYS_INIT(init_fn, level, prio) \
DEVICE_AND_API_INIT(Z_SYS_NAME(init_fn), "", init_fn, NULL, NULL, level,\
prio, NULL)
// (参考 1)
// init_fn = init_static_pools
// level = PRE_KERNEL_1
// prio = CONFIG_KERNEL_INIT_PRIORITY_OBJECTS
//
// Z_SYS_NAME(init_static_pools) = sys_init_init_static_pools3
マクロの嵐で面食らいますが、基本的に引数をそのまま渡していくだけなので 1つずつ見ればさほど難しくありません。
SYS_INIT() は mempool がシステムドライバであることを宣言するための API です(公式ドキュメント Device Driver Model - Zephyr Project Documentation, System Driver 節)。最終的には DEVICE_AND_API_INIT() が呼ばれます。実はこいつもマクロです、あとで紹介します。
若干難しいのは Z_SYS_NAME() でしょうか?このマクロは sys_init_ と、引数 init_fn とカウンタ(__COUNTER__)を連結したトークンを返します。
私の環境でビルドしたときは init_fn は init_static_pools で、__COUNTER__ は 3 でしたから、sys_init_ と init_static_pools と 3 が連結され、sys_init_init_static_pools3 になります。このトークンがDEVICE_AND_API_INIT() の第一引数に渡されます。
シンボル名が sys_init_init_... と init がダブっていて、変な名前だな?と思った方もいるでしょう、この Z_SYS_NAME() マクロが原因でした。ま、それはさておいて、続きを追いかけます。
// zephyr/include/device.h
/**
* @def DEVICE_AND_API_INIT
*
* @brief Create device object and set it up for boot time initialization,
* with the option to set driver_api.
*
* @copydetails DEVICE_INIT
* @param api Provides an initial pointer to the API function struct
* used by the driver. Can be NULL.
* @details The driver api is also set here, eliminating the need to do that
* during initialization.
*/
#ifndef CONFIG_DEVICE_POWER_MANAGEMENT
#define DEVICE_AND_API_INIT(dev_name, drv_name, init_fn, data, cfg_info, \
level, prio, api) \
static const struct device_config _CONCAT(__config_, dev_name) __used \
__attribute__((__section__(".devconfig.init"))) = { \
.name = drv_name, .init = (init_fn), \
.config_info = (cfg_info) \
}; \
static Z_DECL_ALIGN(struct device) _CONCAT(__device_, dev_name) __used \
__attribute__((__section__(".init_" #level STRINGIFY(prio)))) = { \
.config = &_CONCAT(__config_, dev_name), \
.driver_api = api, \
.driver_data = data \
}
#else
/*
* Use the default device_pm_control for devices that do not call the
* DEVICE_DEFINE macro so that caller of hook functions
* need not check device_pm_control != NULL.
*/
#define DEVICE_AND_API_INIT(dev_name, drv_name, init_fn, data, cfg_info, \
level, prio, api) \
DEVICE_DEFINE(dev_name, drv_name, init_fn, \
device_pm_control_nop, data, cfg_info, level, \
prio, api)
#endif
//DEVICE_AND_API_INIT を展開すると最終的に下記のようになる
//
//変数名
// _CONCAT(__config_, dev_name) = __config_sys_init_init_static_pools3
//構造体メンバー
// drv_name = ""
// init_fn = init_static_pools
// cfg_info = NULL
//なので、
static const struct device_config __config_sys_init_init_static_pools3 __used
__attribute__((__section__(".devconfig.init"))) = {
.name = "",
.init = init_static_pools,
.config_info = NULL
};
//変数名
// _CONCAT(__device_, dev_name) = __device_sys_init_init_static_pools3
//セクション名
// level = PRE_KERNEL_1
// prio = 30
// ".init_" #level STRINGIFY(prio) = ".init_PRE_KERNEL_130"
//構造体メンバー
// _CONCAT(__config_, dev_name) = __config_sys_init_init_static_pools3
// api = NULL
// data = NULL
//なので、
static Z_DECL_ALIGN(struct device) __device_sys_init_init_static_pools3 __used
__attribute__((__section__(".init_PRE_KERNEL_130"))) = {
.config = &__config_sys_init_init_static_pools3,
.driver_api = NULL,
.driver_data = NULL
}
//(参考 2: DEVICE_AND_API_INIT の引数と値)
// dev_name = Z_SYS_NAME(init_fn) = Z_SYS_NAME(init_static_pools) = sys_init_init_static_pools3
// drv_name = ""
// init_fn = init_fn = init_static_pools
// data = NULL
// cfg_info = NULL
// level = level = PRE_KERNEL_1
// prio = prio = CONFIG_KERNEL_INIT_PRIORITY_OBJECTS = 30
// api = NULL
//(参考 1: SYS_INIT の引数と値)
// init_fn = init_static_pools
// level = PRE_KERNEL_1
// prio = CONFIG_KERNEL_INIT_PRIORITY_OBJECTS = 30
ちょっと長いですが DEVICE_AND_API_INIT() を見ていくと、最後は上記のように __config_sys_init_init_static_pools3 と __device_sys_init_init_static_pools3 の 2つの構造体の宣言に展開されることがわかります。
後者の __device_sys_init_init_static_pools3 は、先程 nm で zephyr.elf を見たときに出てきた、アイツです。それに加えて __device_sys_init_init_static_pools3 は、.init_PRE_KERNEL_130 という変な名前のセクションに配置されることもわかると思います。
理解が合っているか不安なときは、答え合わせとしてバイナリをチェックです。ビルド時に生成されるオブジェクト build/zephyr/kernel/CMakeFiles/kernel.dir/mempool.c.obj のシンボルテーブルを objdump で確認するとわかりやすいです。
$ riscv64-zephyr-elf-objdump -t zephyr/build/zephyr/kernel/CMakeFiles/kernel.dir/mempool.c.obj ... 00000000 l O .bss.lock 00000000 lock 00000000 l d .devconfig.init 00000000 .devconfig.init 00000000 l O .devconfig.init 0000000c __config_sys_init_init_static_pools3 00000000 l d .init_PRE_KERNEL_130 00000000 .init_PRE_KERNEL_130 00000000 l O .init_PRE_KERNEL_130 0000000c __device_sys_init_init_static_pools3 ...
セクション .init_PRE_KERNEL_130 に __device_sys_init_init_static_pools3 が配置されていることが確認できました。じゃあ、どうやって __device_PRE_KERNEL_1_start に含まれるようになるんでしょう?
続きはまた今度。
引き続き、ドライバの初期化に重要な役割を果たしている __device_PRE_KERNEL_1_start[] の謎を追っていきます。謎は「__device_PRE_KERNEL_1_start[] の実体はバイナリにあるもののソースコードには見当たらない」ことです。前回まででわかったことを復習すると、
配列の要素がどうやってできたかわかったので、残る謎は下記になります。
これまた復習になりますが、__device_PRE_KERNEL_1_start[] は initlevel セクションに配置されていました。
$ riscv64-zephyr-elf-readelf -a zephyr/build/zephyr/zephyr.elf ... Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] vector PROGBITS 80000000 000060 000010 00 AX 0 0 4 [ 2] exceptions PROGBITS 80000010 000070 000258 00 AX 0 0 4 [ 3] text PROGBITS 80000268 0002c8 002604 00 AX 0 0 4 [ 4] sw_isr_table PROGBITS 8000286c 0028cc 000080 00 WA 0 0 4 [ 5] devconfig PROGBITS 800028ec 00294c 000030 00 A 0 0 4 [ 6] rodata PROGBITS 8000291c 00297c 000305 00 A 0 0 4 [ 7] datas PROGBITS 80002c24 002c84 000014 00 WA 0 0 4 [ 8] initlevel PROGBITS 80002c38 002c98 000030 00 WA 0 0 4 ★ここに配置される★ [ 9] _k_mutex_area PROGBITS 80002c68 002cc8 000014 00 WA 0 0 4 [10] bss NOBITS 80002c80 002cdc 000140 00 WA 0 0 8 [11] noinit NOBITS 80002dc0 002cdc 000e00 00 WA 0 0 16 ...
思い出していただきたいのは、配列の要素は .init_PRE_KERNEL_130 セクションに置かれていて、initlevel セクションではなかったことです。つまり誰かがわざわざ initlevel セクションに置き直しています。
そんな芸当ができるのはリンカーしかいませんので、initlevel をキーワードにリンカースクリプトを探します。
// zephyr/include/linker/common-ram.ld ... SECTION_DATA_PROLOGUE(initlevel,,) { DEVICE_INIT_SECTIONS() } GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION) // zephyr/include/linker/linker-defs.h /* * generate a symbol to mark the start of the device initialization objects for * the specified level, then link all of those objects (sorted by priority); * ensure the objects aren't discarded if there is no direct reference to them */ #define DEVICE_INIT_LEVEL(level) \ __device_##level##_start = .; \ KEEP(*(SORT(.init_##level[0-9]))); \ KEEP(*(SORT(.init_##level[1-9][0-9]))); \ /* * link in device initialization objects for all devices that are automatically * initialized by the kernel; the objects are sorted in the order they will be * initialized (i.e. ordered by level, sorted by priority within a level) */ #define DEVICE_INIT_SECTIONS() \ __device_init_start = .; \ DEVICE_INIT_LEVEL(PRE_KERNEL_1) \ DEVICE_INIT_LEVEL(PRE_KERNEL_2) \ DEVICE_INIT_LEVEL(POST_KERNEL) \ DEVICE_INIT_LEVEL(APPLICATION) \ __device_init_end = .; \ DEVICE_BUSY_BITFIELD() \
DEVICE_AND_API_INIT の宣言を思い出すと、ドライバなどで宣言される初期化セクションの名前は .init_(level)(prio) のような名前でした。DEVICE_INIT_LEVEL() はその法則性に従ってセクションを集めます。最終的に集められたセクションは、全て initlevel セクションに配置されます。
例えば DEVICE_INIT_SECTIONS() の 2行目にある DEVICE_INIT_LEVEL(PRE_KERNEL_1) ですと、.init_PRE_KERNEL_1(数字) という名前のセクションを集めます。
というわけで、やっと謎が解けました。マクロやリンカースクリプトの見事な連携プレイですね。
実装を見て気づいたと思いますが、DEVICE_AND_API_INIT() の prio に渡せる数字は 2桁が上限です。DEVICE_INIT_LEVEL() は [0-9] もしくは [1-9][0-9] のパターンしかマッチしません。
試しに優先度を 3桁にするとどうなるでしょう?mempool.c では CONFIG_KERNEL_INIT_PRIORITY_OBJECTS を prio に渡していたので、menuconfig でコンフィグを変えてみます。
$ ninja menuconfig General Kernel Options ---> Initialization Priorities ---> (30) Kernel objects initialization priority このパラメータを 300 に変更すると…? riscv64-zephyr-elf/bin/ld: Undefined initialization levels used. collect2: error: ld returned 1 exit status
リンク時にエラーで怒られました。これはどうやっているのでしょう?実はそんなに難しくありません。initlevel セクションを作るときとほぼ同じ仕組みです。
// zephyr/include/linker/common-ram.ld
/* verify we don't have rogue .init_<something> initlevel sections */
SECTION_DATA_PROLOGUE(initlevel_error,,)
{
DEVICE_INIT_UNDEFINED_SECTION()
}
ASSERT(SIZEOF(initlevel_error) == 0, "Undefined initialization levels used.")
// include/linker/linker-defs.h
/* define a section for undefined device initialization levels */
#define DEVICE_INIT_UNDEFINED_SECTION() \
KEEP(*(SORT(.init_[_A-Z0-9]*))) \
この initlevel_error セクションでは、initlevel セクションが拾い損ねた .init_* 系のセクションを全て集めます。もし 1つでもセクションが拾えた場合、initlevel_error セクションのサイズが 0 ではなくなるため、ASSERT に引っかかる仕組みになっています。リンカースクリプトで ASSERT や SORT ができるとは知らなかったですね……。
エラーチェックもきっちり作られていて Zephyr はさすが良くできているなあと思います。
先日は Zepphyr を QEMU の Spike モードに移植しましたが、Spike モードだと CPU 数は 1 以外選べないので、マルチプロセッサにできません。これは知りませんでした。最初に調べておけば良かったですね……。
マルチプロセッサで Zephyr を動かしてみたかったので、QEMU RISC-V 32 の virt モード(qemu-system-riscv32 の machine を virt にすること)に、前回同様にシリアルとメモリだけ動くような SoC とボードの定義ファイルを作りました。
QEMU virt モードでは、シリアルのハードウェアとして 16550 をエミュレーションします。Zephyr 側には既に 16550 用のドライバがあるので、わざわざ実装する必要もありません。とても楽です、素晴らしいです、とか何とか思いつつ動かしてみると、シリアルドライバがクラッシュします。んあー。
Zephyr を GDB で追うと 16550 の LCR レジスタを読もうとして、ロードアクセスフォルトが起きていました。アドレスのオフセットを見ると、0xc になっています。正しいオフセットは 3 のはずです。
シリアルドライバの実装を見ると、オフセットの計算が 0x3 x 4 = 0xc となっていて、おそらくレジスタサイズが 4バイト単位だと思ってアクセスしているのでしょう。
世の中にはレジスタが 4バイトの実装もあるかもしれませんが、残念なことに QEMU は由緒正しい(?)実装なので 16550 のレジスタは「1バイト」単位です。4バイト単位でアクセスすると、全く関係ない領域が破壊されます。困りました。
問題を解決するには、SoC の定義ファイルのどこか(soc.h 辺りかな?)で、
#define DT_NS16550_REG_SHIFT 0
を定義し、レジスタのサイズが 2^0 つまり 1バイト単位であることを 16550 シリアルドライバに教える必要があるみたいです。とても大事なことだと思いますが、全くドキュメントに書いていないように見えます。
そこそこコード見ないとわからないので、つらいです……。
無事シリアルが動作したので、QEMU を 4 CPU のマルチプロセッサ設定で起動し、GDB で覗いてみます。
$ qemu-system-riscv32 -nographic -machine virt -net none -chardev stdio,id=con,mux=on -serial chardev:con -mon chardev=con,mode=readline -kernel zephyr/build/zephyr/zephyr.elf -cpu rv32 -smp cpus=4 -s -S qemu-system-riscv32: warning: No -bios option specified. Not loading a firmware. qemu-system-riscv32: warning: This default will change in a future QEMU release. Please use the -bios option to avoid breakages when this happens. qemu-system-riscv32: warning: See QEMU's deprecation documentation for details. *** Booting Zephyr OS build v2.2.0-rc1-123-gcaca3f60b012 *** threadA: Hello World from QEMU RV32 virt board! threadB: Hello World from QEMU RV32 virt board! threadA: Hello World from QEMU RV32 virt board! threadB: Hello World from QEMU RV32 virt board! ... $ riscv64-zephyr-elf-gdb zephyr/build/zephyr/zephyr.elf ... Type "apropos word" to search for commands related to "word"... Reading symbols from build/zephyr/zephyr.elf... (gdb) target remote localhost:1234 Remote debugging using localhost:1234 0x00001000 in ?? () (gdb) info threads Id Target Id Frame * 1 Thread 1.1 (CPU#0 [running]) 0x00001000 in ?? () 2 Thread 1.2 (CPU#1 [running]) 0x00001000 in ?? () 3 Thread 1.3 (CPU#2 [running]) 0x00001000 in ?? () 4 Thread 1.4 (CPU#3 [running]) 0x00001000 in ?? ()
確かに 4 CPU が存在していることがわかります。Zephyr のログは GDB 側で continue すると出てきます。アプリケーションは、hello_world ではなく samples/synchronization を使っています。
メモ: 技術系の話は Facebook から転記しておくことにした。大幅に加筆した。
昨日(2020年 2月 20日の日記参照)の続きです。
シリアルのみですが、Zephyr を QEMU RISC-V 32 の virt モードに移植しました。やっと Zephyr が 4 CPU で動くぞ、などと思っていましたが、全然ダメでした。2つ目以降の CPU は全く動きませんでした。
$ riscv64-zephyr-elf-gdb zephyr/build/zephyr/zephyr.elf (gdb) info threads Id Target Id Frame * 1 Thread 1.1 (CPU#0 [halted ]) 0x80000f70 in arch_cpu_idle () at zephyr/soc/riscv/riscv-privilege/common/idle.c:21 2 Thread 1.2 (CPU#1 [halted ]) loop_slave_core () at zephyr/arch/riscv/core/reset.S:45 3 Thread 1.3 (CPU#2 [halted ]) loop_slave_core () at zephyr/arch/riscv/core/reset.S:45 4 Thread 1.4 (CPU#3 [halted ]) loop_slave_core () at zephyr/arch/riscv/core/reset.S:45
理由は簡単で、Zephyr の RISC-V 版は SMP に対応していないからです。何を言ってるんだ?って?ええ、実は私も今この瞬間まで知りませんでした。ビックリしました。
リセット付近のソースコードをみると、
// zephyr/arch/riscv/core/reset.S
/*
* Remainder of asm-land initialization code before we can jump into
* the C domain
*/
SECTION_FUNC(TEXT, __initialize)
/*
* This will boot master core, just halt other cores.
* Note: need to be updated for complete SMP support
*/
csrr a0, mhartid
beqz a0, boot_master_core
loop_slave_core:
wfi
j loop_slave_core
以上のように、はっきり書いてあります。SMP は一番大事な機能だと思っていただけに、未対応とは思わなんだ。
あー、今日の 16550 との戦いは一体何だったんだろう。しょんぼりですわ……。
メモ: 技術系の話は Facebook から転記しておくことにした。大幅に加筆した。
日記が増えすぎて、一覧が欲しくなってきたので作りました。
導入、ブート周り
ボード、ドライバなど
SMP 対応編
浮動小数点数命令など
その他
今後、日記が増えたら追加します。
管理者: Katsuhiro Suzuki(katsuhiro( a t )katsuster.net)
This is Simple Diary 1.0
Copyright(C) Katsuhiro Suzuki 2006-2021.
Powered by PHP 5.2.17.
using GD bundled (2.0.34 compatible)(png support.)