FreeRTOSの話題の続き。前回FreeRTOSのRISC-V版を少しエミュレータで試してみたものの、何も動作せずに終わっていたのだった。 (そしてそれを特に原因究明せず放置していた。)
それではあまりにもつまらないので、自作ISSを使ってFreeRTOSの起動シーケンスをトレースしていきたい。 どうやってトレースするかというと、僕の自作RISC-V ISSは階層トレースモードが付いているので、関数ジャンプなどの場所を検知して関数のとび先をログに示してくれる。
命令トレースと関数トレースを使って、どのように実行されるのかについて調査していこう。
FreeRTOS-RISCVが立ち上がる仕組み
RISC-Vのリセットアドレスは通常だと0x8000_0000だ。ここからまずは汎用レジスタ等のフォーマットが始まり、各種メモリの初期化処理などが入り始める。
riscv64-unknown-elf-objdump -D riscv-spike.elf > riscv-spike.dmp Disassembly of section .text: 0000000080000000 <boot>: 80000000: 00002fb7 lui t6,0x2 80000004: 800f8f9b addiw t6,t6,-2048 80000008: 300f9073 csrw mstatus,t6 8000000c: 0340006f j 80000040 <_mstart> 80000010: 00000013 nop ...
ここまでの命令トレースを追いかけると、以下のようになった。main()
に入ると、今度はいろんな初期化処理が始まる。
<FunctionCall 108121 vSyscallInit(0x800075b4)> <FunctionCall 108128 main(0x80007edc)> <FunctionCall 108132 vCreateBlockTimeTasks(0x8000806c)> <FunctionCall 108138 xQueueGenericCreate(0x80001510)> <FunctionCall 108158 pvPortMalloc(0x80006d78)> <FunctionCall 108163 vTaskSuspendAll(0x800036b4)> <Return: vTaskSuspendAll> <FunctionCall 108177 prvHeapInit(0x8000705c)> <Return: prvHeapInit> <FunctionCall 108308 xTaskResumeAll(0x800036d8)> <FunctionCall 108316 vTaskEnterCritical(0x80005108)> <Return: vTaskEnterCritical> <FunctionCall 108339 vTaskExitCritical(0x80005148)> <Return: vTaskExitCritical> <Return: xTaskResumeAll> <Return: pvPortMalloc> <FunctionCall 108375 xQueueGenericReset(0x800013f0)> <FunctionCall 108384 vTaskEnterCritical(0x80005108)> ...
とりあえず画面に何か表示させたいんだけどどうしたらいいのかな?調査してみると、printf()
があるらしい。
これは中身を見てみると、普通に文字列フォーマットを作って、それをputch()
に渡している。
- arch/clib.c
int printf(const char* fmt, ...) { va_list ap; va_start(ap, fmt); vFormatPrintString((void*) putchar, 0, fmt, ap); va_end(ap); return 0; // incorrect return value, but who cares, anyway? } static void vFormatPrintString(void (*putch)(int, void**), void **putdat, const char *fmt, va_list ap) { register const char* p; const char* last_fmt; register int ch; unsigned long long num; int base, lflag, width, precision; char padc; while (1) { while ((ch = *(unsigned char *) fmt) != '%') { if (ch == '\0') return; fmt++; putch(ch, putdat); } fmt++; // Process a %-escape sequence ...
putch()
の中身はというと、vFormatPrintString
を呼ぶ際に関数ポインタとしてputchar()
を指定しているため、結局putchar()
を読んでいる。
vFormatPrintString((void*) putchar, 0, fmt, ap); ... int putchar(int ch) { static char buf[64] __attribute__((aligned(64))); static int buflen = 0; buf[buflen++] = ch; if (ch == '\n' || buflen == sizeof(buf)) { syscall(SYS_write, 1, (long) buf, buflen); buflen = 0; } return 0; }
改行に到達するか、bufの最大容量まで到達するとシステムコールを呼び出し書き込みを行う。それ以外は、とりあえずbufに一文字ずつ貯めていく。一文字ずつシステムコールが発生しないようにする工夫だ。
システムコールを実行し、デバイスにアクセスするための権限を獲得する。
- arch/clib.c
syscall(SYS_write, 1, (long) buf, buflen);
- 実行時の命令トレース
189132:M:MBar:[80007564][P80007564] 01013503 : ld r10,r02,0x010 r02=>000000008003b8a8 (000000008003b8b8)=>00000001 r10<=0000000000000001 189133:M:MBar:[80007568][P80007568] 00813583 : ld r11,r02,0x008 r02=>000000008003b8a8 (000000008003b8b0)=>80039800 r11<=0000000080039800 189134:M:MBar:[8000756c][P8000756c] 00013603 : ld r12,r02,0x000 r02=>000000008003b8a8 (000000008003b8a8)=>0000000c r12<=000000000000000c ECALL from Machine 189135:M:MBar:[80007570][P80007570] 00000073 : ecall mepc<=0000000080007570 mcause<=000000000000000b mtvec=>0000000080000164 mstatus=>0000000000001800 mstatus<=000000000000c006 pc<=0000000080000164 189136:M:MBar:[80000164][P80000164] ff810113 : addi r02,r02,0xff8 r02=>000000008003b8a8 r02<=000000008003b8a0 189137:M:MBar:[80000168][P80000168] 00513023 : sd r02,r05,0x000 r02=>000000008003b8a0 r05=>0000000000000000 (000000008003b8a0)<=00000000 189138:M:MBar:[8000016c][P8000016c] 342022f3 : csrrs r05,0x342,r00 r00=>0000000000000000 mcause=>000000000000000b r05<=000000000000000b 189139:M:MBar:[80000170][P80000170] fc02cce3 : blt r05,r00,0x7e r05=>000000000000000b r00=>0000000000000000 189140:M:MBar:[80000174][P80000174] 00013283 : ld r05,r02,0x000 r02=>000000008003b8a0 (000000008003b8a0)=>00000000 r05<=0000000000000000 189141:M:MBar:[80000178][P80000178] 00810113 : addi r02,r02,0x008 r02=>000000008003b8a0 r02<=000000008003b8a8 189142:M:MBar:[8000017c][P8000017c] f0810113 : addi r02,r02,0xf08 r02=>000000008003b8a8 r02<=000000008003b7b0 189143:M:MBar:[80000180][P80000180] 00113423 : sd r02,r01,0x008 r02=>000000008003b7b0 r01=>0000000080007738 (000000008003b7b8)<=80007738 189144:M:MBar:[80000184][P80000184] 00213823 : sd r02,r02,0x010 r02=>000000008003b7b0 r02=>000000008003b7b0 (000000008003b7c0)<=8003b7b0
このときのシステムコールの処理だが、arch/syscalls.c
に記述がある。
- arch/syscalls.c
static long prvSyscallToHost(long which, long arg0, long arg1, long arg2) { volatile uint64_t magic_mem[8] __attribute__((aligned(64))); uint64_t oldfromhost; magic_mem[0] = which; magic_mem[1] = arg0; magic_mem[2] = arg1; magic_mem[3] = arg2; __sync_synchronize(); tohost = (long) magic_mem; do { oldfromhost = fromhost; fromhost = 0; } while (oldfromhost == 0); return magic_mem[0]; }
do-whileでfromhostの条件が変わるまでループしているが、自作ISSにそんな機能は無いので、ここを改良する必要がありそうだ。
fromhost, tohostの場所はアドレスでマッピングされており、
less riscv-spike.nm ... 00000000800399c0 B fromhost ... 0000000080039a00 B tohost ...
と、見た感じかなり変動しそうな場所に配置されているので、これも工夫してうまく処理できるようにしなければならない。