2009年7月30日木曜日

startup_32

プロテクトモードに設定するとアドレス0x100000へジャンプしています。ここがプロテクトモードにおけるカーネルのエントリポイントとなります。プロテクトモードにおけるカーネルはいわばカーネル本体なのであって、ここは圧縮された状態で読み込まれます。ソースのパスとしては「arch/x86/boot/compressed」以下のソースに当たります。ここのリンカスクリプトを見てみると(ここでは32bitマシンを想定して「vmlinux_32.lds」を見てみます)、エントリポイントとして「startup_32」を指定していることが分かります。
それではこのstartup_32を見てみましょう。

arch/x86/boot/compressed/head_32.S
=============
startup_32:
cld /* 割り込み禁止 */
/* KEEP_SEGMENTSフラグが立っていたらセグメントを書き換えない */
testb $(1<<6), BP_loadflags(%esi)
jnz 1f

cli /* 割り込み許可 */

/* __BOOT_DSでセグメントレジスタを更新する */
movl $(__BOOT_DS),%eax
movl %eax,%ds
movl %eax,%es
movl %eax,%fs
movl %eax,%gs
movl %eax,%ss
1:

/* スタックポインタを再計算。よく分かりません */
leal (0x1e4+4)(%esi), %esp
call 1f

1: popl %ebp
subl $1b, %ebp

/* EBPにはロードされた場所が入っている。EBXには圧縮したカーネルイメージを
 安全に解凍すべきアドレスを入れてやる。*
#ifdef CONFIG_RELOCATABLE
movl %ebp, %ebx
addl $(CONFIG_PHYSICAL_ALIGN - 1), %ebx
andl $(~(CONFIG_PHYSICAL_ALIGN - 1)), %ebx
#else
movl $LOAD_PHYSICAL_ADDR, %ebx
#endif

/* 圧縮データサイズと解凍したデータサイズを入れ替える */
/* あとはアライメントやら何やらの設定。よく分かりません */
subl input_len(%ebp), %ebx
movl output_len(%ebp), %eax
addl %eax, %ebx
/* Add 8 bytes for every 32K input block */
shrl $12, %eax
addl %eax, %ebx
/* Add 32K + 18 bytes of extra slack */
addl $(32768 + 18), %ebx
/* Align on a 4K boundary */
addl $4095, %ebx
andl $~4095, %ebx

/* 解凍したカーネルをコピーする */
pushl %esi
leal _end(%ebp), %esi
leal _end(%ebx), %edi
movl $(_end - startup_32), %ecx
std
rep
movsb
cld
popl %esi

/* カーネルのスタートアドレスを算出してEBPへ格納 */
#ifdef CONFIG_RELOCATABLE
addl $(CONFIG_PHYSICAL_ALIGN - 1), %ebp
andl $(~(CONFIG_PHYSICAL_ALIGN - 1)), %ebp
#else
movl $LOAD_PHYSICAL_ADDR, %ebp
#endif

/*
* カーネルを再配置する
*/
leal relocated(%ebx), %eax
jmp *%eax

=============

ブートしていた頃のアセンブラよりも複雑でなかなか読み進められません。思いっきり要約してしまうと、セグメントレジスタを初期化して、圧縮されているカーネルの展開先アドレスを
決めて、最終的にジャンプしています。最後のジャンプ先は「leal relocated(%ebx), %ebx」となっていて、これは「EBX=relocated + EBX」ということ。そしてその「relocated」というのはすぐ下にあります。
こちらは次回。

2009年7月23日木曜日

protected_mode_jump

go_to_protected_mode関数で最後に呼んでいるのは「protected_mode_jump」です。
こちらはアセンブラになります。

arch/x86/boot/pmjump.S
=====
protected_mode_jump:
movl %edx, %esi # ブートパラメータ構造体のアドレスをESIにセット

xorl %ebx, %ebx #EBXをクリア
movw %cs, %bx  #CSの値をBXへ
shll $4, %ebx   #EBXの値を4ビット左シフト
addl %ebx, 2f   #EBXの値にラベル「2」のアドレスを加算
jmp 1f # ラベル「1」へジャンプ
1:

movw $__BOOT_DS, %cx #CXに__BOOT_DSの値(24)をセット
movw $__BOOT_TSS, %di #DIに__BOOT_TSSの値(32)をセット

movl %cr0, %edx #CR0の値をEDXにセット
orb $X86_CR0_PE, %dl # OR演算でプロテクトモードフラグを立てる
movl %edx, %cr0   #更新したEDXの値をCR0にセットする

# Transition to 32-bit mode
.byte 0x66, 0xea # キャッシュをクリアするためジャンプして次へ

#ラベル「2」は値の定義です
2: .long in_pm32 # offset
.word __BOOT_CS # segment

.size protected_mode_jump, .-protected_mode_jump

.code32
.type in_pm32, @function

in_pm32:
# セグメントレジスタをECXの値で初期化
movl %ecx, %ds
movl %ecx, %es
movl %ecx, %fs
movl %ecx, %gs
movl %ecx, %ss
# スタックはEDXで初期化
addl %ebx, %esp

# TRレジスタDIの値をセット
ltr %di

# 汎用レジスタをクリア
xorl %ecx, %ecx
xorl %edx, %edx
xorl %ebx, %ebx
xorl %ebp, %ebp
xorl %edi, %edi

# 割り込みディスクリプタテーブル(ここではダミー)をロード
lldt %cx

 #プロテクトモードでのエントリポイントへジャンプ
jmpl *%eax # Jump to the 32-bit entrypoint

.size in_pm32, .-in_pm32
=====
最後にジャンプしているエントリポイントは「protected_mode_jump」関数の第1パラメータで指定しています。
呼び出しもとの「go_to_protected_mode」を見てみると

protected_mode_jump(boot_params.hdr.code32_start,
    (u32)&boot_params + (ds() << 4));
}


となっています。
ブートパラメータのcode32_startメンバはheader.Sで定義した値を読み込んだものでs。

======
code32_start: # here loaders can put a different
# start address for 32-bit code.
#ifndef __BIG_KERNEL__
.long 0x1000 # 0x1000 = default for zImage
#else
.long 0x100000 # 0x100000 = default for big kernel
#endif

======
となっていて、通常のbzImageであれば0x100000へジャンプしています。
で、それはどこなのだ?というのは次回。

2009年7月19日日曜日

setup_idt/setup_gdt

go_to_protected_mode関数のつづきを見ていきましょう。
まずは割り込みハンドラを登録するためのInterrupt Description Tableをセットするsetup_idtです。

arch/x86/boot/pm.c
=====

static void setup_idt(void)
{
static const struct gdt_ptr null_idt = {0, 0};
asm volatile("lidtl %0" : : "m" (null_idt));
}

=====
ここでは何も定義していないダミーのIDTを用意して、lidtl命令でロードしています。
ちゃんとしたものを後で登録するのだと思います。
次はセグメントを登録するためのGlobal Description Tableをセットするsetup_gdtです。

=====

static void setup_gdt(void)
{
static const u64 boot_gdt[] __attribute__((aligned(16))) = {
/* CS: code, read/execute, 4 GB, base 0 */
[GDT_ENTRY_BOOT_CS] = GDT_ENTRY(0xc09b, 0, 0xfffff),
/* DS: data, read/write, 4 GB, base 0 */
[GDT_ENTRY_BOOT_DS] = GDT_ENTRY(0xc093, 0, 0xfffff),
/* TSS: 32-bit tss, 104 bytes, base 4096 */
/* We only have a TSS here to keep Intel VT happy;
we don't actually use it for anything. */
[GDT_ENTRY_BOOT_TSS] = GDT_ENTRY(0x0089, 4096, 103),
};
/* Xen HVM incorrectly stores a pointer to the gdt_ptr, instead
of the gdt_ptr contents. Thus, make it static so it will
stay in memory, at least long enough that we switch to the
proper kernel GDT. */
static struct gdt_ptr gdt;

gdt.len = sizeof(boot_gdt)-1;
gdt.ptr = (u32)&boot_gdt + (ds() << 4);

asm volatile("lgdtl %0" : : "m" (gdt));
}

=====
最初にGDTを定義して最後にlgdtl命令でGDTをロードしています。
ここで設定しているGDTは以下のような設定になっています。

CS:コードセグメント、読み込み/実行用、0開始で4GB
DS:データセグメント、読み込み/書き込み、0開始で4GB
TSS:4096開始で104バイト。

TSSはタスクスイッチの際に実行中のタスク情報を退避させておく領域です。
ここで設定しているGDTもおそらくIDTと同じように暫定的な設定だと思います。

2009年7月18日土曜日

reset_coprocessor/mask_all_interrupts

前回見たenable_a20関数で無事A20ゲートを有効に出来たらgo_to_protected_mode関数では次にコプロセッサをリセットするために「reset_coprocessor」関数をコールします。

arch/x86/boot/pm.c
======
static void reset_coprocessor(void)
{
outb(0, 0xf0);
io_delay();
outb(0, 0xf1);
io_delay();
}
======
outbを使ってコマンドを出力しています。
これできっとリセットしているんでしょう。。

次に呼んでいるのは「mask_all_interrupts」です。

=====
static void mask_all_interrupts(void)
{
outb(0xff, 0xa1); /* Mask all interrupts on the secondary PIC */
io_delay();
outb(0xfb, 0x21); /* Mask all but cascade on the primary PIC */
io_delay();
}
=====

似たようなコードですが、プライマリPIC、セカンダリPICそれぞれの割り込みに対してマスクをかけて割り込みが通知されないようにしています。


2009年7月17日金曜日

enable_a20

go_to_protected_mode関数にて「realmode_switch_hook」の次に呼んでいるのは「move_kernel_around」ですがzImage用の処理なのでここではパス。その次はプロテクトモードへ移行する本体ともいえる「enable_a20」関数になります。

arch/x86/boot/a20.c
======
int enable_a20(void)
{
/*~省略~*/

int loops = A20_ENABLE_LOOPS;
while (loops--) {
/* 既にA20が有効になっていないかチェックする */
if (a20_test_short())
return 0;

/* BIOSを使って有効にしてみる */
enable_a20_bios();
/* なったかな? */
if (a20_test_short())
return 0;

/* キーボードコントローラを読み取ってチェックしてみる */
empty_8042();
/* なったかな?*/
if (a20_test_short())
return 0; /* BIOS worked, but with delayed reaction */

/* キーボードコントローラから有効にしてみる */
enable_a20_kbc();
/* なったかな?(長めのチェック) */
if (a20_test_long())
return 0;

/* ポート92から自力で有効にしてみる */
enable_a20_fast();
/* なったかな?(長めのチェック) */
if (a20_test_long())
return 0;
}

return -1;
#endif
}

======

このように色な手段を使ってA20ゲートを有効にしています。ちなみに、なぜこんなところでキーボードコントローラが出てくるのかというとA20ゲートがキーボードコントローラとつながっているからです。

2009年7月13日月曜日

go_to_protected_mode

main関数最後の「go_to_protected_mode」関数を見ていきましょう。

arch/x86/boot/pm.c
======
void go_to_protected_mode(void)
{
/* プロテクトモードに移行する前にフック関数を呼んでおく */
realmode_switch_hook();

/* セグメントを設定 */
move_kernel_around();

/* A20ゲートを有効にする */
if (enable_a20()) {
puts("A20 gate not responding, unable to boot...\n");
die();
}

/* コプロセッサをリセット */
reset_coprocessor();

/* PICの割り込み設定をすべてマスクする */
mask_all_interrupts();

/* プロテクトモードへの移行*/
setup_idt();
setup_gdt();
protected_mode_jump(boot_params.hdr.code32_start,
    (u32)&boot_params + (ds() << 4));
}

======

今回からはこの中の関数を順に見ていきます。
まずプロテクトモード移行前にフック関数をコールしておく「realmode_switch_hook」です。
======
static void realmode_switch_hook(void)
{
/* ブートパラメータのヘッダ情報にフック指定あれば、それを呼び出す */
if (boot_params.hdr.realmode_swtch) {
asm volatile("lcallw *%0"
     : : "m" (boot_params.hdr.realmode_swtch)
     : "eax", "ebx", "ecx", "edx");
} else {
/* フック指定なければ割り込みを禁止する */
asm volatile("cli");
outb(0x80, 0x70); /* Disable NMI */
io_delay();
}
}

======
ちなみにブートパラメータのヘッダ情報(header.S)のrealmode_swtchの項は
realmode_swtch: .word 0, 0 # default_switch, SETUPSEG
と何も指定していないので、変更しなければelse句に入って割り込み禁止します。

2009年7月7日火曜日

main関数おさらい

長いこと見てきたmain関数ですが、ついに最後の関数「go_to_protected_mode」を残すのみとなりました。
go_to_protected_modeでプロテクトモードに移行します。
その前に、ここで一度main関数をまとめておきましょう。

arch/x86/boot/main.c
======
void main(void)
{
/* ブートパラメータのヘッダ情報を取得する */
copy_boot_params();

/* ヒープの末尾位置をセット */
init_heap();

/* CPU情報を取得してチェック */
if (validate_cpu()) {
~省略~
}
~省略~

/* メモリマップとメモリサイズを取得 */
detect_memory();

/* キーボードのリピートレートをMAXに設定 */
keyboard_set_repeat();

/* ROMテーブルを取得 */
query_mca();

~省略~

/* IST(Intel Speed step Technology)情報を取得 */
query_ist();

/* APM(Advanced Power Management)情報を取得 */
query_apm_bios();

/* EDD(Enhanced Disk Drive)情報を取得 */
query_edd();

/* ビデオカード情報、ディスプレイ情報を取得 */
set_video();

~省略~

/* プロテクトモードへ移行する */
go_to_protected_mode();
}

======

と、ハード関係の情報をブートパラメータ構造体に格納してからプロテクトモードに移行しています。