linuxカーネル読書がしたい(ブート編3)
前回に引き続き読書を続けて行く.main.cの読書の続きから
main.c(リアルモードでのカーネルコードモジュール)
・0ページ目にブートヘッダをコピー
・early-bootコンソールの初期化(コンソールの準備)
・ヒープの初期化
・CPUの検証
↓このページはここから
・BIOSをどのCPUモードで使うかの設定
・メモリレイアウトの検出
・キーボードの初期化
・(もし定義されていれば)APM情報の照会
・(もし定義されていれば)EDD情報の照会
↑ここまで
・ビデオモードの設定
・プロテクトモードへの移行
set_bios_mode()
見てみるとコードそのものは短いようなので以下に載せる.
104 static void set_bios_mode(void) 105 { 106 #ifdef CONFIG_X86_64 107 struct biosregs ireg; 108 109 initregs(&ireg); 110 ireg.ax = 0xec00; 111 ireg.bx = 2; 112 intcall(0x15, &ireg, NULL); 113 #endif 114 }
x86_64が有効であれば,BIOSレジスタとした構造体を初期化する.中身は各種レジスタ群の構造体の共用体であった.initregs(struct biosregs* reg)はregs.cに記述されている.0クリアしたあとでEFLAGSをセットし,セグメントレジスタの中身のコピーをしている.
次の処理のintcallに来て,ここでax=0xec00, bx=2のint 0x15とは何かとなった.
2003年の7月に↑のパッチから,カーネルにこのint 0x15が新たに追加されたらしい.該当箇所の抜粋を以下に載せると,
- Tell BIOS we run in long mode (this is a nop currently, but will help with some future boxes)+# tell BIOS we want to go to long mode + movl $0xec00,%eax # declare target operating mode + movl $2,%ebx # long mode + int $0x15
コメントを見ると,BIOSに対してlong modeで実行したいことを伝えているようである.(要調査)
===== set_bios_mode ここまで =====
detect_memory()
122 int detect_memory(void) 123 { 124 int err = -1; 125 126 if (detect_memory_e820() > 0) 127 err = 0; 128 129 if (!detect_memory_e801()) 130 err = 0; 131 132 if (!detect_memory_88()) 133 err = 0; 134 135 return err; 136 }
eax=0xe820, 0xe801, 0x88の3通りを順次指定し,int 0x15を実行する.
メモリが見つかればerr = 0となり,なければerr = -1のままで返ることになる.各関数を見る前にstruct e820entryの定義を見てみる.e820.hより.
58 struct e820entry { 59 __u64 addr; /* start of memory segment */ 60 __u64 size; /* size of memory segment */ 61 __u32 type; /* type of memory segment */ 62 } __attribute__((packed));
detect_memory_e820()
システムメモリマップを取得する関数.
eaxを0xe820に指定してint 0x15を行う.指定したバッファに,メモリ開始アドレス,メモリサイズ,メモリタイプを格納するため,ちょうど↑の構造体と同じ形式に格納される.これを,取得可能な次のアドレスを返すebxが0(これ以上ない時)になるか,EFLAGSのCF=1(失敗)になるかのどちらかが発生するまで探索して情報を収集する.
ただしイレギュラーもあるようで,出力のeaxには"SMAP"(System memory MAP)のシグネチャが返されるはずが,SMAPを返さないこともあるようである.この時にはcountを0としてループから抜け出す.
最終的なcount数(収集できた数)をboot_params.e820_entriesに格納して返る.
格納されるメモリ情報の詳細は下記の参考に記載されている.勉強になります.
detect_memory_e801
ビッグメモリサイズ取得の関数.
eaxに0xe801を設定してint 0x15を行う.EFLAGSのCF=0で成功.
ax:1MB〜16MBまでのメモリ領域のメモリサイズ(1KB単位)
bx:16MB以上のメモリ領域のサイズ(64KB単位)
cx:1MB〜16MBまでのマザーボードのジャンパで切り替えた時のメモリ領域のサイズ(1KB単位)
dx:16MB以上のマザーボードのジャンパで切り替えた時のメモリ領域のサイズ(64KB単位)
で返ってくる.ax == cx, bx == dxだと思って問題なさそう.
ax > 15*1024の時は偽の値として失敗.
ax == 15*1024の時はboot_params.alt_mem_k = (oreg.bx << 6) + oreg.ax
ax < 15*1024の時はboot_params.alt_mem_k = oreg.ax
0を返す.
detect_memory_88
1MB以上の拡張メモリ領域のサイズを取得する関数.
eaxに0x88を設定してint 0x15を行う.EFLAGSのCF=0で成功.
出力のaxを以下のメンバに代入
boot_params.screen_info.ext_mem_k = oreg.ax
keyboard_init()
キーボードのリピートレートの設定.コメントで(why?)と書かれているのは面白い.
ここではint 0x16が呼ばれている.int 0x16はキーボードサービスに関するものである.AHに0x2を指定して実行するとshiftキーやaltキーなどが押されているかのキーボードステータスをALに入れる.例えば左shiftキーが押されていれば00000010bが返ってくる.これをboot_params.kbd_statusに代入.
次にaxに0x305を指定して実行している.これはキーボードのリピートレートのセットをしている....のだが,セットする値を設定するbh, blの値が設定されていない....調べて見ると,0に設定されていると最大値として設定するようである.
query_ist()
Intel SpeedStep(IST)情報の照会.ISTは電源の種類やCPUへの負荷状況に応じて動作性能を切り替える技術.
一定以上古いBIOSではISTをサポートしているかの問い合わせのint 0x15でクラッシュを起こすようなので,cpu.levelが6未満の場合は弾くようになっている.
axに0xe980,edxに0x47534943(リクエスト値."CISG"?)を指定してint 0x15を実行.実行したあとで各レジスタに入った値をboot_paramsの各該当メンバに代入.
95 boot_params.ist_info.signature = oreg.eax; 96 boot_params.ist_info.command = oreg.ebx; 97 boot_params.ist_info.event = oreg.ecx; 98 boot_params.ist_info.perf_level = oreg.edx;
query_apm_bios()
APM情報を照会する.APMはAdvanced Power Managementの略で,電源管理インターフェース.
はじめにAHに0x53を指定してint 0x15.EFLAGSのCF=0,bx != "PM",!(cx & 0x02)であればエラーとして返る.
次にalに0x04を指定してint 0x15を実行してAPMのインターフェースを切断する.
切断したらalに0x03を指定してint 0x15を実行して32bitでインターフェースに接続する.CF=1で失敗となる.出力される値の詳細は以下の通りである.
ax:プロテクトモードでの32bitコードセグメントの,リアルモードにおけるベースアドレス
ebx:エントリポイントからのオフセット
cx:axの16bitコードセグメント版
dx:axの16bitデータセグメント版
si:32bit APMコードセグメントの長さ
hsi:16bit APMコードセグメントの長さ
di:32bit APMコードセグメントの長さ
これらをboot_params.apm_bios_infoの各メンバに代入する.
その後alを0x00に指定してint 0x15を実行して32-bit接続でインストールチェックを行う.32bit接続で失敗した時には切断してそのまま返る.成功したらバージョン情報とフラグをboot_params.apm_bios_infoの各メンバに代入する.
query_edd()
EDD情報の照会.Enhanced Disk Deviceの略.
まずdo_mbrとdo_eddのフラグが2つある.デフォルトではdo_mbr=1,do_eddはEDDをOFFにする設定ができるなら0,出来ないなら1としている.次にフラグの設定をしている.
・edd=skipmbrもしくはedd=skip → do_edd=1,do_mbr=0
・edd=off → do_edd=0
・edd=on → do_edd=1
ここで初めてcmdline_find_option_boolが出てくるが,これは引数に指定されたオプションが存在するかを判定しているものである.今回はquietが指定されているので,"quiet"があればtrueとなる.
boot_paramsからeddバッファ構造体のポインタと,mbrのsigバッファのポインタを受け取る.
do_edd=0ならここで終了.
be_quiet=0なら"Probing EDD (edd=off to disable)... "を出力.
次のfor文ではBIOSがサポートされているハードディスクをスキャンして,EDD情報を照会する.
get_edd_info(u8 devno, struct edd_info *ei)
関数名からしてEDD情報を取ってくるものだろう.
ah=0x41, bx=EDDMAGIC1(0x55AA),dl=ドライブデバイス番号(0x80~0xff)を指定してint 0x13を実行して,EDDの拡張情報があるかのチェックを行う.CF=1あるいはbx != EDDMAGIC2(0xAA55)であれば失敗として-1を返す.
エラーがなければ,EDD情報の構造体にデバイス番号,EDDバージョン,インターフェースのサポート情報(functionality subsets)を格納する.インターフェースのサポート情報は以下のものがある.
0:拡張ディスクアクセス機能
1:リムーバブルドライブコントローラ機能
2:EDD機能
これが有効ならばそれに応じたint 0x13が使用できる.
次にah=0x48,si=&ei->paramsを指定してint 0x13を実行してドライブパラメータを入手する.成功すればah=0,指定したバッファが埋まる.
ついでah=0x08,es=0(BIOSのバグを防ぐために0が推奨されているようだ)を指定してint 0x13を実行してCHS(Cylinder, Head, Sector)情報を得る.返り値としては以下のものがある.
ch:シリンダ番号の最大の下位8bit
cl:最大のセクタ番号[5:0],シリンダ番号の最大の上位2bit[7:6]
dh:最大のヘッド番号
dl:ドライブ数
特にシリンダに関しては少し複雑な返り値であるため,以下の構造体メンバへの代入の際にぱっと見では分かりにくいことをしている.clレジスタに2つの値が混在するためにANDで切り分けている.
113 if (!(oreg.eflags & X86_EFLAGS_CF)) { 114 ei->legacy_max_cylinder = oreg.ch + ((oreg.cl & 0xc0) << 2); 115 ei->legacy_max_head = oreg.dh; 116 ei->legacy_sectors_per_track = oreg.cl & 0x3f; 117 }
===== get_edd_info ここまで =====
read_mbr_sig(u8 devno, struct edd_info *ei, u32 *mbrsig)
まずはセクタサイズを確認する.対象のEDD情報からei->params.bytes_per_sectorでセクタサイズを得る.0の時には512とする.
_end変数はsetup.ldに定義されている..bss領域の末尾を指しており,同時にヒープ領域の先頭を指している.
52 /* Produce a naturally aligned buffer on the heap */ 53 buf_base = (ds() << 4) + (u32)&_end; 54 mbr_base = (buf_base+sector_size-1) & ~(sector_size-1); 55 mbrbuf_ptr = _end + (mbr_base-buf_base); 56 mbrbuf_end = mbrbuf_ptr + sector_size;
buf_base = ヒープ先頭 + ds * 16(リニアアドレス)
mbr_baseはsector_size=512の時には下位8bitが0クリアされる
要するにヒープ領域の先頭でアライメントされているところからセクタサイズ分の領域を計算したことになっているのだろうか.
↑で計算した領域がヒープ領域内にあるのかのチェック.具体的にはヒープの使用の可否と,mbrbuf_endがヒープ領域を超えていないか.
チェックが済んだら↑で計算した領域を0クリア.
MBRを読みに行く.CF=0なら失敗.
read_mbr(u8 devno, void *buf)
MBR(最初のセクタ)を読みに行く.入力は以下の通り
ah:0x02
al:読むセクタ数(0にしないこと)
ch:シリンダ番号の下位8bit
cl:セクタ番号(1〜63で指定)[5:0],シリンダ番号の上位2bit(HDDのみ)[7:6]
dh:先頭の番号?
dl:ドライブ番号(ハードディスクの為に7bit目はセット)
es:bx:データバッファのポインタ
出力としてCF=0なら成功.
今回,al=0x01,cx=0x0001であるから,シリンダ0の第1セクタから1つ分だけ(つまりMBRのこと)を読みに行くようにしている.
===== read_mbr ここまで =====
read_mbrからmbrbuf_ptrから1セクタ読めなかったらここで-1で返る.
mbrbuf_ptrからEDD_MBR_SIG_OFFSETバイト目から2バイトのシグネチャを取得.
同様にmbrbuf_ptrから510バイト目から2バイトのマジックを取得.
→ 0xAA55であれば0,それ以外は-1を返す.
===== read_mbr_sig ここまで =====
read_mbr_sigから-1が返ったら,boot_params.edd_mbr_sig_buf_entries = devno-0x80+1
===== query_edd ここまで =====
参考
http://dev.ariel-networks.com/Members/ohyama/30fc30c830ed30fc30fc-305d306e4/
http://www.yoshinobrain.com/200008-bootup.pdf