1.获取按键编码
接着上次改善一下程序,让程序在按下一个键时,让程序把所按键的编码在画面上显示出来。
#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); /* 通知PIC0 IRQ-01 已经受理完毕 */
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;
}
io_out8(PIC0_OCW2, 0x61); 这句话用来通知PIC已经知道发生了IRQ1中断了。将“0x60+IRQ号码” 输出给OCW2就可以。执行这句话之后,PIC继续时刻监视着IRQ1中断是否发生。如果不执行PIC就不在监视IRQ1中断,下次不管由键盘输入什么信息,都感知不到。
这里说一下ICW和OCW:
(1) 初始化命令字ICW(initialization command word):ICW1~ICW4,它们必须在初始化时分别写入4个相应的寄存器。并且一旦写入,一般在系统运行过程中就不再改变。
(2) 工作方式命令字或操作命令字OCW(operation command word):OCW1~OCW3,它们必须在设置初始化命令后方能分别写入3个相应的寄存器。它们用来对中断处理过程进行动态的操作与控制。在一个系统运行过程中,操作命令字可以被多次设置。
这样,就可以运行了。不知道是不是我的电脑问题,运行书中的源文件,按下再松开按键之后并没有显示其他数字…太让人头大了…(所显示的数字是键盘扫描码)
2.加快中断处理
为了提高中断处理的速度,先将按键的编码接收下来,保存到变量里,然后由HariMain偶尔去查看这个变量。如果发现有了数据就显示出来。
int.c节选:
struct KEYBUF {
unsigned char data, flag; //flag=0 缓冲区为空,flag=1 缓冲区有数据 满了
};
struct KEYBUF keybuf;
void inthandler21(int *esp)
{
unsigned char data;
io_out8(PIC0_OCW2, 0x61); /* 通知PIC0 IRQ-01 已经受理完毕 */
data = io_in8(PORT_KEYDAT);
if (keybuf.flag == 0) {
keybuf.data = data;
keybuf.flag = 1;
}
return;
}
定义了一个结构体用来冲到缓冲区的作用。
bootpack.c中HariMain节选
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);
}
}
开始要先屏蔽中断,因为在执行后续的处理时,如果又中断进来就会乱套。
在屏蔽中断期间所做的处理非常少,中断处理程序本身做的事情也非常少,这正是我们期待的。这样操作系统才会变得比较利索。
3.制作FIFO缓冲区 & 4.改善FIFO缓冲区
制作FIFO缓冲区 是在上面的基础上做了一个存储多字节的缓冲区,但在读数据是却需要移动操作。所以下面是改善的FIFO缓冲区介绍。
int.c节选:
写数据
struct KEYBUF {
unsigned char data[32];
int next_r, next_w, len;
};
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;
}
读数据
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);
}
}
这样就没有数据移动的操作了。
5.整理FIFO缓冲区
为了让缓冲区具有通用性,可以将缓冲区大小设为可变的,几个字节都行。
struct FIFO8 {
unsigned char *buf;
int p, q, size, free, flags; /*size为缓冲区总字节数,free为缓冲区中没有数据的字节数,
buf为缓冲区地址,p为下一个数据写入地址,q为下一个数据读出地址 */
};
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记录是否溢出
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取得一个数据 */
{
int data;
if (fifo->free == fifo->size) {
/* 如果缓冲区为0 则返回-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;
}
中断处理程序代码:
void inthandler21(int *esp)
{
unsigned char data;
io_out8(PIC0_OCW2, 0x61); /* 通知PIC IRQ-01的受理已经完成 */
data = io_in8(PORT_KEYDAT);
fifo8_put(&keyfifo, data);
return;
}
读取数据显示:
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);
}
}
这样便显得程序代码简单明了。
6.总算讲到鼠标了 & 7.从鼠标接受数据
早期的时候,虽然在主板上做了鼠标用的电路,但只要不执行激活鼠标的指令。
事实上鼠标控制电路包含在键盘控制电路里,如果键盘控制电路初始化正常完成,鼠标电路控制器的激活也就完成了。
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;
}
init_keyboard的任务是,一边确认可否往键盘控制电路传送信息,一边发送模式控制指令,指令中包含着要设为何种模式,模式设定的指令是0x60,利用鼠标模式的模式号码是0x47。
在HariMain中调用init_keyboard,鼠标控制电路就准备好了。
所谓发送鼠标激活指令,归根到底还是要向键盘控制器发送指令。
#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) */
}
make run一下,鼠标就出来了。
鼠标中断已经正常了,下面来取出中断数据,其原理与键盘类似。
鼠标中断处理程序:
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);
fifo8_put(&mousefifo, data);
return;
}
IRQ12是从PIC的第4号,首先要通知IRQ12已经受理完成,然后再通知主PIC,这是因为主从PIC的协调不能自动完成,如果程序不教给主PIC该怎么做,它就会忽视从PIC的下一个中断请求。
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);
}
}
}
这样,键盘和鼠标的中断及数据处理就完成了。