Raspberry Pi2(Linux Kernel)のブートシーケンスを読む(その3) アーキテクチャ依存部

##前回
[「ENTRY(stext)でproc_info_list構造体へのポインタを取得するまで」](http://qiita.com/Mogura1215/items/b8f866b4ede757cdaa73)を説明しました。

##Kernel内のENTRY(stext)の続き(PHYS_OFFSET取得まで)
```linux-rpi-4.1.y/arch/arm/kernel/head.S
#ifdef CONFIG_ARM_LPAE
	mrc	p15, 0, r3, c0, c1, 4		@ read ID_MMFR0
	and	r3, r3, #0xf			@ extract VMSA support
	cmp	r3, #5				@ long-descriptor translation table format?
 THUMB( it	lo )				@ force fixup-able long branch encoding
	blo	__error_lpae			@ only classic page table format
#endif
```

まず、上記の処理を通るには、
コンフィグで[LPAE(Large Physical Address Extension)](https://www.arm.com/ja/products/processors/technologies/virtualization-extensions.php)を有効にする必要があります。
LPAE(仮想化拡張機能)は、[ページテーブル](https://ja.wikipedia.org/wiki/%E3%83%9A%E3%83%BC%E3%82%B8%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB)を2つ用意する手法で、
[ハイパーバイザ](https://ja.wikipedia.org/wiki/%E3%83%8F%E3%82%A4%E3%83%91%E3%83%BC%E3%83%90%E3%82%A4%E3%82%B6)を使用した際のスループットを維持するための仕組みのようです。
ちなみに、前回の説明でハイパーバイザのインストール処理を飛ばしたので、
上記の説明も割愛いたします(後日、ハイパーバイザと共に別記事を書き、リンクを貼ります)。

次の処理に進みます。

```linux-rpi-4.1.y/arch/arm/kernel/head.S
#ifndef CONFIG_XIP_KERNEL
	adr	r3, 2f
	ldmia	r3, {r4, r8}
	sub	r4, r3, r4			@ (PHYS_OFFSET - PAGE_OFFSET)
	add	r8, r8, r4			@ PHYS_OFFSET
#else
	ldr	r8, =PLAT_PHYS_OFFSET		@ always constant in this case
#endif
```
上記の処理は、 [Kernel Execute-In-Place(XIP)](http://elinux.org/Kernel_XIP)が有効/無効で分岐します。
ここでXIPとは、RAMにコード(.textセクションの内容、関数)を展開する事なく、
不揮発性フラッシュメモリ上のコードに直接アクセスして実行する形式です。
私はXIPを使用した事がないので、アドレス空間の問題とか気になります。

ここでの処理の目的は、RAMに展開したデータが物理アドレス0x0000_0000番地から始まらない場合(開始アドレスが0x0000_0000 + PHYS_OFFSETの場合)に備えて、
r8にPHYS_OFFSETを入れる事です。

XIPが有効の場合は、r8にPLAT_PHYS_OFFSETを格納します。
PLAT_PHYS_OFFSETは、
`linux-rpi-4.1.y/arch/arm/mach-bcm2709/include/mach/memory.h`内で、
`#define PLAT_PHYS_OFFSET   UL(CONFIG_PHYS_OFFSET)`と定義されています。
CONFIG_PHYS_OFFSETは、
`arch/arm/configs/bcm2709_defconfig`内で、`CONFIG_PHYS_OFFSET=0`と定義されています。
「値が0なら、この処理は不要では?」と思いますが、
このオフセット値は`__virt_to_phys()`マクロで使用されるため、定義する必要があります。

XIPが無効の場合、r3にPAGE_OFFSETへのラベル(アドレス)を格納します。
PAGE_OFFSETへのラベル(2：)は、head.S内にあります。

```linux-rpi-4.1.y/arch/arm/kernel/head.S
2:	.long	.
	.long	PAGE_OFFSET
```
ここからがアセンブリ言語が読めなくて、
ldmia命令によって、r3(ラベルへのアドレス)から、r4、r8に値をロードされる筈。
どのレジスタに、どの値を?
最終的には、r8にPHYS_OFFSETが格納されますが、それまでの加算減算も理解できてません。
後日、追記します。

この時点での汎用レジスタの状態は、以下の通りです。
>r1 = machine number
>r2 = dtb
>r8 = phys_offset
>r9 = cpu_id
>r10 = proc_info

##Kernel内のENTRY(stext)の続き(ページテーブルの作成まで)
```linux-rpi-4.1.y/arch/arm/kernel/head.S
	bl	__vet_atags
#ifdef CONFIG_SMP_ON_UP
	bl	__fixup_smp
#endif
#ifdef CONFIG_ARM_PATCH_PHYS_VIRT
	bl	__fixup_pv_table
#endif
	bl	__create_page_tables
```
__vet_atagsの定義は、`linux-rpi-4.1.y/arch/arm/head-common.S`内にあります。
この関数では、r2に格納されているポインタ(ATAGS or DTB)の妥当性を確認します。
r2の内容と妥当性を確認するための定数とを比較し、一致しない場合はr2を0にします。
一致している場合は、何も行いません。

__fixup_smp(`linux-rpi-4.1.y/arch/arm/head.S`)の処理は、
CONFIG_SMP_ON_UP([SMP=Symmetric Multi-Processors](http://japan.renesas.com/products/mpumcu/multi_core/child/multicore.jsp))が有効の場合に通る。
Raspberry Pi2のCPUは複数のコアが搭載されているので、SMPに対応しています。
肝心の処理ですが、CPUがSMPに対応しているかどうかの確認以外、
読み取ることができませんでした(後日、追記します)。

__fixup_pv_table(`linux-rpi-4.1.y/arch/arm/head.S`)の処理も、
 CONFIG_ARM_PATCH_PHYS_VIRTが有効の場合のみ通ります。
この関数では、`arch/arm/include/asm/memory.h`内の「仮想アドレス-物理アドレス」を
変換する関数やマクロが使用する変数を決定します。

__create_page_tables(`linux-rpi-4.1.y/arch/arm/head.S`)では、
r4にページテーブルのアドレスを格納します。その後の処理は[ページテーブル](http://softwaretechnique.jp/OS_Development/kernel_development07.html)の作成です。

##Kernel内のENTRY(stext)の続き(MMUの有効化まで)
```
	ldr	r13, =__mmap_switched		@ address to jump to after
						@ mmu has been enabled
	adr	lr, BSYM(1f)			@ return (PIC) address
	mov	r8, r4				@ set TTBR1 to swapper_pg_dir
	ldr	r12, [r10, #PROCINFO_INITFUNC]
	add	r12, r12, r10
	ret	r12
1:	b	__enable_mmu
ENDPROC(stext)
```
__mmap_switched(`linux-rpi-4.1.y/arch/arm/head-common.S`)は、
MMUをONにした後に実行するstart_kernel(`linux-rpi-4.1.y/init/main.c`)を
r13(SP)に格納する。
ここでの処理は、[こちらのサイト](http://masahir0y.blogspot.jp/2012/02/arm-linux-startkernel.html)が参考になります。

最後に、__enable_mmuでMMUがONになった後、
start_kernel(アーキテクチャに依存しない処理、C言語)に処理が移ります。

##疑問点
・XIPが有効の場合は、何故PLAT_PHYS_OFFSETが0なのか。
・__fixup_smpの処理内容
・アセンブラをステップ実行する方法

##次回
[start_kernel()のカナリアコードをスタックに埋め込むまで](http://qiita.com/Mogura1215/items/f2ce8ebbc13bd06c9bb7)を説明しています。

##参考
[ARM startup code analysis Linux](http://qiita.com/drafts/0f29cf4361dce85bfe0f/edit)