like a /dev/null

いつかは捨てられる儚いモノたちの吹き溜まり

Baseなコード

OSなしのCPUで、どうやってCのmain関数の実行にたどりつくのか、というのは概念としては知っているけど、個々のCPUで手順は異なる。

“OSなしのCPUで”などと書いたけど、OSの立ち上げだって同じ話だったりする。ブートローダーというキーワードで片付けられたりするけど。

ということで、電源投入からそれなりに正しい手順でmain関数を実行するまでのコードを作った。

https://github.com/mgwsuzuki/v6pi/tree/master/base

それなりに正しい手順だけど、テスト目的程度のレベルである。

ベクタテーブル

CPUは命令を実行している間に割り込みが入ったとき、ある特定のアドレスにジャンプしたり、特定のアドレスを読み出してそこにジャンプしたりする。このような動作を例外処理と呼び、割り込みは例外処理の1つである。

CPUの例外要因は1つではない。そのため、例外要因ごとに所望の処理ルーチンにジャンプするのを効率化するために、例外要因ごとに番号をつけ、それをインデックスとしたテーブルを作る。これをベクタテーブルなどと呼ぶ。

テーブルの中身はRasPiに使われているARMでは命令そのものである。ただ、CPUごとに実装はそれぞれでジャンプ先のアドレスだったりするものもある。

このテーブルのベースアドレスは0x0か、0xffff_0000を選べる。0xffff_0000はMMUを使ったOSで使用することを想定している。初期状態は0x0である。

ARMでは以下のようなテーブル定義となっている。

開始アドレス 例外/割り込み名
0x0000_0000 リセット
0x0000_0004 未定義命令
0x0000_0008 ソフトウェア割り込み
0x0000_000c プリフェッチアボート
0x0000_0010 データアボート
0x0000_0014 予約
0x0000_0018 割り込み要求
0x0000_001c 高速割り込み要求

このテーブルには命令を記述する必要があり、要するにアセンブラの命令を記述すればよい。ということでvector.sには次のようなコードを記述している。1命令あたり4byteなので、ぴったり8つの要素があるベクタテーブルとなる。

_start:
    ldr     pc, htbl_reset
    ldr     pc, htbl_undef
    ldr     pc, htbl_swi
    ldr     pc, htbl_pabt
    ldr     pc, htbl_dabt
    ldr     pc, htbl_unused
    ldr     pc, htbl_irq
    ldr     pc, htbl_fiq

“htblなんとか”から始まるラベルは次のようになっている。

htbl_reset:
    .word   reset_handler
htbl_undef:
    .word   undef_handler
htbl_swi:
    .word   swi_handler
htbl_pabt:
    .word   pabt_handler
htbl_dabt:
    .word   dabt_handler
htbl_unused:
    .word   unused_handler
htbl_irq:
    .word   irq_handler
htbl_fiq:
    .word   fiq_handler

“なんとかhandler”は例外処理ハンドラの開始アドレスである。つまり、ldr命令で”なんとかhandler”のアドレスをプログラムカウンタに読み込ませてジャンプする。

ちなみに、ldr命令ではなくてb命令でジャンプさせる方法もある。が、32MBより離れたアドレスに分岐できないし、後で説明する回避できない副作用があるため用いない。

リセットハンドラ

リセットハンドラの処理は以下のようになっている。

ベクタテーブルのコピー

名前の通り、電源投入直後やCPUがリセットされたときに実行されるコードである。vector.sでは次のとおり。

        mov     r0, #0x8000
        mov     r1, #0x0000
        ldmia   r0!, {r2, r3, r4, r5, r6, r7, r8, r9}
        stmia   r1!, {r2, r3, r4, r5, r6, r7, r8, r9}
        ldmia   r0!, {r2, r3, r4, r5, r6, r7, r8, r9}
        stmia   r1!, {r2, r3, r4, r5, r6, r7, r8, r9}

やっていることは、0x8000から32byte分のデータを0x0にコピーしている。

実は、_startラベルから始まるベクタテーブルは、アドレス0x8000に配置されている。0x8000は何かというと、RasPiのARMが起動したときに実行が開始されるアドレスなのである。0x0でない。

これはRasPiに搭載されているSoC(及びメーカー配布のブートローダー)がそうなっているため、どうしようもない。だからベクタテーブルを0x8000に配置すれば電源投入直後だけはしのげる。

かといって、ARMのベクタテーブルの開始アドレスはやはり0x0である。なので、0x8000にあるままでは正しく動作しない。ということで、上記のコードで0x8000にあるベクタテーブルを0x0へコピーしている。

ここにベクタテーブルをb命令で記述できない理由がある。b命令はプログラムカウンタをベースとした相対アドレスで分岐先アドレスを決定するため、命令を別のアドレスに移動すると分岐先アドレスが狂ってしまうのである。

ldr命令もプログラムカウンタをベースとした相対アドレスで読み出しアドレスを決定するけど、読み出し先データも矛盾なくコピーするので問題ない。そして、読み出し先に書いてあるジャンプ先アドレスは絶対アドレスなので問題なくジャンプできる。

モードとスタックポインタの設定

ARMはCPUの動作モードして以下のようなものがある。

  • アボート
  • 高速割り込み要求
  • 割り込み要求
  • スーパバイザ
  • システム
  • 未定義
  • ユーザ

これらはcpsr(レジスタ)の[4:0]に値を書き込むことで変更できる。といっても、書き込み権限のないモードになってしまうと書き込めない。

もうひとつ大切なことは、各モードごとにいくつかのレジスタが切り替わる、バンクレジスタとなっていて、スタックポインタもそれに含まれる。

現状のv6piではCPUモードを切り替えるようなレベルではないため、スーパバイザのみ設定しているが、例えば割り込みを使うようになったらそのモードにおけるスタックポインタを正しく設定する必要がある。

        mov     r0, #(0x13|0xc0)
        msr     cpsr_c, r0
        mov     sp, #0x10000000

なお、上のコードでは割り込みを禁止している。

メモリの初期化

ブートローダーによって、SDカードから実行したいプログラムはすでにメモリに読み込まれているが、これを実行する前の準備が必要である。

1つは初期化データのコピー、もうひとつはBSS領域をゼロクリアすることである。

初期化データはプログラムが実行される前にメモリにセットされている値だが、これは.textの後ろに配置されていて、これを.dataの領域にコピーする必要がある。

        ldr     r1, =__text_end__
        ldr     r2, =__data_start__
        ldr     r3, =__data_end__
1:      cmp     r2, r3
        ldrlo   r0, [r1], #4
        strlo   r0, [r2], #4
        blo     1b

このおかげで初期化データをプログラムが変更しても、リセットすることでもとの値へ復活させることができる。

bss領域は、初期値がゼロでなければいけない。ということでゼロクリアする。

        mov     r0, #0
        ldr     r1, =__bss_start__
        ldr     r2, =__bss_end__
2:      cmp     r1, r2
        strlo   r0, [r1], #4
        blo     2b

なお、”__“で始まるシンボルはmemmapに記述されており、リンカの処理で値が確定する。

mainをコール

ここでようやくmain関数をコールすることができる。

        bl      main

書きながら気が付いたが、main関数から戻ってきたときの処理が入っていない。後で無限ループを追加しておこう。

その他のハンドラ

リセット以外のハンドラを記述するためのテンプレートである。現在は何も使っていないのですべて無限ループとなっているが、必要に応じて記述する。

undef_handler:
        b       undef_handler
swi_handler:
        b       swi_handler
pabt_handler:
        b       pabt_handler
dabt_handler:
        b       dabt_handler
unused_handler:
        b       unused_handler
irq_handler:
        b       irq_handler
fiq_handler:
        b       fiq_handler