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

初めに

ついに1週間!マウスに向けて頑張っていくぞー
今回から写経したコードのコメントを増やしていこうかなとか考えています。

目次

本文

キーコードを取得しよう(harib04a)

マウスの問題を設定する前にキーボードの設定をいじって練習しておこうって流れみたいです。
そこでキーを押したら、画面が変わるだけで終わりにしないでキーを押して表示して、それから、割り込み処理で終了する処理を行うみたいです。

ちなみに、書籍にのっているリンクは次のリンクに移動しています。

(PIC)8259A - os-wiki

今回はinthandler21(int *esp)を編集しており以下のようなコードに変更するようです。

void inthandler21(int *esp)
/* PS/2キーボードからの割り込み */
{
  struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
  unsigned char data, s[4];
  io_out8(PIC0_OCW2, 0x61);   /* IRQ-01受付完了をPICに通知 */
  data = io_in8(PORT_KEYDAT);

  sprinf(s, "%02X", data);
  boxfill8(binfo->vram, binfo->scrnx, COL8_000000, 0, 16, 15, 31); 
  putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s); /* sの文字を0×16に描写 */

return;
}

これで行っているのは、まず、io_out8(PIC0_OCW2, 0x61);でPICに割り込みがあることを理解したから持ち場に戻って大丈夫だよ!って伝えているようです。また、dataに0x060を装置番号として入力することでキーコードを読み取ることができます。keydatってkeydataのことかな…?
ここに載っているページはこちらに写っているようです。

(AT)keyboard - os-wiki

キーを押してみると…
f:id:No000:20191122165433p:plain

おぉ…ポチポチ押すたびに変化してる!
ちなみにこのキーコードはとあるエディタがよく使用するボタンを離したときのキーコードです。

割り込み処理は手早く(harib04b)

harib04bには問題があったみたいで文字を描写するのがまずいらしいです。(割り込み処理が長引くとまずい…)で、処理の長い文字描写をどう解決するかというと、バッファを用意してキーコードをHarimain側で覚えておいてもらうようです。
int.c

#define PORT_KEYDAT   0x0060

struct KEYBUF keybuf;

void inthandler21(int *esp)
/* PS/2キーボードからの割り込み */
{
  /* 文字表示表示はHarimainでやるのでsとBOOTINFOは削除 */
  unsigned char data;
  io_out8(PIC0_OCW2, 0x61);   /* IRQ-01受付完了をPICに通知 */
  data = io_in8(PORT_KEYDAT);
  if (keybuf.flag == 0) { /* バッファが空か確認 */
    keybuf.data = data; /* バッファーにdataを格納 */
    keybuf.flag = 1; /* バッファにデータが入ったと教えている */
  }

return;
}

に改造し、構造体でバッファを作りバッファを活用しHarimainにキーコードを教えています。

では文字を表示するHarimain側はどうなっているかというと、改造したbootpack.cがこのようになります

    for (;;) {
        io_cli(); /* 外部割り込み禁止(割り込み処理中の割り込み対策) */
        if (keybuf.flag == 0) { /* バッファが空かの確認 */
            io_stihlt(); /* 外部割り込みの許可sと、CPU停止命令 */
        } else {
            i = keybuf.data; /* キーコードのアドレスを変数iに格納 */
            keybuf.flag = 0; /* flagに0をセット(バッファが空になったことを教えている) */
            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); /* 文字の表示 */
        }
    }

(変数の定義やbootpack.hに変化があっているので注意)
このようにして、ifを利用し、データがある場合とない場合をわけて、画面表示をさせています。
これで起動すると…
f:id:No000:20191122182623p:plain 本で指示されているように右下のCtrlを押すとE0が押したときも離したときも表示されてしまいます。これの原因はCtrlで出てくるキーコードが2バイトだけど、プログラム側で2バイト受け取る準備ができていなかったわけです。
あと、「トイレ行きたくても我慢している状態」という例えが面白かった…

FIFOバッファを作る(harib04c)

FILO型はアセンブリコードとかで見かけていたけど、FILO型は詳しく知るのは初めてなので楽しみ!
作成方法は、data[32]を利用するそうです。
まず、データを渡す側のint.cは次のように改造されます

#define PORT_KEYDAT   0x0060

struct KEYBUF keybuf;

void inthandler21(int *esp)
/* PS/2キーボードからの割り込み */
{
  /* 文字表示表示はHarimainでやるのでsとBOOTINFOは削除 */
  unsigned char data;
  io_out8(PIC0_OCW2, 0x61);   /* IRQ-01受付完了をPICに通知 */
  data = io_in8(PORT_KEYDAT);
  if (keybuf.next < 32) { /* オーバーフロー対策 */
    keybuf.data[keybuf.next] = data; /* keybuf.nex番目にデータを格納 */
    keybuf.next++; /* インクリメント */
  }
return;
}

これでnextを利用し、nextが0から始まる32個の1バイトの箱にキーコードを保管できるようになりました!
次は、受け取り側の設定!

    for (;;) {
        io_cli(); /* 外部割り込み禁止(割り込み処理中の割り込み対策) */
        if (keybuf.next == 0) { /* バッファが空かの確認 */
            io_stihlt(); /* 外部割り込みの許可と、CPU停止命令 */
        } else {
            i = keybuf.data[0]; /* キーコードのアドレスを変数iに格納 */
            keybuf.next--; /* nextをデクリメント */
            for (j = 0; j < keybuf.next; j++) { /* FIFO型に従い繰り返し処理で格納場所をずらす */
                keybuf.data[j] = keybuf.data[j + 1]; /* 1つずらす */
            }
            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); /* 文字の表示 */
        }
    }
}

こちらの方で、FIFO型のデータの格納場所をずらす部分の処理と変数iへの格納を行っていますね。
実行してみると
f:id:No000:20191122193852p:plain

ちゃんとできてますな!しかし、まだまずい部分があるようです。それは、3個程度のずらし操作であれば問題ないのですが、32個とかのデータが来た際にずらすとなると、文字表示程ではないですが処理時間がかかってしまうということです。要は、「割り込み処理に時間がかかりすぎ!だけど外に出せない…」ということです。

FIFOバッファを改造する(harib04d)

harib04eでずらしを処理中にするのはまずいけど、どうしよう…という状況を改善するための作業です。

ここでやる方法というのが、読みnextと書きnextを用意し、それぞれのバッファが31を超えたら0に戻るといった、循環しながら読み書きを行っていく仕組みです。これでうまくやれるそうです、オーバーフローしなければ…
では、改良されたint.c

void inthandler21(int *esp)
/* PS/2キーボードからの割り込み */
{
  /* 文字表示表示はHarimainでやるのでsとBOOTINFOは削除 */
  unsigned char data;
  io_out8(PIC0_OCW2, 0x61);   /* IRQ-01受付完了をPICに通知 */
  data = io_in8(PORT_KEYDAT);
  if (keybuf.len < 32) { /* オーバーフロー対策 */
    keybuf.data[keybuf.next_w] = data; /* keybuf.nex番目にデータを格納 */
    keybuf.len++; /* バッファの容量のインクリメント */
    keybuf.next_w++; /* 読みこみバッファを進める */
    if (keybuf.next_w == 32) {
      keybuf.next_w = 0; /* バッファが32に到達したら0に移動 */
    }
  }
  return;
}

こんな感じで書き込みのkeybuf.next_wがデータを受け取ると、せっせとkeybuf.lenにデータを入れたことを記録し、次のデータを保管する箱に移っています。
では、読み込み側はどうなっているかというと

    for (;;) {
        io_cli(); /* 外部割り込み禁止(割り込み処理中の割り込み対策) */
        if (keybuf.len == 0) { /* バッファが空かの確認 */
            io_stihlt(); /* 外部割り込みの許可と、CPU停止命令(割り込みの終了) */
        } else {
            i = keybuf.data[keybuf.next_r]; /* キーコードのアドレスを変数iに格納 */
            keybuf.len--; /* バッファのデータを1つ呼んだことを記録 */
            keybuf.next_r++;
            if (keybuf.next_r == 32) { /* FIFO型に従い繰り返し処理で格納場所をずらす */
                keybuf.next_r = 0; /* 読み込みnextが31を超えた時0に戻す */
            }
            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); /* 文字の表示 */
        }
    }

このように読み込み側と同じようにkeybuf.lenでどのくらいのデータがあるかを確認してkeybuf.next_rを利用して、入れた個数だけ読みだしています。また、31に達したら0に移動する処理も行っています。
実行すると
f:id:No000:20191123163057p:plain

変わらないけど中身はすっきりした感じ。しかも高速らしくて最高らしい…(はりぼてOS自体が高速すぎて実感できなかった)

FIFOバッファを整理する(harib04e)

次にキーボードから送られてくるデータはここまでのFIFOバッファで処理できるらしいのですが、マウスからのデータを蓄えるとなると話は違ってくるようです。まず、マウスが少し動くだけで3バイトものデータが連続で送られてくるからみたいです。これを対策するために、以下のFIFO8*bufのように構造体を作成するときにバッファの大きさを何バイトでもいいようにしてあるみたいです。

/* fifo.c */
struct FIFO8 {
    unsigned char *buf;          /* バッファの位置を特定するためのアドレス */
    int p, q, size, free, flags;    /* p:書き込み位置,q:読み込み位置,size:バッファの大きさ,free:バッファの空き容量,flags:あふれの確認フラグ, */
};

このfifo.cでは、FIFO型バッファを制御するための4つの関数があります。
一つ目が

void fifo8_init(struct FIFO8 *fifo, int size, unsigned char *buf)
/* FIFOバッファの初期化 */
{
  fifo->size = size;
  fifo->buf  = buf;
  fifo->free = size; /* 空き */
  fifo->p = 0; /* 書き込み位置 */
  fifo->q = 0; /* 読み込み位置 */
  return;
}

で、構造体の番地とバッファに関数パラメータを指定し、書き込み位置と読み込み位置を0に初期化しています。
二つ目が

int fifo8_put(struct FIFO8 *fifo, unsigned char data)
/* FIFOへデータを送り込んで蓄える */
{
  if (fifo->free == 0) {  /* もし空きがなければ */
    /* 空きがなくてあふれた */
    fifo->flags |= FLAGS_OVERRUN;
    return -1;      /* -1を返す */
  }
  fifo->buf[fifo->p] = data;
  fifo->p++;                      /* 書き込み位置を進める */
  if (fifo->p == fifo->size) {    /* 書き込み位置が最後に来たら */
    fifo->p = 0;                  /* 0に移動 */
  }
  fifo->free--;                   /* バッファの空きが減った */
  return 0;                       /* 0を返す */
}

で、FIFO型バッファへデータを収納するのが役割の関数です。
引数として、構造体のポインタと1バイトのデータを受け取り、あふれた場合の処理と無事データを受け取った後に書き込み位置を進める処理を行っている。
3つ目は

int fifo8_get(struct FIFO8 *fifo)
/* FIFOからデータを1つとって来る */
{
  int data;
  if (fifo->free == fifo->size) {
    /* バッファが空っぽの時は、とりあえず-1が返される */
    return -1;
  }
  data = fifo->buf[fifo->q];   /* バッファ上の読み込み位置のデータをdataに格納 */
  fifo->q++;                   /* 読み込み位置を1つ進める */
  if (fifo->q == fifo->size) { /* バッファの最後にたどり着いたら */
    fifo->q = 0;               /* バッファの始めに移動する */
  }
  fifo->free++;                /* バッファのデータを一つ渡したことをfreeに空きが1つ増えたとして記録 */
  return data;                 /* int型でdataを返す */
}

で、FIFOバッファから1バイトのデータを取り出す動作を行う関数で、引数として構造体のポインタを渡すことで、バッファからdataへデータを移している。
4つ目が

int fifo8_status(struct FIFO8 *fifo)
/* どのくらいデータがたまっているかを報告する */
{
  return fifo->size - fifo->free;   /* バッファの大きさから空きの大きさを引く */
}

バッファのアドレスを受け取り、指定されたバッファにどれだけのデータがたまっているかの確認を行っている
で実行をすると
f:id:No000:20191127164244p:plain いつ通りの表示…でも、バッファ関係を関数でまとめてあるのですごいすっきりしてる!

さぁマウスだ(harib04f)

まず、マウスというのは後の方で開発されたもので、それゆえのIRQ12という大きめな割り込み番号になっています。また、当時はマウスに対応しているOSが少なく、しかもマウスは少し動いただけで割り込みをかけてしまうので、まぁ…CPUが知りもしない割り込みが大量に来て混乱するわけで…
それを防ぐために、マウスの割り込みをするためには有効化命令を制御回路とマウス本体に送る必要があることにすることで解決しています。

ということで今回はマウスの割り込みを有効化してあげようというわけです。

そのために、bootpack.cでのkeyboadの初期化を改造し、マウスに有効化命令を送ってあげる関数を作ってあげるみたいです。
一つ目が、キーボードの処理スピードがCPUより遅いために、キーボードが混乱しないようにする関数で

void wait_KBC_sendready(void)
{
    /* キーボードコントローラーがデータを送信可能になるのを待つ */
    for (;;) {
        if ((io_in8(PORT_KEYSTA) & KEYSTA_SEND_NOTREADY) == 0) {        /* ANDでデータ送信可能状態かの確認 */
            break;
        }
    }
    return;
}

となっており、装置番号0x0064から送られてくるデータの下位2ビットが0になると、コマンドが受け付けられるというサインなので、その後無限ループからbreakする関数です。

2つ目がキーボードコントローラの初期化関数の改造で、

void init_keyboard(void)
{
    /* キーボードコントローラーの初期化 */
    wait_KBC_sendready();                       /* 送信可能かの確認 */
    io_out8(PORT_KEYCMD, KEYCMD_WRITE_MODE);    /* モード設定コマンド */
    wait_KBC_sendready();                       /* 送信可能かの確認 */
    io_out8(PORT_KEYDAT, KBC_MODE);             /* マウスモードを指定 */
    return;
}

これらの設定情報は指定されたいつもの場所に乗っているようで

(AT)keyboard - os-wiki

のっているらしいのですが、キーボードのモード変換の0x0060は8042モードレジスタ直接書き込みに書いてあるとおもうのですが、マウスの0x47はステータスレジスタの内容をbitごとでの指定しているものなのですが、01000111で、IRQ-1を発生させる・マウスからデータが来たときにIRQ-12を発生させる・システムフラグをON・スキャンコード01を使うという設定っぽいです。
スキャンコード…キーボードのキーを押したときにキーボードから帰ってくるコードのこと、なぜ01にするかはわからなかった…

3つ目は、混乱しないように最後に回していた、マウスに有効化命令を送る関数で、

void enable_mouse(void)
{
    /* マウス有効化 */
    wait_KBC_sendready();                           /* 送信可能かの確認 */
    io_out8(PORT_KEYCMD, KEYCMD_SENDTO_MOUSE);      /* KBCへマウスへのデータ送信依頼 */
    wait_KBC_sendready();                           /* 送信可能かの確認 */
    io_out8(PORT_KEYDAT, MOUSECMD_ENABLE);          /* マウスへ有効化命令を伝達 */
    return; /* うまくいくとACK(0xfa)が送信されてくる */
}

このように、キーボード制御回路を介してマウスにデータを送っている処理っぽいです。

後は、Harimainをいじれば、
f:id:No000:20191129185322p:plain

認識と…

マウスからのデータ受信(harib04g)

割り込みの次はデータの受信設定のようです
1つ目は、PICに受付通知を行い、データを受け取り、FIFOバッファに格納するという処理を以下のint.cの関数で行い

void inthandler2c(int *esp)
/* PS/2マウスからの割り込み */
{
  unsigned char data;
  io_out8(PIC1_OCW2, 0x64);       /* IRQ-12受付完了をPIC1に伝達(スレーブへ) */
  io_out8(PIC0_OCW2, 0x62);       /* IRQ-02受付完了をPIC0に伝達 (マスタへ)*/
  data = io_in8(PORT_KEYDAT);     /* キーボードと同じ */
  fifo8_put(&mousefifo, data);    /* キーボードと同じ */
  return;
}

この際は、マウスはスレーブのIRQ-12にあたるので、スレーブとマスタを連携させるために両方に受付完了を送っています。あとは、キーボードと同じ。

2つ目は、bootpack.c側のデータを受け取る方の処理になります。

    enable_mouse();         /* マウスの有効化 */

    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をセット(外部割り込みの許可) */
                sprintf(s, "%02X", i);                          /* 指定アドレスの値の取得 */
                boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 47, 31);       /* ボックスの描写 */
                putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s);       /* 文字の描写 */
            }
    }

このように、まず、マウスの有効化を行います。次に、データが来ていることを確認してからキーボードとマウスのデータを選り分け、バッファからデータを取り出し、画面表示するという処理を行っています。
すると
f:id:No000:20191202214832p:plain

データが受信することができた!マウスをぐりぐり動かすと、ぐりぐり数値が動いてくれて感動です。

感想

7日目は、マウスがどのようにデータを飛ばしているかを知ることができ面白かったです。特に、構造体の基礎的な使い方が身についてきた気がします。