30日OS自作入門8日目(Win10)

初めに

最近基礎が少しついたのか、ソースコードから大体の処理内容が分かるようになってきたんですよ!自作OS本様様です。なので、これからは記事の記述の大部分をコメントとして、ソースコードに乗せることで、スピードを上げていきます。

目次

マウス制御ト32ビットモードの切り替え

マウスの解読(1)(harib05a)

コードの修正は、以下のように「最初のデータである0xfaを読み捨てる処理」・「3バイトずつ送られてくるデータを1、2、3バイト目のデータをそれぞれ1バイトの配列で受け取る処理」にif文で切り替えをしているコードです。

    enable_mouse();  
    /* マウス有効化処理 */
    mouse_phase = 0;
     /* マウスから0xfaが来た状態を記録 */

    for (;;) {
        io_cli(); 
        /* 外部割り込み禁止(割り込み処理中の割り込み対策) */
        if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) == 0) { 
        /* どちらからもデータが来てないことの確認 */
            io_stihlt();
             /* 外部割り込みの許可と、CPU停止命令(割り込みの終了) */
        } else {
            if (fifo8_status(&keyfifo) != 0) {      
            /* もしキーボードの方のデータが来ていたら */
                i = fifo8_get(&keyfifo); 
                /* キーコードのアドレスを変数iに格納 */
                io_sti(); 
                /* IFに1をセット(外部割り込みの許可) */ 
                sprintf(s, "%02X", i); 
                /* メモリのデータの参照 */
                boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31); 
                /* 画面のリセット */
                putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s); 
                /* 文字の表示 */
            } else if (fifo8_status(&mousefifo) != 0) {         
            /* もしもマウスのデータが来ていたら */
                i = fifo8_get(&mousefifo);                      
                /* マウスのデータをバッファから取得し */
                io_sti();                                       
                /* IFに1をセット(外部割り込みの許可) */
                if (mouse_phase == 0) {                         
                /* 0xfaが届くまで待機 */
                    /* マウスの0xfaを待っている段階 */
                    if (i == 0xfa) {                            
                    /* 0xfaが届いたら */
                        mouse_phase = 1;                        
                        /* マウスのデータのステータスを1に変更 */
                    }
                } else if (mouse_phase == 1) {                  
                /* マウスのデータのステータスが1ならば(マウスボタン) */
                    /* マウスの1バイト目を待っている段階 */
                    mouse_dbuf[0] = i;                          
                    /* マウスのデータバッファに1バイト目を格納 */
                    mouse_phase = 2;                            
                    /* マウスのデータのステータスを2に変更 */
                } else if (mouse_phase == 2) {                  
                /* マウスのデータのステータスが2ならば(x座標) */
                    /* マウスの2バイト目を待っている段階 */
                    mouse_dbuf[1] = i;                          
                    /* マウスのデータバッファに2バイト目を代入 */
                    mouse_phase = 3;                            
                    /* マウスのデータのステータスを3に変更 */
                }else if (mouse_phase == 3) {                   
                /* マウスのデータのステータスが3ならば(y座標) */
                    /* マウスの3バイト目を待っている段階 */
                    mouse_dbuf[2] = i;                          
                    /* マウスのデータバッファに3バイト目を代入 */
                    mouse_phase = 1;                            
                    /* マウスのデータのステータスを1に変更(完了) */
                    /* データが3バイト揃ったので表示 */
                    sprintf(s, "%02X %02X %02X", mouse_dbuf[0], mouse_dbuf[1], mouse_dbuf[2]);          
                    /* それぞれのデータを文字列sに格納 */
                    boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 32 + 8 * 8 - 1, 31);       
                    /* ボックスの描写 */
                    putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s);                   
                    /* 文字の描写 */
                }
            }
        }

このように、mouse_dbufでデータを整理し、mouse_phaseでデータの種類を分けています。
実行すると
f:id:No000:20191203075747p:plain このように3バイトをそれぞれ表示することができます。

ちょっと整理(harib05b)

Harimainがごちゃごちゃしているので整理するようです。 構造体MOUSE_DECにデータをまとめ、0xfaをenable_mouse側で処理してもらうように変更されています。また、マウスの解読処理は関数mouse_decodeにまとめられています。

マウスの解読(2)(harib05c)

2つ目のマウス解読は3バイトのデータをそれぞれx座標、y座標、クリック等のマウスの状態に変換しています。
まずは、mouse_decodeで配線が切れていた時などにデータが途切れるかもしれないことの対策として

    if (mdec->phase == 1) {                     
    /* ステータスがマウスからのデータ受信が可能になっていたら */
        /* マウスの1バイト目を待っている段階 */
        if ((dat & 0xc8) == 0x08) {                 
        /* 0~3桁目と8~F桁目以外をマスクし、ほしいデータがやってきているかを確認 */
            /* 正しい1バイト目だった */
            mdec->buf[0] = dat;                    
            /* バッファにデータを格納し */
            mdec->phase = 2;                        
            /* 次の段階にステータスを更新 */
        }
        return 0;

次に、x座標とy座標を正確に抽出する

    if (mdec->phase == 2) {                     
    /* 1バイト目のデータをバッファに格納しているならば */
        /* マウスの2バイト目を待っている段階 */
        mdec->buf[1] = dat;                     
        /* バッファに2バイト目のデータを格納し */
        mdec->phase = 3;                        
        /* 次の段階にステータスを更新 */
        return 0;
    }
    if (mdec->phase == 3) {                     
    /* 2バイト目までのデータを受け取っているならば */
        /* ますの3バイト目を待っている段階 */
        mdec->buf[2] = dat;                     
        /* バッファに3バイト目のデータを格納し */
        mdec->phase = 1;                        
        /* 次の段階にステータスをリセット */
        mdec->btn = mdec->buf[0] & 0x07;        
        /* 下位3ビットがボタンのデータなのでマスクする */
        mdec->x = mdec->buf[1];                 
        /* バッファからメンバxへx座標を格納 */
        mdec->y = mdec->buf[2];                 
        /* バッファからメンバyへy座標を格納 */
        if ((mdec->buf[0] & 0x10) != 0) {       
        /* マウスのステータスの1バイト目のデータを使って */
            mdec->x |= 0xffffff00;              
            /* 8ビットより上を1にする */
        }
        if ((mdec->buf[0] & 0x20) != 0) {       
        /* マウスのステータスの1バイト目のデータを使って */
            mdec->y |= 0xffffff00;              
            /* 8ビットより上を1にする */
        }
        mdec->y = - mdec->y;                    
        /* マウスではy方向の符号が画面と反対なので */
        return 1;
    }

これによりデータの整理が完了

                if (mouse_decode(&mdec, i) != 0) {
                    /* データが3バイト揃ったので表示 */
                    sprintf(s, "[lcr %4d %4d]", mdec.x, mdec.y); 
                    /* それぞれのデータを文字列sに格納 */
                    if ((mdec.btn & 0x01) != 0) {                
                    /* 左ボタンのデータ以外のデータをマスクして判定 */
                        s[1] = 'L';                              
                        /* lを大文字に */
                    }
                    if ((mdec.btn & 0x02) != 0) {                
                    /* 右ボタン以外のデータをマスクして判定 */
                        s[3] = 'R';                              
                        /* rを大文字に */
                    }
                    if ((mdec.btn & 0x04) != 0) {                
                    /* 中ボタン以外のデータをマスクして判定 */
                        s[2] = 'C';                              
                        /* cを大文字に */
                    }
                    boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 32 + 15 * 8 - 1, 31);       
                    /* ボックスの描写 */
                    putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s);                   
                    /* 文字の描写 */
                }

これで、ボタン関連のデータの解読も完了します。
実行すると
f:id:No000:20191203222127p:plain おお!わかりやすくなった!

動けマウス(harib05b)

ついにマウスが動かせる!!!!楽しみでたまらないです!
コード自体は簡単らしく(実際簡単だった)

                    boxfill8(binfo->vram, binfo->scrnx, COL8_008484, mx, my, mx + 15, my + 15);         
                    /* マウスを消す */
                    mx += mdec.x;                  
                    /* マウスのx座標にx座標の移動量を追加 */
                    my += mdec.y;                  
                    /* マウスのy座標にy座標の移動量を追加 */
                    if (mx < 0) {                  
                    /* 左端に来たら */
                        mx = 0;                    
                        /* x座標を維持し続ける */
                    }
                    if (my < 0) {                  
                    /* 上端に来たら */
                        my = 0;                    
                        /* y座標を維持し続ける */
                    }
                    if (mx > binfo->scrnx -16) {   
                    /* 一番右の長さからマウスの大きさ分を引いた点に来たら */
                        mx = binfo->scrnx -16;     
                        /* x座標を維持し続ける */
                    }
                    if (my > binfo->scrny -16) {   
                    /* 一番下の位置からマウスの大きさ分を引いた点に来たら */
                        my = binfo->scrny -16;     
                        /* y座標を維持し続ける */
                    }
                    sprintf(s, "(%3d, %3d)", mx, my);                                   
                    /* 指定アドレスのデータを文字列にしsに格納 */
                    boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 0, 79, 15);     
                    /* 背景と同じ色の描写 */
                    putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, s);     
                    /* 座標の描写 */
                    putblock8_8(binfo->vram, binfo->scrnx, 16,16, mx, my, mcursor, 16); 
                    /* マウスの描写 */

感じた点は、 ・渡されたデータを3バイトごとに再描画している
・これでは重ね合わせ処理をしないと、1色しか表示できない
といったところです。重ね合わせ処理をどうやるか楽しみです!

32ビットモードへの道

32ビットモードに移行するときの説明を端折った分を解説する内容でした。

説明としてはasmhead.asmの```bootpack.cに処理が移るまでに何を行っているかの説明となっていました。

; PICが一切の割り込みを受け付けないようにする
; AT互換機の仕様では、PICの初期化をするなら、
; こいつをCLI前にやっておかないと、たまにハングアップする
; PICの初期化はあとでやる

    MOV     AL,0xff ; ALに0xffを代入
    OUT     0x21,AL ; PIC0_IMRを指定して0xffコマンド(割り込み停止)
    NOP             ; OUT命令を連続させるとうまくいかない機種があるらしいので(1クロック休憩)
    OUT     0xa1,AL ; PIC1_IMRを指定して0xffコマンド(割り込み停止)

    CLI             ; さらにCPUレベルでも割り込み禁止

CPUのモードを変更するために、割り込み禁止を出しています。

; CPUから1MB以上のメモリにアクセスできるように、A20GATEを設定
; 16ビットCPUのメモリから32ビットCPUのメモリの大きさに変化

    CALL    waitkbdout  ; KBCコマンド出力待機
    MOV     AL,0xd1     ; ALにポート番号を格納
    OUT     0x64,AL     ; アウトプットポートに書き込み
    CALL    waitkbdout  ; KBCコマンド出力待機
    MOV     AL,0xdf     ; enable A20(A20アドレスライン有効化コマンド)
    OUT     0x60,AL     ; キーボードエンコーダーにコマンドを送信
    CALL    waitkbdout  ; KBCコマンド出力待機

KBCにコマンドを送り、A20GATE信号線というものをONにしています。これで、メモリが1MBの制限から解き放たれます

; プロテクトモード移行(保護あり仮想メモリモード)

[INSTRSET "i486p"]      ; 486命令まで使いたいという記述

    LGDT    [GDTR0]     ; 仮のGDTを読み込み
    MOV     EAX,CR0         ; コントロールレジスタ0のデータをEAXに読みだす
    AND     EAX,0x7fffffff  ; bit31を0にする(ページング禁止のため)
    OR      EAX,0x00000001  ; bit0を1にする(プロテクトモード移行のため)
    MOV     CR0,EAX         ; コントロールレジスタ0にステータスを戻す
    JMP     pipelineflush   ; 機械語が変化することによるJMP命令(必須)
pipelineflush:              ; パイプラインがかかわっているので
    MOV     AX,1*8          ; AXに0x0008を代入
    MOV     DS,AX           ; データセグメントをプロテクトモードに対応
    MOV     ES,AX           ; エクストラセグメントをプロテクトモードへ対応
    MOV     FS,AX           ; Fセグメントをプロテクトモードへ対応
    MOV     GS,AX           ; Gセグメントをプロテクトモードへ対応
    MOV     SS,AX           ; スタックセグメントをプロテクトモードへ対応
; CSは後回し(混乱するらしい)

ここで32ビットに切り替わります。
・コントロールレジスタのフラグを変えることによりセグメント方式の指定やプロテクトモードの設定を行っている。
・32ビットモード移行にあたり、命令の解釈の違いでパイプライン処理に影響が出ないようにする処理
・CS以外のセグメントレジスタをプロテクトモードのアドレスに対応させる

*プロテクトモードとは、日本語で「仮想アドレス保護モード」と呼ぶ、これが設定されることで仮想記憶、マルチタスクやページング等も設定できるようになる。

; bootpackの転送

    MOV     ESI,bootpack    ; 転送元
    MOV     EDI,BOTPAK      ; 転送先
    MOV     ECX,512*1024/4  ; 転送サイズ(ダブルワードなので1/4)
    CALL    memcpy          ; データをメモリ上で上の3命令で指定したデータに従い転送

; ついでにディスクデータも本来の位置へ転送

; まずはブートセクタから

    MOV     ESI,0x7c00      ; 転送元
    MOV     EDI,DSKCAC      ; 転送先
    MOV     ECX,512/4       ; サイズ指定
    CALL    memcpy          ; メモリ上でのデータ転送

; 残り全部

    MOV     ESI,DSKCAC0+512 ; 転送先
    MOV     EDI,DSKCAC+512  ; 転送元
    MOV     ECX,0           ; 下位4バイトのみのデータにするためにリセット
    MOV     CL,BYTE [CYLS]
    IMUL    ECX,512*18*2/4  ; シリンダ数からバイト数/4に変換
    SUB     ECX,512/4       ; IPLの分だけ差し引く
    CALL    memcpy

プロテクトモード移行にともない、データがあるアドレスの調整

; asmheadでしなければいけないことは全部し終わったので、
; あとはbootpackに任せる

; bootpackの起動

    MOV     EBX,BOTPAK
    MOV     ECX,[EBX+16]
    ADD     ECX,3       ; ECX += 3;
    SHR     ECX,2       ; ECX /= 4;
    JZ      skip        ; 転送すべきものがない(JZは直前の計算結果が0の場合)
    MOV     ESI,[EBX+20]    ; 転送元
    ADD     ESI,EBX
    MOV     EDI,[EBX+12]    ; 転送先
    CALL    memcpy          ; はりぼてアプリケーション時に説明
skip:
    MOV     ESP,[EBX+12]            ; スタックの初期値
    JMP     DWORD 2*8:0x0000001b    ; bootpack.hrbの0x1b番地にジャンプ(特別なJMP命令らしい)
; 詳細は書籍のメモリマップを見たらわかりやすい

はりぼてアプリケーションのための設定

waitkbdout:
    IN      AL,0x64                 ; キーボードにたまっていたキーボード、マウスのデータの受け取り
    AND     AL,0x02                 ; データがまだあるかの確認?
    IN      AL,0x60                    ; 空読み(受信バッファが悪さをしないように)
    JNZ     waitkbdout       ; ANDの結果が0でなければwaitbdoutへ
    RET

キーボードやマウスにデータがたまっていないかと、受けとる処理

memcpy:                     ; メモリを4バイトずつコピーするラベル
    MOV     EAX,[ESI]       ; 転送元のデータをEAXに格納
    ADD     ESI,4           ; アドレスを4進める
    MOV     [EDI],EAX       ; メモリのデータを送信先に格納
    ADD     EDI,4           ; アドレスを4進める
    SUB     ECX,1           ; データのサイズをマイナス1する
    JNZ     memcpy          ; 引き算した結果が0でなければmemcpyへ
    RET
; memcpyはアドレスサイズプリフィクスを入れ忘れなければ、ストリング命令でも書ける

メモリ上のデータを別のアドレスにコピーする仕組み
EAXを利用して、4バイトずつせっせと転送してる

    ALIGNB  16              ; GDT0のアドレスをプロテクトモードに伴い8の倍数に修正
GDT0:                       ; 
    RESB    8               ; nullセクタなので飛ばす
    DW      0xffff,0x0000,0x9200,0x00cf ; 読み書き可能セグメント32bit
    DW      0xffff,0x0000,0x9a28,0x0047 ; 実行可能セグメント32bit (bootpack用)

    DW      0               ; 
GDTR0:                      ; LGDT命令へのヒント
    DW      8*3-1           ; 16ビットのリミット(終わり)
    DD      GDT0            ; 32ビットの開始番地

    ALIGNB  16              ; 16倍にする
bootpack:                   ; bootpackの始まり!

ALIGNB命令でアドレスを8の倍数にして、暫定的なGDTを作ってる。GTR0はLGDT命令に対する指示

要は、32ビットプロテクトモードに移行する準備をして、移行したあとにデータを対応させている感じになります

感想

最後は伏兵でしたが、何とか切り抜けました。マウスが動いたのはほんと感動です。