MacOS环境-手写操作系统-13-鼠标中断

简介: MacOS环境-手写操作系统-13-鼠标中断

鼠标内核的中断处理

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

相关文章
|
11小时前
|
iOS开发 MacOS
MacOS环境-手写操作系统-40-进程消息通讯 和 回车键处理
MacOS环境-手写操作系统-40-进程消息通讯 和 回车键处理
9 2
|
13小时前
|
存储 Java C语言
MacOS环境-手写操作系统-04-实模式进入保护模式
MacOS环境-手写操作系统-04-实模式进入保护模式
6 1
|
12小时前
|
缓存 Java C语言
MacOS环境-手写操作系统-12-键盘中断机制
MacOS环境-手写操作系统-12-键盘中断机制为键盘建立中断机制
7 0
|
12小时前
|
存储 Java C语言
MacOS环境-手写操作系统-14-控制鼠标移动
MacOS环境-手写操作系统-14-控制鼠标移动
6 0
|
11小时前
|
存储 C语言 iOS开发
MacOS环境-手写操作系统-25-实现定时器
MacOS环境-手写操作系统-25-实现定时器
8 0
|
12小时前
|
存储 iOS开发 MacOS
MacOS环境-手写操作系统-24-消除鼠标闪烁
文章介绍了如何通过记录图层像素点对应的窗体编号来消除鼠标在窗口间移动时的闪烁现象。主要方法是在图层数据结构中添加变量map,存储每个像素点的窗体标识,并在窗口移动或尺寸改变时更新这个映射,从而避免不必要的窗口刷新,提高显示性能。
6 0
|
11小时前
|
存储 算法 调度
MacOS环境-手写操作系统-29-进程切换
MacOS环境-手写操作系统-29-进程切换
5 0
|
11小时前
|
iOS开发 MacOS
MacOS环境-手写操作系统-26-利用时钟开发光标闪烁
MacOS环境-手写操作系统-26-利用时钟开发光标闪烁
6 0
|
11小时前
|
算法 调度 iOS开发
MacOS环境-手写操作系统-31-进程自动切换
MacOS环境-手写操作系统-31-进程自动切换
6 0
|
11小时前
|
iOS开发 MacOS
MacOS环境-手写操作系统-37-切换窗口 键盘输入
MacOS环境-手写操作系统-37-切换窗口 键盘输入
7 0