FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

FreeRTOS-RISCVの起動シーケンスを追いかける(1)

FreeRTOSの話題の続き。前回FreeRTOSのRISC-V版を少しエミュレータで試してみたものの、何も動作せずに終わっていたのだった。 (そしてそれを特に原因究明せず放置していた。)

msyksphinz.hatenablog.com

それではあまりにもつまらないので、自作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
...

と、見た感じかなり変動しそうな場所に配置されているので、これも工夫してうまく処理できるようにしなければならない。