x86続き。
ブートローダまででは保護モードに移行しても1Mの範囲内に納まっていたので
BIOSのコールは単にリアルモードに変更するだけで可能だった。1M以上にロー
ドしたカーネルからでもBIOSを使いたい。
なので1M以下の部分にBIOSを呼ぶルーチンを置いておき、それを呼ぶことにした。
ROM領域(0xa0000-0xfffff)ではないところで、0x80000からの64KBにした。
そこに置きたいオブジェクトファイルをldscriptに入れて配置した。
OUTPUT_FORMAT ("elf32-i386")
MEMORY
{
lma16 : o = 0x80000, l = 0x10000
vma16 : o = 0, l = 0x10000
vma32 : o = 0x100000, l = 0x100000
}
SECTIONS
{
_stack_start = 0x7fff0;
.realmode :
{
mode16_longcall_subr.o
bios_console_vga.o
bios_console_serial.o
bios_int13_rw.o
bios_int13_rw_ext.o
bios_service.o
} > vma16 AT > lma16
.text :
{
*(.text)
*(.rodata*)
. = ALIGN (4);
} > vma32
.data :
{
data_start = .;
*(.data)
. = ALIGN (4);
data_end = .;
} > vma32
.bss :
{
bss_start = .;
*(.bss)
} > vma32
bss_end = .;
}
こうすると
ELF entry point 0x1004c0
# of program header: 2
[0] vaddr=0x0 memsz=0x1ec paddr=0x80000 offset=0x1000 filesz=0x1ec
[1] vaddr=0x100000 memsz=0x6a10 paddr=0x100000 offset=0x2000 filesz=0x57c4
のように配置される。重複はしなかった。最初のプログラムヘッダーの部分は
このようになる。
kernel.elf: file format elf32-i386
Disassembly of section .realmode:
00000000 <__lcall16_subr>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 56 push %esi
4: 57 push %edi
5: 53 push %ebx
6: 66 bb 30 00 mov $0x30,%bx
a: 8e db mov %ebx,%ds
なので、これを呼ぶにはセグメントを切り替える必要がある。
この領域を保護モードから呼ぶためのセグメント(CODE32_BIOS, DATA32_BIOS)
と、リアルモードのリミットを設定するだけのセグメント(CODE16, DATA16)を用意。
(この場合、Dビットの違いしかないので、CODE32_BIOSを設定すればそれだけで済む
ので蛇足)
struct gdt_config gdt_config [] = {
// 4G flat.
{ 0x0, 0x100000, SIZE_UNIT_4KB, CODE_32, CODE_SEGMENT, "CODE32" },//0x08
{ 0x0, 0x100000, SIZE_UNIT_4KB, CODE_32, DATA_SEGMENT, "DATA32" },//0x10
// For Real-mode.
{ 0x80000, 0x10000, SIZE_UNIT_1B, CODE_32, CODE_SEGMENT, "CODE32_BIOS" },//0x18
{ 0x80000, 0x10000, SIZE_UNIT_1B, CODE_32, DATA_SEGMENT, "DATA32_BIOS" },//0x20
{ 0x80000, 0x10000, SIZE_UNIT_1B, CODE_16, CODE_SEGMENT, "CODE16" },//0x28
{ 0x80000, 0x10000, SIZE_UNIT_1B, CODE_16, DATA_SEGMENT, "DATA16" },//0x30
};
----------------------------------------------------------------------
BIOSを呼ぶ一番最初は512Kからのセグメントにジャンプするところから。
----------------------------------------------------------------------
// int __lcall16 (int (*)(int, int), int, int)__attribute__((regparm (3)))
// call real-mode subroutine from protect-mode with segment switch.
FUNC (__lcall16)
ENTER_32
// suspend interrupt.
ここでcliを使わないのは、呼ばれた時の割り込み状況をそのまま保持してリ
ターンしたいため。callee savedの%esiにとっておく。
pushf
movl (%esp), %esi
andl $~0x200,(%esp)
popf
BIOSが割り込みを使うので、IDTをリアルモード用に戻しておく。0x0-0x3ff。
// Install Descriptor for real-mode.
// Real-mode IDT
lidt realmode_lidt_arg
現在のスタックをcallee savedの%ediにとっておく。
movl %esp, %edi
データ/スタック領域のセグメントを0x80000からの64KBのセグメントに変更。
movw $GDT_DATA32_BIOS, %bx
movw %bx, %ss
movw %bx, %ds
movw %bx, %es
movw %bx, %fs
movw %bx, %gs
// Change stack.
movl $0xfff0, %esp
コードセグメントの変更と同時にサブルーチンコール。
lcall $GDT_CODE32_BIOS, $__lcall16_subr
元のコードセグメントに戻すために即座にロングジャンプ。
ljmpのフェッチまではベースが0x80000のセグメント。
// Return to current segment.
ljmp $GDT_CODE32, $1f
ここからはベースが0x0のセグメント。
1: movw $GDT_DATA32, %bx
movw %bx, %ss
movw %bx, %ds
movw %bx, %es
movw %bx, %fs
movw %bx, %gs
movl %edi, %esp
このIDTはプログラムで設定した保護モード用もの。
// Restore IDT
lidt protectmode_lidt_arg
// Resume interrupt
pushf
orl %esi, (%esp)
popf
LEAVE_32
ret
.section .data
.balign 4
realmode_lidt_arg:
.short 0x3ff
.long 0x0
----------------------------------------------------------------------
0x80000にあるBIOSを呼び出すところ。
----------------------------------------------------------------------
.code32
FUNC (__lcall16_subr)
ENTER_32
リアルモード用のリミットの設定。
// GDT set segment limit for real-mode.
movw $GDT_DATA16, %bx
movw %bx, %ds
movw %bx, %es
movw %bx, %ss
ljmp $GDT_CODE16, $1f // Load CS descriptor for real-mode.
1: .code16
ここからDビットが落ちているのでアドレス解釈が16bitモードになる。CPUは
32bit モード。 Dビットはpush,popのサイズの違い、IPかEIPか(ここではもう
64KB以内になるようになっているのでどちらでも同じになる)、レジスタアクセ
スの幅が変わる。CR0の1bit目を変更するだけなので、どちらでも同じになる。
これをやるのは、コードセグメントのリミット値の設定の目的だけ。
// Change to real-mode.
movl %cr0, %ebx
andl $~1, %ebx
movl %ebx, %cr0
ここではじめてリアルモード。
ljmp $REALMODE_CS, $2f // Load real-mode CS with pipeline flush.
2:
// Set real-mode DS,ES,SS
REALMODE_CSは0x8000。スタックはリアルモードと保護モードで共用できる設定
にしてあるのでセグメントの変更だけでいい。
movw $REALMODE_CS, %bx
mov %bx, %ds
mov %bx, %es
mov %bx, %ss
// Now Real-mode.
// Setup argument.
mov %ax, %bx
mov %dx, %ax
mov %cx, %dx
// Call requested function.
call *%bx
// Return to Protect-mode.
movl %cr0, %ebx
orl $0x1, %ebx
movl %ebx, %cr0
まずここで、0x80000がベースのセグメントに戻る。
ljmp $GDT_CODE32_BIOS, $4f // Load CS with pipeline flush.
4: .code32
// Load SS
movw $GDT_DATA32_BIOS,%bx
movw %bx, %ss
LEAVE_32
lret
これでBIOSコールが使えるようになった。(ここまではVGA直書きでデバッグ)
LBA 136 -> track 3, head 1, sector 11
bios_fdd_read: drive 0 sector 136 (T3/H1/S11)
LBA 137 -> track 3, head 1, sector 12
bios_fdd_read: drive 0 sector 137 (T3/H1/S12)
ELF entry point 0x1004c0
# of program header: 2
[0] vaddr=0x0 memsz=0x1ec paddr=0x80000 offset=0x1000 filesz=0x1ec
[1] vaddr=0x100000 memsz=0x6a10 paddr=0x100000 offset=0x2000 filesz=0x57c4
stack_start: 0x7fff0
data: 0x104ee0-0x1057c4 2276byte
bss: 0x1057e0-0x106a10 4656byte
EFLAGS=0x6
IDT: base=0x0 limit=0x3ff
IDT: base=0x105d60 limit=0x7ff
board_main arg=330
app0_thread: arg=abcdef90
IRQ0
IRQ0
IRQ0
IRQ0
IRQ0
