rpi_start.Sを読む (第3回)
2013/11/04(月) 22:16 NetBSD はてブ情報 はてブに登録 はてブ数

続きです。

まずは、コメントです。
    132 	/*
    133 	 * Set up a preliminary mapping in the MMU to allow us to run
    134 	 * at KERNEL_BASE with caches on.
    135 	 */
キャッシュをONにしてKERNEL_BASEで動かすため、MMUに準備的なマッピングをセットアップします、とでも訳すのでしょうか? なんのこっちゃ。
MMUに最低限の準備をします、ぐらいにとらえておけばよいのでしょうかねぇ。

さて、今回のメインはこちらです。
    136 	/* Build page table from scratch */
    137 	ldr	r0, Ltemp_l1_table	/* The page table address - entered into TTB later */
    138 	mov	r1, r0			/* Start address to clear memory. */
    139 	/* Zero the entire table so all virtual addresses are invalid. */
    140 	mov	r2, #L1_TABLE_SIZE	/* in bytes */
    141 	mov	r3, #0
    142 	mov	r4, r3
    143 	mov	r5, r3
    144 	mov	r6, r3
    145 	mov	r7, r3
    146 	mov	r8, r3
    147 	mov	r10, r3
    148 	mov	r11, r3
    149 1:	stmia	r1!, {r3-r8,r10-r11}
    150 	stmia	r1!, {r3-r8,r10-r11}
    151 	stmia	r1!, {r3-r8,r10-r11}
    152 	stmia	r1!, {r3-r8,r10-r11}
    153 	subs	r2, r2, #(4 * 4 * 8)	/* bytes per loop */
    154 	bne	1b
ちょっと長いですが、似た命令が多いので、大丈夫でしょう。
    136 	/* Build page table from scratch */
ページテーブルをスクラッチからつくりますというコメントです。なるほど、以降の処理はページテーブルに関する処理というわけですね。

先に進みます。
    137 	ldr	r0, Ltemp_l1_table	/* The page table address - entered into TTB later */
Ltemp_l1_tableのアドレスをr0にロードします。TTBとはTransration Table Baseの略ですね。Ltemp_l1_tableのところは、
    258 Ltemp_l1_table:
    259 	/* Put the temporary L1 translation table just below the kernel. */
    260 	.word	0x4000
と書かれています。kernelのすぐ後ろにテンポラリのL1変換テーブルを置く、と読めます。0x4000の大きさのテーブルが確保されているってことですかね。

まぁ、とにかくL1変換テーブルの先頭をr0に代入したという感じです。

次に進みます。
    138 	mov	r1, r0			/* Start address to clear memory. */
これは、先ほどのr0つまりL1変換テーブルの先頭をr1にも代入しています。
あとでr0を使うので温存しておいて、r1のほうを作業用に使うという感じですかね。

次に進みます。
    139 	/* Zero the entire table so all virtual addresses are invalid. */
テーブル全体をゼロにして、すべての仮想アドレスをinvalidにします。というコメントです。なんだかよくわかりませんが、ゼロにするようです。
    140 	mov	r2, #L1_TABLE_SIZE	/* in bytes */
L1_TABLE_SIZEはarch/arm/include/arm32/pte.hで
  #define    L1_TABLE_SIZE    0x4000        /* 16K */
と定義されていますので、
  r2 = 0x4000; /* L1_TABLE_SIZE */
ということになりますね。そういえば、さっきのL1_temp_tableは
    260 	.word	0x4000
でしたね。なるほど、関係ありそうです。

先に進みましょう。
    141 	mov	r3, #0
    142 	mov	r4, r3
    143 	mov	r5, r3
    144 	mov	r6, r3
    145 	mov	r7, r3
    146 	mov	r8, r3
    147 	mov	r10, r3
    148 	mov	r11, r3
レジスタr3に0を代入し、それをレジスタr4, r5, r6, r7, r8, r10, r11に代入しています。つまり、8つのレジスタをぜんぶゼロにしているようです。

ここから、ループに入ります。
    149 1:	stmia	r1!, {r3-r8,r10-r11}
    150 	stmia	r1!, {r3-r8,r10-r11}
    151 	stmia	r1!, {r3-r8,r10-r11}
    152 	stmia	r1!, {r3-r8,r10-r11}
stmiaです。stmはレジスタ-メモリ転送命令の複数版で、iaはIncrement Afterということで、メモリアクセス後にポインタを増やす、でしたね。

ということで、r1つまりL1_temp_tableの先頭からゼロを代入した8つのレジスタをどんどんメモリに転送していきます。

次に進みます。
    153 	subs	r2, r2, #(4 * 4 * 8)	/* bytes per loop */
subsのsubは減算命令です。subsの後ろのsはステータスレジスタを更新するためののsです。

L1_TABLE_SIZE(つまり0x4000)が格納されたr2から4*4*8を引きます。レジスタは32ビットなので4, stmiaが4回なので4, レジスタは8個転送されているので4*4*8ですね。

次に進みます。
    154 	bne	1b
bneのbは分岐命令です。先のsubsの末尾のsで更新されたステータスフラグをみて、分岐します。見るステータスフラグはZ、つまりゼロかどうかです。

bneのneはNot Equalつまりbneはr2がゼロでなければ1bに飛べと言っています。1bとは149行目の1:というラベルです。1bのbはbackのbでしょうか?

今回の処理をCっぽく書いてみると次のような感じでしょうか。
  r0 = &(Ltemp_l1_table);
  r1 = r0;
  r2 = 0x4000; /* L1_TABLE_SIZE */
  r3 = 0;
  r4 = r3;
  r5 = r3;
  r6 = r3;
  r7 = r3;
  r8 = r3;
  r10 = r3;
  r11 = r3;
  
  do {
    for (int i = 0; i < 4; i++) {
      *(r1++) = r3;
      *(r1++) = r4;
      *(r1++) = r5;
      *(r1++) = r6;
      *(r1++) = r7;
      *(r1++) = r8;
      *(r1++) = r10;
      *(r1++) = r11;
    }
    
    r2 -= (4 * 4 * 8);
  
  } while (r2 != 0)
素朴に、r3に0をセットして0x1000回のループにすればよいと思ったのですが、stmiaで8レジスタ一気に転送して、0x4000/(4*4*8)回のループのほうが効率的なんでしょうね。

さてこれで、Ltemp_l1_tableが全部ゼロクリアされた状態になりました。

名前:  非公開コメント   

  • TB-URL  http://www.tokuda.net/diary/0805/tb/

rpi_start.Sを読む (第2回)
2013/11/04(月) 21:21 NetBSD はてブ情報 はてブに登録 はてブ数

つづきです。

今回読むのはここです。
    127 	mrs	r0, cpsr
    128 	bic	r0, r0, #PSR_MODE
    129 	orr	r0, r0, #(I32_bit | F32_bit | PSR_SVC32_MODE)
    130 	msr	cpsr, r0
まず127行目。
    127 	mrs	r0, cpsr
mrsはステータスレジスタのシステムコールです。mrsはロード、msrはストア。
read status, store registerの頭文字でしょうか。

cpsrは現在のステータスレジスタです(Current Program Status Register)。

現在のステータスレジスタをr0にロードする、ということですね。

次に進みます。
    128 	bic	r0, r0, #PSR_MODE
bicはビットクリアです。#PSR_MODEはarm/include/armreg.hで定義されていて下位5ビットに1が立っています。
  #define PSR_MODE    0x0000001f    /* mode mask */
ということで、r0(ステータスレジスタと同値)の下位5ビットをクリアする、ということですね。

次に進みます。
    129 	orr	r0, r0, #(I32_bit | F32_bit | PSR_SVC32_MODE)
orrは文字どおりorつまり論理和をとります。

先ほど下位5ビットをクリアしたr0にビットを立てています。I32_bit, F32_bit,PSR_SVC32_MODEのそれぞれです。これまたarm/include/armreg.hに定義されています。
     62 #define I32_bit (1 << 7)	/* IRQ disable */
     63 #define F32_bit (1 << 6)	/* FIQ disable */
     77 #define PSR_SVC32_MODE	0x00000013
コメントから想像するに、割り込みを禁止するビットのようですね。
    130 	msr	cpsr, r0
最初のmrs(リード)に対するmsr(ストア)ですね。ここまでr0を操作してきた内容をステータスレジスタにストアしています。

ということで、ここでは割り込みを禁止する一連の処理をしていたようです。

名前:  非公開コメント   

  • TB-URL  http://www.tokuda.net/diary/0804/tb/

rpi_start.Sを読む (第1回)
2013/11/04(月) 20:47 NetBSD はてブ情報 はてブに登録 はてブ数

ARMアセンブリ言語を勉強するため、CPUのスタートアップを題材にソースコードを読んでみようと思います。

といっても、ARMアセンブリ言語の知識は皆無なので、Webで一命令ずつ調べながら、わからないところは大胆に飛ばしながら読んでいくという無責任企画です。

今話題のRaspberry Piをターゲットに、少しづつ勉強していきます。

src/sys/arch/evbarm/rpi/rpi_start.Sの1.7がターゲットになります。コメント含めて328行の小さなプログラムですが、自分にとっては楽しめそうです。

さて、はじめましょう。

一番最初に実行されるエントリーポイントから読んでいきます。ソースの最初の部分は少し飛ばして、122行目からスタートです。
    122 	.global	_C_LABEL(rpi_start)
    123 _C_LABEL(rpi_start):
ラベルですね。_CとついているとCのプログラムから呼べるんでしたっけ? 先に進みましょう。
    124 	adr	r8, rpi_boot_regs
いきなりのadrはGNU asの疑似命令です。指定したレジスタにラベル(ここだとrpi_boot_regs)のアドレスをロードします。プログラムカウンタr15にadd or subして実現されているそうです。r15なんて書かれてないから不思議な感じですね。

rpi_boot_regsには
    327 rpi_boot_regs:
    328 	.space 4 * 4
と書かれています。ブート用のレジスタ(?)の領域を確保ということかな? 4*4なので32bitのアドレスを4つ確保と読むのかな。

ということで、ラベルrpi_boot_regsのアドレスをr8に読み込むという命令ですね。

adrという疑似命令は、ARMでは命令の長さが4バイトに固定されるため、32ビット(4バイト)の整数 やラベルの値(メモリアドレス)を直接レジスタに転送することができません。 とのこと。

さて次に進みます。
    125 	stmia	r8!, {r0-r3}
stmはレジスタ-メモリ転送命令の複数版です。stmはストアマルチ、ldmはロードマルチかな。iaはIncrement Afterということで、メモリアクセス後にポインタを増やす。つまり、以下のようなイメージでしょうか。
  addr = r8;
  r0 = *(addr++);
  r1 = *(addr++);
  r2 = *(addr++);
  r3 = *(addr++);
 r8!の!は最後にr8に書き戻すという意味なので
  r8 = addr;
ということなのでしょう。

rpi_boot_regがr0からr3に割り当てられたということですね。

しかし、rpi_boot_regは、どこにも使っていそうな場所がありませんね。うーん。

名前:  非公開コメント   

  • TB-URL  http://www.tokuda.net/diary/0803/tb/