30日でできるOS自作入門_DAY7

harib04a: キーコードの取得

DAY6で作った、キーボード入力時の割り込み処理を改善していく。

int.c - 修正前

void inthandler21(int *esp)
/* PS/2キーボードからの割り込み */
{
    struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
    boxfill8(binfo->vram, binfo->scrnx, COL8_000000, 0, 0, 32 * 8 - 1, 15);
    putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, "INT 21 (IRQ-1) : PS/2 keyboard");
    for (;;) {
        io_hlt();
    }
}

int.c - 修正後

#define PORT_KEYDAT     0x0060

void inthandler21(int *esp)
{
    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);

    sprintf(s, "%02X", data);
    boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31);
    putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);

    return;
}

コメントの通り。IRQ1だったので、0x61にしているが、 IRQ3の場合だったら、インクリメントされて0x63になるとのこと。 これをPICに実行してあげると、割り込み信号のブロッキングが解除されて、 PICによる割り込み信号のモニタが再開される?

io_out8(PIC0_OCW2, 0x61);  /* IRQ-01受付完了をPICに通知 */

その後、以下で装置番号0x0060から8bitを読み出し、 符号なしchar型のdataに代入。

data = io_in8(PORT_KEYDAT);

その後、sprintfでdataの内容をsにコピー。 putfont8で画面に描画している。

sprintf(s, "%02X", data);
    boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31);
    putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);

これで、入力した1文字に対応の文字コードが都度画面に表示されるようになった。

harib04b: キーボードからの割り込み処理と文字描画処理の分割

画面描画は処理コストが高く、今のコードだとそれに引きずられて割り込み処理も 処理が遅くなってしまうので分割を行う。

まずは、int.cに手を入れ、描画処理を外す。 新たにflagを立てる機構を作り、未出力の入力データがdataに 格納されているか否かを判定できるようにする。

int.c

#define PORT_KEYDAT     0x0060

struct KEYBUF keybuf;

void inthandler21(int *esp)
{
    unsigned char data;
    io_out8(PIC0_OCW2, 0x61);   /* IRQ-01受付完了をPICに通知 */
    data = io_in8(PORT_KEYDAT);
    if (keybuf.flag == 0) {
        keybuf.data = data;
        keybuf.flag = 1;
    }
    return;
}

次にbootpack.cの処理を変えていく。(今までは無限ループで、io_hlt();するのみだった)

bootpack.c

for (;;) {
        io_cli();
        if (keybuf.flag == 0) {
            io_stihlt();
        } else {
            i = keybuf.data;
            keybuf.flag = 0;
            io_sti();
            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);
        }
    }

まず、 io_cli(); で割り込み処理を禁止する。

flagが0の場合は、未処理の入力データがない状態なので、 io_stihlt();で STI(割り込み禁止の解除?) と HLT(CPUお休み) を実行して終わり。

flagが1の場合は、flagを0にした後で、これまで int.cに書いていた描画処理を行う。

harib04c: キーボードからの割り込み処理のキューイング(FIFOバッファ)

04bの実装だと、入力された1byteを処理中に実行された割り込みは 全て読み捨てられてしまうため、そうではなくキューイングを行うようにする。

int.c

struct KEYBUF keybuf;

void inthandler21(int *esp)
{
    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.next++;
    }
    return;
}

使いされた部分のコードが以下のように動き、値を32個まで配列に格納していく。

1回目の処理

  • keybuf.data[0] = (初回で入力された値)
  • keybuf.next ++ / 0 → 1に代わる /

2回目の処理

  • keybuf.data[1] = (2回目入力された値)
  • keybuf.next ++ / 1 → 2に代わる /

bootpack.c

for (;;) {
        io_cli();
        if (keybuf.next == 0) {
            io_stihlt();
        } else {
            i = keybuf.data[0];
            keybuf.next--;
            for (j = 0; j < keybuf.next; j++) {
                keybuf.data[j] = keybuf.data[j + 1];
            }
            io_sti();
            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);
        }
    }
}

ここは本文の説明で問題なく理解できた。

harib04d: キューイング機構(FIFOバッファ)の改良

04cの実装だと、最大32個の配列の要素をfor文でずらす必要が出てくる。 この処理をなくしたいので、実装を変える。

まずは、構造体の定義から。 next_r , next_w ,len が追加された。

bootpack.h

/* int.c */
struct KEYBUF {
    unsigned char data[32];
    int next_r, next_w, len;
};

次にキューへの入力処理。 入力された値は、常に keybuf.data[keybuf.next_w] に入る。

keybuf.lenは「現在何個のキューを持っているのか」 keybuf.next_wは「現在添え字を何個使ったのか」をカウントしており、32に達すると0にリセットされる。

int.c

void inthandler21(int *esp)
{
    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.len++;
        keybuf.next_w++;
        if (keybuf.next_w == 32) {
            keybuf.next_w = 0;
        }
    }
    return;
}

キューからの取り出し処理もそれに合わせて書き直されている。

bootpack.c

for (;;) {
        io_cli();
        if (keybuf.len == 0) {
            io_stihlt();
        } else {
            i = keybuf.data[keybuf.next_r];
            keybuf.len--;
            keybuf.next_r++;
            if (keybuf.next_r == 32) {
                keybuf.next_r = 0;
            }
            io_sti();
            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);
        }
    }
}

harib04e:キューイング機構(FIFOバッファ)の共通モジュール化

04b-04dで開発のキューイング機構を他の処理(マウスの入力とか)にも使えるように コードを修正していく。

まずは構造体の定義から。FIFOバッファの最大長を変数で自由に設定できるように変更。

bootpack.h

/* fifo.c */
struct FIFO8 {
    unsigned char *buf;
    int p, q, size, free, flags;
};

構造体の初期化関数。

fifo.c

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

キューへのPUSH処理。 fifo->buf[fifo→p] 値を格納したあとで、fifo->p++;し、fifo->free–;でfree領域の残数をデクリメントする。 fifo→pがキューの最大中に達したら、カウンターをリセットする機能と、 溢れたときにflagを立てる処理付き。

fifo.c

int fifo8_put(struct FIFO8 *fifo, unsigned char data)
/* FIFOへデータを送り込んで蓄える */
{
    if (fifo->free == 0) {
        /* 空きがなくてあふれた */
        fifo->flags |= FLAGS_OVERRUN;
        return -1;
    }
    fifo->buf[fifo->p] = data;
    fifo->p++;
    if (fifo->p == fifo->size) {
        fifo->p = 0;
    }
    fifo->free--;
    return 0;
}

キューからのPOP処理。 やっていることは、PUSHとほぼ同じ。

fifo.c

int fifo8_get(struct FIFO8 *fifo)
/* FIFOからデータを一つとってくる */
{
    int data;
    if (fifo->free == fifo->size) {
        /* バッファが空っぽのときは、とりあえず-1が返される */
        return -1;
    }
    data = fifo->buf[fifo->q];
    fifo->q++;
    if (fifo->q == fifo->size) {
        fifo->q = 0;
    }
    fifo->free++;
    return data;
}

最後にステータスチェック用のコード。 FIFOバッファの最大中 - 残っているfree領域を引き算して、 キュー長を戻り値で返しているだけ。

fifo.c

int fifo8_status(struct FIFO8 *fifo)
/* どのくらいデータが溜まっているかを報告する */
{
    return fifo->size - fifo->free;
}

以上を踏まえて、init.cを書き直していく。

push側の処理は、fifo8_put(keyfifo, data); だけになった。 FIFO8型の構造体 keyinfoを作成後、fifo8_put関数でput処理している。

init.c

struct FIFO8 keyfifo;

void inthandler21(int *esp)
{
    unsigned char data;
    io_out8(PIC0_OCW2, 0x61);   /* IRQ-01受付完了をPICに通知 */
    data = io_in8(PORT_KEYDAT);
    fifo8_put(keyfifo, data);
    return;
}

POP側の処理も、 i = fifo8_get(keyfifo); とシンプルになった。

bootpack.c

for (;;) {
        io_cli();
        if (fifo8_status(keyfifo) == 0) {
            io_stihlt();
        } else {
            i = fifo8_get(keyfifo);
            io_sti();
            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);
        }
    }

harib04f:マウスが動くようにする

マウスは初期状態では、割り込み信号がブロックされている。 ので、その解除が必要であり、そのために bootpack.c を修正し、 キーボードの初期化処理と一緒にマウスの初期化処理を実装する。

キーボードが制御信号で制御可能な状態になると、 io_in8(PORT_KEYSTA)の2ビット目が0になる。のでそれまでひたすら無限ループを回して待つ。

bootpack.c

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

キーボードの初期化関数。 前述の wait_KBC_sendready(void) で制御Readyになったかを見つつ、 io_out8で

bootpack.c

#define PORT_KEYDAT             0x0060
#define PORT_KEYSTA             0x0064
#define PORT_KEYCMD             0x0064
#define KEYSTA_SEND_NOTREADY    0x02
#define KEYCMD_WRITE_MODE       0x60
#define KBC_MODE                0x47


void init_keyboard(void)
{
    /* キーボードコントローラの初期化 */
    wait_KBC_sendready();
    io_out8(PORT_KEYCMD, KEYCMD_WRITE_MODE);  /* キーボードをWRITE_MODEに */
    wait_KBC_sendready();
    io_out8(PORT_KEYDAT, KBC_MODE);
    return;
}

最後に以下でマウスを有効化する。

bootpack.c

#define KEYCMD_SENDTO_MOUSE     0xd4
#define MOUSECMD_ENABLE         0xf4

void enable_mouse(void)
{
    /* マウス有効 */
    wait_KBC_sendready();
    io_out8(PORT_KEYCMD, KEYCMD_SENDTO_MOUSE);
    wait_KBC_sendready();
    io_out8(PORT_KEYDAT, MOUSECMD_ENABLE);
    return; /* うまくいくとACK(0xfa)が送信されてくる */
}

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

割り込みは受け取れるようになったので、今度はマウスを動かす度に、 都度割り込みが発生するようにコードを変えていく。

キーボードと同様に割り込み処理を受け付けたら、 PICに受付完了を通知後に、 fifo8_put でputするようにする。

init.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;
}

次に、bootpack.cの中でのキューのPOP処理のelseにマウス処理のパターンを足す。(書き方はキーボードと同様)

fifo8_init(mousefifo, 128, mousebuf);

    enable_mouse();

    for (;;) {
        io_cli();
        if (fifo8_status(keyfifo) + fifo8_status(mousefifo) == 0) {
            io_stihlt();
        } else {
            if (fifo8_status(keyfifo) != 0) {
                i = fifo8_get(keyfifo);
                io_sti();
                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();
                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);
            }
        }
    }

マウスが動くたびに、座標が画面に描画されるようになった!

/* https://sunrise033.com/entry/hatena-blog-how-to-hierarchicalize-categories */