25
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-03-20

##前回
「電源投入からKernelに制御が移行するまで」を説明しました。
目的や使用機材についても記載があります。

##Kernelのソースコードを読む前に(事前準備)
私のような初心者は、途中でソースコードを追えなくなります。
また、正しいコードを読んでいるのかを確認するために、ブート時のログを取得します。

ログは、Raspberry Piの起動後に以下のコマンドを入力する事で取得できます。
piはユーザ名です。boot_log.txtは好きなファイル名で問題ありません。
$ dmesg > /home/pi/Desktop>boot_log.txt

コマンド入力後、以下の内容が記載されたテキストファイルが出力されます。


[ 0.000000] Booting Linux on physical CPU 0xf00
[ 0.000000] Initializing cgroup subsys cpuset
[ 0.000000] Initializing cgroup subsys cpu
[ 0.000000] Initializing cgroup subsys cpuacct
(以下省略)

このログメッセージを以下のように検索する事で、該当する処理が記載されたファイルが分かります。
この際、慣れないと正しいファイルが判断できない事があります。慣れるしかないです。
$ cd /home/pi/Desktop/linux-rpi-4.1.y
$ grep -nr "Booting Linux on physical CPU" *

##Kernel内のENTRY(stext)へ移動
一番最初の起動ログを検索すると、
$ grep -nr "Booting Linux on physical CPU" *
linux-rpi-4.1.y/arch/arm/kernel/setup.c内の"void notrace cpu_init(void)"が
該当の処理と分かります。

しかし、実際はこの関数に辿り着く前に、細かな処理を行っています。
Raspberry Pi2はARM CPU(Cortex-A7)なので、
linux-rpi-4.1.y/arch/arm/kernel/head.SのENTRY(stext)から処理が開始されます。

ENTRY(stext)は、kernel.imgの先頭からオフセット(0x8000)を足した位置にあります。
オフセットの値を知る方法は、以下の2通りです。
 ・start.elfを逆アセンブル:$ arm-none-eabi-objdump -m arm -D start.elf
 ・linux-rpi-4.1.y/arch/arm/kernel/vmlinux.ldsの中身を読む

ENTRY(stext)を実行した直後のCPUの状態は、以下の通りです。

MMU = OFF
D-cache = OFF
I-cache = Don't care
r0 = 0
r1 = machine number
r2 = atargs or DTB pointer

MMU(メモリ管理ユニット)はOFF、
D-Cache(データキャッシュ)もOFF、
I-cache(命令キャッシュ)はON/OFFどちらでも問題がありません。
r0は0(ARM CPUのr0は引数渡しではなく、返り値を渡す時に使用されるため)、
r1はlinux-rpi-4.1.y/arch/arm/tools/mach-typesに定義されているボード固有を格納し、
r2はatags構造体かDTB(Device Tree Blob)へのポインタが格納されています。

r0~r2に値を入れたのは、Bootloaderです。
atags構造体、DTB(Device Tree)は、ボード固有のハードウェア情報を記述したものです。
前者はKernel内(arch/arm/mach-*/)にハードコーディングするのに対して、
後者はKernel外(Device Tree)に記述しています。
一般的に、ARM環境(Raspberry Pi)ではDTBが使用されます。

##ENTRY(stext)での処理(CPU IDの取得まで)
以下に概略を記載します。

linux-rpi-4.1.y/arch/arm/kernel/head.S
ENTRY(stext)
 ARM_BE8(setend	be )			@ ensure we are in BE8 mode

 THUMB(	adr	r9, BSYM(1f)	)	@ Kernel is always entered in ARM.
 THUMB(	bx	r9		)	@ If this is a Thumb-2 kernel,
 THUMB(	.thumb			)	@ switch to Thumb now.
 THUMB(1:			)

#ifdef CONFIG_ARM_VIRT_EXT
	bl	__hyp_stub_install
#endif
	@ ensure svc mode and all interrupts masked
	safe_svcmode_maskall r9

	mrc	p15, 0, r9, c0, c0		@ get processor id
	bl	__lookup_processor_type		@ r5=procinfo r9=cpuid
	movs	r10, r5				@ invalid processor (r5=0)?
 THUMB( it	eq )		@ force fixup-able long branch encoding
	beq	__error_p			@ yes, error 'p'

 1)CPUをビッグエンディアン形式に変更
 2)CPUの動作モードをARM(32bit命令モード)に変更
 3)CPUをスーパバイザモードに変更
 4)全ての割り込みをマスク
 5)c0(プロセッサに関する情報を含むレジスタ)からCPU IDをr9に格納
 6)__lookup_processor_typeにより、r5にproc_info_list構造体へのポインタを格納

linux-rpi-4.1.y/arch/arm/kernel/head-common.S
__lookup_processor_type:
	adr	r3, __lookup_processor_type_data
	ldmia	r3, {r4 - r6}
	sub	r3, r3, r4			@ get offset between virt&phys
	add	r5, r5, r3			@ convert virt addresses to
	add	r6, r6, r3			@ physical address space
1:	ldmia	r5, {r3, r4}			@ value, mask
	and	r4, r4, r9			@ mask wanted bits
	teq	r3, r4
	beq	2f
	add	r5, r5, #PROC_INFO_SZ		@ sizeof(proc_info_list)
	cmp	r5, r6
	blo	1b
	mov	r5, #0				@ unknown processor
2:	ret	lr
ENDPROC(__lookup_processor_type)

まず、proc_info_listへのポインタをCPU ID(r9)を用いて入手します。
r4には__proc_info_begin(仮想アドレス)、r6には__proc_info_end(仮想アドレス)が格納され、
これらのポインタを物理アドレスに変換します。
(このタイミングでは、MMUが有効化されていないため)

次に、ldmia命令でr3にCPU IDを格納し、r4にCPU MASKを格納します。
r4(__proc_info_begin)とr9(CPU ID)の論理積をとります。

最後に、r3(CPU ID)とr4(__proc_info_beginとCPU IDの論理積)を比較します。
一致していれば、ラベル2:に移動し、ret命令により、呼び出し元にリターンします。
値が異なる場合、ポインタをオフセット分(PROC_INFO_SZ)移動させ、
次のproc_info_list構造体の中身を調べます(ラベル1:に飛びます)。
このオフセットは、linux-rpi-4.1.y/arch/arm/kernel/asm-offsets.c内で、
DEFINE(PROC_INFO_SZ, sizeof(struct proc_info_list));と定義されています。

##proc_info_list構造体について
以下の構造体はアセンブリ言語で定義されている(C言語ではない)。

linux-rpi-4.1.y/arch/arm/include/asm/procinfo.h
struct proc_info_list {
	unsigned int		cpu_val;
	unsigned int		cpu_mask;
	unsigned long		__cpu_mm_mmu_flags;	/* used by head.S */
	unsigned long		__cpu_io_mmu_flags;	/* used by head.S */
	unsigned long		__cpu_flush;		/* used by head.S */
	const char		*arch_name;
	const char		*elf_name;
	unsigned int		elf_hwcap;
	const char		*cpu_name;
	struct processor	*proc;
	struct cpu_tlb_fns	*tlb;
	struct cpu_user_fns	*user;
	struct cpu_cache_fns	*cache;
};

定義は以下の通りです(後日追記します)

linux-rpi-4.1.y/arch/arm/mm/proc-v7.S
	/*
	 * Standard v7 proc info content
	 */
.macro __v7_proc name, initfunc, mm_mmuflags = 0, io_mmuflags = 0, hwcaps = 0, proc_fns = v7_processor_functions
	ALT_SMP(.long	PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \
			PMD_SECT_AF | PMD_FLAGS_SMP | \mm_mmuflags)
	ALT_UP(.long	PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \
			PMD_SECT_AF | PMD_FLAGS_UP | \mm_mmuflags)
	.long	PMD_TYPE_SECT | PMD_SECT_AP_WRITE | \
		PMD_SECT_AP_READ | PMD_SECT_AF | \io_mmuflags
	initfn	\initfunc, \name
	.long	cpu_arch_name
	.long	cpu_elf_name
	.long	HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB | HWCAP_FAST_MULT | \
		HWCAP_EDSP | HWCAP_TLS | \hwcaps
	.long	cpu_v7_name
	.long	\proc_fns
	.long	v7wbi_tlb_fns
	.long	v6_user_fns
	.long	v7_cache_fns
.endm

##次回
「Raspberry Pi2(Linux Kernel)のブートシーケンスを読む(その3)」
start_kernelに移行するまで(アーキテクチャ依存部の最後まで)の説明を記述しています。

##参考サイト
Device Tree 入門
Device Tree についてのまとめ

25
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?