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<>?_ '