鼠标内核的中断处理
1.简介
如果大家还记得描述8259A中断控制器那一小节的话
鼠标发送中断信号的数据线在从8259A芯片的IRQ4信号线
因此 为了接收鼠标中断信号 我们在初始化中断控制芯片时 必须启用该信号线
同时 从8259A芯片是通过主8259A的IRQ2信号线连接在一起的
所以也必须同时启动主8259A芯片的IRQ2信号线
这样 我们在内核中要对init8259A代码段做一些改动
init8259A: ... mov al, 11111001b ;允许键盘中断 out 021h, al call io_delay mov al, 11101111b ;允许鼠标中断 out 0A1h, al call io_delay ret
mov al, 11111001b 这一句指令 启用了主8259A芯片的IRQ1和IRQ2两根信号线
mov al, 11101111b 这句指令启用了从8259A的IRQ4信号线 这根信号线就是用来发送鼠标信号的
只要是外接硬件 要想使用 就得对其进行配置和初始化
硬件的初始化 一般就是对给定端口发送几个数据而已 鼠标自然也不例外
2.代码
鼠标电路的初始化
鼠标电路对应的一个端口是 0x64
通过读取这个端口的数据来检测鼠标电路的状态 内核会从这个端口读入一个字节的数据
如果该字节的第二个比特位为0 那表明鼠标电路可以接受来自内核的命令
因此 在给鼠标电路发送数据前 内核需要反复从0x64端口读取数据 并检测读到数据的第二个比特位 知道该比特位为0时 才着手发送控制信息
代码如下:
#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() { for(;;) { if ((io_in8(PORT_KEYSTA) & KEYSTA_SEND_NOTREADY) == 0) { break; } } }
for循环一直从端口读取数据 然后检测比特位 只有对应比特位是0时 才返回
大家看到 上面代码中 居然有一个端口是 0x60
你可能会困惑 0x60不是键盘电路的端口吗?
没错 鼠标的初始化 就是得通过键盘电路来实现的
当对应比特位为0 也就是鼠标可以接收数据了
这时候 我们就得通过向端口0x60发送数据来配置鼠标
void init_keyboard(void) { wait_KBC_sendready(); io_out8(PORT_KEYCMD, KEYCMD_WRITE_MODE); wait_KBC_sendready(); io_out8(PORT_KEYDAT, KBC_MODE); return; }
上面代码中 先等待0x64端口返回可写信号
然后继续向端口发送一个字节数据 这个字节数值是 0x60 该数据让键盘电路进入数据接收状态
紧接着向端口0x60发送一个字节的数据0x47 这个数据要求键盘电路启动鼠标模式
这样 鼠标硬件所产生的数据信息 都可以通过键盘电路端口0x60读到
至于为什么鼠标会跟键盘电路勾搭在一起 我也不清楚 也不知道当时IBM的设计人员是怎么想的(哈哈)
当我们想向鼠标发送数据时
先向端口发送一个字节的数据 改数据的值是0xd4
完成这一步后 任何向端口0x60写入的数据都会被传送给鼠标:
#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; }
io_out8(PORT_KEYCMD, KEYCMD_SENDTO_MOUSE)
这一句就是向端口0x64写入一个字节的数据 即0xd4
然后o_out8(PORT_KEYDAT, MOUSECMD_ENABLE)
这一句是向端口0x60写入一字节数据 该数据的数值为0xf4
这个数据会被键盘电路发送给鼠标 该数据的作用是对鼠标进行激活 鼠标一旦接收到该数据后 立马向CPU发送中断信号
如果这时候 我们设置好鼠标的中断处理函数的话 相关函数的代码就会被CPU执行
我们先看看如何设置鼠标的中断函数
LABEL_IDT: %rep 33 Gate SelectorCode32, SpuriousHandler,0, DA_386IGate %endrep .021h: Gate SelectorCode32, KeyBoardHandler,0, DA_386IGate %rep 10 Gate SelectorCode32, SpuriousHandler,0, DA_386IGate %endrep .2CH: Gate SelectorCode32, mouseHandler,0, DA_386IGate
我们在初始化8259A芯片时 将主8259A的初始中断向量设置为0x20,把从8259A的初始中断向量设置为0x28
由于鼠标中断信号线是从8259A的IRQ4
所以鼠标的中断向量就是0x28 + 4 = 0x2C
从上面代码看来 鼠标的中断处理函数叫mouseHandler
我们看看它的代码:
_mouseHandler: mouseHandler equ _mouseHandler - $$ push es push ds pushad mov eax, esp push eax call intHandlerForMouse pop eax mov esp, eax popad pop ds pop es iretd
我们又调用了来自C语言实现的函数叫intHandlerForMouse
我们再看看其实现:
void intHandlerForMouse(char* esp) { char*vram = bootInfo.vgaRam; int xsize = bootInfo.screenX, ysize = bootInfo.screenY; showString(vram, xsize, 0, 0, COL8_FFFFFF, "PS/2 Mouse Handler"); for(;;) { io_hlt(); } }
3.编译运行
编译C文件
i386-elf-gcc -m32 -fno-asynchronous-unwind-tables -s -c -o write_vga_desktop_mouse_init.o write_vga_desktop_mouse_init.c
反汇编o文件
./objconv -fnasm write_vga_desktop_mouse_init.o write_vga_desktop_mouse_init.asm
删除无用部分
修改kernel
%include "write_vga_desktop_mouse_init.asm"
修改boot(直接放大一点)直接读了18个扇区 肯定够用了
mov AL, 18 ; AL 表示要练习读取几个扇区
编译boot
nasm -o boot.bat boot.asm
编译kernel
nasm -o kernel.bat kernel.asm
运行java 生成system.img
4.缓存机制的改进
鼠标激活后 只要鼠标稍微有点移动 它都会向CPU发送大量的坐标数据
因此内核要能够把鼠标发送的数据合适的存储起来 以便中断函数进行相应的处理
我们上面提供的缓存机制不够灵活 因为缓存空间只限定在32字节 这对鼠标来说是不够用的
这里我们对原有机制进行改进,以便用于处理鼠标发送的信息:
struct FIFO8 { unsigned char* buf; int p, q, size, free, flags; }; void fifo8_init(struct FIFO8 *fifo, int size, unsigned char *buf) { fifo->size = size; fifo->buf = buf; fifo->free = size; fifo->flags = 0; fifo->p = 0; fifo->q = 0; return ; } #define FLAGS_OVERRUN 0x0001 int fifo8_put(struct FIFO8 *fifo, unsigned char data) { 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) { int data; if (fifo->free == fifo->size) { 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; }
FIFO8 是用于数据缓存的结构体 里面的buf可以根据不同的需求进行变换
如果用于键盘缓冲 可以通过fifo8_init设置32字节的内存
如果用于键盘缓存 也可以通过fifo8_init设置128字节的缓存用于鼠标
修改后代码为:
write_vga_desktop_fifo.c (这里我就不编译运行了)
5.从鼠标接受数据
在原有的鼠标中断处理函数中做如下改进:
static struct FIFO8 keyinfo; static struct FIFO8 mouseinfo; static char keybuf[32]; static char mousebuf[128]; void CMain(void) { .... fifo8_init(&mouseinfo, 128, mousebuf); .... int data = 0; for(;;) { io_cli(); if (fifo8_status(&keyinfo) + fifo8_status(&mouseinfo) == 0) { io_stihlt(); } else if(fifo8_status(&keyinfo) != 0){ io_sti(); data = fifo8_get(&keyinfo); char* pStr = charToHexStr(data); static int showPos = 0; showString(vram, xsize, showPos, 0, COL8_FFFFFF, pStr); showPos += 32; } else if (fifo8_status(&mouseinfo) != 0) { show_mouse_info(); } } } void show_mouse_info() { char*vram = bootInfo.vgaRam; int xsize = bootInfo.screenX, ysize = bootInfo.screenY; unsigned char data = 0; io_sti(); data = fifo8_get(&mouseinfo); char* pStr = charToHexStr(data); static int mousePos = 16; if (mousePos <= 256) { showString(vram, xsize, mousePos, 16, COL8_FFFFFF, pStr); mousePos += 32; } } void intHandlerForMouse(char* esp) { unsigned char data; io_out8(PIC1_OCW2, 0x20); io_out8(PIC_OCW2, 0x20); data = io_in8(PORT_KEYDAT); fifo8_put(&mouseinfo, data); }
在入口函数CMain 中 先初始化鼠标的缓存结构体
在intHandlerForMouse中 PIC1_OCW2 的值是0xA0 也就是从8259A芯片的端口
PIC_OCW2是主8259A芯片的端口 每当中断处理后 要想再次接收中断信号 就必须向中断控制器发送一个字节的数据
这个字节数据叫OCW2 它值得我们详细了解下:
OCW2[0-2] 用来表示中断的优先级
OCW2[3-4]这两位必须设置为0
OCW[5]这一位称之为End of Interrupt
这一位设置为1 表示当前中断处理结束 控制器可以继续调用中断函数处理到来的中断信号 要想下一次继续处理中断信号 这一位必须设置为1
OCW2[6-7]这两位我们不用关心 设置为0即可 我们代码中发送OCW2时的数值是0x20 也就是仅仅把OCW[5]设置为1即可
接着把鼠标发送的数据从端口0x60读取 并通过fifo8_put写入到鼠标缓冲区中
在CMain中,通过
if (fifo8_status(&keyinfo) + fifo8_status(&mouseinfo) == 0)
判断键盘或鼠标缓冲区是否有数据到达
如果有 上面的if判断就会成立 成立后 要进一步判断是数据在键盘缓冲区还是鼠标缓冲区
如果是在鼠标缓冲区 则调用show_mouse_info将数据显示到桌面上
6.最终编译运行
编译C文件
i386-elf-gcc -m32 -fno-asynchronous-unwind-tables -s -c -o write_vga_desktop.o write_vga_desktop.c
反汇编o文件
./objconv -fnasm write_vga_desktop.o write_vga_desktop.asm
删除无用部分
修改kernel
%include "write_vga_desktop.asm"
修改boot(直接放大一点)直接读了18个扇区 肯定够用了
mov AL, 18 ; AL 表示要练习读取几个扇区
编译boot
nasm -o boot.bat boot.asm
编译kernel
nasm -o kernel.bat kernel.asm
运行java 生成system.img