目次: Zephyr
前回はデフォルトコンフィグが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は小文字で書くことが多いですが、マクロ名は大文字に変換しています。
目次: Zephyr
前回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ボード上で動いたということですね。次回は面倒くさかった手動コンフィグの撲滅に挑みます。
目次: 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は見るのが嫌になるくらい大量のエラーを表示して失敗します。続きはまた今度。
目次: Zephyr
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のエントリポイントがどうやって決まるのか、少しわかった気がします。
目次: Zephyr
以前Zephyr OSを実行しました(2019年1月12日の日記参照)が、今回はRISC-V 32bit版で試してみようと思います。
この例ではbuildをビルドディレクトリとします。どこに置いても動くはずですが、私はとりあえずzephyrの下に置いています。
前回同様Zephyr SDKもしくはCrosstool-NGが使えます。参考までにCrosstool-NGのコンフィグを載せておきます。
$ ./ct-ng menuconfig Target options ---> Target Architecture (riscv) ---> [*] Build a multilib toolchain (READ HELP!!!) Bitness: (64-bit) ---> (rv32ima) Architecture level (ilp32) Generate code for the specific ABI Toolchain options ---> (zephyr) Tuple's vendor string Debug facilities ---> [*] gdb ---> [*] Build a static cross gdb
一見すると32bit CPUのコードをビルドする予定なのに、Bitness: 64bitにしており、不思議なコンフィグに見えるかもしれませんが、Zephyr OSのビルドはクセがあって、この設定が必要です。
Architecture level, Generate code for the specific ABIはGCCが生成するバイナリの命令セットを指定しており、それぞれ -march=rv32ima, -mabi=ilp32に対応します。特に何も指定しないとrv32gc, ilp32fになるようです。
zephyr/cmake/toolchain/xtools/target.cmake set(CROSS_COMPILE_TARGET_riscv riscv64-zephyr-elf) set(CROSS_COMPILE_TARGET ${CROSS_COMPILE_TARGET_${ARCH}})
ZephyrのCMakefileを見るとわかるんですが、riscv64もriscv32も区別せず同じコンパイラでビルドし、しかもコンパイラ名は常にriscv64-zephyr-elf-gccだと思っています。したがって64bit版をビルドする必要があり、multilibを有効にしています。
(余談)riscv向けの -mabi, -marchオプションの値を決めているのはzephyr/cmake/compiler/gcc/target.cmakeで、rv32ima固定になっています。
- list(APPEND TOOLCHAIN_C_FLAGS -mabi=ilp32 -march=rv32ima)
+ list(APPEND TOOLCHAIN_C_FLAGS -mabi=ilp32f -march=rv32gc)
上記のように変えると、Crosstool-NGでArchitecture level, Generate code for the specific ABIの設定をしなくても良くなりますが、動くかどうかは試していません。
実ボードを持っていないのでQEMUで試します。SiFive HiFive1をエミュレートしているそうです。
$ cat ~/.zephyrrc export ZEPHYR_TOOLCHAIN_VARIANT=xtools export XTOOLS_TOOLCHAIN_PATH=/home/katsuhiro/x-tools $ cd zephyr $ source zephyr-env.sh $ mkdir build $ cd build $ cmake -G Ninja -DBOARD=qemu_riscv32 ../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 qemu_riscv32 -- Loading /home/katsuhiro/share/projects/oss/zephyr/boards/riscv/qemu_riscv32/qemu_riscv32.dts as base Devicetree configuration written to /home/katsuhiro/share/projects/oss/zephyr/build/zephyr/include/generated/devicetree.conf Parsing /home/katsuhiro/share/projects/oss/zephyr/Kconfig Loaded configuration '/home/katsuhiro/share/projects/oss/zephyr/boards/riscv/qemu_riscv32/qemu_riscv32_defconfig' Merged configuration '/home/katsuhiro/share/projects/oss/zephyr/samples/hello_world/prj.conf' Configuration saved to '/home/katsuhiro/share/projects/oss/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: /home/katsuhiro/share/projects/oss/zephyr/build
実行するときはninja runで良いんですが、前回同様にninjaが何を起動しているか調べてみます。
$ /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 -s -S (gdbからcontinueをすると、下記が出力される) *** Booting Zephyr OS build zephyr-v2.1.0-1471-g7e7a4426d835 *** Hello World! qemu_riscv32
オプション -sはGDBの接続をlocalhost:1234で受け付けます、という意味です。オプション -SはエミュレータをHalted状態で起動します。もしGDBをつなぐ必要がなければ -sや -Sを削って起動してください。
$ riscv64-zephyr-elf-gdb GNU gdb (crosstool-NG 1.24.0.60-a152d61) 8.3.1 Copyright (C) 2019 Free Software Foundation, Inc. ... (gdb) set arch riscv:rv32 The target architecture is assumed to be riscv:rv32 (gdb) target remote localhost:1234 Remote debugging using localhost:1234 warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x00001000 in ?? () (gdb) continue Continuing.
実行できました。おなじみのHello World! です。
目次: GCC
新し目のAArch64のクロスコンパイル用ツールチェーンを作ろうとして、かなりハマったのでメモしておきます。
基本的には前回(2019年4月29日の日記参照)ご紹介した手順でビルドします。GCCとglibcのコードを変えなくて良い組み合わせは下記の通りです。特に新しいバージョンを使う理由がなければ、この組み合わせが無難です。
私は新しいGCCが使いたかったので、HEADにしました(バージョン的には10.0相当)。どうやらGCCのエラーチェックが厳しくなるらしく、glibcのビルドが通らなくなります。たくさんエラーが出ますが、一例を挙げると、下記のようなエラーです。
./../include/libc-symbols.h:534:26: error: '__EI___errno_location' specifies less restrictive attributes than its target '__errno_location': 'const', 'nothrow' [-Werror=missing-attributes] 534 | extern __typeof (name) __EI_##name \ | ^~~~~
エラーはglibcを新しくすると解決されるかと思いきや、よりおかしなことになります。例えばGCC 8.3のままglibc 2.30(おそらく2.29でも同じ症状が出る)にすると、下記のような変なエラーが出ます。
crosstool-builder-new-aarch64/buildroot/lib/gcc/aarch64-unknown-linux-gnu/8.3.0/../../../../aarch64-unknown-linux-gnu/bin/ld: crosstool-builder-new-aarch64/build/glibc/support/links-dso-program.o: Relocations in generic ELF (EM: 62) crosstool-builder-new-aarch64/buildroot/lib/gcc/aarch64-unknown-linux-gnu/8.3.0/../../../../aarch64-unknown-linux-gnu/bin/ld: crosstool-builder-new-aarch64/build/glibc/support/links-dso-program.o: Relocations in generic ELF (EM: 62) ...
エラーの原因となっているオブジェクトlinks-dso-program.oを調べると、AArch64向けにビルドしているにも関わらず、なぜかx86_64用のオブジェクトが生成されています。
build/glibc/support$ file *.o echo-container.o: ELF 64-bit LSB relocatable, ARM aarch64, version 1 (SYSV), with debug_info, not stripped links-dso-program.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), with debug_info, not stripped ★★★★これ★★★★ shell-container.o: ELF 64-bit LSB relocatable, ARM aarch64, version 1 (SYSV), with debug_info, not stripped stamp.o: empty test-container.o: ELF 64-bit LSB relocatable, ARM aarch64, version 1 (SYSV), with debug_info, not stripped true-container.o: ELF 64-bit LSB relocatable, ARM aarch64, version 1 (SYSV), with debug_info, not stripped
いったい何ですかね、これ。バグなのか、仕様なのかわかりません……。
GCCとglibcをお互い最新にした組み合わせ、すなわち下記の組み合わせにしたとき、
先ほど説明した、両方のエラーに遭遇してビルドできませんので、glibcにパッチを当ててビルドエラーを回避します。
まずはコンパイルエラーを無視するパッチです。本来はエラーを無視するのではなく、エラーが指摘している事項を直すべきですけど、今回の主眼ではないのと、いずれglibc本家が直るだろうことを期待しておきます。
diff --git a/Makeconfig b/Makeconfig
index fd36c58c04..106688e210 100644
--- a/Makeconfig
+++ b/Makeconfig
@@ -916,7 +916,8 @@ ifeq "$(strip $(+cflags))" ""
endif # $(+cflags) == ""
+cflags += $(cflags-cpu) $(+gccwarn) $(+merge-constants) $(+math-flags) \
- $(+stack-protector)
+ $(+stack-protector) \
+ -Wno-zero-length-bounds -Wno-array-bounds -Wno-maybe-uninitialized
+gcc-nowarn := -w
# Each sysdeps directory can contain header files that both will be
次のlinks-dso-programはコミットログを見る限り、テストのサポート用ライブラリなので、とりあえず無くても動くはずです。ビルド自体をやめるパッチをあてます。
diff --git a/support/Makefile b/support/Makefile
index ab66913a02..19c3de2043 100644
--- a/support/Makefile
+++ b/support/Makefile
@@ -184,12 +184,12 @@ CFLAGS-support_paths.c = \
-DSBINDIR_PATH=\"$(sbindir)\" \
-DROOTSBINDIR_PATH=\"$(rootsbindir)\"
-ifeq (,$(CXX))
-LINKS_DSO_PROGRAM = links-dso-program-c
-else
-LINKS_DSO_PROGRAM = links-dso-program
-LDLIBS-links-dso-program = -lstdc++ -lgcc -lgcc_s $(libunwind)
-endif
+#ifeq (,$(CXX))
+#LINKS_DSO_PROGRAM = links-dso-program-c
+#else
+#LINKS_DSO_PROGRAM = links-dso-program
+#LDLIBS-links-dso-program = -lstdc++ -lgcc -lgcc_s $(libunwind)
+#endif
ifeq (yes,$(have-selinux))
LDLIBS-$(LINKS_DSO_PROGRAM) += -lselinux
クロスコンパイル環境は、各モジュールのバージョンアップですぐ壊れてしまって辛いです。ARMがこれだけ覇権を握っているにも関わらず、gccもglibcもあまりチェックしてないんですかね……??
わざわざMakefileを書き換えなくてもmake LINKS_DSO_PROGRAM= のように、make実行時にLINKS_DSO_PROGRAM変数の値を強制的に空文字列に上書きすれば回避可能でした。理由は昔の日記で書いた通り(2019年9月17日の日記参照)、コマンドラインからの変数指定はMakefile内の代入より強いからです。
Makefile書き換えよりは多少スマートですけども、クロスコンパイルの時だけこんな指定が必要なのは妙ですね。まだ何か見落としているんでしょうかね?
目次: C言語とlibc
くそ長いですが、C言語の未定義動作怖いね、printfでタイミング以外も動き変えられるよ、という話です。
環境ですがx86_64向けDebian GNU/Linux 9.2で実行しています。またGCCのバージョンはgcc (Debian 9.2.1-22) 9.2.1 20200104です。
未定義動作のため、コンパイラの種類や、GCCのバージョンにより結果が変わると思われます。お家のマシンで試すならご留意ください。
この日記の最後に貼ったプログラム(このプログラムをコンパイルすると、激しい警告が出ます)をgcc -Wall -O2 a.c && ./a.outのように実行すると、
0: 0 0 0 1: 0 1 0 2: 0 2 0 3: 0 3 0 4: 0 4 0 ... 47: 0 47 0 48: 0 48 0 49: 0 49 0 1770: 1770
こうなります。0〜59の和は1770です。あってます。良かったですね。
なに?そういう問題じゃない?「なぜarray終端を超えてguard2にバッファオーバーランしない?」と考えた方、するどいです。しかし世の中そう単純ではありません。
10行目のprintfのコメントを外してローカル変数のアドレスを表示させると、
0x7ffd9b348a10 0x7ffd9b348ae0 0x7ffd9b348bb0 0: 0 0 52 1: 0 1 53 2: 0 2 54 3: 0 3 55 4: 0 4 56 ... 45: 0 45 0 46: 0 46 0 47: 0 47 0 48: 0 48 0 49: 0 49 0 1770: 1770
こうなります。突然オーバーランするようになりました。printfが何かしたんでしょうか、不思議ですね?
どうしてforループを無意味に2分割したのか?くっつけてみたらわかります。Segmentation Fault します。
0: 0 0 0 1: 0 1 0 2: 0 2 0 3: 0 3 0 4: 0 4 0 ... 45: 0 45 0 46: 0 46 0 47: 0 47 0 48: 0 48 0 49: 0 49 0 Segmentation fault
もう意味不明ですよね。何が起こっているんでしょう?
この60回のforループは「配列の終端を超えたアクセス」がC言語仕様上の未定義動作なので、何が起きても正しい、つまりどの結果も正しいです。
これだけだと、何言ってんのか意味不明だと思うので「printf有効/無効」「forループ1つ/2つ」に着目して説明します。
3番目の実験の裏打ちとして、試しにループ回数を80回くらいにするとforループが1つだろうが2つだろうが、リターンアドレスがぶっ壊れてSegmentation Fault します。10行目のprintfを有効にするとguard1, guard2がスタックに配置されて、受け止めてくれるので、80回でも耐えます。
バッファオーバーランを期待していた向きには残念(?)かもしれませんが、guard1, guard2はメモリ上に置いても置かなくても、C言語仕様に矛盾しないなら、どっちでも良いです。もっというとC言語仕様に矛盾しないなら、コンパイラの最適化は何をやってもOK です。
この「C言語仕様に矛盾しないなら」はおそらくコンパイラ開発者には常識なのでしょうけども、C言語の仕様は人間に優しくないのと、大多数のC言語プログラマは言語仕様(特に未定義動作)を理解しておらず、何となく使っています。
難解な仕様、曖昧な理解、過激な最適化の相乗効果により、今日も世界のどこかで
「最適化で動きが変になっちゃったよ……。どうして…どうして……?」
とコンパイラとすれ違ったプログラマが泣いているでしょう。。。
大したものではありませんが、ソースコードを載せておきます。
#include <stdio.h>
#include <string.h>
int undefined()
{
int guard1[50];
int array[50];
int guard2[50];
int sum = 0, i;
memset(guard1, 0, sizeof(guard1));
memset(guard2, 0, sizeof(guard2));
//printf("%p %p %p\n", &guard1[0], &array[0], &guard2[0]);
for (i = 0; i < 60; i++) {
array[i] = i;
}
for (i = 0; i < 60; i++) {
sum += array[i];
}
for (i = 0; i < 50; i++) {
printf("%2d: %d %d %d\n", i, guard1[i], array[i], guard2[i]);
}
return sum;
}
int main(int argc, char *argv[])
{
int sum1 = 0, sum2 = 0, i;
sum1 = undefined();
for (i = 0; i < 60; i++) {
sum2 += i;
}
printf("%d: %d\n", sum1, sum2);
return 0;
}
< | 2020 | > | ||||
<< | < | 02 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | - | - | - | - | - | 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
合計:
本日: