OS(9) キーボード割り込みの処理追加
ひとまず完成
作りながら学ぶOSカーネル―保護モードプログラミングの基本と実践 では単にキー入力があっただけしか分からないのが面白くなかったので
- 入力した文字を画面に表示する
- Enterキーに対応して改行する
- 画面が一杯になったら、スクロール
などと欲張ってみる。
このおかげで、結構はまる ^^;
キーボード割り込み
キーボード割り込みは入力された文字のコードではなく、スキャンコード、早い話キーの番号がコントローラから送られてくる。手元の資料(『PC DOS J6.1V BIOSインターフェース技術解説書 』)に、このコードと文字の対応表が載っているのだけれど、資料自体が古い(PC-DOSですからねぇ)のと、そもそも資料に載っていないキーもあるので、結局割り込みで得られたコードを画面に表示して確認。
普段はVirtualPCで実行してるのだけれど、一部のキーはVirtualPCそのものが取っているためか、動きが微妙におかしい。この点VMwareでは普通に取れる模様。
VMwareの仮想ディスク作成
無料で配布されているVMware Playerには仮想ディスク作成機能がないので、vmxEditorを使って環境構築。vmxEditor、改めて検索するとリンクが切れている。以前別のPCでダウンロードしたものを持ってきて何とか完了。
FDイメージのサイズの制限もないし、開放しなくてもファイルを買い替え可能だし、こっちが良いかな。でも、キー入力のウィンドウ切り替えが面倒なのが難点。
subversion
コードサイズもそこそこ増えてきたので、試行錯誤でソースを書き換えても復旧できるよう、subversionでの管理を開始。
defs.inc
; ; defs.inc ; PROG_START equ 0x10200 ; プログラム開始アドレス CSEG equ 0x10 ; コードセグメント DSEG equ 0x18 ; データセグメント VRAM_SEG equ 0x20 ; VRAMセグメント
setup.asm
; ; setup.asm プロテクトモードカーネルの起動準備 ; %include "defs.inc" ;------------------------------------------------------------------------ ; 1000:0 で開始, ss:sp はIPL設定のまま [bits 16] [org 0] ;------------------------------------------------------------------------ start: mov ax, cs mov ds, ax cli ; 割り込み禁止 call init_PIC ; PICの初期化 lgdt [GDTR] ; GDTの設定 mov eax, cr0 ; プロテクモードへ移行 or eax, 1 mov cr0, eax jmp dword CSEG:PROTECT_start + 0x10000 ;------------------------------------------------------------------------ ; PICの初期化 ;------------------------------------------------------------------------ init_PIC: ; ICW1 mov al, 0x11 out 0x20, al out 0xa0, al ; ICW2 mov al, 0x20 ; int 0x20-0x27を使用する out 0x21, al mov al, 0x28 ; int 0x28-0x2fを使用する out 0xa2, al ; ICW3 mov al, 0x04 ; IRQ2へスレーブPIC接続 out 0x21, al mov al, 0x02 out 0xa1, al ; ICW4 mov al, 0x01 ; 8086 モード out 0x21, al out 0xa1, al ; IMR mov al, 0xff ; 全割り込み禁止 out 0x21, al out 0xa1, al ret ;------------------------------------------------------------------------ GDTR: dw initial_GDT_end - initial_GDT - 1 dd initial_GDT + 0x10000 ; アドレス補正 ;------------------------------------------------------------------------ initial_GDT: ; NULLセグメント+ダミー dw 0, 0, 0, 0 ; NULLセグメント dw 0, 0, 0, 0 ; ダミー ; CSEG 0x10 - コードセグメント dw 0xffff ; limit(0-15) dw 0 ; base(0-15) db 0, 0x9a ; base(16-23), code db 0xcf, 0 ; G,32bit,limit(16-19) ; DSEG 0x18 - データセグメント dw 0xffff dw 0 db 0, 0x92 ; data db 0xcf, 0 ; VRAM_SEG 0x20 - VRAMセグメント dw 0xffff dw 0x8000 db 0x0b, 0x92 db 0x40, 0 initial_GDT_end: ;------------------------------------------------------------------------ times 512 -( $ - $$) db 0 PROTECT_start:
kernel.asm
; ; kernel.asm ; %include "defs.inc" [bits 32] [org PROG_START] ;------------------------------------------------------------------------ ; プログラム開始 ;------------------------------------------------------------------------ PROETCT_start: mov ax, DSEG mov ds, ax mov es, ax mov ss, ax mov esp, 0xa0000 ; メモリホール直前からスタック xor ax, ax ; fs, gs は使用しない mov fs, ax mov gs, ax mov esi, msg ; 開始メッセージ表示 mov ah, 0x1f call puts mov edi, initial_IDT ; タイマ割り込みのISR登録 add edi, 0x20 * 8 mov eax, ISR_20_timer mov [edi], ax shr eax, 16 mov [edi + 6], ax mov edi, initial_IDT ; KBD割り込みのISR登録 add edi, 0x21 * 8 mov eax, ISR_21_kbd mov [edi], ax shr eax, 16 mov [edi + 6], ax lidt [IDTR] ; IDTを設定 in al, 0x21 ; IMR and al, 0xfc ; タイマ、KBDを許可 out 0x21, al sti int 0x40 ; ソフトウェア割り込みテスト int 0x80 int 0xff ;;mov al, 0 ; ゼロ除算フォルト ;;div al ; これを行うと動きが取れなくなる jmp $ msg: db 'Hello, world', 0 ;------------------------------------------------------------------------ ; 1文字表示 al: 文字コード, ah:色指定 ; 1)0x0d(CR)を受け取ると、改行する ; 1)画面最下行で改行するとスクロールアップする ;------------------------------------------------------------------------ putc: push es push edi push eax mov ax, VRAM_SEG mov es, ax pop eax cmp al, 0x0d ; 改行文字なら jz .@do_CR ; 改行処理へ push eax push edx xor eax, eax ; 書き込み位置の計算 mov ax, 80 mul word [.@ypos] add ax, [.@xpos] shl eax, 1 mov edi, eax ; edi へセット pop edx pop eax mov [es:di], ax ; 書き込み inc word [.@xpos] cmp word [.@xpos], 80 ; 右端? jb .@ret .@do_CR: mov word [.@xpos], 0 inc word [.@ypos] cmp word [.@ypos], 25 ; 最下行? jb .@ret push ds ; 1行スクロールする mov ax, VRAM_SEG mov ds, ax push esi push ecx xor edi, edi mov esi, 80 * 2 mov ecx, 80 * 24 * 2 / 4 rep movsd mov ecx, 80 ; 最下行はデフォルトで埋める mov eax, 0x1f201f20 rep stosd pop ecx pop esi pop ds dec word [.@ypos] ; 24へ戻しておく .@ret: pop edi pop es ret .@xpos: dw 0 ; 次に文字を書き込む x座標 .@ypos: dw 0 ; 次に文字を書き込む y座標 ;------------------------------------------------------------------------ ; 文字列表示 ds:esi 文字列(ASCIZ) ; 1)文字列表示後、改行 ;------------------------------------------------------------------------ puts: lodsb or al, al ; 終端か? jz .@done call putc jmp short puts .@done: mov al, 0x0d call putc .@ret: ret ;------------------------------------------------------------------------ IDTR: dw initial_IDT_end - initial_IDT - 1 dd initial_IDT ;------------------------------------------------------------------------ ; 割り込み番号の表示 ax 割り込み番号 ; 1) puts("INT xx") を実行 ;------------------------------------------------------------------------ show_number: pusha push es push ds cld mov edx, eax mov ax, DSEG mov ds, ax mov es, ax mov eax, edx shr al, 4 and al, 0xf add al, '0' cmp al, '9' jbe .@next add al, 'A' - ('9' + 1) .@next: mov [msg_int_num], al mov eax, edx and al, 0xf add al, '0' cmp al, '9' jbe .@next2 add al, 'A' - ('9' + 1) .@next2: mov [msg_int_num + 1], al mov esi, msg_int mov ah, 0x1c call puts pop ds pop es popa ret msg_int: db 'INT ' msg_int_num: db ' ', 0 ;------------------------------------------------------------------------ ; ISR作成マクロ定義 ;------------------------------------------------------------------------ %macro make_ISR 1 ISR_%1: push eax mov eax, %1 call show_number pop eax iret %endmacro ;------------------------------------------------------------------------ ; ISRの作成 ;------------------------------------------------------------------------ ; ISR作成 %assign n 0 %rep 256 make_ISR n %assign n n + 1 %endrep ;------------------------------------------------------------------------ ; IDT作成マクロ ;------------------------------------------------------------------------ %macro make_IDT 1 %assign ofs (ISR_%1 - $$ + PROG_START) dw ofs & 0xffff dw CSEG db 0x0, 0x8e dw (ofs >> 16) & 0xffff %endmacro ;------------------------------------------------------------------------ ; IDT作成の作成 ;------------------------------------------------------------------------ initial_IDT: %assign n 0 %rep 256 make_IDT n %assign n n + 1 %endrep initial_IDT_end: ;------------------------------------------------------------------------ ; INT 00ハンドラ(0除算フォルト) ;------------------------------------------------------------------------ ISR_0_zero_divide: pusha push ds push es mov al, 0x20 out 0x20, al ; EOI mov esi,.@msg_zero_div mov ah, 0x15 call puts pop es pop ds popa iret .@msg_zero_div: db 'Divide by Zero', 0 ;------------------------------------------------------------------------ ; INT 20ハンドラ(タイマ割り込み) ;------------------------------------------------------------------------ ISR_20_timer: pusha push ds push es mov al, 0x20 ; EOI out 0x20, al inc byte [.@count_timer] cmp byte [.@count_timer], 30 ; 20回実行? jb .@next mov byte [.@count_timer], 0 mov esi, .@msg_timer ; 20回毎実行でメッセージ表示 mov ah, 0x13 call puts .@next: pop es pop ds popa iret .@count_timer: db 0 .@msg_timer: db 'Timer Interrupt', 0 ;------------------------------------------------------------------------ ; INT 21ハンドラ(KBD割り込み) ;------------------------------------------------------------------------ ISR_21_kbd: pusha push ds push es mov ax, DSEG mov ds, ax mov es, ax mov al, 0x20 ; EOI out 0x20, al in al, 0x60 ;; call show_number ;; jmp .@ret ; SHIFTキーのチェック cmp al, 0x2a ; 左SHIFTキー MAKE jz .@shift_on cmp al, 0x36 ; 右SHIFTキー MAKE jz .@shift_on cmp al, 0x2a + 0x80 ; 左 SHIFTキー BREAK jz .@shift_off cmp al, 0x36 + 0x80 ; 右SHIFTキー BREAK jnz .@other_key .@shift_off: mov byte [.@shift], 0 jmp short .@ret .@shift_on: mov byte [.@shift], 1 jmp short .@ret .@other_key: test al, 0x80 jnz .@ret ; 離された割り込みは無視する cld ; キーコードを検索 mov edi, .@key_code mov ecx, key_code_len and al, 0x7f repnz scasb jnz .@ret ; 見つからなければ終了 dec edi sub edi, .@key_code ; コードに対応する文字コード cmp byte [.@shift], 0 jnz .@shift_state add edi, .@code ; シフトキー押されていない jmp .@show_key .@shift_state: add edi, .@code_shift ; シフトキーが押されている .@show_key: mov al, [es:edi] or al, al jz .@ret mov ah, 0x1f ; 画面表示 call putc .@ret: pop es pop ds popa iret .@shift: db 0 ; シフトキーの状態を記録 ; スキャンコード .@key_code: db 02h, 03h, 04h, 05h, 06h, 07h, 08h, 09h, 0ah, 0bh, 0ch, 0dh, 7dh db 10h, 11h, 12h, 13h, 14h, 15h, 16h, 17h, 18h, 19h, 1ah, 1bh, 1ch db 1eh, 1fh, 20h, 21h, 22h, 23h, 24h, 25h, 26h, 27h, 28h, 2bh db 2ch, 2dh, 2eh, 2fh, 30h, 31h, 32h, 33h, 34h, 35h, 73h, 39h key_code_len equ $ - .@key_code .@code: ; 文字コード db '1234567890-^\' db 'qwertyuiop@[',0dh db 'asdfghjkl;:]' db 'zxcvbnm,./\ ' .@code_shift: ; 文字コード(シフト) db '!"#$%&', 61h, '()',0,'=~|' db 'QWERTYUIOP`{',0dh db 'ASDFGHJKL+*}' db 'ZXCVBNM<>?_ '