目次: Linux
OpenSBIのブート部分を調べます。OpenSBIはいくつか動作モードがあるのですが、今回はペイロードを持ったタイプを調べます。対象のファイル名はbuild/platform/generic/firmware/fw_payload.binもしくは.elfです。
OpenSBIのエントリポイントは_startで、アセンブラ実装のコードです。
//opensbi/firmware/fw_base.S
_start
_try_lottery
_bss_zero
fw_save_info() // firmware/fw_payload.S
//何もしない
fw_platform_init() // platform/generic/platform.cだが独自の実装もある(k210など)
_scratch_init
//scratch領域を確保する、先頭アドレスはtpレジスタに格納される
fw_next_arg1() // firmware/fw_payload.S
// FW_PAYLOAD_FDT_ADDRが定義済みならFW_PAYLOAD_FDT_ADDRを返す
// FW_PAYLOAD_FDT_OFFSETが定義済みなら_fw_start + FW_PAYLOAD_FDT_OFFSETを返す
// いずれも未定義ならばa1レジスタの値を返す
// 結果はscratch->next_arg1に格納
fw_next_addr() // firmware/fw_payload.S
// payload_binのアドレスを返す
// FW_PAYLOAD_PATHが定義済みなら.incbin FW_PAYLOAD_PATH
// 未定義ならwfi命令の無限ループ
// 結果はscratch->next_addrに格納
fw_next_mode() // firmware/fw_payload.S
// PRV_Sを返す
// 結果はscratch->next_modeに格納
fw_options()
// 0を返す
// 結果はscratch->optionsに格納
_start_warm
// tpの値をmscratchレジスタへ
_reset_regs
// mscratchレジスタを第一引数(a0レジスタ)にしてsbi_init()を呼ぶ
sbi_init(struct sbi_scratch *scratch)
結構長いですが、ブートに必要な情報を扱っている部分を抜粋してコメントを付け加えました。
ブート処理で重要なデータがstruct sbi_platform platformです。これは各platformごとに定義されていて、QEMU用のバイナリであればgeneric/platform.cの定義が使われるはずです。fw_platform_init()関数にて初期化されます。
//opensbi/platform/generic/platform.c
struct sbi_platform platform = {
.opensbi_version = OPENSBI_VERSION,
.platform_version =
SBI_PLATFORM_VERSION(CONFIG_PLATFORM_GENERIC_MAJOR_VER,
CONFIG_PLATFORM_GENERIC_MINOR_VER),
.name = CONFIG_PLATFORM_GENERIC_NAME,
.features = SBI_PLATFORM_DEFAULT_FEATURES,
.hart_count = SBI_HARTMASK_MAX_BITS,
.hart_index2id = generic_hart_index2id,
.hart_stack_size = SBI_PLATFORM_DEFAULT_HART_STACK_SIZE,
.heap_size = SBI_PLATFORM_DEFAULT_HEAP_SIZE(0),
.platform_ops_addr = (unsigned long)&platform_ops
};
Cの構造体をアセンブラのコードから参照するときはちょっと面倒で、
こんな処理を行う必要があります。Cの構造体を書き換えるとアセンブラ側で定義しているオフセットとずれてしまう問題があるため、
//opensbi/include/sbi/sbi_platform.h
/**
* Prevent modification of struct sbi_platform from affecting
* SBI_PLATFORM_xxx_OFFSET
*/
_Static_assert(
offsetof(struct sbi_platform, opensbi_version)
== SBI_PLATFORM_OPENSBI_VERSION_OFFSET,
"struct sbi_platform definition has changed, please redefine "
"SBI_PLATFORM_OPENSBI_VERSION_OFFSET");
(...以下同様に続く...)
全てのオフセットマクロに対してアサーションが書かれています。sbi_platform構造体が頻繁に変わることはないでしょうし、メンバもせいぜい10個くらいなので、手書き&気合で乗り切れます。
こういうオフセットマクロがあまりにも多い場合は、Cのコードからオフセットマクロを自動生成する場合もあります。OSのコードでたまに見かけますね。
もうひとつ重要なデータはスクラッチ領域(データ型はstruct sbi_scratch)です。アセンブラのコード_scratch_initにて領域の確保と値の設定が行われ、Cのコードではscratch->aaaaのような形で参照します。
まず領域の確保ですが、OpenSBIのバイナリの終端部分から下記のようにスタックとヒープ領域が確保されます。
スクラッチ領域はスタック領域の末尾に確保されます。またtpレジスタはスクラッチ領域の先頭アドレスとなります。この値は後でsbi_init()関数の第一引数に渡すために使います。
OpenSBIからペイロード(今回はLinuxをペイロードにしています)へ制御を移す部分のコードを調べます。
sbi_init()
init_coldboot() //1コアだけ : lib/sbi/sbi_init.c
sbi_AAAAAA_init() //初期化関数の名前はこんな感じ
//
//初期化、メッセージ表示など
//
sbi_hsm_hart_start_finish()
hsm_start_ticket_release()
sbi_hart_switch_mode()
// mepc = next_addr : payload_binのアドレス
// a0 = arg0 = hartid
// a1 = arg1 = next_arg1 : FDTのアドレス
// mretでmepcに飛ばす、つまりペイロードであるLinuxに飛ぶ
init_warmboot() //他のコア : lib/sbi/sbi_init.c
...
いろんな処理を行ないますが、ペイロードへ制御を移す部分はsbi_hsm_hart_start_finish()とsbi_hart_switch_mode()関数です。
//opensbi/lib/sbi/sbi_hsm.c
void __noreturn sbi_hsm_hart_start_finish(struct sbi_scratch *scratch,
u32 hartid)
{
unsigned long next_arg1;
unsigned long next_addr;
unsigned long next_mode;
struct sbi_hsm_data *hdata = sbi_scratch_offset_ptr(scratch,
hart_data_offset);
if (!__sbi_hsm_hart_change_state(hdata, SBI_HSM_STATE_START_PENDING,
SBI_HSM_STATE_STARTED))
sbi_hart_hang();
next_arg1 = scratch->next_arg1;
next_addr = scratch->next_addr;
next_mode = scratch->next_mode;
hsm_start_ticket_release(hdata);
sbi_hart_switch_mode(hartid, next_arg1, next_addr, next_mode, false); //★制御を移す関数★
}
//opensbi/lib/sbi/sbi_hart.c
void __attribute__((noreturn))
sbi_hart_switch_mode(unsigned long arg0, unsigned long arg1,
unsigned long next_addr, unsigned long next_mode,
bool next_virt)
{
#if __riscv_xlen == 32
unsigned long val, valH;
#else
unsigned long val;
#endif
switch (next_mode) {
case PRV_M:
break;
case PRV_S:
if (!misa_extension('S'))
sbi_hart_hang();
break;
case PRV_U:
if (!misa_extension('U'))
sbi_hart_hang();
break;
default:
sbi_hart_hang();
}
val = csr_read(CSR_MSTATUS);
val = INSERT_FIELD(val, MSTATUS_MPP, next_mode);
val = INSERT_FIELD(val, MSTATUS_MPIE, 0);
#if __riscv_xlen == 32
if (misa_extension('H')) {
valH = csr_read(CSR_MSTATUSH);
valH = INSERT_FIELD(valH, MSTATUSH_MPV, next_virt);
csr_write(CSR_MSTATUSH, valH);
}
#else
if (misa_extension('H'))
val = INSERT_FIELD(val, MSTATUS_MPV, next_virt);
#endif
csr_write(CSR_MSTATUS, val);
csr_write(CSR_MEPC, next_addr);
if (next_mode == PRV_S) {
if (next_virt) {
csr_write(CSR_VSTVEC, next_addr);
csr_write(CSR_VSSCRATCH, 0);
csr_write(CSR_VSIE, 0);
csr_write(CSR_VSATP, 0);
} else {
csr_write(CSR_STVEC, next_addr);
csr_write(CSR_SSCRATCH, 0);
csr_write(CSR_SIE, 0);
csr_write(CSR_SATP, 0);
}
} else if (next_mode == PRV_U) {
if (misa_extension('N')) {
csr_write(CSR_UTVEC, next_addr);
csr_write(CSR_USCRATCH, 0);
csr_write(CSR_UIE, 0);
}
}
//★各引数に入っている値は下記の通り★
//arg0 = hartid
//arg1 = scratch->next_arg1
//next_addr = scratch->next_addr
//next_mode = scratch->next_mode
//next_virt = false
register unsigned long a0 asm("a0") = arg0;
register unsigned long a1 asm("a1") = arg1;
__asm__ __volatile__("mret" : : "r"(a0), "r"(a1));
__builtin_unreachable();
}
レジスタa0とa1に引数を入れ、mepcレジスタにペイロード先頭のアドレス(scratch->next_addr)を設定してmretします。
fw_next_addr() // firmware/fw_payload.S
// payload_binのアドレスを返す
// FW_PAYLOAD_PATHが定義済みなら.incbin FW_PAYLOAD_PATH
// 未定義ならwfi命令の無限ループ
// 結果はscratch->next_addrに格納
なぜscratch->next_addrにペイロード先頭のアドレスが入っているのか?については再掲するとともに、該当部分のソースコードを掲載します。
//opensbi/firmware/fw_payload.S
.section .entry, "ax", %progbits
.align 3
.global fw_next_addr
/*
* We can only use a0, a1, and a2 registers here.
* The next address should be returned in 'a0'.
*/
fw_next_addr:
lla a0, payload_bin //★payload_binのアドレスを返す★
ret
...
.section .payload, "ax", %progbits
.align 4
.globl payload_bin
payload_bin:
#ifndef FW_PAYLOAD_PATH //★FW_PAYLOAD_PATHが未定義ならwfi命令の無限ループ★
wfi
j payload_bin
#else
.incbin FW_PAYLOAD_PATH //★FW_PAYLOAD_PATHが定義済みならFW_PAYLOAD_PATHで指定されたファイルをこの場所に展開★
#endif
//opensbi/firmware/fw_base.S
/* Store next address in scratch space */
MOV_3R s0, a0, s1, a1, s2, a2
call fw_next_addr
REG_S a0, SBI_SCRATCH_NEXT_ADDR_OFFSET(tp) //★結果はscratch->next_addrに格納★
MOV_3R a0, s0, a1, s1, a2, s2
最後にFW_PAYLOAD_PATHマクロはどこから来るかを紹介しておきます。
#opensbi/firmware/objects.mk
firmware-bins-$(FW_PAYLOAD) += fw_payload.bin
ifdef FW_PAYLOAD_PATH
FW_PAYLOAD_PATH_FINAL=$(FW_PAYLOAD_PATH)
else
FW_PAYLOAD_PATH_FINAL=$(platform_build_dir)/firmware/payloads/test.bin
endif
firmware-genflags-$(FW_PAYLOAD) += -DFW_PAYLOAD_PATH=\"$(FW_PAYLOAD_PATH_FINAL)\"
ビルド時に指定したFW_PAYLOAD_PATH変数が、Makefile内で-Dオプションとしてコンパイラに渡されてマクロとして定義されます。シンプルですね。
次回以降はメモリマップやデバイスツリーについて調べようと思います。
目次: Arduino
Debian Testingをアップデートしたところ先日ビルドしたKiCad 7.0が動かなくなり、今回はビルドすらできなくなりました。cmakeを実行するとTKIGESライブラリが見つからないと言われてしまいます。
(略) -- Found the following HarfBuzz libraries: -- HarfBuzz (required): /usr/lib/x86_64-linux-gnu/libharfbuzz.so -- Found HarfBuzz: /usr/include/harfbuzz (found version "8.3.0") -- Found Fontconfig: /usr/lib/x86_64-linux-gnu/libfontconfig.so (found version "2.15.0") -- Checking for module 'ngspice' -- Found ngspice, version 42 -- Found ngspice: /usr/include -- Found OCC: /usr/include/opencascade (found version "7.8.1") *** OpenCascade library missing *** Could not find a library for TKIGES at /usr/lib/x86_64-linux-gnu Verify your OpenCascade installation or pass CMake the library directory as '-DOCC_LIBRARY_DIR=<path>' CMake Error at cmake/FindOCC.cmake:192 (message): Call Stack (most recent call first): CMakeLists.txt:807 (find_package) -- Configuring incomplete, errors occurred!
エラーメッセージさん曰く、OpenCascadeのライブラリが見つからないとのこと。バージョンを確認するとDebian StableはOpenCascad 7.6.3、Debian TestingはOpenCascade 7.8.1でした。この間にlibTKIGES.soのライブラリ名が変わったようです。
OpenCascadeのソースコード(リポジトリへのリンク)のコミットログを見ていると、ライブラリ名が一気に変わった瞬間がありました。
commit bd651bbbd9e30fc5a73d32d11e0ea1a1821afd76 Author: dpasukhi <dpasukhi@opencascade.com> Date: Sun Nov 19 11:09:33 2023 +0000 0033531: Configuration - Rework DataExchange ToolKits organization Integrated DE plugin functionality. Reworked DE components: - TKDESTEP: Handling STEP file format. - TKDEOBJ: Handling OBJ file format. - TKDEIGES: Handling IGES file format. - TKDEGLTF: Handling GLTF file format. - TKDEVRML: Handling VRML file format. - TKDEPLY: Handling PLY file format. - TKDESTL: Handling STL file format. Reworked DE DRAW components: TKXSDRAWSTEP: Container for DE command to work with STEP. TKXSDRAWOBJ: Container for DE command to work with OBJ. TKXSDRAWIGES: Container for DE command to work with IGES. TKXSDRAWGLTF: Container for DE command to work with GLTF. TKXSDRAWVRML: Container for DE command to work with VRML. TKXSDRAWPLY: Container for DE command to work with PLY. TKXSDRAWSTL: Container for DE command to work with STL. TKXSDRAW rework to be base DRAW plugin to keep DE session and utils. Updated documentation Updated samples
TKIGES以外にも盛大に変わっていて、OpenCascadeを使っている人たちは全滅しているんじゃないかな?と思います。どこで変わったか調べるとv7.7.2とv7.8.0の間で変わったようですね。
$ git log V7_7_2..V7_8_0 | grep bd651b commit bd651bbbd9e30fc5a73d32d11e0ea1a1821afd76
コミットの中身を参考にしつつKiCadを修正します。
diff --git a/cmake/FindOCC.cmake b/cmake/FindOCC.cmake
index af249c9ce2..923ccb4c7a 100644
--- a/cmake/FindOCC.cmake
+++ b/cmake/FindOCC.cmake
@@ -45,7 +45,6 @@ set( OCC_LIBS
TKGeomAlgo
TKGeomBase
TKHLR
- TKIGES
TKLCAF
TKMath
TKMesh
@@ -55,18 +54,16 @@ set( OCC_LIBS
TKPrim
TKService
TKShHealing
- TKSTEP209
- TKSTEPAttr
- TKSTEPBase
- TKSTEP
- TKSTL
TKTObj
TKTopAlgo
TKV3d
- TKVRML
+ TKDESTL
+ TKDEVRML
TKXCAF
- TKXDEIGES
- TKXDESTEP
+ TKDE
+ TKDECascade
+ TKDEIGES
+ TKDESTEP
TKXMesh
TKXmlL
TKXml
OpenCascadeやKiCadの中身を全くわかっていないので、APIや実装が大きく変わっていたらお手上げでした。しかし幸いなことにライブラリ名が変わっただけなので、OpenCascadeのライブラリを探しているcmake/FindOCC.cmakeの修正だけで乗り切れました。
Debian TestingのKiCadは8.0系に移行しましたし、そろそろKiCad 7.0系を引きずるのをやめてバージョンアップを考えないといけないかなあ……?
目次: Linux
以前、Berkeley Bootloader(riskv-pkのbbl)を使用してRISC-V Linuxを立ち上げました(2019年2月26日の日記参照)が、今回はOpenSBIを使ってLinuxの起動を試みようと思います。
前回との違いはブートローダをbblからOpenSBIに変更したことです。
前回同様なので省略します。
前回同様にbusyboxと手作業でinitramfsを作りますが、ビルドディレクトリを使うように手順を変更しています。バージョンは1.35.0を使います(タグ名: 1_35_0)。
$ git clone https://git.busybox.net/busybox $ cd busybox $ mkdir build $ make \ O=build \ ARCH=riscv \ CROSS_COMPILE=riscv64-unknown-linux-gnu- \ menuconfig - Settings ---> [*] Build static binary (no shared libs) $ make \ O=build \ ARCH=riscv \ CROSS_COMPILE=riscv64-unknown-linux-gnu- \ all
ビルドに成功すると実行ファイルbusyboxが生成されるはずです。次にinitramfsを作成します。基本的な流れはディレクトリに必要なファイルを配置し、cpioで固めるだけです。
$ mkdir initramfs-work-riscv $ mkdir initramfs-work-riscv/root $ cd initramfs-work-riscv/root $ mkdir bin $ cp ../../build/busybox bin/ $ ln -s busybox bin/sh $ ln -s bin/busybox init $ mkdir dev $ sudo cp -a /dev/tty* dev/ $ find . | cpio --format=newc -o > ../initramfs.cpio
横着してホストマシンのデバイスファイルをコピーしてます。RISC-V Linux上で何か操作したければinit, sh以外のシンボリックリンクも作った方が便利です。
前回との違いは下記のとおりです。linux-nextでも手順は同じですが、linux-nextはたまにデグレして動作せず話がややこしくなるので、linux本線の少し古めのバージョンを使います。
バージョンは6.9を使います(タグ名: v6.9)。
$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git $ cd linux $ mkdir build $ make \ O=build \ ARCH=riscv \ CROSS_COMPILE=riscv64-unknown-linux-gnu- \ defconfig $ make \ O=build \ ARCH=riscv \ CROSS_COMPILE=riscv64-unknown-linux-gnu- \ menuconfig - General setup ---> (../busybox/initramfs-work-riscv/initramfs.cpio) Initramfs source file(s) $ make \ O=build \ ARCH=riscv \ CROSS_COMPILE=riscv64-unknown-linux-gnu- \ all
前回のようにexportで環境変数を設定しても良いですし、makeに逐一指定しても良いです。
ブートローダーにはOpenSBIを使います。バージョンは1.5を使います(タグ名: v1.5)。
$ git clone https://github.com/riscv-software-src/opensbi.git $ cd opensbi $ make \ CROSS_COMPILE=riscv64-unknown-linux-gnu- \ PLATFORM=generic \ FW_PAYLOAD_PATH=../linux/build/arch/riscv/boot/Image \ clean all
BBLもそうでしたけど、ブートローダーとカーネルは1つのバイナリになります。FW_PAYLOAD_PATHにはカーネルのバイナリ(vmlinuxではない)のパスを指定します。PLATFORMはSoCやボードの種類を指定します、QEMU向けの場合はgenericです。
エミュレータにはQEMUを使います。システムにインストールされているバージョンが古い場合があるので、ソースコードからビルドする方法も紹介します。QEMU実行時にオプション--netdev userを使うのであれば、QEMUをビルドする前にlibslirp-devをインストールしておく必要があります。
$ cd qemu $ mkdir build $ cd build $ ../configure \ --target-list=riscv32-softmmu,riscv32-linux-user,riscv64-softmmu,riscv64-linux-user \ --enable-debug \ --disable-docs $ ninja
バージョンはv9.0.2を使いました。target-listやdisable-docsはビルド時間の短縮のためで指定しなくても良いです。使わないアーキテクチャ用(ARMとか)のエミュレータがビルドされるためビルドに若干時間がかかります。enable-debugはQEMUをデバッグする可能性があれば指定してください。
ここまでの準備でブートローダーとLinuxカーネルの初期部分は確認できるはずですので、動作確認します。
$ cd qemu/build $ ./qemu-system-riscv64 \ -machine virt \ -nographic \ -bios none \ -chardev stdio,id=con,mux=on \ -serial chardev:con \ -mon chardev=con,mode=readline \ -smp 4 \ -kernel ../../opensbi/build/platform/generic/firmware/fw_payload.bin
うまくいっていればこんな感じになるはずです。
OpenSBI v1.5 ____ _____ ____ _____ / __ \ / ____| _ \_ _| | | | |_ __ ___ _ __ | (___ | |_) || | | | | | '_ \ / _ \ '_ \ \___ \| _ < | | | |__| | |_) | __/ | | |____) | |_) || |_ \____/| .__/ \___|_| |_|_____/|____/_____| | | |_| Platform Name : riscv-virtio,qemu Platform Features : medeleg Platform HART Count : 4 Platform IPI Device : aclint-mswi Platform Timer Device : aclint-mtimer @ 10000000Hz Platform Console Device : uart8250 Platform HSM Device : --- Platform PMU Device : --- Platform Reboot Device : syscon-reboot Platform Shutdown Device : syscon-poweroff Platform Suspend Device : --- Platform CPPC Device : --- Firmware Base : 0x80000000 Firmware Size : 357 KB Firmware RW Offset : 0x40000 Firmware RW Size : 101 KB Firmware Heap Offset : 0x4f000 Firmware Heap Size : 41 KB (total), 2 KB (reserved), 11 KB (used), 27 KB (free) Firmware Scratch Size : 4096 B (total), 416 B (used), 3680 B (free) Runtime SBI Version : 2.0 Domain0 Name : root Domain0 Boot HART : 0 Domain0 HARTs : 0*,1*,2*,3* Domain0 Region00 : 0x0000000000100000-0x0000000000100fff M: (I,R,W) S/U: (R,W) Domain0 Region01 : 0x0000000010000000-0x0000000010000fff M: (I,R,W) S/U: (R,W) Domain0 Region02 : 0x0000000002000000-0x000000000200ffff M: (I,R,W) S/U: () Domain0 Region03 : 0x0000000080040000-0x000000008005ffff M: (R,W) S/U: () Domain0 Region04 : 0x0000000080000000-0x000000008003ffff M: (R,X) S/U: () Domain0 Region05 : 0x000000000c400000-0x000000000c5fffff M: (I,R,W) S/U: (R,W) Domain0 Region06 : 0x000000000c000000-0x000000000c3fffff M: (I,R,W) S/U: (R,W) Domain0 Region07 : 0x0000000000000000-0xffffffffffffffff M: () S/U: (R,W,X) Domain0 Next Address : 0x0000000080200000 Domain0 Next Arg1 : 0x0000000082200000 Domain0 Next Mode : S-mode Domain0 SysReset : yes Domain0 SysSuspend : yes Boot HART ID : 0 Boot HART Domain : root Boot HART Priv Version : v1.12 Boot HART Base ISA : rv64imafdch Boot HART ISA Extensions : sstc,zicntr,zihpm,zicboz,zicbom,sdtrig,svadu Boot HART PMP Count : 16 Boot HART PMP Granularity : 2 bits Boot HART PMP Address Bits: 54 Boot HART MHPM Info : 16 (0x0007fff8) Boot HART Debug Triggers : 2 triggers Boot HART MIDELEG : 0x0000000000001666 Boot HART MEDELEG : 0x0000000000f0b509 [ 0.000000] Linux version 6.9.0 (katsuhiro@blackbird) (riscv64-unknown-linux-gnu-gcc (GCC) 14.1.0, GNU ld (GNU Binutils) 2.42.50.20240622) #1 SMP Tue Jul 16 18:44:37 JST 2024 [ 0.000000] random: crng init done [ 0.000000] Machine model: riscv-virtio,qemu [ 0.000000] SBI specification v2.0 detected [ 0.000000] SBI implementation ID=0x1 Version=0x10005 [ 0.000000] SBI TIME extension detected [ 0.000000] SBI IPI extension detected [ 0.000000] SBI RFENCE extension detected [ 0.000000] SBI SRST extension detected [ 0.000000] SBI DBCN extension detected [ 0.000000] efi: UEFI not found. ...
今後はOpenSBIやLinuxカーネルの動作を調べる予定です。
< | 2024 | > | ||||
<< | < | 07 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | 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 | 31 | - | - | - |
合計:
本日: