LoginSignup
25
21

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