1.为什么要有BIOS这个东西
BIOS实际就是ROM里面的一段小程序(芯片总容量大概就是几M),在主板通电时候会硬件加载执行。基本功能就是进行硬件检测,检查没问题就引导bootloader和系统。
为什么需要这个?
第一个原因,即使只有一块主板,什么外部连接的存储介质(磁盘,U盘等存储)都没有(更别说操作系统),这种情况下,一个主板其实也应该能进行一些硬件检查( 甚至是设置),这就是BIOS存在并且嵌入到主板的一个原因。再者,不同主板不一样的硬件配置,对应的bios代码也不一样。不过两个不同bios,对外提供统一的一些统一的操作硬件的接口,他们遵循bios规范【http://blog.chinaunix.net/uid-27033491-id-3239348.html】,以前的操一些操作系统例如DOS就是直接使用bios提供的硬件接口访问硬件,现在的linux访问硬件就是绕过bios访问硬件,下面我们会测试通过bios去访问硬件。
2.bios加载MBR扇区
首先主板通电,硬件自动加载bios程序。bios程序检查硬件,初始化和设置硬件,然后加载磁盘(启动盘)的MBR扇区到0x7c00,然后跳到0x7c00执行。为什么是0x7c00?可以看【http://www.ruanyifeng.com/blog/2015/09/0x7c00.html】。主要是兼容历史的原因。
3.什么是启动扇区?
一个磁盘分为多个磁面,一个磁面一个head(读写磁头),一个磁面多个磁道(track),一个磁道多个扇区(sector),一个扇区512字节。其中0磁头0磁盘第一个扇区称为这个磁盘的第一个扇区。如果这个扇区(512字节)最后两个字节是0xaa55.识别这个扇区是MBR主启动扇区。
现在我们就来测试一下bios去加载MBR扇区的流程。
我们这里使用redhat7.2的qemu虚拟化来做实验最为简单。
首先新建一块1G的虚拟磁盘:
qemu-img create -f qcow2 /home/disk.qcow2 2G
使用已有磁盘新建一台虚拟机,启动虚拟机,查看屏幕输出信息:
现在设置磁盘的第一个扇区为启动扇区MBR.
#vim mbr.asm
start:
times (510-($-$$)) db 0 ;510字节前面都设置为0,$表示当前地址,$$表示起始地址
dw 0xaa55 ;511和512字节设置为AA55,表示启动扇区
Device Drivers --->
[*] Block devices --->
Network block device support
# make
#insmod drivers/block/nbd.ko max_part=8
# qemu-nbd -c /dev/nbd0 /home/disk.qcow2 ----------------虚拟磁盘文件挂在为真正的磁盘设备
# dd if=./mbr of=/dev/nbd0 bs=1 count=512 ----------------把上面的程序mbr写入磁盘的头512字节,也就是第一个扇区
#qemu-nbd -d /dev/nbd0
重启启动虚拟机,虚拟机的屏幕输出:
这说明bios已经识别到磁盘的MBR分区并且已经把MBR分区加载到了0x7c00地址执行。
3.利用执行MBR扇区的代码实现键盘操作功能。
现在处理器的控制权已经在mbr扇区代码的手里。现在我们就可以通过mbr扇区的代码去控制我们的处理器,进而操作我们的主板。
现在我们就实现键盘的操作功能,其实bios已经有相关的键盘的接口提供。但是我们目前先不用它的接口。我们直接操作我们的键盘。
目前我的键盘是ps2接口的键盘, 使用的ps2控制器是Intel的8042.
所以我们先链接一下ps2接口,以及连接的电路图。
键盘链接ps2插头:
电脑ps2控制器连接图:
上面两个图整合为一个整体就是:
我们cpu可以控制ps2控制器8042进而控制我们的键盘。所以我们需要了解一下ps2的协议以及8042的芯片资料。其中ps2协议可以了解一下时钟信号和数据信号(电信号)的一些传输协议,当然这些信号我们只需要了解就行,因为这些信号ps2控制器可以帮我们处理,我们只要操作ps2控制器。ps2协议可以自己百度,这里只提供一下数据传输的信号:
电信号采集数据要不就是高电平(二进制1),要不就是低电平(二进制0),上面8个数据刚好可以组成一个字节的数据。控制器就是通过这些信号和键盘进行通信的。当然,这是控制器的工作,我们可以重点不放在这里【有兴趣可以自己百度一下"ps2协议"或者"ps2 protocol"】。我们重点看如何操作ps2控制器。现在我们看一下8042控制器的操作:
这里有一篇不错的介绍文章:http://blog.chinaunix.net/uid-25099259-id-3409632.html
8042控制器的寄存器有四个:状态寄存器,输出缓存控制器,输入缓冲控制器,控制寄存器。我们通过处理器操作端口就可以控制8042寄存器:
IO Port | Access Type | Purpose |
---|---|---|
0x60 | Read/Write | Data Port |
0x64 | Read | Status Register |
0x64 | Write | Command Register |
关于寄存器的 每一个位的意思,可以看一下这个文章【http://wiki.osdev.org/%228042%22_PS/2_Controller】,里面有详细的寄存器信息介绍,还有如何初始化和使用控制器。我们就参考这边文章来操作的我们的寄存器。由于汇编不好,代码可能写的有点不精简。
开始之前我们需要先要调试工具。例如读取到数据我们如何知道数据是什么?
通过显示是最直观的。显示暂时没有去研究,我们可以直接使用bios提供的显示接口。例如打印0xaa,怎么打印?代码如下
代码写的有点挫,熟悉的人可以修改一下:
start: jmp run
tran_ascii:;数字转为对应的ASCII
cmp al,9
jle add_30h
jmp add_57h
add_30h:
add al,0x30;eg:0的ASCII 0x30
ret
add_57h:
add al,0x57;eg:a的ASCII 0x61
ret
print_8bit:;dl has data need print,eg:0xAB
push ax
push bx
push cx
push dx
xor ax,ax ;clear
xor bx,bx
mov al,dl ;被除数
mov bl,16 ;除数
div bl;AL存储除法操作的商,AH存储除法操作的余数
;把要打印的数据先保存起来
mov di,need_print
stosw
;显示高四位
mov ah,0x09
mov bh,0 ;显示页码
mov cx,1 ;重复输出字符的次数
call tran_ascii
int 0x10;功能描述:在当前光标处按指定属性显示字符
;移动光标到下一处
mov ah,0x03;读取当前光标位置
int 0x10
add dl,1;移动光标到下一处
mov ah,0x02
mov bh,0
int 0x10
;显示低四位
mov si,need_print
lodsw
mov al,ah
mov ah,0x09
mov cx,1 ;
call tran_ascii
int 0x10
pop dx
pop cx
pop bx
pop ax
ret
need_print: DW 0x00
run:
mov dl,0x1b
call print_8bit
times (510-($-$$)) db 0
有了这个我们就可以打印出控制器端口读出来的数据了。下面开始真正编码操作ps2控制器:
start: jmp run
;
;数字转为对应的ASCII
tran_ascii:
cmp al,9
jle add_30h
jmp add_57h
add_30h:
add al,0x30;eg:0的ASCII 0x30
ret
add_57h:
add al,0x57;eg:a的ASCII 0x61
ret
print_8bit:
push ax
push bx
push cx
push dx
;dl has data need print,eg:0xAB
xor ax,ax ;clear
xor bx,bx
mov al,dl ;被除数
mov bl,16 ;除数
div bl;AL存储除法操作的商,AH存储除法操作的余数
;save
mov di,need_print
stosw
;移动光标到下一处
mov ah,0x03;读取当前光标位置
int 0x10
add dl,1;移动光标到下一处
mov ah,0x02
mov bh,0
int 0x10
;显示high四位
mov si,need_print
lodsw
mov ah,0x09
mov bh,0 ;显示页码
mov cx,1 ;重复输出字符的次数
call tran_ascii
int 0x10;功能描述:在当前光标处按指定属性显示字符
;移动光标到下一处
mov ah,0x03;读取当前光标位置
int 0x10
add dl,1;移动光标到下一处
mov ah,0x02
mov bh,0
int 0x10
;显示low四位
mov si,need_print
lodsw
mov al,ah
mov ah,0x09
mov cx,1 ;
call tran_ascii
int 0x10
pop dx
pop cx
pop bx
pop ax
ret
wait_data:
in al,0x64
and al,1b
jz wait_data
in al,0x60
ret
need_print: DW 0x00
run:
cli
;Disable Devices
mov al,0xad
out 0x64,al
;Flush The Output Buffer
flush_data:
in al,0x64
and al,0x01
jz flush_ok
in al,0x60
jmp flush_data
flush_ok:
;Set the Controller Configuration Byte
mov al,0x20;read configure
out 0x64,al
in al,0x60
mov dl,al
add dl,11101100b;disabled interrupt,use poll mode
mov al,0x60;set configure
out 0x64,al
mov al,dl
out 0x60,al
;Perform Controller Self Test
mov al,0xaa
out 0x64,al
call wait_data
cmp al,0x55
jne err
;Enable Devices
mov al,0xae ;first port for keyboard
out 0x64,al
test_port_1:
mov al,0xAB
out 0x64,al
call wait_data
cmp al,0x00
jne err
read_input:
call wait_data
mov dl,al
call print_8bit
jmp read_input
err:
times (510-($-$$)) db 0
dw 0xaa55
上面就是键盘输入:bear和ctl+alt的扫描码输出。
各个按键的扫描码可以查看:http://wiki.osdev.org/PS/2_Keyboard
0x30 | B pressed |
0xB0 | B released |
0x12 | E pressed |
0x92 | E released |
0x1E | A pressed |
0x9E | A released |
0x13 | R pressed |
0x93 | R released |
0x1D | left control pressed |
0x38 | left alt pressed |
0xB8 | left alt released |
0x9D | left control released |