メッセージ欄

2013年11月の日記

一覧で表示する

rpi_start.Sを読む (第9回)
2013/11/10(日) 11:01 NetBSD はてブ情報 はてブに登録 はてブ数

今回読むのは次の部分です。
    186 	.unreq	va
    187 	.unreq	pa
    188 	.unreq	n_sec
    189 	.unreq	attr
    190 	.unreq	itable
    191 	.unreq	l1table
    192 	.unreq	l1sfrm
これは、157行目からの
    157 	l1table	.req r0
    158 	va	.req r1
    159 	pa	.req r2
    160 	n_sec	.req r3
    161 	attr	.req r4
    162 	itable	.req r5
    163 	l1sfrm	.req r6
に対して、反対のことをしているわけですね。

これで終わりです。だと面白くないので、ちょっと振り返ってみましょう。

第6回から第8回までは、第3回でゼロに初期化したL1テーブルを設定する一連の処理でした。

処理は、
  • mmu_init_tableに設定された三つのva, pa, n_sec, attrによるループ
  • n_secによるループ
という二重ループから構成されていました。

mmu_init_tableは、次のようなテーブルでした。
vapansecattr
0x00x00x6430330x412
0xC00000000x00x6430330x041E
0xF20000000x200000000x010FFFFF0x0412
これが、L1テーブルに格納されるころには次のようになり、
vapan_sec
0x00000x000004120x06
0x20000x0000041E0x06
0x3C800x200004120x10
L1テーブルは次のように設定されたということになります。
L1 offsetL1 entry
00000x00000412
00040x00000412
00080x00000412
000C0x00000412
00100x00000412
00140x00000412
00180x00000000
001C0x00000000
(中略)
1FF80x00000000
1FFC0x00000000
20000x0000041E
20040x0000041E
20080x0000041E
200C0x0000041E
20100x0000041E
20140x0000041E
20180x00000000
201C0x00000000
(中略)
3C780x00000000
3C7C0x00000000
3C800x20000412
3C840x20000412
3C880x20000412
3C8C0x20000412
3C900x20000412
3C940x20000412
3C980x20000412
3C9C0x20000412
3CA00x20000412
3CA40x20000412
3CA80x20000412
3CAC0x20000412
3CB00x20000412
3CB40x20000412
3CB80x20000412
3CBC0x20000412
3CC00x00000000
3CC40x00000000
(以下略)

rpi_start.Sを読む (第8回)
2013/11/10(日) 10:24 NetBSD はてブ情報 はてブに登録 はてブ数

前回の終わりに169行目にジャンプしたところから再開します。

今回読むのは以下の部分です。
    169 2:	str	pa, [l1table, va]
    170 	add	va, va, #4
    171 	add	pa, pa, #(L1_S_SIZE)
    172 	adds	n_sec, n_sec, #-1
    173 	bhi	2b
まずは169行目から。
    169 2:	str	pa, [l1table, va]
strはストア命令です。無印なので32ビットストアですね。レジスタpaをアドレス[l1table, va]にストアします。角かっこがいやらしいですね。

[Rn, Rm]はMem[Rn + Rm]ですから、
  *(l1table + va) = pa;
ということになります。Ll1_tableは.word 0x4000の空間でしたね。
    170 	add	va, va, #4
    171 	add	pa, pa, #(L1_S_SIZE)
addは加算ですね。L1_S_SIZEはセクションのサイズ、つまり1MBですから
  va += 4;
  pa += 0x00100000; /* L1_S_SIZEは1M */
となります。

次に進みます。
    172 	adds	n_sec, n_sec, #-1
    173 	bhi	2b
addsのaddは加算命令で、のこりのsはステータスレジスタの更新あり、でしたね。
セクションから1減算し、ゼロでなければラベル2にジャンプする、という処理になりますね。つまり、今回読んでいるソースはn_secでループしているわけですね。

ループを抜けると、再びラベル3の処理に入ります。そして、新しいMMU_INITマクロで設定したva, pa, n_sec, attrを読み込んで、vaとpaを適切に変換して、また、今回読んだループに入るというわけです。二重ループをこうやって実現しているんですね。

しかし、よくわからないのが173行目にあるbhiのhiです。
これは、キャリーフラグが1かつゼロフラグが0である、という意味になります。

キャリーフラグが1とは桁あふれが発生した場合で、ゼロフラグが0とは命令の結果が0でない(0ならフラグは1)である、という意味です。

なるほど、n_secから1を引いたときにゼロになればゼロフラグが1になりそうです。
一方で、キャリーフラグはn_secつまり0x06や0x10からマイナス1されるだけなので、ちっとも桁あふれなど起きなさそうです。

良く考えると、マイナス1を2の補数で表すと全部の桁が1になります。全部の桁が1に対して正の数を足せば、桁あふれが起きそうな気がします。もしかすると、それでキャリーフラグが立つのかもしれません。

だったら、ゼロフラグだけ見ていればよい気もします。すっきりしないなぁ。

すっきりしませんが、今回読んだ部分をCっぽく書いて終わりにしましょう。
do {
  *(l1table + va) = pa;
  va += 4;
  pa += 0x00100000; /* L1_S_SIZEは1M */
} while (--n_sec > 0)

rpi_start.Sを読む (第7回)
2013/11/09(土) 5:28 NetBSD はてブ情報 はてブに登録 はてブ数

さて、前回175行目にジャンプしたところで終わったので、175行目から始めましょう。

今回は、どどんと10行読みます。ちょっと長いかな。
    175 3:	ldmia	itable!, {va,pa,n_sec,attr}
    176 	mov	n_sec, n_sec, lsr #L1_S_SHIFT
    177 	/* Convert va to l1 offset:	va = 4 * (va >> L1_S_SHIFT)	*/
    178 	mov	va, va, LSR #L1_S_SHIFT
    179 	mov	va, va, LSL #2
    180 	/* Convert pa to l1 entry:	pa = (pa & L1_S_FRAME) | attr	*/
    181 	and	pa, pa, l1sfrm
    182 	orr	pa, pa, attr
    183 	cmp	n_sec, #0
    184 	bne	2b
さて始めましょう。
    175 3:	ldmia	itable!, {va,pa,n_sec,attr}
ldmは複数アドレスからのロード、iaは転送ごとにitableをインクリメントするのでした。
itableにはmmu_init_tableのアドレスが入っており、
vapansecattr
0x00x00x6430330x412
0xC00000000x00x6430330x041E
0xF20000000x200000000x010FFFFF0x0412
といった感じにメモリに展開されていたわけですから、ldmia命令によって
  va = 0x0
  pa = 0x0;
  n_sec = 0x643033;
  attr = 0x0412;
てな感じに、各レジスタに値が読み込まれます。

先に進みましょう。
    176 	mov	n_sec, n_sec, lsr #L1_S_SHIFT
movでシフト演算ができるそうで、176行目もそのパターンですね。
lsrは(Logical Shift Right)で右シフト。L1_S_SHIFTが20だったので、
  n_sec = n_sec >> 20;
ということで0x643033 >> 20ですから0x6になります。n_sec = 0x6。
MMU_INITマクロの3つ目はn_secが0x010FFFFFだったので、0x010FFFFF >> 20はn_sec = 0x10ですね。

カーネルのサイズが4MBのところを0x6MBぶん、ペリフェラルの所は16MBなので0x10MBぶん。カーネルは1MB多めにするって書いてありましたけど2MB増えているような気がしますが、よくわかりません。

n_secはセクション数ということで、カーネルは4セクション、ペリフェラルは16セクションということになります。

次に進みます。
    177 	/* Convert va to l1 offset:	va = 4 * (va >> L1_S_SHIFT)	*/
    178 	mov	va, va, LSR #L1_S_SHIFT
    179 	mov	va, va, LSL #2
これもmovを使ったシフト演算です。やっていることはコメントのとおりですね。vaをL1_S_SHIFT分だけ右シフトさせ、4倍(つまり左シフト2)します。

ではなぜ、4倍するんでしょうか?

コメントにva からL1オフセットに変換する、とありますね。32ビットのpa+attrを入れるとアドレスは4つ進むから、L1オフセットは4刻みで増える、だから4倍するのですね。

実際の数字がどうなるかというと、カーネルとペリフェラルのそれぞれで次のようになります。
  va = 4 * (0xC0000000 >> 20) = 0x00002000
  va = 4 * (0xF2000000 >> 20) = 0x00003C80
L1オフセットが計算できました。

つぎは、PAをL1エントリに変換です。
    180 	/* Convert pa to l1 entry:	pa = (pa & L1_S_FRAME) | attr	*/
    181 	and	pa, pa, l1sfrm
    182 	orr	pa, pa, attr
これもコメントのとおりですね。pa & L1_S_FRAMEしてからattrでORをとると。

L1_S_FRAMEが0xFFF00000ですから、ページテーブルのフォーマットに合わせて、上位12ビットにセクションを入れて下位20ビットにアトリビュートを入れているわけですね。

第4回で触れたページテーブルのフォーマットを再掲しておきます。
31-20191817161514-1211-1098-543210
セクションのベースアドレス(12bit)SBZ0nGSAPXTEXAPIMPドメインXNCB10
先に進みましょう。
    183 	cmp	n_sec, #0
    184 	bne	2b
ここはループの条件ですね。n_secと0を比較して、番兵の0つまりMMU_INIT(0, 0, 0, 0)じゃなければラベル2、つまり169行目にジャンプせよ、ということになります。

ということで、今回ぶんをCっぽくしてみると次のような感じでしょうか。
  va = *(itable++);
  pa = *(itable++);
  n_sec = *(itable++);
  attr = *(itable++);

  n_sec = n_sec >> L1_S_SHIFT; /* L1_S_SHIFT = 20 */
  va = 4 * (va >> L1_S_SHIFT);
  pa = (pa & L1_S_FRAME) | attr; /* L1_S_FRAME = 0xFFF00000 */
  if (n_sec != 0)
    goto 162行目

rpi_start.Sを読む (第6回)
2013/11/09(土) 27:46 NetBSD はてブ情報 はてブに登録 はてブ数

第3回で154行目まできていましたが、第4回と第5回のMMU_INITマクロでずいぶんと寄り道してしまいました。

今回は、第3回の154行目から空行をはさんで、156行目から始めましょう。
    156 	/* Now create our entries per the mmu_init_table. */
    157 	l1table	.req r0
    158 	va	.req r1
    159 	pa	.req r2
    160 	n_sec	.req r3
    161 	attr	.req r4
    162 	itable	.req r5
    163 	l1sfrm	.req r6
    164 
    165 	adr	itable, mmu_init_table
    166 	ldr	l1sfrm, Ll1_s_frame
    167 	b	3f
まずは、mmu_init_tableのエントリーを作ります。というコメントからスタートです。
    156 	/* Now create our entries per the mmu_init_table. */
    157 	l1table	.req r0
    158 	va	.req r1
    159 	pa	.req r2
    160 	n_sec	.req r3
    161 	attr	.req r4
    162 	itable	.req r5
    163 	l1sfrm	.req r6
157行目から163行目に.recというのが出てきています。これはGNU ASのディレクティブと呼ばれるものだそうです。

レジスタに名前を付ける、と思っていればよさそうです。r0はl1tableと呼びますよ、ということでしょうね。ここから先が読みやすくなっていいですねぇ。

それでは次です。
    165 	adr	itable, mmu_init_table
adrは先ほども出てきた疑似命令です。mmu_init_tableのアドレスをitableにロードします。mmu_init_tableは、第3回と第4回でさんざんやったMMU_INITマクロですね。

先に進みます。
    166 	ldr	l1sfrm, Ll1_s_frame
ldrはロード命令です。l1sfrmにラベルLl1_s_frameの値をロードします。
Ll1_s_frameは後ろのほうに定義されています。
    255 Ll1_s_frame:
    256 	.word	L1_S_FRAME
つまりL1_S_FRAMEの値をl1sfrmにロードするわけです。L1_S_FRAMEは先のpte.hに書かれています。
  #define    L1_S_SIZE    0x00100000    /* 1M */
  #define    L1_S_OFFSET    (L1_S_SIZE - 1)
  #define    L1_S_FRAME    (~L1_S_OFFSET)
  #define    L1_S_SHIFT    20
つまり、L1_S_FRAME = ~(0x00100000 - 1)=0xFFF00000です。

ということで、l1sfrm = 0xFFF00000;になります。
    167 	b	3f
bだけなので無条件ジャンプ。ラベルは3fなので3、つまり175行目にジャンプします。3fのfはなんでしょうか... forwardのfかな?

ということで、今回は実質3行だけでした。Cっぽく書いてみます。
  itable = &mmu_init_table;
  l1sfrm = 0xFFF00000; /* L1_S_FRAME (12bit) */
  goto 175行目

rpi_start.Sを読む (第5回)
2013/11/09(土) 27:22 NetBSD はてブ情報 はてブに登録 はてブ数

MMU_INITマクロの続編です。前回だいぶ解明したおかげで残りは順調に進みそうです。

二つ目のMMU_INITマクロを読み解きましょう。
    314 	MMU_INIT(KERNEL_BASE, 0x0,
    315 	    (_end - KERNEL_BASE + 2 * L1_S_SIZE - 1),
    316 	    L1_S_PROTO | L1_S_AP_KRW  | L1_S_B | L1_S_C)
三つめまでの引数は先ほどのほぼ同様ですね。KERNEL_BASEもわかっているので問題ありません。
最後の引数も新しいのはL1_S_B, L1_S_Cの二つです。
おなじみ、pte.hで定義されています。先ほどのL1_S_IMPの上にいました。
  /* L1 Section Descriptor */
  #define L1_S_B          0x00000004      /* bufferable Section */
  #define L1_S_C          0x00000008      /* cacheable Section */
  #define L1_S_IMP        0x00000010      /* implementation defined */
ですから、L1_S_PROTO | L1_S_AP_KRW | L1_S_B | L1_S_C = 0x0012 | 0x0400 | 0x0004 | 0x0008 = 0x041Eです。

ということで、
    314 	MMU_INIT(KERNEL_BASE, 0x0,
    315 	    (_end - KERNEL_BASE + 2 * L1_S_SIZE - 1),
    316 	    L1_S_PROTO | L1_S_AP_KRW  | L1_S_B | L1_S_C)
というMMU_INITは
  va = 0xC0000000;
  pa = 0x0;
  n_sec = 0x643033;
  attr = 0x041E;
と表現できることがわかります。

さて、最後のMMU_INITマクロです。
コメントによると16MBのペリフェラルのマップとのこと。メモリマップI/Oされた周辺機器に関するエントリのようですね。
    318 	/* Map the 16MB of peripherals */
    319 	MMU_INIT(RPI_KERNEL_IO_VBASE, RPI_KERNEL_IO_PBASE,
    320 	    (RPI_KERNEL_IO_VSIZE + L1_S_SIZE - 1),
    321 	    L1_S_PROTO | L1_S_AP_KRW)
これは、四つ目の引数が一つ目のMMU_INITマクロと同じですから最初の三つの引数が読み解ければよいですね。

まずは、RPI_KERNEL_IO_VBASE, RPI_KERNEL_IO_PBASEです。

これは、arch/evbarm/rpi/rpi.hにある
  #define    RPI_KERNEL_IO_VBASE        BCM2835_PERIPHERALS_VBASE
  #define    RPI_KERNEL_IO_PBASE        BCM2835_PERIPHERALS_BASE
  
  #define    RPI_KERNEL_IO_VSIZE        BCM2835_PERIPHERALS_SIZE
からのarch/arm/broadcom/bcm2835reg.hにおいて定義されています。
  #define    BCM2835_PERIPHERALS_BASE    0x20000000
  #define    BCM2835_PERIPHERALS_SIZE    0x01000000    /* 16MBytes */
  
  #define BCM2835_IOPHYSTOVIRT(a) \
      ((0xf0000000 | (((a) & 0xf0000000) >> 4)) + ((a) & ~0xf0000000))
  
  #define    BCM2835_PERIPHERALS_VBASE \
      BCM2835_IOPHYSTOVIRT(BCM2835_PERIPHERALS_BASE)
RPI_KERNEL_IO_PBASEはそのままBCM2835_PERIPHERALS_BASEですから0x20000000になります。

RPI_KERNEL_IO_VBASEはBCM2835_IOPHYSTOVIRTというマクロを経由しますが、計算するとRPI_KERNEL_IO_VBASE = 0xF2000000になります。

三つ目のMMU_INITマクロである
    318 	/* Map the 16MB of peripherals */
    319 	MMU_INIT(RPI_KERNEL_IO_VBASE, RPI_KERNEL_IO_PBASE,
    320 	    (RPI_KERNEL_IO_VSIZE + L1_S_SIZE - 1),
    321 	    L1_S_PROTO | L1_S_AP_KRW)
については次のように表現できます。
  va = 0xF2000000
  pa = 0x20000000
  n_sec = 0x01000000 + 0x00100000 - 1 = 0x010FFFFF
  attr = 0x0412
最後のMMU_INITマクロは、
    323 	/* end of table */
    324 	MMU_INIT(0, 0, 0, 0)
ということで、テーブルの最後にすべてを0にしたものです。テーブルの最後を示す、番兵の役目ということでしょうか。

番兵を除く、MMU_INITマクロを表にしてみましょう。
vapansecattr
0x00x00x6430330x412
0xC00000000x00x6430330x041E
0xF20000000x200000000x010FFFFF0x0412