panda's tech note

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もスタックに保存されるのでこれらも取り出します。

まとめと明日の予定

今日割り込みハンドラを設定してキーボード入力を扱いました。明日はタイマ周りを使ってブートモニタを実装していこうと思います。