戏说BIOS之CMOS
1. Introduction
CMOS全称为complementary metal oxide semiconductor, 翻译成中文就是互补金属氧化物半导体,它是由一颗小的纽扣电池供电的128/256 bytes ram(现在的chipset通常提供256 bytes或者更大的空间)。它主要用于存放RTC以及一些oem的系统配置信息,所以除了RTC等部分其它的很多信息都是undocumented& non-standard。RTC 标准的(documented&standard) ram bank如下表 1 所示:
Index |
Name |
00h |
Seconds |
01h |
Seconds Alarm |
02h |
Minutes |
03h |
Minutes Alarm |
04h |
Hours |
05h |
Hours Alarm |
06h |
Day of Week |
07h |
Day of Month |
08h |
Month |
09h |
Year |
0Ah |
Register A |
0Bh |
Register B |
0Ch |
Register C |
0Dh |
Register D |
0Eh-7Fh |
114 Bytes of User RAM |
表 1
2. Access Cmos
访问cmos通常是透过70h,71h这两个IO port实现的,有些chipset支援256 bytes的cmos ram,访问128bytes以后的空间需要开启chipset的始能register,有些chipset使用72h,73h访问扩展的空间如intel chipset,有些仍然使用70h,71h如sis chipset,因为这部分是非标准的,故后面的练习程序就不去读写这部分ram space。读写cmos的过程非常简单,读特定的index的内容只需要将index送给70h,然后就可以从71h读出对应的数据,具体过程如下述code所示:
;-------------------------------------------------------------
; read_cmos
; read the contents of a specific CMOS register
; call with: al = CMOS address to read
; returns: ah = Contents of register
; used registers: ax
;-------------------------------------------------------------
read_cmos proc near
cli
or al,80h ;disable NMI
out 70h, al
call io_delay
in al, 71h
call io_delay
mov ah, al
xor al,al
out 70h,al ;enable NMI
sti
ret
read_cmos endp
写操作和读类似,只是要将待写入的数据送给71h即可代码如下所示:
;-------------------------------------------------------------
; write_cmos
; write the contents of a specific CMOS register
; call with: al = CMOS address to write
; ah = Contents of register
; returns: NULL
; used registers: ax
;-------------------------------------------------------------
write_cmos proc near
cli
or al,80h ;disable NMI
out 70h,al
call io_delay
mov al,ah
out 71h,al
call io_delay
xor al,al
out 70h,al ;enable NMI
sti
ret
write_cmos endp
另外有些细节需要注意:a.读写过程中都需要关掉中断以防止,中断处理程序访问CMOS以及RTC更新过程中可能会导致并发访问。b.NMI(non-maskable interrupt)是一种中断向量为2的中断,但是与常规中断不同的是它不能通过mask register屏蔽掉而且sti,cli指令也对它无效;NMI通常用于一些无法恢复的硬件错误,访问CMOS时也可能产生NMI,所以需要关掉。NMI可以通过70h bit7做开关。c.状态寄存器A bit7记录了RTC是否正在更新,如果正在更新则等到更新结束再去读RTC(我写的cmosdump因为偷懒没有检查这一个bitJ)。
3. Msg Based Event Driven
知道了以上的知识,我们就有能力写一个类似RU中dump cmos的工具了,下图1就是我写的cmosdump.exe:
我觉得访问cmos本身并不困难,画个UI倒是挺费劲的,一个劲狂call vbios;在完成这支tool的过程中我更深刻的体会到知识是相通的了,windows编程的经验在这里发挥了优势,为了能够动态更新,实时修改我就借鉴了windows下的“基于消息,事件驱动”的机制Mainloop->GetMsg->TranslateMsg->DispatchMsg一路下来好不快活!这部分的代码如下所示:
mainloop:
call show_index
call show_cmos
input_msg:
mov ah,0
int 16h
cmp ah,01h ;esc
je exit
cmp ah,48h ;up arrow
je up
cmp ah,50h ;down arrow
je down
cmp ah,4bh ;left arrow
je left
cmp ah,4dh ;right arrow
je right
call input_byte
cmp bl,1
jne msg_loop
mov ch,ah
mov ah,0
int 16h
cmp al,0dh ;enter
je enter
jmp msg_loop
enter:
call get_index
mov ah,ch
mov al,INDEX
call write_cmos
msg_loop:
jmp mainloop
up:
cmp ROW,MINROW
jbe roll_up
dec ROW
jmp bypass_up
roll_up:
mov ROW,MAXROW
bypass_up:
call set_cursor
jmp mainloop
down:
cmp ROW,MAXROW
jae roll_down
inc ROW
jmp bypass_down
roll_down:
mov ROW,MINROW
bypass_down:
call set_cursor
jmp mainloop
left:
cmp COL,MINCOL
jbe roll_left
sub COL,3
jmp bypass_left
roll_left:
mov COL,MAXCOL
bypass_left:
call set_cursor
jmp mainloop
right:
cmp COL,MAXCOL
jae roll_right
add COL,3
jmp bypass_right
roll_right:
mov COL,MINCOL
bypass_right:
call set_cursor
jmp mainloop
exit:
call clr_screen
mov ax,4c00h
int 21h
以上就是cmosdump.exe的核心架构J,完成以后觉得使用asm好别扭啊,可能是c/c++写的太多了,有点适应不过来了,以后还是要多写asm,增强驾驭asm的能力,让我的asm和c/c++一样熟练。最后开放cmosdump.exe完整的source code供有兴趣的朋友参考,source code和可执行文件在这里下载。
Enjoy it!
That’s all!
Peter