peterhu 发表于 2009-7-6 16:05:24

戏说BIOS

戏说BIOS之Hello BIOS


“我所知道的EC”系列基本结束了,现在我终于可以有空玩一玩BIOS了。可是需要特别强调的是我是一名EC而不是BIOS,所以我看BIOS的视角可能会不够专业,也不够正统,这也是为什么我将这个系列取名为”戏说BIOS”的原因。可能有些朋友会觉得我不务正业,身为EC却去玩什么BIOS(呵呵…我本来就是不务正业J),我觉得PC本身是一个非常复杂的系统,从经验来看很多问题都不是孤立的,通常会牵扯到很多的环节,因此如果将视野局限于自己的一亩三分地则很有可能见树不见林,看不清问题的本质。所以我觉得全面细致的理解系统的各个环节非常有必要;而且我的技术目标也是希望通过n年的努力能够贯通PC系统从EC->BIOS->OS->DRIVER->APP的整个链条,把握系统的运作的脉络。关于这个“戏说BIOS”系列,我打算先练习一些BIOS新人学习作业,如:cmos dump,kbc access,pci scan,smbiosdump等等;然后在对我感兴趣的一些BIOS的领域做一些study。现在让我向BIOS世界打个招呼吧: “Hello BIOS,I am coming!”。
data segment

HelloBIOS db 'Hello BIOS,I am coming!$'

data ends

code segment

assume cs:code,ds:data

start:

mov ax,data

mov ds,ax

mov dx,offset HelloBIOS

mov ah,9

int 21h

mov ax,4c00h

int 21h

code ends

end start

That’s all!
Peter

[ 本帖最后由 peterhu 于 2009-7-8 09:13 编辑 ]

peterhu 发表于 2009-7-6 16:07:54

戏说BIOS之CMOS

1. Introduction

CMOS全称为complementary metal oxide semiconductor, 翻译成中文就是互补金属氧化物半导体,它是由一颗小的纽扣电池供电的128/256 bytesram(现在的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 ;enableNMI

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 ;enableNMI

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:


我觉得访问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

cmpah,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:

callset_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:

movCOL,MINCOL

bypass_right:

call set_cursor

jmp mainloop


exit:

callclr_screen

movax,4c00h

int21h

以上就是cmosdump.exe的核心架构J,完成以后觉得使用asm好别扭啊,可能是c/c++写的太多了,有点适应不过来了,以后还是要多写asm,增强驾驭asm的能力,让我的asm和c/c++一样熟练。最后开放cmosdump.exe完整的source code供有兴趣的朋友参考,source code和可执行文件在附件下载。
Enjoy it!


That’s all!

Peter

[ 本帖最后由 peterhu 于 2009-7-6 16:17 编辑 ]

peterhu 发表于 2009-7-6 16:26:28

戏说BIOS之Keyboard

1. Introduction



Keyboard System看起来好像挺简单,但事实上它远比想象中的复杂,硬件上Keyboard System需要两颗cpu完成key stroke的转换以及和Host的通信过程,一颗用于处理keyboard的make&break过程,另一颗作为keyboard controller和host交换信息。一次按键过程在软件的层面上也要经过多次转化才能成为最终被用户理解的ASCII码。这个过程通常需要经历ma=>mv=>set2=>Set1=>ASCII。Keyboard System的架构框图如下图1所示:

   
这时MB中常见的架构,在NB中这部分已经被放入EC之中成为EC的一个部分KBC,但是工作原理依旧如此。

2. How It Works?

那么当我们按下一个键,需要做哪些动作,才能让我们看到最终的字符呢?
听我慢慢道来。当我们按下一个键‘k’时(make),键盘内部的8031会将k的set2 scan code‘2Ch’
通过上图1的串行连接送给8042,8042会查一张set2转set1的表将该set2 scan code转成 set1的‘14h’,而且8042会引发IRQ1通知host,表示有按键事件发生。Host将会读取60Port获取set1的scancode‘14h’,而后host会将‘14h’转化为ASCII码‘k’,当我们松开一个键时过程同按下比较像了,不过键盘内部的8031会先送‘F0h’,然后再送‘2Ch’给8042,8042看到‘f0h’会将Set1的‘14h’的bit7设置为1即94h,以表示这是一个break。Host端也会收到中断IRQ1,可是host通常不处理break code。Make&Break

key也被称之为通码和断码。最终host会将set1以及ASCII码放在BDA 之中。Host端对于键盘系统处理分为以下几类:a.字符键b.功能键c.控制键d.双态键e.特殊功能键,对于这几种不同按键host处理方式也会有所不同对于c&d host会在BDA中置flag;对于a host会保存set1和ASCII码在BDA之中(大小写根据控制键的flag确定);b会影响到set1的值;对于e host可能会通过中断调用相关的function。
下图2显示host的处理流程:
         

接下来我将分别用C和ASM演示BIOS处理keyboard system的大致过程代码的原理是通过hook int9接管BIOS的中断处理过程,然后读取EC的6064 port获得kbc的data和status,并转为ASCII码显示出来,有一个要注意的地方就是EOI,EOI是特指8259或者8259兼容设备的中断清除指令。需要在中断服务程序结束之前向8259发送EOI指令,如果在中断程序一开始就发送EOI指令的话,中断服务程序一旦比较大,运行时间较长,可能会产生中断嵌套,严重会造成死机。如果中断服务程序结束之后还没有发送EOI指令的话,那么以后将屏蔽该IRQ以及优先级低于该IRQ的所有中断。我最初就没有送EOI,害得我调试了好久。
C代码如下所示:

#include <dos.h>
#include <graphics.h>
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
#include <bios.h>

void interrupt new_int9_handler(); /* interrupt prototype */
void interrupt (*old_int9_handler)(); /* interrupt function pointer */
unsigned char ESC_Press_Flag = 0;
unsigned char fifo={0};
unsigned char start=0;
unsigned char stop=0;

int main(void)
{

printf("Used to test keyboard set1 scancode\n");

printf("@author:peterhu\t\t@Version 1.0\n");

printf("Copyright(C) LGPL\t to Quit\n");

          /* store old interrupt vector */

old_int9_handler = getvect(9);
          /* set up new interrupt handler */

setvect(0x09,new_int9_handler);

          while(1)

{
                   if(ESC_Press_Flag)
                           break;

                   while(stop != start)

{

printf("[%.2x],",fifo);

stop = (++stop)%0x10;

}

}


setvect(0x09,old_int9_handler);

clrscr();
          return 1;
}

void interrupt new_int9_handler()
{
          unsigned char status;
          unsigned char set1;

disable();


status = inportb(0x64);
          if(status & 0x01)

{

set1 = inport(0x60);

fifo = set1;

start = (++start) % 0x10;
                   if(set1 == 0x01)


ESC_Press_Flag = 1;



//printf("[%.2x],",set1);

}

outportb(0x20,0x20);


enable();
}

ASM代码如下所示:
data segment

SET1 db 0
H2A   db [','0','0',']','$'
MSGdb 'Used to test keyboard set1 scancode',0Ah,0Dh,'@author:peterhu',09h,09h,'@Version
            1.0',0Ah,0Dh,'Copyright(C) LGPL',09h,' to Quit',0Ah,0Dh,'$'
OLDINT9 dd 0
ESCPR db 0

data ends

code segment

assume cs:code,ds:data

start:


mov ax,data

mov ds,ax

mov ax,1ch

call hex2asi

call show_set1

call show_title

call back_int9

call install_int9

l0:

xor cx,cx

mov cl,SET1

jcxz nokey

xor ax,ax

mov al,SET1

call hex2asi

call show_set1

mov SET1,00h

nokey:

mov cl,ESCPR

jcxz l0

call restore_int9

mov ax,4c00h

int 21h

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;sub routine for store and show set1 scancode
;;for keybord strok maybe something error :/
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

int9_handler:
cli

in al,64h

and al,01h

cmp al,01h

jne exit9

in al,60h

mov ah,00h

cmp al,01h

je escp

jmp exit9

escp:

mov ESCPR,01h

exit9:

mov SET1,al

mov al,20h

out 20h,al

sti

iret


back_int9:


push ax

xor ax,ax

mov es,ax

mov ax,es:

mov word ptr OLDINT9,ax

mov ax,es:

mov word ptr OLDINT9+2,ax

pop ax

ret

restore_int9:


push ax

xor ax,ax

mov es,ax

mov ax,es:

mov ax,word ptr OLDINT9

mov ax,es:

mov ax,word ptr OLDINT9+2

pop ax

ret

install_int9:

push ax

push ds

push bx

mov ax,0

mov ds,ax

mov bx,24h

cli

mov word ptr,offset int9_handler

mov word ptr,seg int9_handler

sti

pop bx

pop ds

pop ax

ret

show_title:

push dx

mov dx,offset MSG

mov ah,9

int 21h

pop dx

ret

show_set1:

push dx

mov dx,offset H2A

mov ah,9

int 21h

pop dx

ret


hex2asi:

push dx

push cx

push si

push bx

mov bx,0

mov si,offset H2A

mov byte ptr ,'0'

mov byte ptr ,'0'

ha1:

mov cx,10h

mov dx,0

div cx

mov cx,ax

jcxz ha3

cmp dx,10d

jnb ha2

add dx,30h

push dx

inc bx

jmp short ha1

ha2:

sub dx,10d

add dx,'A'

push dx

inc bx

jmp ha1

ha3:

cmp dx,10d

jnb ha4

add dx,30h

push dx

inc bx

mov cx,bx

jmp ha5

ha4:

sub dx,10d

add dx,'A'

push dx

inc bx

mov cx,bx

jmp ha5

ha5:
pop
ax

mov ,al

inc si

loop s4

ok:

pop bx

pop si

pop cx

pop dx

ret


code ends
end start
上述程序运行状况如下图3所示,一旦有按键动作该程序就会显示set1的scancode(没有给出相应的ASCII码,凑合着用吧J)上述代码可以在附件下载。



REFF:
<<The Undocumented PC>>

Enjoy it!
That’s all

Peter

[ 本帖最后由 peterhu 于 2009-7-6 16:36 编辑 ]

peterhu 发表于 2009-7-6 16:41:36

戏说BIOS之Beep

1. Introduction

大凡用过电脑的朋友都应该听到过BIOS的报警声,有时PC开机的时候就会听到嘀的一声,有过修理PC经验的话就更清楚了“一短内存刷新失败,二短内存校验错误,一长三短内存错误,一长八短显示错误”等等诸如此类,可能各家的BIOS定的规则不同,但目的都是通过报警音获悉系统运行状况,找出病灶对症下药(有点像中医诊断中“望闻问切”中的闻J)。

2. 8253/8254

Beep声我们都听过,但是有没有想过这是怎么实现的呢?带着疑惑随我开始探索之旅。提到这个Beep,它可算是历史悠久了,追溯到IBM的第一台PC,那时工程师们可能觉得pc功能太过单调枯燥,于是他们就祈求上帝给我点声音吧,于是声音就有了J。他们将一个简单的扬声器加入了最初的pc硬件之中。光有speaker肯定是不足以产生音乐的,因为音乐得有音调和节奏才能组成。虽然我不懂音乐,但是我知道一点就是声音的高低和频率有关,所以还要有能制造频率的东东这就是8253/8254。既然提到我就大致的讲讲8253/8254,8253/8254是可编程的定时器,8254是8253的增强版本差异主要在可以外接clock频率不同,其实使用上无差了。8253有三个独立工作16位的计数器t0、t1、t2分别使用40h、41h、42h port去操纵,除此之外还有一个43h port用于设定控制字。三个计数器分别编程,但是在使用之前必须先配置控制字,控制字主要用于选定哪一个计数器,选择计数器的工作模式等。控制字的格式如下表1所示:

Bit 0
计数值格式 0表示binary;1表示bcd
Bit1~3
模式选择
Bit4~5
读写指示
Bit6~7
选择计数器

             表 1

其实这三个计数器在PC内部已经规划好了功能,基本上不需要用户参与了J
t0:用于系统时钟提供定时基准,它的输出端与中断控制器的IRQ0相连。
t1:用于DRAM更新的信号,每隔15.2us刷新一次。
t2:用于控制扬声器发声,作为speaker的音频频率。
所以我们知道t2用于提供speaker的音频频率,驱动speaker发生。这个部分早期驱动电路如下图1所示:



由上图1我们可以看到61h PB0控制T2的gate2,也就说只有将PB0 pull high T2才能工作。另外PB1与T2的输出端OUT2经过一个与门运算然后再驱动speaker,所以PB1也要pull high这样T2的输出就可以操纵speaker的频率了。图中的61h是没有介绍过的,那就再来聊聊61h port。61h在XT系统中集成在8255之中,8255是一颗可编程的外围接口芯片,61h对应8255的port B,它是一个8 bit的 IO port,每一个bit代表的意义如下表2所示:

Bit0
t2 gate2 控制位

Bit1
Speaker 控制位
Bit2
DIP相关
Bit3
录音马达
Bit4
RAM同步更新检查位
Bit5
I/0通道检查
Bit6
Keyboard 电平控制
Bit7
Keyboard 始能控制

             表 2
AT以后8255已不再使用,port61h也使用别的IC代替了但是它的主要的bit功能还是保留了下来,所以仍然可以使用I/O指令读写61h port。

3. Beep~~~~~~

知道了以上的知识,我们就来写一个“一长三短的内存错误”的报警声玩玩咯J。需要做的工作有三个:a.通过操作61h port始能speaker input和t2gate2;b.操作8253控制beep音的音调;c. 音调保持一定的时间(也就是声音的长短)。我们逐个的实现上述功能。
a最简单只要将61h port的bit0&1 pull high/low即可始能或者禁能,代码如下所示:
;----------------------------------------------------------------------------

;speak_set
;en/dis speaker input control&t2 gate2 control
;called with:cx
;used registers: ax
;-----------------------------------------------------------------------------
speak_set proc near

push ax

in al,61h

jcxz se_d

jmp se_e

ss_d:

and al,0fch

jmp se_done

ss_e:

or al,03h

ss_done:

out 61h,al

pop ax

ret

speak_set endp


b就需要设置8253计数器2的模式工作频率,操纵8253的步骤为先向43h port选择所要使用的计数器以及工作模式参数类型等,然后再向42h port装入t2的计数初始值,代码如下所示:
;----------------------------------------------------------------------------

;t2_set
;enable t2 & set work mode & out 2 frequency
;called with:di(frequency demanded)
;used registers:ax,dx
;-----------------------------------------------------------------------------
t2_set proc near

push dx

push ax

mov al,0b6h ;t2 lsb,msb,mode 3,binary

out 42h,al

mov dx,12h

mov ax,348ch

div di

out 42h,al

mov al,ah

out 42h,al

pop ax

pop dx

ret

t2_set endp

c可以通过执行loop达到延时的目的,可是loop延时和处理器的类型频率有关,不同种类的cpu执行同样指令所需的时钟周期不同,就算相同种类但是主频不同的cpu要达到同样的延时效果计数的基准也会不同。那么有没有精确延时的方法呢?书上给出的答案是通过检测61h port的bit4 ram刷新检查位,每隔15us该bit会发生一次变化,所以检测它可以获得比较精确的时间(我猜测这个bit会和8253 t1同步变化,因为t1的输出脉冲用作DRAM的刷新定时信号,而该信号要求15us刷新一次)。延时的代码如下所示:
;----------------------------------------------------------------------------

;delay
;delay time base on 15us unit
;called with:cx (counts of time unit)
;used registers:ax
;-----------------------------------------------------------------------------
delay proc near

push ax

dloop:

in al,61h

and al,10h

cmp al,ah

je dloop

mov ah,al

loop dloop

pop ax

ret

delay endp

以上就是beep的主要代码了,最后开放完整的source code供有兴趣的朋友参考。

REFF:


PC硬體元件控制詳解

《IBM-PC汇编语言程序设计》



Enjoy it!


That’s all!

Peter

[ 本帖最后由 peterhu 于 2009-7-6 16:47 编辑 ]

peterhu 发表于 2009-7-6 16:50:33

戏说BIOS之PCI Scan

1. Introduction

PCI由intel公司在1990年前后开发的,后续经过若干年的发展以及标准化,它已然成为server&pc上的标准总线。PCI以其出色的设计以及不错的通信速率在计算机领域攻城掠地,不断的取代诸如:MCA,ISA,
EISA,VESA,NuBus等传统总线。PCI相对于传统总线有非常多的优点,如:1.它是数据总线和地址总线是分时复用的,这样减少了pin脚节省了空间,而且这样也可以方便实现突发式数据传输。2.它是即插即用的(plug & play),当device插入系统时,系统会自动对device进行资源分配并加载对应driver,而传统的ISA device则需要做复杂的手工配置。3.中断共享,传统的总线有一个致命的缺陷就是它们是中断独占的,本来系统的中断就非常紧缺所以增加新的device会出现中断不够使用的麻烦,而pci irq routing机制使得不同的device irq共用成为现实。可是技术的发展总是长江后浪推前浪,前浪死在沙滩上J! PCI又逐渐被更好的总线PCIE所取代渐渐退出PC的历史舞台,后续我会再去研究一下PCIE。

2. PCI Arch

可能是软体背景的原因,因此我看PCI spec也会习惯性的使用软件设计的视角去理解PCI的设计(我觉得有关设计、架构的理论应该是相通的,正如软件中经典的design pattern的思想来源于建筑学一样)。我的视角里PCI同经典的接口编程或者插件式设计非常接近。接口本质上是一组规则的集合它是对同类事物行为上的表示,它的主要目标是实现相同类别的不同对象行为上的多态性。面向接口的编程是OO思想的精髓所在。它的好处体现在哪里呢?首先它增强了系统的灵活性,只要遵循接口定义的规则,系统的底层实现部分就可以灵活的替换、扩充如:PCI总线定出了设备的统一的硬件接口,这样遵循该接口pci device就可以方便的扩展入系统;另外相同的接口可以接入不同厂家的设备就像同样的sata接口可以接三星的光驱也可以接LG的。其次规则给出以后,实现该接口的部件就会有共通的接口但是不同的实现,如此系统端就可以通过接口灵活实现对部件的操作配置。PCI定义出了三种规格的配置空间,根据配置空间提供的信息系统端可以方便的识别设备的种类,功能甚至于厂商和版本号,获得非常丰富的系统端知识;而且该功能也使得设备可以动态的配置资源进而能够做到plug & play。

3. PCI Scan


PCI Configuration Space是大小为256字节的一块空间,它由header和device specific两个部分组成,其中header部分是固定的而device specific部分则是与device相关的,不同的device会有不同的layout。配置空间被用于配置,初始化以及灾难性错误处理的功能。下图1是type 00h Configuration Space Header:



图1
PCI Scan的重要任务就是读出该256bytes 配置空间,那么如何读取这部分的信息呢? 有下述两种方法:
1.使用0CF8-0CFB, 0CFC这两组IO port存取PCI Configuration Space。将总线号、设备号、功能号和寄存器号组合成一个双字送到配置地址端口(CF8H-CFBH),然后读写配置数据端口 (CFCH)即可获得配置空间的数据,下图2是配置地址寄存器的格式定义:



图2
所以我们先要build一个config-address然后再去透过端口存取配置空间。
下述代码用于build config-address:
;----------------------------------------------------------------------------
;build _pci_cfg_add:
;build pci config address
;used registers:eax,ebx
;-----------------------------------------------------------------------------
build_pci_cfg_add proc near

push eax

push ebx

xor eax,eax

xor ebx,ebx

mov PCI_CFG_ADDRESS,80000000h

mov al,PCI_BUS_NUM

shl ax,08h

mov bl,PCI_DEV_NUM

shl bx,03h

or ax,bx

or al,PCI_FUN_NUM


shl eax,08h

or PCI_CFG_ADDRESS,eax

pop ebx

pop eax

ret

build_pci_cfg_add endp


config-address准备好以后接下来就是透过IO port读取pci configuration space了,下述代码演示读取的过程:
;----------------------------------------------------------------------------
;read_cfg_space
;read pci config space use io port
;Called with:NULL
;used registers:eax,edx
;returned regs:eax
;-----------------------------------------------------------------------------
read_cfg_space proc near

mov eax,PCI_CFG_ADDRESS

or eax,edx

mov dx,PCI_CFG_APORT

out dx,eax

mov dx,PCI_CFG_DPORT

in eax,dx

ret

read_cfg_space endp


理论上PCI bus支持256条总线,每条总线支持32个device,每个device又支持8个function,所以我们组合出上面所有的可能就可以遍历出所有的PCI 设备了。可是实际上PC上面PCI 总线通常只有1条,最多也不会超过四条所以我们只扫4条总线就可以了,不用做太多的无用功。有了前面的准备,我们就来实现一个类似RU中的PCI scan吧,下图3就是我写的pciscan运行的状况了:

                                             

图 3
在该界面下按下esc就会退出该程序;移动↑↓键就可以选中device,然后敲enter就会看到该device的Configuration space 如下图4所示



图 4
当前界面下如果想返回到上一个界面只需要按下F6就会回到图3的界面了。

2. Call PCI BIOS int1A同样也可以获取PCI device的信息。其中AH=B1h,AL=function id所有的function id如下所示:
01h: INSTALLATION CHECK
02h: FIND PCI DEVICE
03h: FIND PCI CLASS CODE
06h: PCI BUS-SPECIFIC OPERATIONS
08h: READ CONFIGURATION BYTE
09h: READ CONFIGURATION WORD
0Ah: READ CONFIGURATION DWORD
0Bh: WRITE CONFIGURATION BYTE
0Ch: WRITE CONFIGURATION WORD
0Dh: WRITE CONFIGURATION DWORD
0Eh: GET IRQ ROUTING INFORMATION
0Fh: SET PCI IRQ
81h: INSTALLATION CHECK (32-bit)
82h: FIND PCI DEVICE (32-bit)
83h: FIND PCI CLASS CODE (32-bit)
86h: PCI BUS-SPECIFIC OPERATIONS (32-bit)
88h: READ CONFIGURATION BYTE (32-bit)
89h: READ CONFIGURATION WORD (32-bit)
8Ah: READ CONFIGURATION DWORD (32-bit)
8Bh: WRITE CONFIGURATION BYTE (32-bit)
8Ch: WRITE CONFIGURATION WORD (32-bit)
8Dh: WRITE CONFIGURATION DWORD (32-bit)
8Eh: GET IRQ ROUTING INFORMATION (32-bit)
8Fh: SET PCI IRQ (32-bit)
我们使用function id 09h就可以从configuration space 中读取出一个字,这样的操作明显简单的多了,只需call 一次int1a中断即可。下述c代码演示了读取Vendor id的过程,如需读取其它部分只要算出具体config-address即可。

#include <stdio.h>
#include <conio.h>
#include <dos.h>

int main(int argc,char** argv)
{

   union REGS reg;

argc = argc;

argv = argv;

reg.x.ax = 0xB109;

reg.x.bx = 0x80000000;


reg.x.di = 0;


int86(0x1A, ®, ®);

   if(reg.x.cx != 0xffff)

{

printf("Vendor : %4.4X\n", reg.x.cx);

}

   return 0;
}

程序运行结果如下图5所示:



图 5

最后依旧是开放完整的source code和可执行文件供有兴趣的朋友下载。


Enjoy it!


That’s all!

Peter

[ 本帖最后由 peterhu 于 2009-7-6 16:55 编辑 ]

kivno 发表于 2009-7-7 11:28:28

厉害!!!!!!!!!!!!~~~~~~~~~~

depp 发表于 2009-7-7 13:35:14

Peter Open Source 的观念很强啊,想当初小弟刚开始接触BIOS的时候,要是碰上你这样的大虾,那该多好啊...
UI 就不用画那么辛苦了 :lol

LightSeed 发表于 2009-7-7 13:40:56

同感,同感!!

perhapszy 发表于 2009-7-7 14:47:21

支持LZ   !

bini 发表于 2009-7-7 15:50:58

预祝Peter转业成功!

peterhu 发表于 2009-7-9 10:17:02

戏说BIOS之Clock Generator

戏说BIOS之Clock Generator

1. Introduction

Clock Generator是主板上面一颗极为重要的IC,说它极为重要一点都不为过,因为Clock generator负责提供主板上的clock, 一旦Clock Generator除了问题,板子基本上就完蛋了。Clock generator供给的clock部件有CPU clock,PCI clock,AGP clock,PCIE clock,SATA clock,USB clock等。


2. How to work?

Clock generator 是一颗IC,它有一颗外接晶振,内部会有锁相环放大调整电路,可以将外接的晶振产生的clock放大调整然后再分频输出到各个外围器件和总线,提供器件和总线工作所需的clock。Clock generator的工作原理如下图1所示:


Clock generator通常是一个smbus device,接在SB的smbus controller上所以通过SB的smbus controller,使用标准的smbus protocol就可以存取Clock generator上的configure data从而达到配置各个外围器件Clock的目的。鉴于clock generator的重要性,所以BIOS在非常早的阶段就会配置它(boot block阶段),让CPU、memory等的工作有一个稳定的clock。下面我就以VIA平台为例演示Clock generator读取过程:

[*]首先要通过PCI配置空间找到SB smbus controller的base address。[*]查看Clock generator smbus slave address。ICS9UM700 slave address:42h[*]透过SB smbus controller下达slave address & protocol完成clock generator数据的读写。
下述code演示了获得smbus contoller base address的过程:
;----------------------------------------------------------------------------
;get smbus base address
;used registers:eax,edx
;called with:NULL
;-----------------------------------------------------------------------------
get_smbus_bar proc near

push edx

push eax

mov dx,PCI_CFG_ADD

mov eax,PCI_SMBUS_ADD

out dx,eax

mov dx,PCI_CFG_DAT

in eax,dx

and eax,0FFFEh

mov SMBUS_REG_BAR,ax

pop eax

pop edx

ret
get_smbus_bar endp

下面的code演示使用read block protocol读取clock generator configure
data的过程:
;----------------------------------------------------------------------------
;read via clock gen data by read block protocol
;called with:NULL
;used registers: ax,dx,bx,cx

;-----------------------------------------------------------------------------
read_via_smbus_block proc near

push dx

push cx

push bx

push ax


call get_smbus_bar

;reset host status registers

mov dx,SMBUS_REG_BAR

or dl,SMBUS_HSTS_REG;(00h)

mov al,05eh

out dx,al


;set smbus slave address

mov dx,SMBUS_REG_BAR

or dl,SMBUS_HADD_REG

mov al,SLAVE_ADDRESS

or al,01h

out dx,al

call io_delay


;clear smbus status

mov dx,SMBUS_REG_BAR

or dl,SMBUS_HSTS_REG;(00h)

mov al,05eh

out dx,al

call io_delay


;clear smbus command byte

mov dx,SMBUS_REG_BAR

or dl,SMBUS_HCMD_REG;(03h)

mov al,00h

out dx,al


;block read protocol

mov dx,SMBUS_REG_BAR

or dl,SMBUS_HCTL_REG;(02h)

mov al,54h

out dx,al

call io_delay


;wait for smbus finished

mov dx,SMBUS_REG_BAR

or dl,SMBUS_HSTS_REG;(00h)

rvsb_wait_smbus_fi:

in al, dx

call io_delay

test al, 01h

jnz rvsb_wait_smbus_fi


;read data count

mov dx,SMBUS_REG_BAR

or dl,SMBUS_HDA0_REG

in al,dx

;read data from smbus block data

mov cl,14h

mov bx,offset REGSTR
rvsb_read_data:

mov dx,SMBUS_REG_BAR

or dl,SMBUS_BLKDA_REG

in al,dx

call hex2asc

dec cl

inc bx

inc bx

cmp cl,01h

jae rvsb_read_data

pop ax

pop bx

pop cx

pop dx

ret
read_via_smbus_block endp

下图2演示ClockGen.exe运行时的状况:


            图2
ClockGen.asm只演示了VIA platforma + ICS9UM700的dump过程,至于其它的平台,有兴趣的朋友可以如法炮制。那么这只tool可不可以做成通用的呢?当然可以,只要从PCI configure space读出Vendor id识别出平台然后再根据具体的平台spec去获取smbus controller bar(可能要多看几份spec了J)然后就可以操作smbus了,clock generator slave address可以让用户输入(我看到的几个都是42h,但是也会有特例)。我比较懒,有兴趣的朋友可以试着完善它。最后依旧是开放完整的source code和可执行文件供有兴趣的朋友下载。


Enjoy it!


That’s all!

Peter

[ 本帖最后由 peterhu 于 2009-7-9 10:26 编辑 ]

zhaolw 发表于 2009-7-10 15:03:12

向Peter致敬!!!!!

Alexander 发表于 2009-7-10 19:36:38

Peter兄,这怎么能叫不务正业呀,我也是BIOS和EC都玩的。

peterhu 发表于 2009-7-10 21:21:19

Alexander 你功力深厚啊!
呵呵...向您学习.

[ 本帖最后由 peterhu 于 2009-7-10 21:22 编辑 ]

azziporah 发表于 2009-7-13 14:40:45

好强大的帖子 我正处在入门阶段 能看到这么细致深入的教程感觉真是很激动 拜读中

libeili 发表于 2009-7-16 13:14:42

peter,可嘉可贺!

Faintsnow 发表于 2009-7-20 17:08:06

俺也搞了个类似RU的东东,献丑了。

Albert 发表于 2009-7-31 23:17:14

Faintsnow好样的~
这个Tool实在是太棒了, 很好用哦~
我顶~~~~~~~:lol :lol

coman 发表于 2009-9-4 23:24:56

顶楼主。:)
拜读啦,O(∩_∩)O~

5loveyou 发表于 2009-9-11 00:12:07

还真没有几个能够“戏说”bios的啊。 你算是个高手了。
页: [1] 2 3
查看完整版本: 戏说BIOS