본문 바로가기

OS 개발일지

[OS개발] 7일차 - 반응해라 마우스!

7일차(점점 글쓰는 텀이 길어지는 듯한...)

7일차에는 인터럽트로 받은 값을 FIFO(First In First Out) 구조체를 만들어 넣고 빼는 방법을 배우고 마우스 제어의 밑밥을 깔았다.


# 일단 키보드의 인터럽트에 OS가 반응하도록 6일차에 만들었으므로 이제는 키 코드를 받아오도록 해보자.

지난 시간에 만든 인터럽트 함수를 수정한다.

#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;
}

IN 명령으로 0x0060 포트에서 키값을 받아오고 있다. 근데 윗 줄을 보면 OUT 명령으로 무언가 보내고 있는 모습이다. 이는 PIC가 인터럽트를 발생시킨후 다시 제자리로 알아서 돌아가지 않기 때문에 'IRQ 1번(0x0060 + 1)은 확인했으니 돌아가서 감시해'라는 의미이다.

여기까지 하면 실행 화면에서 키 코드가 출력 되는 것을 볼 수 있다.

다만, 코드가 이렇게 되면 인터럽트 처리 중 화면 처리를 하게 되는 것이므로 그 사이 다른 인터럽트가 들어와서 막힐 수도 있으므로 별도의 공간에 인터럽트 값을 저장 해놓고 main루프에서 반영하는 방법을 쓰도록 한다.

예제는 단일 버퍼를 사용함으로써 생길 수 있는 문제를 다루었지만 po패스wer. 바로 FIFO 구조체를 사용하는 방법으로 넘어가 보자

키보드의 특정 키나 마우스는 한번에 1개 이상의 코드 값을 날리기도 한다. 이렇게 동시에 날아오는 여러 코드들을 처리하기 위해 FIFO(A.K.A. 큐) 구조체를 만들어서 순서대로 처리한다. 일단 구조체의 구조와 초기화 함수는 다음과 같다.

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

/* fifo.c */
#define FLAGS_OVERRUN		0x0001

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; /* write 위치 */
	fifo->q = 0; /* read 위치 */
	return;
}

준비된 버퍼를 가리킬 buf, 쓰기지점 p, 읽기지점 q, size, 남은 공간의 수 free, 처리 여부 플래그 flags로 이루어진다.무슨 특공대 소개 같다

이 구조체를 어떻게 써먹느냐면 그림에서 보듯이 인터럽트가 들어오면 쓰기지점 p에 코드를 쓰고 p는 한칸 이동한다. 그리고 main에서 코드를 읽으면 읽기지점 q의 값을 읽고 q는 한칸 이동한다. p와 q모두 버퍼의 끝에 도달하면 버퍼의 처음으로 돌아가서 버퍼를 벗어나기 않고 계속해서 돌도록 만든다.연병장 뺑뺑이

p가 q를 따라잡으면 버퍼가 꽉 찬것이고 q가 p를 따라잡으면 더 이상 읽을 값이 없다는 소리가 된다.

이것을 코드로 구현하면 이렇게 된다.

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

int fifo8_get(struct FIFO8 *fifo)
/* FIFO로부터 데이터를 1개 가져온다 */
{
	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;
}

int fifo8_status(struct FIFO8 *fifo)
/* 어느 정도 데이터가 모여 있을까를 보고한다 */
{
	return fifo->size - fifo->free;
}


그리고 인터럽트 처리 부분도 이렇게 깔끔해 진다.

#define PORT_KEYDAT		0x0060

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

메인함수에서 대신 그래픽 처리를 맡게 된다.

/* bootpack의 메인 */

#include "bootpack.h"
#include <stdio.h>

extern struct FIFO8 keyfifo;

void HariMain(void)
{
	struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
	char s[40], mcursor[256], keybuf[32];
	int mx, my, i;

	init_gdtidt();
	init_pic();
	io_sti(); /* IDT/PIC의 초기화가 끝났으므로 CPU의 인터럽트 금지를 해제 */

	fifo8_init(&keyfifo, 32, keybuf);
	io_out8(PIC0_IMR, 0xf9); /* PIC1와 키보드를 허가(11111001) */
	io_out8(PIC1_IMR, 0xef); /* 마우스를 허가(11101111) */

	init_palette();
	init_screen8(binfo->vram, binfo->scrnx, binfo->scrny);
	mx = (binfo->scrnx - 16) / 2; /* 화면 중앙이 되도록 좌표 계산 */
	my = (binfo->scrny - 28 - 16) / 2;
	init_mouse_cursor8(mcursor, COL8_008484);
	putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16);
	sprintf(s, "(%d, %d)", mx, my);
	putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, s);

	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);
		}
	}
}


# 이제 마우스를 처리해본다! 일주일 걸려서 겨우 마우스...

마우스는 키보드에 비하면 상당히 신세대 물건이라고 볼 수 있다. 실제로 IRQ번호도 12번으로 1번인 키보드랑 거리가 꽤 있다. 그래서 초창기의 컴퓨터들은 마우스를 지원하지 않는 경우도 많았는데, 이런 이유로 인해 마우스는 기본장치가 아니였고 별도의 유효화를 거치지 않으면 작동하지 않는 기기였다. 따라서 마우스의 입력을 받기 위해서는 이러한 유효화 과정을 거쳐야 한다.

두 가지 유효화를 해주어야 하는데, 먼저 마우스 제어 회로를 유효화 시키고 그 다음에 마우스 자체를 유효화 시켜준다.


우스 제어 회로는 키보드 제어 회로(KBC: KeyBoard Controller) 안에 포함되어있으므로 키보드 제어 회로를 통해 제어한다. 먼저 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 wait_KBC_sendready(void)
{
	/* 키보드 컨트롤러가 데이터 송신이 가능하게 되는 것을 기다린다 */
	for (;;) {
		if ((io_in8(PORT_KEYSTA) & KEYSTA_SEND_NOTREADY) == 0) {
			break;
		}
	}
	return;
}

void init_keyboard(void)
{
	/* 키보드 컨트롤러의 초기화 */
	wait_KBC_sendready();
	io_out8(PORT_KEYCMD, KEYCMD_WRITE_MODE);
	wait_KBC_sendready();
	io_out8(PORT_KEYDAT, KBC_MODE);
	return;
}

wait_KBC_sendready가 init_keyboard에서 OUT 명령마다 들어가있는데, 이 함수는 0x0064에서 읽은 데이터 하위 2비트가 0인지를 검사해서 0이면 아직 KBC가 설정할 준비가 된 것이 아니므로 무한루프를 돌려서 기다리도록 만든다. 이는 CPU와 KBC의 속도 차이가 있기 때문이다.

0x60으로 모드를 설정할 준비를 하고 0x47로 마우스를 유효화 시킨다.


마우스의 유효화 역시 KBC를 통해서 한다.

#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)가 송신되어 온다 */
}

init_keyboard랑 거의 똑같다. 다만 0xD4로 마우스에 직접 신호를 보내게 하였으며 0xF4로 보내는 값도 다르다.


이제 키보드 때 처럼 인터럽트 함수를 만들어서 준비해주면... (IDT, 인터럽트 설정)

/* dsctbl.c */
void init_gdtidt(void)
{
	struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT;
	struct GATE_DESCRIPTOR    *idt = (struct GATE_DESCRIPTOR    *) ADR_IDT;
	int i;

	/* GDT의 초기화 */
	for (i = 0; i <= LIMIT_GDT / 8; i++) {
		set_segmdesc(gdt + i, 0, 0, 0);
	}
	set_segmdesc(gdt + 1, 0xffffffff,   0x00000000, AR_DATA32_RW);
	set_segmdesc(gdt + 2, LIMIT_BOTPAK, ADR_BOTPAK, AR_CODE32_ER);
	load_gdtr(LIMIT_GDT, ADR_GDT);

	/* IDT의 초기화 */
	for (i = 0; i <= LIMIT_IDT / 8; i++) {
		set_gatedesc(idt + i, 0, 0, 0);
	}
	load_idtr(LIMIT_IDT, ADR_IDT);

	/* IDT의 설정 */
	set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);
	set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);

	return;
}

/* int.c */
void inthandler2c(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 2C (IRQ-12) : PS/2 mouse");
	for (;;) {
		io_hlt();
	}
}

반응하기 시작하는 마우스


# 마우스가 반응을 하기 시작하니 데이터도 받아보도록 하자.

일단 마우스 전용 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;
}

그리고 키보드와는 별개의 루틴으로 따로 처리한다.

/* bootpack.c의 메인루틴 */
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);
		}
	}
}

(else if 구조를 안 쓰고 if문만으로 했으면 키보드와 마우스를 동시에 처리 할 수 있을텐데...한는 생각을 해본다)

마우스를 움직이거나 버튼을 누르면 반응을 한다!


오늘은 여기까지.
대학교는 시험기간은 한적한 주제에 오히려 평소가 더 바쁘니 원... 요즘 진도를 못 빼었더니 이틀에 하루치 하는 꼴... 힝...