系统的简单注解

来源:互联网 发布:小额贷款骗局知乎 编辑:程序博客网 时间:2024/06/09 15:51


该文件用于初始化鼠标中断、处理鼠标事件、在屏幕上绘制鼠标。

 

首先,我们来看看鼠标的控制方法。以下文字摘自哈尔滨工业大学的谢煜波:

根据PS/2协议,鼠标是由键盘的控制器(i8042)进行控制的,键盘控制器(i8042)总共有两个通道,一个通道由键盘使用,另一个通道由鼠标使用,我们对鼠标进行操作也是通过i8042芯片来完成的,因此,现在的重点就是了解并熟悉怎样对i8042进行编程,来完成对鼠标的控制。

i8042支持两种工作模式——AT模式及PS/2模式,这都是由IBM所定义的一些规范,i8042在计算机启动时会自动检测用户是否使用的支持PS/2协议的键盘及鼠标,以决定是否工作在PS/2模式下,现在我们假设我们使用的都是PS/2键盘及鼠标,因此,现在i8042工作在PS/2模式下(请记住这一点,即i8042可以工作在AT模式或者PS/2模式下,并且现在假设其工作在PS/2模式下,这在后面将会用到)。

i8042有关的I/O端口共有两个,一个是0x60端口,一个是0x64端口,如果我们想获得i8042的状态,我们需要读0x64端口,这将返回i8042中状态寄存器的内容。如果我们想向i8042发送命令,我们需要先把命令发送到0x64端口,然后紧接着把这个命令的参数发送到0x60端口,然后我们可以通过读0x60端口,获得i8042返回给我们的数据。

下面我们就来看看,应当发送什么样的命令去控制鼠标,这涉及到下面几个需要发送给i8042的命令:

0xA8命令:许可i8042的鼠标通道,即允许鼠标操作。

0xD4命令:把发往0x60端口的参数数据发给鼠标。

0x60命令:把发往0x60端口的参数数据写入i8042的控制寄存器。

从上面的分析我们可以基本窥见怎样操作鼠标。首先,我们应向i80420x64端口发送0xA8命令,以许可i8042的鼠标通道,以便完成对鼠标的操作。其次我们应向i80420x64端口发送0xD4命令,以通知i8042我们需要控制鼠标,并把控制鼠标的命令发到i80420x60端口,再从i80420x60端口取回鼠标发给我们的数据,这一过程无疑是比较简单的,我们先来看看,我们应向鼠标发送什么样的控制命令,然后再看看实际的代码。

控制鼠标的命令非常之多,比如0xFF命令可以让鼠标复位;0xFE命令可以让鼠标重新发送上次的数据包;0xF3命令可以设置鼠标的采样率,也即鼠标滑动时的灵敏度;0xF4命令可以允许鼠标向主机发送数据包等。这里最重要的就是0xF4命令,而其它的设置鼠标的命令我们暂时可以不用理会,因为使用默认值已经能很好的完成本实验了。要理解0xF4命令有什么作用,我们需要先了解一下鼠标的四种工作模式:Reset模式,Stream模式,Remote模式及Wrap模式。

首先是Reset模式,当鼠标初次加电或收到0xFF命令之后,鼠标就处于此模式,然后鼠标将进行一系列的初始化及检测工作,包括设定默认的采样率等,完成初始化极检测之后,鼠标将进入Stream模式。

Stream模式下,如果鼠标被移动,或者有键被按下,那么鼠标将向主机发送数据包,并提请一个中断请求,主机需要响应此中断,并在中断处理程序中取得鼠标发送的数据包。如果在Stream模式下,我们向鼠标发送0xF0命令,将使鼠标进入Remote模式。

Remote模式同Stream模式差不多,主要工作就是检测鼠标是否被移动及是否有键被按下,不过它与Stream模式的区别在于,它并不会主动的向主机提请中断请求,也即它不会主动的向主机发送数据包,而是被动的等待主机使用0xEB(读数据命令)后,再向主机提请中断请求,发送数据包。换句话说,如果在Remote模式下,你每次欲读数据时,均需要向鼠标发送0xEB命令,而如果是在Stream模式下,鼠标会自动向你发送数据。

Wrap模式主要用于检测鼠标与主机之间的连线是否正常,主机向它发送命令,它一般不会执行此命令,而只是简单的将此命令原封不同的回送给主机,主机可比较它发出的命令及接收到的命令是否一致,并以此来认定鼠标与主机之间的连线是否正常。

从上面的描述中我们可以看出,我们需要关心的只有Reset模式及Stream模式,但对于操作系统编写人员而非BIOS编写人员来说,真正需要关心的只有Stream模式,这是因为当计算机启动的时候,BIOS会自动检测鼠标,与鼠标进行通信,这个时候它会向鼠标发送0xFF(复位)命令,然后鼠标会自检,并通知主机自检是否正常,然后鼠标就将处于Stream模式,此时,鼠标已经开始检测鼠标是否移动及是否有键按下了,但是它不会立即就向主机发送数据,因为有可能主机还没有进入真正的操作系统,主机还正在启动中,因此,鼠标会等待主机的通知,直到主机给它发送0xF4命令后,它才开始向主机发送数据。

 

对于鼠标的中断控制字:

鼠标控制字为三个字节,分别代表了:命令、X轴的移动值、Y轴的移动值;

左键点击 09 00 00左键弹起 08 00 00

右键点击 0A 00 00左键弹起 08 00 00

下图是鼠标对应X/Y轴的方向,这个和我们在图形中使用的方向是不同的:Y轴的方向是反的。

 

 

  

当我们鼠标移动方向与上图方向不同时,得到的X的值或者Y的值是大于0x80的。

 


文件中的接口:

rtm_0x74_interrupt_handle:鼠标中断处理代码;

install_0x74_interrupt:安装0x74号中断(鼠标)

init_keyboard:初始化鼠标相关寄存器;

enable_mouse:启动鼠标;

move_mouse:从鼠标数据缓冲区获取数据,并在屏幕上移动鼠标;

redraw_mouse:绘制鼠标;


;===============================================================================
;=== 本程序包括了鼠标的主要功能:                                            ===
;=== 1.鼠标中断初始化                                                        ===
;=== 2.鼠标控制                                                              ===
;===============================================================================

;-------------------------------------------------------------------------------
rtm_0x74_interrupt_handle:              ;鼠标中断处理代码

     pushad

     mov al,0x64                        ;中断结束命令EOI
     out 0xa0,al                        ;向8259A从片发送
     mov al,0x62
     out 0x20,al                        ;向8259A主片发送

     mov ebx, [mouse_buf_wpnt]          ;写偏移
  
     in al, 0x60                        ;获取鼠标数据
     cmp al, 0xfa                       ;判断是否是标志字0xfa
     jnz _rtm_0x74_data_ok              ;如果不是
     mov edx, [mouse_enabled]           ;再判断是否鼠标已经启动成功
     cmp edx, 0x0                       ;是否已经启动成功
     jnz _rtm_0x74_data_ok              ;已经启动成功
     mov dword [mouse_enabled], 0x55    ;确实是启动成功标志,设置启动成功标志,跳转出去
     jmp _rtm_0x74_out  

_rtm_0x74_data_ok:        
     ;将收到的数据放入缓冲区
     mov [mouse_buf_addr + ebx], al     ;数据写入鼠标缓冲区
     inc ebx                            ;写指针加一
     cmp ebx, mouse_buf_end             ;判断写指针的位置
     jb _rtm_0x74_out                  ;如果未到了缓冲区尾部,结束
     xor ebx, ebx                       ;已到缓冲区尾部,写指针翻转
  
_rtm_0x74_out:  
     mov [mouse_buf_wpnt], ebx          ;保存新的写指针
     popad

     iretd

;-------------------------------------------------------------------------------
install_0x74_interrupt:                 ;安装0x74号中断(鼠标)
        
     ;配置鼠标相关寄存器
  call init_keyboard
 
     ;安装鼠标中断向量,开启鼠标中断 
     mov eax, rtm_0x74_interrupt_handle ;中断向量
     mov ebx, 0x74                      ;中断向量号
     mov ecx, 0x0100                    ;ch-1从片,cl-0对应bit0
  call install_XXX_interrupt
 
     ret

;-------------------------------------------------------------------------------
init_keyboard:                          ;初始化鼠标相关寄存器

_init_kb_check1:
     in al,0x64                         ;键盘缓冲区状态
     and al, 0x02
     jne _init_kb_check1
  
     mov al, 0x60
     out 0x64, al
_init_kb_check2:
     in al,0x64                         ;键盘缓冲区状态
     and al, 0x02
     jne _init_kb_check2  
  
     mov al, 0x47
     out 0x60, al  

     ret

;-------------------------------------------------------------------------------
enable_mouse:                          ;启动鼠标

_en_mouse_check1:
     in al,0x64                        ;键盘缓冲区状态
     and al, 0x02
     jne _en_mouse_check1
  
     mov al, 0xd4
     out 0x64, al
_en_mouse_check2:
     in al,0x64                        ;键盘缓冲区状态
     and al, 0x02
     jne _en_mouse_check2  
  
     mov al, 0xf4
     out 0x60, al
  
     ret
 
;-------------------------------------------------------------------------------
move_mouse:                             ;从鼠标数据缓冲区获取数据,并在屏幕上移动鼠标
        
     pushad

     cli                                ;关中断

_mouse_check_loop:
  
     ;判断缓冲区是否有数据
     mov eax, [mouse_buf_wpnt]          ;获取写指针
     mov ebx, [mouse_buf_rpnt]          ;获取读指针
     cmp eax, ebx                       ;读、写指针比较
     jz _check_finished                 ;如果读、写指针相同,退出   
  
     ;从缓冲区获取一个字符
     xor cx, cx                         ;cx清零
     mov cl, [mouse_buf_addr + ebx]     ;从读指针处获取一个字符
     inc ebx                            ;读指针后移
     cmp ebx, mouse_buf_end             ;读指针是否已经到缓冲区末尾
     jb _rpnt_wrt                       ;未到末尾,直接回写新的读指针值
     xor ebx, ebx                       ;读指针已经到了缓冲区末尾,清零                    
_rpnt_wrt:
     mov [mouse_buf_rpnt], ebx          ;保存新的读指针  

     ;根据解析状态来处理字符
     mov eax, [mouse_chk_state]         ;当前处理状态
     cmp eax, 1                         ;是否是状态1
     jz _handle_x                       ;处理x的值
     cmp eax, 2                         ;是否是状态2
     jz _handle_y                       ;处理y的值

     ;处理鼠标命令字 [mouse_chk_state] = 0
     mov byte [mouse_cmd], cl           ;保存命令字
     mov dword [mouse_chk_state], 1     ;状态变为1
     jmp _move_mouse_out           

     ;处理x的值 [mouse_chk_state] = 1
_handle_x:
     xor eax, eax
     mov ax, [mouse_x_pnt]              ;获取原鼠标x轴的值
     mov [mouse_x_old_pnt], ax          ;保存原x轴的值
     cmp cl, 0x80                       ;x的值是否大于0x80。如果x的值大于0x80,鼠标向左移动
     jb _x_mov_right
     not cl                             ;取反加一
     inc cl
     cmp cx, ax                         ;要判断x轴是否移动到了屏幕最左端
     jb _x_left_ok                      ;如果移动值小于原x轴的值
     mov cx, ax                         ;鼠标已经移到屏幕最左侧了,调整减数,使鼠标停留在屏幕最左侧
_x_left_ok:  
     sub ax, cx                         ;原x轴的值减去鼠标左移的值,得到新的x轴的值
     jmp _x_check_ok                    ;鼠标左移处理完毕,保存新值,并改变命令字状态
  
_x_mov_right:                           ;鼠标右移
     add ax, cx                         ;原鼠标x轴的值加上右移步数
     cmp ax, 318                        ;是否已经超过屏幕右侧
     jb _x_check_ok                     ;未超过,直接保存x轴的值即可
     mov ax, 318                        ;否则,让x的值保持在320  
_x_check_ok:  
     mov [mouse_x_pnt], ax              ;将新的x轴的值写入内存
     mov dword [mouse_chk_state], 2
     jmp _move_mouse_out
  
     ;处理y的值 [mouse_chk_state] = 2
_handle_y:
     xor eax, eax
     mov ax, [mouse_y_pnt]              ;获取原鼠标y轴的值
     mov [mouse_y_old_pnt], ax          ;保存原y轴的值
     cmp cl, 0x80                       ;y的值是否大于0x80。如果y的值大于0x80,鼠标向下移动
     jb _y_mov_up
     not cl
     inc cl
     add ax, cx                         ;要判断y轴是否移动到了屏幕最下端
     cmp ax, 198
     jb _y_down_ok                      ;如果移动值小于原y轴的值
     mov ax, 198                        ;鼠标已经移到屏幕最上端了,调整减数,使鼠标停留在屏幕最上侧
_y_down_ok:  
     jmp _y_check_ok                    ;鼠标上移处理完毕,保存新值,并改变命令字状态
  
_y_mov_up:                                  ;鼠标上移
     cmp ax, cx                         ;原鼠标y轴的值加上下移步数
     jae _y_up_ok                       ;是否已经超过屏幕最下端了
     mov cx, ax
_y_up_ok: 
     sub ax, cx                         ;否则,让的值保持在199  
_y_check_ok:  
     mov [mouse_y_pnt], ax              ;将新的y轴的值写入内存
     mov dword [mouse_chk_state], 0  
        
     ;在屏幕上绘制鼠标
     call redraw_mouse
  
     ;判断是否点击了鼠标左键
     cmp byte [mouse_cmd], 0x09
     jnz _move_mouse_out                ;如果没有点击鼠标,继续判断后续
     cmp word [mouse_y_pnt], 100        ;如果没有点击在屏幕的下半部分,退出
     jb _move_mouse_out
     cmp word [mouse_x_pnt], 160
     jb _click_other_app                ;x轴小于160,对应APP1,否则是APP0
     mov byte [USR1_PROC + proc_ctrl.cmd], 0x55 ;APP0被点击了
     jmp _move_mouse_out   
_click_other_app:
     mov byte [USR2_PROC + proc_ctrl.cmd], 0x55 ;APP1被点击了
_move_mouse_out:       
     jmp _mouse_check_loop              ;在缓冲区中数据处理完毕之前,死循环

_check_finished:
     sti                                ;开中断
  
     popad  
        
     ret
 
;--------------------------------全局变量---------------------------------------
redraw_mouse:                           ;在屏幕上绘制鼠标

     pushad
 
     ;覆盖旧的坐标点
     xor eax, eax  
     mov bx, [mouse_y_old_pnt]
     mov ax, 320
     mul bx
     xor ebx, ebx
     mov bx, [mouse_x_old_pnt]
     mov cl, [mouse_old_point]
     mov byte [pic_mem_addr + eax + ebx], cl          ;左上
     mov cl, [mouse_old_point + 1]
     mov byte [pic_mem_addr + eax + ebx + 1], cl   ;右上
     mov cl, [mouse_old_point + 2]
     mov byte [pic_mem_addr + eax + ebx + 320], cl    ;左下
     mov cl, [mouse_old_point + 3]    
     mov byte [pic_mem_addr + eax + ebx + 321], cl    ;右下
 
     ;按照新的坐标绘图  
     xor eax, eax  
     mov bx, [mouse_y_pnt]
     mov ax, 320                                      ;y轴做乘法(y×320)
     mul bx
     xor ebx, ebx
     mov bx, [mouse_x_pnt]
     mov cl, [pic_mem_addr + eax + ebx]               ;记录被覆盖点的颜色(左上)
     mov [mouse_old_point], cl
     mov cl, [mouse_draw_color]
     mov byte [pic_mem_addr + eax + ebx], cl
     mov cl, [pic_mem_addr + eax + ebx + 1]           ;记录被覆盖点的颜色(右上)
     mov [mouse_old_point + 1], cl
     mov cl, [mouse_draw_color + 1]
     mov byte [pic_mem_addr + eax + ebx + 1], cl
     mov cl, [pic_mem_addr + eax + ebx + 320]         ;记录被覆盖点的颜色(左下)
     mov [mouse_old_point + 2], cl
     mov cl, [mouse_draw_color + 2]
     mov byte [pic_mem_addr + eax + ebx + 320], cl
     mov cl, [pic_mem_addr + eax + ebx + 321]         ;记录被覆盖点的颜色(右下)
     mov [mouse_old_point + 3], cl
     mov cl, [mouse_draw_color + 3] 
     mov byte [pic_mem_addr + eax + ebx + 321], cl    ;被覆盖点用蓝色填写    

  ;鼠标4个点的颜色互换,作出闪烁效果
     mov dl, [mouse_draw_color]
     mov dh, [mouse_draw_color + 1]
     mov [mouse_draw_color], dh
     mov dh, [mouse_draw_color + 2]
     mov [mouse_draw_color + 1], dh 
     mov dh, [mouse_draw_color + 3]
     mov [mouse_draw_color + 2], dh
     mov [mouse_draw_color + 3], dl
 
     popad 
     ret 
 
;--------------------------------全局变量---------------------------------------
align 4
    
     ;下列变量用于鼠标控制
     mouse_cmd        db 0,0,0,0               ;鼠标命令字
     mouse_old_point  db 0,0,0,0               ;鼠标的显示是一个小正方形,4个点,需要4个字节保存
     mouse_draw_color db 1,2,3,4               ;鼠标4个点的颜色(红绿黄蓝) 
        
     mouse_x_pnt      dw 159                   ;鼠标当前在屏幕上的x值
     mouse_y_pnt      dw 60                    ;鼠标当前在屏幕上的y值
     mouse_x_old_pnt  dw 159                   ;鼠标在原屏幕上的x值
     mouse_y_old_pnt  dw 60                    ;鼠标在原屏幕上的y值
  
     mouse_enabled    dd 0x0                   ;鼠标是否初始化成功
     mouse_buf_wpnt   dd 0x0                   ;鼠标数据写指针
     mouse_buf_rpnt   dd 0x0                   ;鼠标数据读指针
     mouse_chk_state  dd 0x0                   ;处理数据数据时的状态标志   

align 4
 
 


0 0
原创粉丝点击