目次: Zephyr
前回はリセット直後から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のソースコード上には存在しないように見えます。これは一体何者なんでしょう?
続きはまた今度。
目次: Zephyr
以前(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言語の世界です。
目次: Zephyr
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より後発ですが、ビルド周りは退化したように感じます。不思議。
目次: Zephyr
前回はシリアルドライバの枠組み、というか、何も実装されていないドライバを追加しました。今回はシリアルドライバを実装します。
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
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
やっと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
今回は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つずつ解消していくことになります。続きはまた次回です。
目次: Zephyr
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は通過できません。長くなってきましたので、続きはまた今度。
目次: 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は小文字で書くことが多いですが、マクロ名は大文字に変換しています。
< | 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 |
合計:
本日: