第六天已经能够处理键盘中断了,但是还不能区分不同的按键,所以今天的第一个任务是把所按键的编码在画面上显示出来,并通过队列缓冲区来加快中断速度,然后激活鼠标,最后从鼠标接收数据
一、获取按键编码
bootpack.h节选
struct BOOTINFO { /* 0x0ff0-0x0fff */
char cyls; /* ブートセクタはどこまでディスクを読んだのか */
char leds; /* ブート時のキーボードのLEDの状態 */
char vmode; /* ビデオモード 何ビットカラーか */
char reserve;
short scrnx, scrny; /* 画面解像度 */
char *vram;
};
#define ADR_BOOTINFO 0x00000ff0
int.c节选
#define PORT_KEYDAT 0x0060
void inthandler21(int *esp)
{
struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO; //声明画面参数结构体指针
unsigned char data, s[4]; //data按键编码,s显示到画面上的字符串
io_out8(PIC0_OCW2, 0x61); //bootpack.h中定义了(#define PIC0_OCW2 0x0020)将“0x60+IRQ号码”输出给OCW2,通知PIC0“IPQ-01已经受理完毕”,执行完这句话后,PIC继续监视IRQ1中断是否发生,否则PIC不再监视IRQ中断
data = io_in8(PORT_KEYDAT); //从键盘(0x0060)获取按键编码
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;
}
二、优化中断并使用缓冲区
中断处理就是打断CPU本来的工作,这就要求中断要快速,一中在中断的过程中进行了字符显示,将字符显示从中断中分离会加快中断的处理速度,我们可以在中断处理(inthandler21)中将获取的按键编码放入缓冲区,由主函数(在bootpack.c中)偶尔去查看缓冲区,如果缓冲区中有数据,就将其显示。
缓冲区为一个循环队列,在键盘中断处理程序中加入按键编码,在显示程序中取出按键编码,我们将关于缓冲区的实现和操作单独放在一个文件fifo.c中,类似于面向对象语言中的封装,其他文件中再用到缓冲区,只需要调用缓冲区的函数就好,这样结构会更加清晰。
bootpack.h节选
struct FIFO8 {
unsigned char *buf;
int p, q, size, free, flags;
};
void fifo8_init(struct FIFO8 *fifo, int size, unsigned char *buf);
int fifo8_put(struct FIFO8 *fifo, unsigned char data);
int fifo8_get(struct FIFO8 *fifo);
int fifo8_status(struct FIFO8 *fifo);
fifo.c
#include "bootpack.h"
#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; /* 下一个数据写入位置 */
fifo->q = 0; /* 下一个数据读出位置 */
return;
}
int fifo8_put(struct FIFO8 *fifo, unsigned char data)
/* 向FIFO中传送数据并保存 */
{
if (fifo->free == 0) {
/* 空余没有了,溢出 */
fifo->flags |= FLAGS_OVERRUN; //将flags置1
return -1;
}
fifo->buf[fifo->p] = data; //如果容量未满,将按键编码放入缓冲区
fifo->p++;
if (fifo->p == fifo->size) { //写入的next已经到达缓冲区容量,则返回0,等同于fifo->p%=fifo->size
fifo->p = 0;
}
fifo->free--;
return 0;
}
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;
}
int fifo8_status(struct FIFO8 *fifo)
/* 报告目前缓冲区中有多少数据 */
{
return fifo->size - fifo->free;
}
int.c节选
#define PORT_KEYDAT 0x0060
struct FIFO8 keyfifo;
void inthandler21(int *esp)
{
unsigned char data;
io_out8(PIC0_OCW2, 0x61); //通知PIC0“IPQ-01已经受理完毕”
data = io_in8(PORT_KEYDAT); //从键盘(0x0060)获取按键编码
fifo8_put(&keyfifo, data); //将按键编码放入缓冲区
return;
}
bootpack.c节选
char s[40], mcursor[256], keybuf[32];
fifo8_init(&keyfifo, 32, keybuf);
for (;;) {
io_cli(); //屏蔽中断
if (fifo8_status(&keyfifo) == 0) { //缓冲区为空,执行STI和HLT
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);
}
}
三、激活鼠标
鼠标属于新兴的外部输入设备,这一点从分配给鼠标的中断编号上就可以看出,键盘为IRQ1,鼠标为IRQ12,刚刚出现鼠标时,几乎所有的操作系统都不支持鼠标,所以如果不执行激活鼠标的指令,就不会产生鼠标的中断信号,如果想要CPU接收鼠标的中断信号,需要执行指令让鼠标控制电路和鼠标本身有效。
鼠标控制电路包含在键盘控制电路里,如果键盘控制电路的初始化正常,鼠标电路控制器也就激活完成了。
bootpack.c节选
void enable_mouse(void);
void init_keyboard(void);
void HariMain(void)
{
init_keyboard();
enable_mouse();
}
#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) { //CPU的电路比键盘控制电路快,所以要先不断询问键盘控制电路是否准备好了,如果键盘控制电路可以接受CPU指令了,CPU从键盘控制电路(0x0064)中所读取的数据的倒数第二位应该是0,否则CPU不断循环查询
break;
}
}
return;
}
void init_keyboard(void)
{
/* 初始化键盘控制电路 */
wait_KBC_sendready(); //确认可以向键盘控制电路发送信息
io_out8(PORT_KEYCMD, KEYCMD_WRITE_MODE); //向键盘控制电路发送0x60(模式设定)
wait_KBC_sendready();
io_out8(PORT_KEYDAT, KBC_MODE); //向键盘发送0x47(利用鼠标)
return;
}
#define KEYCMD_SENDTO_MOUSE 0xd4
#define MOUSECMD_ENABLE 0xf4
void enable_mouse(void)
{
/* 激活鼠标 */
wait_KBC_sendready();
io_out8(PORT_KEYCMD, KEYCMD_SENDTO_MOUSE); //向键盘控制电路发送0xd4,下一个数据就会自动发送给鼠标
wait_KBC_sendready();
io_out8(PORT_KEYDAT, MOUSECMD_ENABLE); //向键盘(转发给鼠标)发送0xf4,激活鼠标
return; /* 键盘控制会返回ACK(0xfa) */
}
四、从鼠标接收数据
鼠标和键盘原理几乎相同,所以程序也非常相似。
int.c节选
struct FIFO8 mousefifo;
void inthandler2c(int *esp)
/* 来自PS/2鼠标的中断 */
{
unsigned char data;
io_out8(PIC1_OCW2, 0x64); /* 通知PIC1 IRQ-12的受理已经完成 */
io_out8(PIC0_OCW2, 0x62); /* 通知PIC0 IRQ-02的受理已经完成 */
data = io_in8(PORT_KEYDAT); //从键盘(0x0060)获取按键编码
fifo8_put(&mousefifo, data);//将按键编码放入鼠标缓冲区,激活鼠标后发送的第一个编码为0xfa
return;
}
bootpack.c节选
char s[40], mcursor[256], keybuf[32], mousebuf[128];
fifo8_init(&mousefifo, 128, mousebuf);
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);
}
}
}