##前回
「電源投入から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の取得まで)
以下に概略を記載します。
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構造体へのポインタを格納
__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言語ではない)。
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;
};
定義は以下の通りです(後日追記します)
/*
* 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 についてのまとめ