今天的任务是让鼠标真正移动起来,之前的路走的真是艰辛呀。终于到走到这里啦,下面正式开始了——
鼠标解读(harib05a)
昨天我们已经能从鼠标取得数据了,接着要读取这些数据,看看鼠标是怎么移动的,在根据鼠标的动作,让鼠标指针动起来。
先对bootpack.c的HariMain函数做一些修改:
unsigned char mouse_dbuf[3], mouse_phase;
enable_mouse();
mouse_phase = 0; /* 进入到等待鼠标的0xfa的状态 */
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();
if (mouse_phase == 0) {
/* 等待鼠标0xfa的状态 */
if (i == 0xfa) {
mouse_phase = 1;
}
} else if (mouse_phase == 1) {
/* 等待鼠标的第一字节 */
mouse_dbuf[0] = i;
mouse_phase = 2;
} else if (mouse_phase == 2) {
/* 等待鼠标的第二字节 */
mouse_dbuf[1] = i;
mouse_phase = 3;
} else if (mouse_phase == 3) {
/* 等待鼠标的第三字节 */
mouse_dbuf[2] = i;
mouse_phase = 1;
/* 鼠标的3字节都齐了,显示出来 */
sprintf(s, "%02X %02X %02X", mouse_dbuf[0], mouse_dbuf[1], mouse_dbuf[2]);
boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 32 + 8 * 8 - 1, 31);
putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s);
}
}
}
}
这段程序首先是将0xfa舍弃,每次鼠标送来的数据都是三个一组,每当数据累积到3,就显示到屏幕上。
变量mouse_phase用来记住接收鼠标数据的工作进展到了什么阶段(phase),接收到的数据存放在mouse_dbuf[0~2]中。
运行一下看看——
尝试移动鼠标,发现后面三个数字会变化。仔细观察会发现,移动鼠标时,“28”部分(mouse_dbuf[0])的“2”位,会在0~3范围内变化。
仅移动鼠标,“28”部分的“8”不会变化,只有点击鼠标时才会变化,这个值会在8~F范围内变化。“12”部分(mouse_dbuf[1])与鼠标的左右移动相关,“34”部分(mouse_dbuf[2])与鼠标的上下部分相关。
HariMain函数整理(harib05b)
HariMain函数有点乱,我们先来整理一下
struct MOUSE_DEC {
unsigned char buf[3], phase;
};
void enable_mouse(struct MOUSE_DEC *mdec);
int mouse_decode(struct MOUSE_DEC *mdec, unsigned char dat);
void HariMain(void)
{
(略)
struct MOUSE_DEC mdec;
(略)
enable_mouse(&mdec);
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();
if (mouse_decode(&mdec, i) != 0) {
/* 3字节都凑齐了,把它们都显示出来 */
sprintf(s, "%02X %02X %02X", mdec.buf[0], mdec.buf[1], mdec.buf[2]);
boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 32 + 8 * 8 - 1, 31);
putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s);
}
}
}
}
}
void enable_mouse(struct MOUSE_DEC *mdec)
{
/* 鼠标有效 */
wait_KBC_sendready();
io_out8(PORT_KEYCMD, KEYCMD_SENDTO_MOUSE);
wait_KBC_sendready();
io_out8(PORT_KEYDAT, MOUSECMD_ENABLE);
/* 顺利的话,ACK(0xfa)会被送过来 */
mdec->phase = 0; /* 等待0xfa的阶段 */
return;
}
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) {
/* 等待鼠标第一字节的阶段 */
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;
return 1;
}
return -1; /* 应该不可能到这里来 */
}
我们创建了一个结构体MOUSE_DEC(DEC是decode的缩写),将读取鼠标信息的各个变量都放在这个结构体里。因为鼠标已经激活了,在函数enable_mouse的最后,附加了将phase归零的处理,将读到的0xfa舍去。我们将读取鼠标信息的函数从HariMain函数里抽离出来,放到了mouse_decode函数里。
测试运行一下“make run”,没问题~
读取鼠标信息(harib05c)
首先对mouse_decode函数进行修改,bootpack.c节选:
struct MOUSE_DEC {
unsigned char buf[3], phase;
int 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;
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、btn,分别用于存放移动信息和鼠标按键状态。x和y需要使用第一字节中对鼠标移动有反应的几位,将x和y的第8位及之后全部设备1,就能正确读取x和y了。在最后对符号y进行了取反操作,鼠标与屏幕的y方向正好相反,为了保持一致对y符号进行了取反操作。这样读取鼠标信息的部分就完成了,我们再来修改一下显示部分。
HariMain节选:
} 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();
if (mouse_decode(&mdec, i) != 0) {
/* 数据的3个字节都齐了,显示出来吧 */
sprintf(s, "[lcr %4d %4d]", mdec.x, mdec.y);
if ((mdec.btn & 0x01) != 0) {
s[1] = 'L';
}
if ((mdec.btn & 0x02) != 0) {
s[3] = 'R';
}
if ((mdec.btn & 0x04) != 0) {
s[2] = 'C';
}
boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 32 + 15 * 8 - 1, 31);
putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s);
}
}
最后的三个if语句中的第一个if语句可以理解为,如果mdec.btn的最低位是1,就将小写字符置换成大写字符。
make run一下看看——
运行正常,我们移动一下鼠标。
点击一下鼠标:
移动鼠标(harib05d)
我们修改一下图形显示部分,让鼠标指针在屏幕上动起来 。
HariMain节选:
} else if (fifo8_status(&mousefifo) != 0) {
i = fifo8_get(&mousefifo);
io_sti();
if (mouse_decode(&mdec, i) != 0) {
/* 数据的3个字节都齐,显示出来 */
sprintf(s, "[lcr %4d %4d]", mdec.x, mdec.y);
if ((mdec.btn & 0x01) != 0) {
s[1] = 'L';
}
if ((mdec.btn & 0x02) != 0) {
s[3] = 'R';
}
if ((mdec.btn & 0x04) != 0) {
s[2] = 'C';
}
boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 32 + 15 * 8 - 1, 31);
putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s);
/* 鼠标指针的移动 */
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); /* 描画鼠标 */
}
}
鼠标指针移动部分,先是隐藏鼠标指针。然后在鼠标指针的坐标上,加上得到的位移量。这里不能让鼠标指针跑到屏幕外面,进行了调整。
运行一下make run——
经历了GDT/IDT/PIC初始化、使用栈、FIFO缓冲区、处理键盘后,鼠标指针终于完成了!还挺有成就感的。
但是最后还是有点问题,还是留到后面解决吧。
分析asmhead.nas
我们之前一直都没有说明asmhead.nas中的程序,现在正好具体看一下。
asmhead.nas节选:
; PIC关闭一切中断
; 根据AT兼容机的规格、如果要初始化PIC
; 必须在CLI之前进行,否则有时会挂起
; 随后进行PIC的初始化
MOV AL,0xff
OUT 0x21,AL
NOP ; 如果连续执行OUT指令,有些机种会无法正常运行
OUT 0xa1,AL
CLI ; 禁止CPU级别的中断
上面的程序等同于以下内容的C程序:
io_out(PIC0_IMR,0xff); /*禁止主PIC的全部中断*/
io_out(PIC1_IMR,0xff); /*禁止从PIC的全部中断*/
io_cli(); /*禁止CPU级别的中断*/
如果CPU进行模式转换时进来了中断信号,会带来麻烦。PIC初始化时也不允许有中断发生,因此我们把中断全部屏蔽掉。
NOP指令什么都不做,只是让CPU休息一个时钟周期。
; 为了让CPU能够访问1MB以上的内存空间,设定A20GATE
CALL waitkbdout
MOV AL,0xd1
OUT 0x64,AL
CALL waitkbdout
MOV AL,0xdf ; enable A20
OUT 0x60,AL
CALL waitkbdout
上段程序等同于下面的C语言程序:
#define KEYCMD_WRITE_OUTPORT 0xd1
#define KBC_OUTPORT_A20G_ENABLE 0xdf
/* A20GATE的设定 */
wait_KBC_sendready();
io_out8(PORT_KEYCMD,KEYCMD_WRITE_OUTPORT);
wait_KBC_sendready();
io_out8(PORT_KEYCMD,KEYCMD_OUTPORT_A20G_ENABLE);
wait_KBC_sendready(); /*这句话是为了等待完成执行指令*/
程序的基本结构与init_keyboard完全相同,功能只是往键盘控制电路发送指令。
这里发送的指令,是指令键盘控制电路的附属端口输出0xdf。
通过这个连接主板上很多地方的附属端口,发送不同的指令,就可以实现各种各样的控制功能。
输出0xdf所要完成的功能,是让A20GATE信号线变成ON的状态,A20GATE信号线是使得内存1MB以上的部分变成可使用状态。
"wait_KBC_sendready();"是为了等待A20GATE的处理切实完成。
; 切换到保护模式
[INSTRSET "i486p"] ; “想要使用486指令”的叙述
LGDT [GDTR0] ; 设定临时GDT
MOV EAX,CR0
AND EAX,0x7fffffff ; 设bit31为0(为了禁止分页)
OR EAX,0x00000001 ; 设bit0为1(为了切换到保护模式)
MOV CR0,EAX
JMP pipelineflush
pipelineflush:
MOV AX,1*8 ; 可读写的段 32bit
MOV DS,AX
MOV ES,AX
MOV FS,AX
MOV GS,AX
MOV SS,AX
INSTRSET指令,是为了能够使用386以后的LGDT、EAX、CR0等关键字。
LGDT指令,是把任意的GDT读进来。
CR0(control register 0)是一个非常重要的寄存器,只有操作系统才能操作它。
保护模式与先前的16位模式不同,段寄存器的解释操作系统受到CPU的保护,因此成为保护模式。保护模式分为两种,一种是带保护的16位模式,另一种是带保护的32位模式,我们使用的是,带保护的32位模式。在变成保护模式之后,有些地方会发生变化。变化之一是机器语言的解释方式变了。
也就是说,前一条指令还在执行的时候就开始解释下一条指令了,但是因为模式变了,就需要重新解释一遍,因此使用JMP指令。另一个变化是段寄存器的含义,不再是乘以16以后再加算的意思,而是除了CS以外所有段寄存器的值都从0x0000变成了0x0008(相当于“gdt+1”的段)。CS保持不变是为了避免混乱。
asmhead.nas节选
; 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
上边的程序也可以类似写成如下的C语言程序:
memcpy(bootpack, BOTPAK, 512*1024/4);
memcpy(0x7c00, DSKCAC, 512/4);
memcpy(DSKCAC0+512, DSKCAC+512, cyls*512*18*2/4-512/4);
函数memcpy是复制内存的函数,语法如下:
memcpy(转送源地址,转送目的地址,转送数据的大小);
转送数据大小是以双字节为单位,因此数据大小用字节数除以4来指定。
中间一条程序:
memcpy(0x7c00, DSKCAC, 512/4);
DSKCAC是0x00100000,上句的含义是从0x7c00复制到512字节,正好是将启动扇区复制到1MB以后的内存中去。
下一条:
memcpy(0x7c00, DSKCAC, 512/4);
就是讲开始于0x00008200的磁盘内容,复制到0x00100200里。
第一条:
memcpy(bootpack, BOTPAK, 512*1024/4);
这句是将bootpack.hrb复制到0x00280000号地址的处理。
接着往后看,
asmhead.nas节选:
; 必须由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
SHR指令是向右移位指令。
JS(jump if zero)是条件跳转指令,根据前面的结果是否为0来决定是否跳转,为0就跳转。
asmhead.nas节选:
waitkbdout:
IN AL,0x64
AND AL,0x02
JNZ waitkbdout ; AND的结果如果不是0,就跳到waitkbdout
RET
它与wait_KBC_sendready相同,添加了从0x60号设备进行IN的处理内容。也就是说,如果控制器里有键盘代码或者鼠标代码,就把他们读出来。
asmhead.nas节选:
ALIGNB 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:
ALIGB指令,意思是一直添加DBO,直到时机合适为止。这里如果最初地址能被16整除,ALIGB指令不做任何处理。
GDT0也是一个特别的指令,0号是空区域(null sector),不能在那里定义段。
到此为止,asmhead.nas的说明就结束了。
在最初,GDT在asmhead.nas里,而不在0x00270000~0x0027ffff。
这时IDT没有设定,仍处于中断禁止状态,这时应该放开中断接收数据。
因此在HariMain里,在调色版palette初始化和画面准备之前,先重新创建GDT和IDT,初始化PIC,并执行“io_sti()”。
HariMain函数节选:
void HariMain(void)
{
struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
char s[40], mcursor[256], keybuf[32], mousebuf[128];
int mx, my, i;
struct MOUSE_DEC mdec;
init_gdtidt();
init_pic();
io_sti(); /* IDT/PIC的初始化已经完成,于是开放CPU中断 */
fifo8_init(&keyfifo, 32, keybuf);
fifo8_init(&mousefifo, 128, mousebuf);
io_out8(PIC0_IMR, 0xf9); /* 开放PIC1和键盘中断(11111001) */
io_out8(PIC1_IMR, 0xef); /* 开放鼠标中断(11101111) */
init_keyboard();
init_palette();
init_screen8(binfo->vram, binfo->scrnx, binfo->scrny);
最后看一下目前系统的内存分布图——
https://gitee.com/mint1993/myos.git
|