Advent Calendar 2018: advos
Day 4: ブートローダ(割り込みハンドラ)
今日はリアルモードの割り込みハンドラを設定します。
MBRと同様の処理を追加
MBRに書いたディスク読み込みやエラーメッセージの表示など,共通する処理を書きます。冗長ですが,MBRはサイズ制約が厳しいため,今回は同じ処理ですが,ブートモニタの処理を将来拡張することも考えてコピペします。
割り込みハンドラの設定:キーボード入力
今回追加するのは割り込みハンドラを設定してキーボード入力を行う処理です。
リアルモードの割り込み処理はProgrammable Interrupt Controller (PIC)を使います。IntelアーキテクチャのチップセットではIntel 8259(A)が割り込みを扱うコントローラです。キーボードからの割り込みを扱うには,キーボードからの割り込みに対応した割り込みハンドラーをInterrupt Vector Table (IVT)に設定します。キーボード割り込みはInterrupt Request (IRQ)の1番にワイヤリングされているため,IRQ 1に対応するIVTに割り込みハンドラルーチンを割り当てます。以下のコードはintr_irq1
をIRQ 1に割り当てます。
#define IVT_IRQ0 0x08 /* IRQ0 = 0x08 (BIOS default) */
/* Setup programmable interrupt controller (PIC) */
xorw %ax,%ax
movw %ax,%es
movw $intr_irq1,%ax
movw $(IVT_IRQ0+1),%bx
call setup_intvec
リアルモードのIVTはアドレス0番から1ワードごとに割り当てられています。つまり,IRQ N番の割り込みハンドラはN * 4
に割り込みハンドラのアドレスとセグメントを設定します。
setup_intvec:
pushw %bx
shlw $2,%bx
movw %ax,(%bx)
addw $2,%bx
movw %es,(%bx)
popw %bx
ret
割り込みハンドラの中身は以下の通りです。キーボード入力はI/O命令で取得します。キーボードコントローラのマニュアルは8042 Keyboard Controller (From IBM Technical Reference Manual)を参照してください。
キーボードコントローラのデータおよびステータスコマンドはポート60hおよび64hに割り当てられています。なのでキーボードコントローラからの入力文字(データ)はinb $0x60,reg
で取得できます。データはキーボードのReleaseで最上位ビットがセットされています。このフラグとコードによりシフトキーが押下されているかを.data
セクションのkeyboard_shift
に保存にしています。その他のキーは押下に文字を出力しますが,キーコードとASCIIコードがマッピングされていないため,keymap_base
またはkeymap_shift
を参照して,キーコードをASCIIコードに変換して文字を出力します。
intr_irq1:
pushw %ax
pushw %bx
xorw %ax,%ax
inb $0x60,%al /* Scan code from the keyboard controller */
1:
movb %al,%bl /* Ignore the flag */
and $0x7f,%bl /* indicating released in %bl */
cmpb $KBD_LSHIFT,%bl /* Left shift */
je 4f /* Jump if left shift */
cmpb $KBD_RSHIFT,%bl /* Right shift */
je 4f /* Jump if right shift */
/* Otherwise */
testb $0x80,%al /* Released? */
jnz 6f /* Yes, then ignore the key */
cmpb $0,(keyboard_shift) /* Shift key is released? */
je 2f /* Yes, then use base keymap */
movw $keymap_shift,%bx /* Otherwise, use shifted keymap */
jmp 3f
2:
movw $keymap_base,%bx /* Use base keymap */
3:
addw %ax,%bx
movb (%bx),%al /* Get ascii code from the keyboard code */
call putc /* Print the character */
jmp 6f
4:
testb $0x80,%al /* Released? */
jnz 5f /* Yes, then clear shift key */
movb $1,(keyboard_shift) /* Set shift key */
jmp 6f
5:
movb $0,(keyboard_shift) /* Clear shift key */
6:
movb $0x20,%al /* Notify */
outb %al,$0x20 /* End-Of-Interrupt (EOI) */
popw %bx
popw %ax
iret
上記のコードを上から少しずつ見ていきます。
pushw %ax
pushw %bx
まずはこの割り込みハンドラ内で使うレジスタをスタックに退避します。割り込みハンドラは別の処理を実行中に呼ばれるため,割り込みハンドラ内で変更されるすべてのレジスタを保存・復元する必要があります。
xorw %ax,%ax
inb $0x60,%al /* Scan code from the keyboard controller */
キーボードコントローラから%al
レジスタにキーボードのスキャンコード(ASCIIコードとはk異なります)を読み込みます。スキャンコードとASCIIコードの対応はKeyboard-internal scancodesなどを参照してください。
movb %al,%bl /* Ignore the flag */
and $0x7f,%bl /* indicating released in %bl */
cmpb $KBD_LSHIFT,%bl /* Left shift */
je 4f /* Jump if left shift */
cmpb $KBD_RSHIFT,%bl /* Right shift */
je 4f /* Jump if right shift */
こちらのコードは,スキャンコードから最上位ビットを取り除いたものを%bl
レジスタに保存しています。%bl
がシフトキーであった場合はラベル4
に飛んでシフトキーの状態を保存します(後述)。
testb $0x80,%al /* Released? */
jnz 6f /* Yes, then ignore the key */
cmpb $0,(keyboard_shift) /* Shift key is released? */
je 2f /* Yes, then use base keymap */
movw $keymap_shift,%bx /* Otherwise, use shifted keymap */
jmp 3f
2:
movw $keymap_base,%bx /* Use base keymap */
その他のキーの場合はキーがリリースされた場合(最上位ビットが立っている場合)はラベル6
に飛びます。押されたとき(最上位ビットがクリアされている場合)は,シフトキーの状態を確認して,それに応じたキーマップのアドレスを読み込みます。キーマップは.data
セクションのkeymap_base
またはkeymap_shift
で定義しています。
3:
addw %ax,%bx
movb (%bx),%al /* Get ascii code from the keyboard code */
call putc /* Print the character */
jmp 6f
キーマップから読み込んだ文字を表示します。
4:
testb $0x80,%al /* Released? */
jnz 5f /* Yes, then clear shift key */
movb $1,(keyboard_shift) /* Set shift key */
jmp 6f
5:
movb $0,(keyboard_shift) /* Clear shift key */
こちらのコードはスキャンコードがシフトキーのときに呼び出されます。押下されたかリリースされたかを確認し,.data
セクションのkeyboard_shift
にシフトキーの状態を保存します。
6:
movb $0x20,%al /* Notify */
outb %al,$0x20 /* End-Of-Interrupt (EOI) */
popw %bx
popw %ax
iret
こちらの処理はスキャンコードがどのキーであっても最後に実行されます。
movb $0x20,%al /* Notify */
outb %al,$0x20 /* End-Of-Interrupt (EOI) */
の2行はI/Oポート20h
を通じてProgrammable Interrupt Controller (PIC)にIRQ処理の終了を通知します。
iret
最後のiret
命令はret
命令と同様に呼び出し元のアドレスをスタックから取り出し元の処理に戻る命令ですが,割り込みハンドラの呼び出し時にCSとEFLAGSもスタックに保存されるのでこれらも取り出します。
まとめと明日の予定
今日割り込みハンドラを設定してキーボード入力を扱いました。明日はタイマ周りを使ってブートモニタを実装していこうと思います。