目次: Zephyr
Zephyr RTOSのRISC-V向けマルチコアブート処理がバグっていて修正したので解析したメモを残しておきます。
ZephyrではCONFIG_RV_BOOT_HARTというコンフィグで設定したhartidと、mhartid CSRの値が一致するhartがメインの初期化を担当します。Zephyrのコードではfirst coreと呼び、メイン以外はsecondary coreと呼んでいます。あまり聞いたことがない呼び方ですね。この日記では素直にメインコア/サブコアと書きます。
メインコアはarch_start_cpu()という関数からサブコアを起床します。呼び出しの経路は下記です。
z_thread_entry() bg_thread_main() z_smp_init() arch_start_cpu()
このbg_thread_main()関数はメインスレッド実行前に必要な初期化を実施していて、最終的にmain()を呼び出します。実行タイミングはカーネルの初期化が終わって、メインスレッドにコンテキストスイッチしたあとです。ブートからそれなりに時間が経過しています。
// zephyr/arch/riscv/core/smp.c
void arch_start_cpu(int cpu_num, k_thread_stack_t *stack, int sz,
arch_cpustart_t fn, void *arg)
{
riscv_cpu_init[cpu_num].fn = fn;
riscv_cpu_init[cpu_num].arg = arg;
riscv_cpu_sp = Z_KERNEL_STACK_BUFFER(stack) + sz;
riscv_cpu_wake_flag = _kernel.cpus[cpu_num].arch.hartid; //★★1-1: riscv_cpu_wake_flagに起床させるhartidを設定する★★
#ifdef CONFIG_PM_CPU_OPS
if (pm_cpu_on(cpu_num, (uintptr_t)&__start)) {
printk("Failed to boot secondary CPU %d\n", cpu_num);
return;
}
#endif
//★★1-2: riscv_cpu_wake_flagが0になるまで待つ★★
while (riscv_cpu_wake_flag != 0U) {
;
}
//★★1-3: riscv_cpu_wake_flagが0になったら継続★★
}
サブコアは下記のようにブート直後にフラグチェックし、起動の指示があるまで待っています。
// zephyr/arch/riscv/core/reset.S
SECTION_FUNC(TEXT, __initialize)
csrr a0, mhartid //★★a0 <- mhartid★★
li t0, CONFIG_RV_BOOT_HART
beq a0, t0, boot_first_core
j boot_secondary_core
//...
boot_secondary_core:
#if CONFIG_MP_MAX_NUM_CPUS > 1
//★★2-1: riscv_cpu_wake_flagがmhartidになるまで待つ★★★
la t0, riscv_cpu_wake_flag
lr t0, 0(t0)
bne a0, t0, boot_secondary_core
//★★2-2: riscv_cpu_wake_flagがmhartidになったら継続する★★★
/* Set up stack */
la t0, riscv_cpu_sp
lr sp, 0(t0)
la t0, riscv_cpu_wake_flag
sr zero, 0(t0) //★★2-3: riscv_cpu_wake_flagに0をセット★★
j z_riscv_secondary_cpu_init
コードの想定する動作は下記の通りです。例としてhart0がメイン、hart1がサブの2コアのブートとします。
このときriscv_cpu_wake_flagの初期値はいずれのサブコアのmhartidとも一致しないのが期待値です。
このコードはriscv_cpu_wake_flagの初期値がいずれかのサブコアのmhartidと一致するとハングアップします。riscv_cpu_wake_flagはBSS領域に配置されておりメインコアがいずれ0に初期化しますが、サブコアはメインコアがBSS領域を初期化する前にriscv_cpu_wake_flagを参照するので間に合いません。
例えば起動直後にriscv_cpu_wake_flagが偶然1だったとすると下記のような動きをしてハングアップします。
この実装をどう変更したら良くなるのか?なぜか?については少々長くなるので、以前の日記(2021年9月28日の日記参照)をご覧ください。
サブコアを起こすフラグriscv_cpu_wake_flag(初期値は-1)とサブコアが起きたことを示すフラグriscv_cpu_boot_flag(初期値は0)に分けます。riscv_cpu_wake_flagはサブコアから-1に初期化してから起動待ちに入るようにして、不定値問題に対処します。
Zephyr RTOSのプロジェクトにPull Requestを送ったところあっさり取り込まれました(Zephyrへのリンク)。1週間以上は掛かるかと思っていましたが、早かったです。今は修正に向いている時期なんでしょうか。
とまあ、ここまで書いていて対策後のコードも間違っているような気がしてきました。
メインコアがhart0じゃない場合、BSS領域の初期化でriscv_cpu_wake_flagが0に変わってしまうので、hart0が間違って起動するのではなかろうか……?それは良くないな、後で確かめようと思います。
< | 2023 | > | ||||
<< | < | 11 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | - | - | 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 | 30 | - | - |
合計:
本日: