DAY8:鼠标控制与32位模式切换
1.鼠标解读
我们在移动鼠标时,会同时传来三个参数。
如上图所示,屏幕中出现“38 FE FF”这3字节数字,其中“38”(即mouse_dbuf[0])其中“3”会在0~3范围内变化。同时,只有点击(左击,右击或中间滚轮时)鼠标会改变“8”那部分,变化范围为8 ~ F间变化。其中“FE”部分(mouse_dbuf[1])与鼠标左右移动有关。“FF“部分(mouse_dbuf[2])则与鼠标上下移动有关。
因此,我们在解读鼠标数据时,要注意三个部分(mouse_dbuf 这个数组)
struct MOUSE_DEC {
unsigned char buf[3], phase;
int x, y, btn;//其中x,y存放移动信息,btn存放鼠标按键状态。
};
/*******鼠标字节解码**********/
int mouse_decode(struct MOUSE_DEC *mdec, unsigned char dat)
{
if (mdec->phase == 0) {
/* 等待鼠标的0xfa的阶段 */
if (dat == 0xfa) {
mdec->phase = 1;
}
return 0;
}
if (mdec->phase == 1) {
/* 等待鼠标第一字节的阶段 */
if ((dat & 0xc8) == 0x08) {
/* 如果第一字节正确 (具体参考前文加粗部分)*/
mdec->buf[0] = dat;
mdec->phase = 2;
}
return 0;
}
if (mdec->phase == 2) {
/* 等待鼠标第二字节的阶段 */
mdec->buf[1] = dat;
mdec->phase = 3;
return 0;
}
if (mdec->phase == 3) {
/* 等待鼠标第三字节的阶段 */
mdec->buf[2] = dat;
mdec->phase = 1;
mdec->btn = mdec->buf[0] & 0x07;//0x07=0000 0111 &运算取出低三位,对应前文的“8”
mdec->x = mdec->buf[1];
mdec->y = mdec->buf[2];
if ((mdec->buf[0] & 0x10) != 0) {
mdec->x |= 0xffffff00;
}
if ((mdec->buf[0] & 0x20) != 0) {
mdec->y |= 0xffffff00;
}
mdec->y = - mdec->y; /* 鼠标的y方向与画面符号相反 */
return 1;
}
return -1; /* 应该不会到这儿来 */
}
将x和y的第8位及第8位以后全部都设成1,或全部都保留为0。这样就能正确地解读x和y。在解读处理的最后,对y的符号进行了取反的操作。这是因为,鼠标与屏幕的y方向正好相反,为了配合画面方向,就对y符号进行了取反操作。
2.移动鼠标指针
鼠标的解读工作完成,接下来的工作是让鼠标指针在屏幕上面动起来。
/* 鼠标指针的移动 */
boxfill8(binfo->vram, binfo->scrnx, COL8_008484, mx, my, mx + 15, my +
15); /* 隐藏鼠标 */
mx += mdec.x;
my += mdec.y;
/*防止鼠标跑出去*/
if (mx < 0) {
mx = 0;
}
if (my < 0) {
my = 0;
}
if (mx > binfo->scrnx - 16) {
mx = binfo->scrnx - 16;
}
if (my > binfo->scrny - 16) {
my = binfo->scrny - 16;
}
sprintf(s, "(%3d, %3d)", mx, my);
boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 0, 79, 15); /* 隐藏坐
标 */
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, s); /* 显示坐
标 */
putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16); /* 描画鼠标 */
}
}
运行的还行。
3.通往32位模式之路
下面开始学习asmhead.nas程序:
; haribote-os boot asm (声明语句)
; TAB=4
BOTPAK EQU 0x00280000 ; bootpack地址
DSKCAC EQU 0x00100000 ;
DSKCAC0 EQU 0x00008000 ;
; BOOT_INFO信息
CYLS EQU 0x0ff0 ;
LEDS EQU 0x0ff1
VMODE EQU 0x0ff2 ;
SCRNX EQU 0x0ff4 ; X方向解析度
SCRNY EQU 0x0ff6 ; y方向解析度
VRAM EQU 0x0ff8 ; VRAM 地址
ORG 0xc200 ; 程序开始的地方
; 画面设定
MOV AL,0x13 ; VGA显示设置320x200x8bit
MOV AH,0x00
INT 0x10
MOV BYTE [VMODE],8 ; 记录画面模式(参照C语言)
MOV WORD [SCRNX],320
MOV WORD [SCRNY],200
MOV DWORD [VRAM],0x000a0000
; 传达BIOS键盘的LED状态
MOV AH,0x02
INT 0x16 ; keyboard BIOS
MOV [LEDS],AL
; PIC关闭一切中断
; 根据AT兼容机的规格,如果要初始化PIC,
; 必须在CLI之前进行,否则有时会挂起。
; 随后进行PIC的初始化。
MOV AL,0xff
OUT 0x21,AL
NOP ; 如果连续执行OUT指令,有些机种会无法正常运行
OUT 0xa1,AL
CLI ; 禁止CPU级别的中断
; 为了让CPU能够访问1MB以上的内存空间,设定**A20GATE**(具体参考下文的文字部分)
CALL waitkbdout
MOV AL,0xd1
OUT 0x64,AL
CALL waitkbdout
MOV AL,0xdf ; enable A20
OUT 0x60,AL
CALL waitkbdout
;切换到保护模式
[INSTRSET "i486p"] ; 即“想使用486指令”
LGDT [GDTR0] ; 设定临时GDT
MOV EAX,CR0
AND EAX,0x7fffffff ; 设bit31为0(为了禁止分页)
OR EAX,0x00000001 ; 设bit0为1(为了切换到保护模式)
MOV CR0,EAX ;通过CR0切换到保护模式时,要马上执行JMP指令(下文有详细解释)
JMP pipelineflush
pipelineflush:
MOV AX,1*8 ; 可读写的段 32bit
MOV DS,AX
MOV ES,AX
MOV FS,AX
MOV GS,AX
MOV SS,AX
; bootpack的转送
MOV ESI,bootpack ; 传送源
MOV EDI,BOTPAK ; 传送目的地
MOV ECX,512*1024/4
CALL memcpy
; 磁盘数据最终转送到它本来的位置去
; 首先从启动扇区开始
MOV ESI,0x7c00 ; 传送源
MOV EDI,DSKCAC ; 传送目的地
MOV ECX,512/4
CALL memcpy
; 所有剩下的
MOV ESI,DSKCAC0+512 ; 传送源
MOV EDI,DSKCAC+512 ; 传送目的地
MOV ECX,0
MOV CL,BYTE [CYLS]
IMUL ECX,512*18*2/4 ;从柱面数变换为字节数/4
SUB ECX,512/4 ; 减去 IPL
CALL memcpy ;memcpy(转送源地址, 转送目的地址, 转送数据的大小);
; 必须由asmhead来完成的工作,至此全部完毕
; 之后由bootpack来完成
; bootpack的启动
MOV EBX,BOTPAK
MOV ECX,[EBX+16]
ADD ECX,3 ; ECX += 3;
SHR ECX,2 ; ECX /= 4;
JZ skip ; 没有要传送的东西
MOV ESI,[EBX+20] ; 传送源
ADD ESI,EBX
MOV EDI,[EBX+12] ; 传送目的地
CALL memcpy
skip:
MOV ESP,[EBX+12] ; 栈初始值
JMP DWORD 2*8:0x0000001b
waitkbdout:
IN AL,0x64
AND AL,0x02
IN AL,0x60 ;空读(为了清空数据接收缓冲区中的垃圾数据)
JNZ waitkbdout ; AND的结果如果不是0,就跳到waitkbdout
RET
memcpy:
MOV EAX,[ESI]
ADD ESI,4
MOV [EDI],EAX
ADD EDI,4
SUB ECX,1
JNZ memcpy ; 减法运算的结果如果不是0,就跳转到memcpy
RET
; memcpy如果不忘记输入地址大小前缀,也可以用串命令来写
ALIGNB 16 ;一直添加DBO知道凑成能被16整除的地址,方便向段寄存器复制
GDT0:
RESB 8 ; NULL selector
DW 0xffff,0x0000,0x9200,0x00cf ; 可以读写的段(segment)32bit
DW 0xffff,0x0000,0x9a28,0x0047 ; 可以执行的段(segment)32bit(bootpack
用)
DW 0
GDTR0:
DW 8*3-1
DD GDT0
ALIGNB 16
bootpack:
目前系统的内存分布图:
新知识说明:
其中A20GATE这条信号线能使内存的1MB以上的部分变成可使用的状态。但为了兼容旧版的操作系统(16位),在执行激活指令之前,电路被限制为只能使用1MB内存。和
鼠标的情况很类似。A20GATE信号线正是用来使这个电路停止从而让所有内存都可以使用的东西。
INSTRSET指令:是为了能够使用386以后的LGDT,EAX,CR0等关键字。
LGDT指令:把随意准备的GDT给读进来。
保护模式:在这种模式下,应用程序既不能随便改变段的设定,又不能使用操作系统专用的段。操作系统受到CPU的保护。
**通过代入CR0而切换到保护模式时,要马上执行JMP指令。因为变成保护模式后,机器语言的解释要发生变化。CPU为了加快指令的执行速度而使用了管道(pipeline)这一机制,就是说,前一条指令还在执行的时候,就开始解释下一条甚至是再下一条指令。因为模式变了,就要重新解释一遍,所以加入了JMP指令。
okk结束