小華的部落格

搜尋此網誌

網頁

星期三, 10月 31, 2007

SlickEdit® 2007's Tags






對於SlickEdit® 2007 如何建立一個專案檔我這邊就不說明了,這邊只強調如何建立Tags (標籤)。在ㄧ般我建立一個Workspace後,我會在裡面建立專案(Project)接著選取你的資料匣;而這個資料匣就是你的Source code的目錄,例如P 廠商Assembly的Corebase目錄或是I 廠商UEFI的Codebase目錄。

建立的時候因為你的目錄內可能會有很多種不同的檔案類型(*.asm ; *.c ;*.inc ...) 所以你在Add Tree的時候必須把你的檔案類型都加進去,不然預設值只會找C++的檔案格式的檔案,所以建立Tags的時候會找不到你要的檔案內的定義。如同上圖中所示,你把你要的通通都加進去。

在點選【OK】後,你會看到下圖所示的畫面,他會開始去建立Tags ....














對於建立好的專案檔中,你看到的畫面ㄧ般都是分類成Source file / Header files...等(下圖左邊),如果你想要用目錄型態顯示,就使用下面我說的方式,在專案瀏覽的地方按滑鼠右鍵,會跳出ㄧ個選單(下圖右邊),然後選擇Directory View 來顯示就可以了:







最後,你開啟你要編輯的檔案,把滑鼠移動到關鍵字中,接著按滑鼠右鍵,在跳出來的選單中選擇Go to definition 或是Go to Reference ,這樣子就可以做到很便捷的Trace Code 的方式。

不過,我忘記預設的SlickEdit中到底有沒有這兩個選項,因為如同我之前文章說的,這個工具功能強大不過要自己手動增加功能,因此我在下面的圖中有說明如何去設定他,在下圖中你可以選擇Edit Menu,然後你會看到ㄧ個設定畫面; 接著設定一些你要的功能,至於哪些指令可以做哪些功能就請你自己參考他的Help 文件檔的說明,因為我也是從那邊看,然後去設定這些功能的。

附帶一提,這個工具還可以自己擴充 "巨集Macro",我記得這個工具的目錄內有一個資料匣,裡面放了很多巨集,你可以修改它來擴充你的編輯器功能,像是點Tab 兩下就關閉你開啟的檔案...etc,利用Google應該也可以找到ㄧ些範例。


星期日, 10月 28, 2007

ACPI Table 基本知識

ACPI Table基本知識(BIOS觀點):

1.包在BIOS ROM中 的 ASL Code 就是ACPI Table,而ACPI Table有分成不同類型的Table(RSDT/FADT/DSDT...etc.)。

2.像是我們比較常使用的DSDT Table就是放了一些event code,簡單說就是當某個H/W event 發生後,系統OS會去依照描述在這個DSDT Table中的ASL Code去執行,而這些Acpi Table包在BIOS ROM中的形式是AML Code (類似機器碼)。

3.BIOS在開機過程中會把包在BIOS ROM中的Acpi Table 載入到RAM 中,然後留下一些資訊給OS來找到他們,最簡單的例子就是RSDP Table會放在1M以下的某個位址(一般都是在E0000h~FFFFFh),然後OS就可以透過搜尋"Signature"(某個標記字)的方式來找到其他的Acpi Table entry point。

[勘誤] 應該是RSDP Structure 不是 RSDP Table ,打錯了 >.< ,感謝糾正! 這個Struct內的RSD PTR欄位會指向RSDT Table ,而如何找這個Struct的方式就是在1M以下的地方找"RSD PTR",有興趣的自己用Debug.com 找看看吧 ^^。 4. OS 需要有一個 AML 翻譯器去翻譯這些AML Code,然後執行他們。 5. 每個Platform 的Host Controller 的暫存器位址會不同,所以BIOS需要透過ACPI Table來告知OS這些暫存器的位址,這樣子做的好處是OS不需要知道你是在什麼Platform,因為他只單純看BIOS所提供的H/W 資訊。 6. ACPI Table還可以用來當作OS 啟動金鑰,例如SLIC Table就是一個例子,OS透過檢查包在BIOS ROM中的ACPI Table的內容來決定是不是一個台OEM 電腦。 其他詳細介紹請參閱ACPI Spec 3.0b 以及微軟網站介紹,下圖為參考微軟文件說明:

SCI v.s SMI 基本概念

這邊大概描述一下這兩種東西的不同處(BIOS 觀點):

SMM is entered via the SMI (system management interrupt).
SMI - System Managment interrupt
SCI - System Control interrupt

由上面字義的解釋可以得知幾件事情:
1. SMI pin 觸發後會使CPU 進入SMM Mode,而BIOS會把某些程式碼放在SMM 所在位置(SMRAM)
所以簡單說就是SMI pin 觸發後,CPU會去執行BIOS中的某個程式碼,至於是哪段程式碼,就要看BIOS註冊了哪個Functon。

SMI pin--> CPU SMM Mode --> BIOS routine

2. SCI pin 觸發後是系統OS 接手後面的事情。 所以SCI觸發後,CPU會去執行某個放在中斷描述表中的程式(IDT),一般都是指向某個驅動程式(ACPI.sys )。
所以簡單說就是當SCI pin 觸發後,ACPI driver會通知相關的驅動程式(ACPI EC Driver),然後跟EC BIOS取得一個代碼,而這個代碼就是ASL Code中的Qxx Number ,接著OS 會去執行某一段ASL Code ,一般都是Method(Qxx)。至於Method(Qxx) 內要做什麼事情就要看一些BIOS Features Spec的定義。

SCI pin --> OS Acpi Driver --> ASL Code (Q event) --> ? (看BIOS Spec需求)

結論:
IA32架構中,EC Chipset會連接 2 pins(SCI/SMI)到I/O Control Host ,接著當EC 去拉這兩根pin的電位的時候(Low/High active) 南橋會告知CPU 處理後續動作(進入SMM Mode執行某段BIOS Code或是執行OS內的ACPI Driver),接著後續的動作就如同你們在BIOS 程式碼中看到的部份。

至於詳細設定資料請參考ACPI Spec、ICH Spec與IA32 Spec SMM Mode章節所描述,這部份是屬於經驗累積,江湖一點訣點破就不值錢 ^^b。

另外,為何要用SCI/SMI 則是在IA32 系統發展史中有說明,如果有興趣的人可以從APM/ACPI 歷史開始看。

星期三, 10月 24, 2007

SlickEdit & Beyond Compare 小技巧

今天在Debug BIOS code 過程中發現軟體工具的一些小技巧:

SlickEdit : 可以直些按下Ctrl + D 去開啟"某路徑的檔案"
例如:

某個xxx.dsc 檔案內容中有一行內容為
Chipset\Ich9\Ich9Init.inf
如果你想要開啟這個inf的時候,只要把游標移動到這一行,然後按下Ctrl+D ,那麼你就可以在SlickEdit中看到它開啟了Chipset\Ich9\Ich9Init.inf 這個檔案來讓你編輯。

Beyond Compare : 可以使用外部檢視工具來編輯
一般使用Beyond Compare比對兩個不同目錄內的檔案後,你可以直接點選檔案,然後Beyond Compare會使用自帶的檔案編輯器來比較檔案內容,並且顯示不同的地方,但是當你要修改內容的時候,你只能在視窗最下面"一行一行"修改,沒辦法一次修改兩行以上。

所以我們改使用另一個比較工具(Araxis Merge v6.5)來當作檔案編輯器:
1.Beyond Compare中點選 工具選項-->偏好設定-->外部檢視工具選項 然後點選"新增"
2.接著依照下面設定:

說明: AraxisMerge 快速鍵: Ctrl+A
命令列參數(你安裝的路徑): C:\Program files\....\AraxisMerge.exe %f %f
(%f %f 代表比較兩個檔案)

設定好之後,下次要開啟編輯器來比較內容的時候就按下Ctrl+A 你就可以改用Araxis Merge來編輯。

有人可能會問說,Araxis Merge 跟Beyond Compare都是一樣的東西,可以比較目錄或是檔案,幹麻要在Beyond內改用Araxis Merge所提供的檔案編輯器,直接使用Araxis Merge 來比較就好了 ?

這是因為Beyond Compare的比較畫面的視窗比較好看很清晰明瞭,但是編輯器很討厭,只能一行一行編輯,而Araxis Merge則是比較的畫面很難看,但是編輯器很好用,可以一次編輯很多行,所以我只好混搭,這樣子比較好用。

星期二, 10月 23, 2007

POST Card & Port 80h

[前言 -什麼是Debug Card]
對於BIOS 工程師來說,常常會因為我們在移植程式碼到不同平台的時候,因為忽略了一些細節而讓BIOS程式碼無法工作,最常見的就是當機。

由於BIOS是屬於一個Loader(引導程式,或是初始化程式),可能當機的地方在VGA還沒辦法顯示的地方,所以錯誤訊息可能沒辦法顯示在螢幕上,因此透過其他的方式來讓我們知道BIOS 程式碼已經執行到了哪個階段,這樣子有助於我們去Debug 。

另外對於ㄧ台已經出貨給使用者的電腦,也可以透過這個方式來維修。而對於BIOS工程師來說I/O Port 80h 是一個很實用的Debug Port,而這個I/O Address已經成為一個標準,所以我們可以透過寫入代碼到Port 80h的方式,將代碼送到ㄧ張有七段顯示器的卡片來顯示,而顯示的那張卡片就稱為Debug Card或是Post Card,而顯示在卡上面的代碼就稱為POST Code(0x00~0xFF),當然也會因為不同BIOS廠商所以代碼也會有所不同。

[IA32 架構下的Port 80h]
Port 80h 與Debug Card 的路徑是如何被初始化的呢? 這部份是討論你寫入Port 80h 的資料是如何經過硬體電路然後解碼到Debug Card?

ㄧ般而言,目前IA32架構下的電腦中POST Card可以連接到PCI/LPC...etc 不同型態的bus,在IA32架構底下I/O Control Host 會負責將寫入到Port 80h的資料往PCI/LPC Bus送,然後我們就可以顯示在七段顯示器上面。

所以,由上述可以得知:
1.BIOS端需要去組態(設定) I/O Control Host ,然後選擇Port 80h資料要送到哪邊(PCI/LPC)。
2.HW需要設計一塊POST Card來負責將I/O Control Host 送過來的資料解碼(decode)成七段顯示器能夠顯示的訊號。

所以看起來很簡單吧,其實實做的時候只要拿著ICH Spec然後ㄧ步一步下去設定就可以了。

可能會有人想問,BIOS不是自己會去初始化這部分嗎,那幹麻還要自己做?
如果你想問這個問題的話,又剛好你是一個BIOS工程師時,你可以自己實驗一樣東西,就是在FFFF_FFF0的指令跳躍之後,你寫一段下面的Code 來做一個簡單的測試,這段code最主要是在POST Card上面ㄧ直顯示99h:

FirstPostCode:
mov al,099h
mov dx,80h
out dx,al
jmp FirstPostCode

由於BIOS一開始執行的地方你就輸出資料到Port 80h,此時系統上面的POST Card 不會顯示99h,這是因為你沒有初始化I/O Control Host 所以會造成系統當機,這也就是這篇文章的重點 "如何自己動手做"。

因為BIOS 能開始讓你寫入資料到Port 80h的地方可能不是一開始的BIOS entry point ,所以如果你想要在更早的地方去輸出ㄧ個代碼到Port 80h時,你就必須這樣子做。

由於我沒觸過PCI介面的POST Card,所以沒研究要如何針對這種類型的POST card初始化,但是對於LPC bus的方式則是去組態LPC中的暫存器以及RCRB中的設定就可以了讓寫入Port 80h的資料正確的送到LPC Bus,相關詳細資訊可參考I/O Control Host Spec中的說明。

[註]
P廠商的BIOS 在Post 階段一開始的時候就會去初始化Port 80h的相關設定,而I廠商的BIOS 則會到PEI 的某個點才會去做這部份的動作。

但是不管是哪一家的BIOS,如果你想更早一點就丟Port 80h,你就必須這樣子做,當然也有例外,例如硬體電路預設就已經是對的路徑的時候(例如預設PCI Bus,而你也用PCI Debug Card),這篇文章就沒有用了...呵呵 ^^


圖 Mini PCI Debug Card

星期二, 10月 16, 2007

[我所知道的BIOS]->[Remaining POST Tasks] 11

除了之前所提的 tasks外,還有下列 main tasks是 BIOS POST會執行的:

[USB init]
=> initialize USB的部份.這方面的code幾乎是 kernel code,再加上少許的 chipset code(因為,chipset's USB controller是哪一種需要 customize, Ex. OHCI or UHCI...etc).

主要在 implement USB spec的部份(USB 1.1/2.0 and USB legacy). (天書也...沒看過...)

[KB init]
=> init KBC的部份

[HDD init]
=> 這是 locate IPL(Initial Program Load)的code.因為最終系統需要 load OS,而OS是存在 storage device中,因此需要讓storage....動起來 !

主要的工作有:
- 設定 chipset端的 storage controller(PATA or SATA)'s settings
- identify HDD
- config HDD by set-feature(<-設定 device端的 settings)

這樣一來,controller端與 device端有了 "相同的認知" 後,就可以 access HDD了 !

[ACPI]
=> 主要是將所有的 ACPI tables建至 memory中;讓ACPI OS可以 parse & config.

[Boot-to-OS]
=> 最後 BIOS's 工作是: invoke int19h(for Boot-strap) !此中斷服務程式會嘗試去找 boot device,並從中(storage)將 boot-loader 載入至 memory中;然後跳到 boot loader處,開始執行載入OS的工作 !


* 一但進入OS後,與BIOS會有關的....幾乎只剩 ACPI 了 !

BIOS不是雞肋 ^_^

[我所知道的BIOS]->[VGA init] 10

一般而言,BIOS會在POST時 locate 3 devices:
- Input device(Ex. Keyboard)
- Output device(Ex. Display device)
- IPL(Initial Program Load, Ex. HDD)

這次要提到的是 Display device,即 VGA !

在PCI_SCAN之後,BIOS會在記憶體中建立一個data structure,代表整個系統的 PCI architecture.
Ex. Ponter-> NB->P2P->SB->IDE->AUDIO->LAN->USB 2.0->USB 1.1->...->VGA->...->End

在VGA_init的階段,BIOS會去 go-through this list,一個個的問:"有沒有人需要shadow Option ROM的 ?"

-------------------------------
*1 在此,先break,並說明一些觀念:
1. Option ROM是 for H/W的 firmware;像BIOS一樣是 for MB.有可能直接在硬體上 ,or 包在BIOS image中
2. 有Option ROM的 H/W可能有: VGA card,Lan card, RAID card,...etc
3. VGA's Option ROM 也就是 VBIOS ! 專門處理 screen I/O operation(主要是int10h)
4. VGA "shadow" 即代表: 將 VBIOS copy 到 shadow RAM, Ex. C0000h~C7FFFh處(32K)
5. VGA init這個階段只 consider "VGA device" ! for 其他 device,之後再考慮其 shadow的事宜
-------------------------------

(承接前面的 flow):此時,VGA device會舉手說:"我要" !此時,BIOS會去尋找VGA device's Option ROM(即VBIOS)在哪裡;此時,VBIOS有可能在card上 or "當初" 被包在 BIOS image中(*2)

一但找到,則會先 作一些 checks:Ex.
- Option ROM signature is 0xAA55 ?
- 比較 Option ROM內的 Vendor ID/Device ID = H/W's IDs ?
- class code and sub-class code correct ?
- length = 0 ?
...etc...

若都符合,則視它為 VGA Option ROM(VBIOS) ! 之後,利用 memory2memory copy將之 copy到 shadow memory,從 C0000h處開始存放...

複製完後,再 check "checksum"是否正確;if yes then jump to "entry of initialization code",控制權自此轉移至 VGA Option ROM,由它去做 initialize VGA的工作 ! ( 若是CRT螢幕,user會聽到ㄉ一ㄤ的一聲 ! 即代表 initialize VGA成功 !!! )

<- 此為 VGA_init的工作 !!!

*2 說"當初"的原因是: VGA BIOS若包在BIOS image中,在 BIOS shadow時,也會被一併 copy到記憶體的某處放著;當然,會記住存放處 !

[Note] 一般遇到 VGA init fail的issue時,可以先 check:是否 VGA BIOS已被 copy 至 C0000h處;若有,則check是否已經 jump to VBIOS or NOT;若否,則可以往前 check是否前面所列的一些 "關卡"沒過 (Ex. ID不 match, or checksum 不相等...etc )

VGA 若 ok,電腦就是彩色的 ^_^

---------------------------
- 相關討論 -----------------
---------------------------


通常都是板子一來就會去開始Debug VGA...
VBIOS 若壞,螢幕是黑ㄟ
VBIOS若好,螢幕是彩色ㄟ
沒螢幕的情況下要繼續Porting BIOS就比較麻煩一點

[Q]Option ROM是 for H/W的 firmware;像BIOS一樣是 for MB.有可能直接在硬體上 ,or 包在BIOS image中。前者我叫它SW ROM,後者叫legacy ROM,您已經提到sw rom的load方式,那麽legacy rom load方式是否應該有所不同呢?一但找到OpROM,則BIOS 會先作一些 checks:
Ex.
- Option ROM signature is 0xAA55 ?
- 比較 Option ROM內的 Vendor ID/Device ID = H/W's IDs ?
- class code and sub-class code correct ?
- length = 0 ?
...etc...

兩种rom都是通過這種方式嗎?

Ans: 我所說的都是 rough flow,而這兩種ROM的"處理方式"不同,但"檢查的機制"大同小異...建議去trace BIOS code比較清楚

[補充1]
1.可以查看PCI ROM Spec... 裡面有詳細說明如何Load OpROM 的方式,而檢查項目就各家BIOS不同...

2.可以用HEX編輯器直接開啟ROM File,例如VBIOS.bin or OpROM.dat ...etc 來查看內容(Binary file),例如某VBIOS.DAT 的內容如下:

00000 55 AA 80 E9 4B ....
00010 30 30 00 22 E9 19 21 5F 40 00
.......................................
00040 50 43 49 52 86 80 42 2A ..............

其中:
a)55AA是這個ROM的Signature <--代表他是符合規範的ROM
b)80 這個ROM的Code size <--需查閱Spec確定一下
c) 接著是EntryPoint Address <--實際上會執行的程式碼進入點的位址,所以一般都是講
Jmp OpRomBaseAddr+3 <-- 你如果trace BIOS 程式碼,這就是 +3 的由來
d) 000018h=40 代表另一個PCI Header在Offset 00040h的地方
所以 50 43 49 52 是Signature,如果用ACSII 來看就是"PCIR"
其他更多資訊可以查看PCI ROM Spec說明.....

e) "PCIR"後面緊跟著這個OpRom的VID與DID ...即8086 2A42 <--Intel=8086

3.如果想要改OpRom裡面的Code,可以使用反組譯工具去修改或是用Debug去修改
如果比較簡單的OpROM要做實驗的話我都用Debug.com 把OpROM 載入到Mem,接著利用T /P 指令單步執行去追蹤與修改,修改好之後再查看機器碼,再利用Hex編輯器 or 反組譯工具寫回去OpROM (找Bug時可以嘗試這樣寫啦,不過違反智慧財產權,最好還是叫Vendor幫你改,而且光看懂OpROm的流程就需要一點時間了 ^^!)

如果要用組合語言寫一個Dummy OpRom的做法就像下面範例去模擬一個OpROM...
至於你BIOS端可不可以run 就要看你的檢查機制嚴謹程度或是你自己修改你的BIOS code來達到模擬的目的 :
.586p
.code
YourCodeStart:
ORG 0
db 55h, 0AAh
TotalCodeSize db (offset YourCodeEnd) - (offset YourCodeStart)
ORG 3
jmp entrypoint

entrypoint:
........

YourCodeEnd:
END

組譯完之後可以利用微軟工具EXE2BIN.EXE 把他轉成xxx.bin (binary file)然後你就可以包進去BIOS測試。

[補充2]
當VGA init時,那時VGA BIOS的放置處可能有:
case 1: 在 memory中(當初在 shadow stage時被 copy 到 memory)
case 2: 在 card上(Ex. 一般的 external VGA card上都有一顆小rom)

因此,這兩種case的處理方式便不同.
Ex. In case 2 若VGA card在 bridge後面,則還需要 config 該 bridge's resource window使Option(in card)可以被正確的 accessed...<- 所以處理方式有所不同 !

至於檢查機制;因為Option ROM不管放哪裡,其 content都是一樣 ! 因此檢查機制大同小異...還有,不同家BIOS的 "checks" 也未必完全相同 !!!

星期三, 10月 10, 2007

SCI Check List

前陣子新板子的SCI不能work ,在Debug之餘,剛好做做筆記。

【檢查項目】

-------------------------------
1. Registers (先得到GPIO & PMBase,檢查SCI 是否繞到IRQ9,GPI是否組態成SCI)
-------------------------------
GPIOBASE—GPIO Base Address Register (LPC I/F —D31:F0)Offset Address: 48h–4Bh
PMBASE—ACPI Base Address Register (LPC I/F—D31:F0) Offset Address: 40h–43h

ACPI_CNTL—ACPI Control Register (LPC I/F — D31:F0) Offset Address: 44h
GPIO_ROUT—GPIO Routing Control Register(PM—D31:F0)Offset Address: B8h – BBh

-------------------------------
2. GPIOBase Registers (先確認是Native還是GPIO,然後設定成GPI還是GPO,需不需要Low active)
-------------------------------

GPIO_USE_SEL—GPIO Use Select Register [31:0]Offset Address: GPIOBASE + 00h
GP_IO_SEL—GPIO Input/Output Select Register [31:0] Offset Address: GPIOBASE +04h
GPI_INV—GPIO Signal Invert Register [31:0] Offset Address: GPIOBASE +2Ch

-------------------------------
3.PMBase Registers (SCI總開關有沒有開,OS有沒有設定Enable bit)
-------------------------------
PM1_CNT—Power Management 1 Control I/O Address: PMBASE + 04h
GPE0_EN—General Purpose Event 0 Enables Register I/O Address: PMBASE + 28h

-------------------------------
4.ASL Code (OS依照EC.ASL 內的_GPE來設定GPE0_EN bit)
-------------------------------

(a)EC.ASL 內的Name(_GPE,0xNN)
(b)GPE.ASL 內的Method(_Lxx) 先移除,避免不必要的誤會
(c)PCIIRQ.ASL 內的_PRS(_CRS不用改),把IRQ9 保留下來,以免跟別人共享造成SCI反應過慢
(d)_REG 或是_INI 內檢查一下是否有其他ASL Code會造成系統當機...etc因素存在
(e)檢查FACP Table中的SCI_INT欄位是否與ICH9 Registers內填的值一樣(IRQ9)
(f)檢查MADT Table中的Interrupt override field內的IRQ設定是否有對SCI_INT做覆蓋,且設定是否正確? (微軟Spec中提到APIC也使用SCI_INT,但是如果有MADT時,會以Interrupt override設定來覆蓋SCI_INT設定,一般都是設定成IRQ9,所以有沒有覆蓋都無所謂,但不一樣的時候會以MADT Table為主。)

-------------------------------
5. Debug
-------------------------------
a) 利用SE.EXE 工具在Windows下觀察GPE0_STS—General Purpose Event 0 Status Register
I/O Address: PMBASE + 20h 是否有變化,當有SCI event時,對應的bit=1,但是一般速度很快所以不易觀察有沒有SCI event,如果有看到變化則代表SCI 應該是正常的。

b) 利用EC BIOS提供的工具在Windows下讀取EC RAM/EC Cmd Data Port,一般EC 如果有進入ACPI Mode or 拉SCI pin之後,會把一些訊息留在EC RAM,例如某某bit=1=ACPI Mode Enable,或是EC Data Port有Qxx Number,而這些資訊可以問EC BIOS工程師,另外Qxx Number則可透過標準ACPI 66 cmd來得到。

-------------------------------
6.ACPI-defined port 66 commands
-------------------------------
0x80 Read EC (write 0x80 to port 66, write address byte to port 62, read data byte from port 62)
0x81 Write EC (write 0x81 to port 66, write address byte to port 62, write data byte to port 62)
0x82 Burst Enable (write 0x82 to port 66, read data byte from port 62 - the data byte is "burst ACK", value 0x90)
0x83 Burst Disable (write 0x83 to port 66, wait for port66.IBF=0)
0x84 Query EC (i.e. read SCI event queue) (write 0x84 to port 66, read data byte from port 62). When the data byte is 0, it means that the SCI event queue is empty.


如果EC有拉SCI pin,則利用自己撰寫的工具透過Port 66 cmd 0x84 讀取的時候,應該會讀取到0,因為如果SCI有發送且OS有收到,則OS會自己透過這個cmd把Qxx Number讀走,讀走後我們應該讀取不到值。

-------------------------------
6. 示波器(oscilloscope)
-------------------------------
在檢查所有暫存器設定與ASL Code設定後,在一切正常的情況下用SE.EXE 去觀察GPE0_STS 變化,但是一直沒有變化,所以懷疑OS沒收到SCI 或是EC 沒進入ACPI Mode,接著與EC BIOS溝通後,使用EC BIOS撰寫的wKX 工具去讀取EC RAM 的某個bit 來確定EC是否進入ACPI Mode,檢查後確認EC已經在ACPI Mode;

"wKx P80 Q43 J" <--送80 cmd到Port 66,讀取EC RAM Offset 43的位址的值,然後利用J參數顯示

接著利用wKx工具讀取Qxx Number "wKx P84",在EC 發送SCI訊號後(目前是利用LID變化或是AC/DC變化來使EC發出SCI訊號)讀取Qxx Number,結果發現還是可以讀取到Qxx Number,所以代表OS沒有收到SCI訊號。

在綜合上述實驗,決定請HW在EC 端與ICH9 端各拉一條線出來檢查訊號,因此利用示波器去觀察EC是否有拉SCI訊號,在觀察兩端訊號後發現,EC並沒有正確的把SCI訊號拉Low (Low Active),所以EC BIOS會檢查哪裡出現問題。

[我所知道的BIOS]->[PCI SCAN] 9

這次要提的是: PCI !

[About PCI device]
1. 每一個PCI device都有其 unique PFA(PCI Function Address). PFA由 bus number,device number & function number所組成.

Ex. USB device PFA is (0,6,0) <- USB is a PCI device and its bus/dev/function is 0/6/0


2. 有了PFA,就可以存取其 PCI configuration registers.

Ex. write USB PCI register 43h bit1 = 1
=>
mov eax, 80003040h
mov dx, 0cf8h
out dx, eax

mov dx, 0cffh
in al, dx
or al, 00000010b
out dx, al

* IO port 0cf8/0cfc 為 PCI config address port & data port,意即:將 address(80003040h)送到config port(0cf8h),然後從 data port(0cfch + 3)來存取 data(al)

* 注意: 32-bit address(80003040h) 中 bit[1:0] = 00b(固定的),所以雖然存取的是 43h,但還是寫成40h ! 而要存取到 43h,則從 0cfch+3來達成 (因為: 0cfch<-> 40h,0cfdh<->41h,0cfeh<->42h, 0cffh<->43h)

3. 基本的PCI device的 config registers可分成 2 parts:

A. header region(offset 00h~3Fh)
B. device specific region(40h~FFh)

在BIOS's PCI_SCAN stage中,會touch到 part A. Ex. command byte, BARs, Interrupt line, latency timer,...etc. 而Part B是製作 or design這個device的廠商所附加的 function/feature.

4. 每個PCI device都可以 request 之前所提的 4 resources:
A. memory resource:透過 Base Address Register(BAR)
B. IO resource:透過 Base Address Register(BAR)
C. Interrupt: 透過 interrupt pin
D. DMA: 這需要 device本身即具有 bus master function(status byte會indicate)


[Why need PCI SCAN]
現在的computer system泰半由許多PCI devices所組成,因此,BIOS POST中另一個重要的 task is : PCI_SCAN !!!

它代表的是: BIOS會掃瞄 whole system,找出所有的PCI devices; initial them and build a linked list of PCI devices.在此list中的每一個node都代表一個PCI device,且含有其 characteristics !

Ex. Vendor ID,Device ID, PFA,Option ROM exist or NOT,...etc.

一旦建好此表,以後的 tasks 隨時都可以參考 !!!

所以, after PCI_SCAN,有兩件事完成了:
1. PCI device initialization;device config registers(Part A) are correctly set ...
2. One data structure is built to describe the PCI devices in whole system(建在memory中)

這也是屬於kernel code part ^_^ ( system 一般很少 hang at this stage...)


符合PCI spec的device即稱為........PCI device ^_^

[補充] PCIe device

PCIe device => 符合PCIe spec的device(...廢話...)

對軟體而言,它仍是PCI device. 因此,基本的 header region and device specific region也有. 不過,PCIe新定義了 extended config space,即 offset 100h(含)以上,直到 FFFh( 所以, 最大可以至 4096 bytes )

存取 PCI config space的方式,用原來 0cf8/0cfc的方式依然可行,但只能 access offset 00h~FFh. 要 access 100h(含)以上的 extended config space,則必須用 memory transaction的方式 !

Ex. mov ax, [50400000h] <- read device (4,0,0)'s register 0;2 bytes

here 50000000h: PCIe extended base address. 可以從 chipset register得知
bit[27:20]: Bus information
[19:15]: Device information
[14:12]: Function information
[11: 8]: Extended Register
[7:2]: DW number
[1:0]: Byte enable

因此,只要知道 PCIe extended base address,就可以像以前一樣,可以任意存取 PCIe config registers, even > 0FFh !

除此之外, PCIe device可以由其 Capability pointer(points to a linked list of capabilities)辨認出來. 因為,在眾多的 capabilities中,會有一個 PCIe capability;其 ID value = 10h.

Note: PCIe extended base address 要 reserve and report to OS. Size is 256MByte. 這是BIOS需要做的. (當然,BIOS也要將此 base address寫入 chipset register,讓 chipset 知道:有這樣的 cycle時,是給PCIe device的 ! )

[補充] For P2P bridge

P2P bridge = PCI-to-PCI bridge. 其存在可以 introduce 另一 new PCI bus,可以容納更多的 PCI device.

P2P bridge亦有其 PCI config space,但是 lauout 與 PCI device有點不同,大家可以參閱P2P spec並與PCI device's config space比較一下.

在 P2P config space中,我常遇到的 issue是和下列 register有關的:
- Primary bus register: offset 18h
- Secondary bus register: offset 19h
- Subordinate bus register: offset 1Ah

----------------------------------------
Notes:
這三個 registers是BIOS在 PCI_SCAN時會決定的;所代表的意義是:這個 P2P bridge的上面 PCI bus number is ? 下面的PCI bus number is ? 及包含此 P2P bridge的 "branch" 最深的 PCI bus number is ?

Ex. 18/19/1Ah of one P2P bridge is 0/2/3
=> 此 P2P bridge 是 "bridge" PCI bus 0 and 2的(橋接在 PCI bus 0 and 2之間);而包含此P2P bridge的 PCI branch(想像成 tree structure) 最大(深)的PCI bus number is 3
----------------------------------------


- memory base/limit
- IO base/limit

----------------------------------------
Notes:
這兩個是 BIOS 在 PCI_SCAN時所 assign的. 所代表的是: resource "window" for devices behind this bridge.意即:若P2P bridge下面(就上例言:是 Bus 2上)有 PCI devices,則他們的 BARs 必須被包含在此 window 之內 !!!
----------------------------------------

[Practice]
[Q]假設有一個 P2P bridge ,下面有一PCI device;現在必須要去 accessPCI device's Device ID/Vendor ID(that is, PCI config read). 但是,問題是:做這事的"點"要在 PCI_SCAN之"前"....那要如何做到呢 ^_^ ?


Ans:假如要在很早前(Ex. PCI SCAN之前)去 access P2P bridge後面的 device,照理是做不到的,因為: P2P bridge沒有被正確的 configed...

在此例中,P2P bridge的 primary/secondary/subordinate bus要被 set,後面的device才能被 accessed到 !

所以,假如要在 PCI SCAN前就 access,則BIOS必須手動去 set 此 3 bytes;然後,PCI config access才能被 forward to 其後的 PCI devices...

[Q] 如何 disable memory or IO resource window ?
Ans: 只要將 base設成比 limit "大" 即可 !!!


---------------
- 相關討論 Part1 -
---------------

前輩已經提到P2P Bridge我就直接問我的問題了

[Q1] Bridge 是用來擴充PCI Bus,在PCI Bus Spec與PCI-PCI Bridge Spec中定義,PCI Device是透過IDSEL來決定他的身分,其中PCI Bus Spec定義AD[31:11],而PCI Bridge Spec定義AD[31:16] 當做IDSEL,這中間差異為何? 為什麼一個要從Bit 11開始當作是Dev 0 ,而另一個由Bit 16 才當作是 Dev 0 ?

Ans: 1. IDSEL對PCI device而言是 input,是用來當作 device's "chip-select"訊號. 而且,IDSEL "如何連接" 是 H/W決定的,BIOS無法決定.

假如將板子上某個device's IDSEL "割斷",則此 device將無法接受 PCI configuration read/write(以 ru.exe來說,就是按F6後,是看不到它的...)

那要如何決定 device's IDSEL ? 一般而言, board designer會將 "unused AD lines"拿來做 IDSEL,以連接至 PCI device.

在 configuration access時,所下的 Ex. "o cf8 80001020"(看起來是 I/O transaction)會被 host bridge轉成 configuration transaction;此時, host bridge即可判斷此 transaction 要 access的 device是否在該 PCI bus上;if YES 轉成 Type 0 transaction;If NO 轉成 Type 1 transaction,並往下發送...(host bridge只要 check latched "bus information"即可完成此判斷 !)

以 Type 0言,AD bus上的 format as follows:
bit[1:0] = 00 ( indicates "Type 0" )
bit[7:2] indicates register number
bit[10:8] indicates function number

那 Bus number ? Device number ?
=> Bus number不必知道 ! 因為:Type 0產生即代表 bus number = 現在的 bus #
=> Device number呢 ? 因為,此時(Config transaction && address phase) AD bus bit[31:11]沒人用 !!! 因此, board designer會把此 21 bits拿來做 IDSEL用 !

因此, AD bus bit11 <-> device 0
12 <-> device 1
.......

當然,不可能 21 bits都拿來接 PCI devices;因為電路上的現實考量...

.................以上為:我所知為何從 bit11開始來當作 IDSEL................

以 Type 1言,PCI-PCI bridge收到後,會將其 bus information與自己的 secondary bus number比較;若是 addressed device是在 secondary bus上,則將 Type 1 -> Type 0;若否,繼續包成Type 1往下一層送...

在P2P spec v1.1 page 22 有一張表,說明 IDSEL generation(from primary address -> secondary address),其中有提到: if primary address bit[15:11] =0,則 secondary address AD [31:16] = 0000 0000 0000 0001;以此類推.

所以,我覺得為什麼 for P2P bridge 其 IDSEL可由 bit[31:16] 來決定的原因在此 !!!(表的關係...)

.................以上為:我推論為何從 bit16開始來當作 IDSEL................
[補充]
PCI config index register裡面的資料其實和硬體解出configuration cycle是相關的.
一.轉換出來是type 0 cycle的話. 硬體只要做以下兩件事.
1. mask 掉bus number(bit 16 ~ 31)以上的部份.
2.解碼 device number的部份即可到對應的 AD bit. 所以其最低可以使用的就是AD11.也就是說一個bus上最多只能有 21個 devices(只是由於推動力問題, 往往是做不到的).
Note:其實也可以設計成其他大於AD 11開始, 這要看chip設計者決定了.
二. 轉換出來是type 1 cycle的話. 只要做
1. mask 掉reserved以上的部份(bit 24 ~31)
2. bit 0 = 1
由於P2P跟其他device不同的地方就是, 除了type 0 clcye以外, 還必須處理 type 1 cycle. 這也是分成兩部份
一. type 1 -> type 0. 當 bus number 等於 secondary bus number 時候出現.
1. 解碼 device number 到對應的 AD. spec中有提到轉換的表. dev 0 = AD16....etc
2. 把 bit 0 由1 變成 0
二. type 1-> type 1. 當 bus number 介於 secondary bus number和 subordinate bus number
1. 直接往下一層送即可.交給其他的P2P 處理.

[Q2] 在IA32下,CONFIG_ADDRESS 會被轉成Configuration Cycles,當Bus Number <>0 時,NB會轉成Type 1 然後往 DMI送到SB,當P2P Bridge收到後,然後定址到Slot上面的PCI Device,這樣說法對嗎?

Ans: 總而言之, 是自己local bus上的,就會轉成 Type 0,然後打在AD bus上,等待認領;若否就轉成 Type 1,往下一個bridge送,繼續尋找...對的人...for each bridge,都是一樣...
[補充]
對 PCI spec是, 如果以Intel PCI express架構來說. 那個已經被封裝成 pci express的 package了.沒有所謂的 type 0, type 1 cycle了.


[Q3]PCI Device透過IDSEL來決定身分,那PCIE Device呢? 我查過資料,好像PCIE不需要IDSEL那他是如何決定Device Number ?

Ans: 我所遇的 PCIe device也是由 AD bit[31:11]中找線拉至 device's IDSEL決定的.不知其他家 chipset是如何 implement.
[補充]
PCI express 是internal routing. PCI express是個跟PCI 完全不同的架構. 只是為了軟體相容性的關係, 把software架構做的跟PCI bus一樣. PCI express是point-to-point架構, 一個link 只會連接一個device. 跟PCI 這種可以多個device在同一bus上是不一樣的. 所以 device number對PCI express是完全不重要的.
Note. AMD的Hyper transport 也是基於一樣的心態來設計軟體架構的.

※ PCIe 的device是 internal routing. 以規格來看,下一層的 device number都是為0.

------------------
- 相關討論 Part 2 -
------------------

DMI指的是 Intel 南北橋中間的通道 ! 之前也是不知P2P bridge部份關於IDSEL的配置,查了表才知道原來有這樣的 mapping(primary address<->secondary address). 其實,可以說 "unused AD bus 會被拿來當 IDSEL用"就是了吧 ^_^
[補充]
是的, 只要軟體能夠知道routing 關係.怎麼接都可以. 只要bus controller控好實際的IDSEL即可. P2P之所以會有嚴格規定(兩項, 1. IDSEL&device number表, 2. secondary bus IRQ routing)是因為P2P 不一定是在板子上. 包含卡都可以有P2P bridge. 在板子上的P2P 可以靠BIOS來建立正確的 routing, 但是插卡不行. 所以必須把這些定義好. 這樣 PnP software(BIOS or OS)才能正確的完成IRQ 分配.讓卡正常工作. 所以如果觀察某些板廠. 就算是真的p2p 沒有存在在板子上, 很多PCI slot的IRQ routing都是依據p2p spec裡面的規定做(因為SB的PCI bus還是落在P2P之後).


在 PCI scan時,BIOS會掃描整個系統的PCI architecture(包含 device & bridge);其掃描方式由BIOS's PCI kernel來決定 !
[補充]
其實了解PCI spec. 要寫PCI scan其實可以效率好又正確. 常見的新進工程師寫法大概就是 3個 loop來處理. bus:0~255, device:0~31, function:0~7. 掃個 256*32*8次, 反正都是程式做, 結果往往也看來正確.這種寫法其實是不對的. (其實,若是多了解硬體的架構,就可以寫出有效率的code了 ! 這也是F/W工程師的價值...)


Ex. Assume 系統架構是這樣的: NB,P2Px3, PCIe bridge x2;其中:
A. 3 P2Ps的配置 is: P2P0下面接P2P1;P2P下面接P2P2
B. PCIe x 2 & P2P0都在 bus 0;其PFA為
NB(0,0,0)
P2P0(0,1,0)
PCIE0(0,4,0)
PCIE1(0,5,0)

=> 最後的 PCI achitecture is:

Bus 0----------------------
NB(0,0,0),P2P0(0,1,0),PCIE0(0,5,0),PCIE1(0,6,0)

*下面 Bus 1/2/3由 P2P0/1/2所 introduce:
Bus 1----------------------
P2P1(1,0,0)
Bus 2----------------------
P2P2(2,0,0)
Bus 3----------------------

*下面 Bus 4由 PCIE0所 introduce:
Bus 4----------------------

*下面 Bus 5由 PCIE1所 introduce:
Bus 5----------------------

所以,Bus number 是由BIOS's scanning "algorithm"所決定的;假如採用 depth-first,則會產生上述的結果 ! 決定後的值會填到 bridge的 Primary/secondary/subordinate bus number registers !

[Q]順便問個問題好了. 其實function number不應該是永遠需要scan的, 為什麼?什麼時候才需要scan function number?
Ans: 我想,對於 function number的問題,應該是: PCI header region offset 0Eh bit7代表: milti-function or NOT ! 因此,可以先 check此bit,再決定要不要往下掃了...這樣又少做了許多虛工...^_^

Ex. PFA (0,3,0) 有回應(that is, Vendor ID/Device ID != 0xFFFFFFFF),則先check (0,3,0)'s PCI Reg0Eh bit7; if "1" then 此device為 multi-function device,還要再往下找 Ex. (0,3,1~7) 有無回應;if "0" then try next device number...!


[補充,加快速的的方式]
檢查multi function bit是正確的, 但是不只是因為效率問題. 而是PCI 規格中, single function裝置可以不解碼 config cycle type 0 bit[8~10], 也就是說 一個 single function裝置, 會對 所有的function number回應, 也就是會出現 8 個相同的device.

順便說一下我的scan加速法. 其實我不是使用 vandor ID & device ID來判斷裝置存在與否. 我是用 class/subclass/interfae ID來作判斷. default 只scan bus 0, 遇到 P2P bridge才會把taget bus number+1, 如果遇到multi host(host bridge 數量> 1)的板子才會完整掃描 255個 bus. ^_^



---------------------------------------------------------------------------------
---------------------------------------------------------------------------------
---------------------------------------------------------------------------------

~轉貼自艾克索夫實驗室~
Rootkit in PCI Option ROM


「Rootkit」一字來自 UNIX 界;但目前通常用於描述 Windows 木馬程式作者所運用的隱形技術。
起初,Rootkit指的是一組程式,可讓駭客躲過偵測。 為達成此目的,可執行的系統檔案 (如 login、ps、ls、netstat 等) 或系統程式庫 (libproc.a) 會遭到更換,或安裝核心模組。 這兩種動作只有一個相同目的;防止使用者收到正確資訊,知道電腦上發生了什麼事。

首先介紹PCI的基本常識, PCI Bus是在約1990年由Intel發展出來, 用來連接主機板上的各項裝置的匯流排標準, 後來成為業界的標準之一, Spec可以在PCI-SIG註冊會員之後下載, 架構上簡單的說, 就是一個Host bridge, 在一般的PC上通常指的就是North Brdige, 這個brdige後面就是bus#0(當然也有Multi Host-Bridge的狀況, 這邊舉例的是最單純的情況), 然後接到South Bridge, South Bridge之後可能接的是ISA Bus, IDE Controller, USB, IDE, DMA Controller等等, 如果bus#0上還有別的PCI Bridge, 這個Bridge後面就是bus#1, 如果有多個Bridge存在(PCI最多可以有256個bus), bus#就不一定是固定的了, 一個PCI Bus上可以有32個device, 每個device可以有8個function, 每個function都有屬於自己的256個register. 在PCI的規範裡, 256個register中的前0×40個是公定的功能, 從0×40到0xff則由各家廠商自行實作, 存取這些register的方法, 一般的PC上是透過IO port 0xCF8~0xCFF, 如果是新的PCI-Express則是直接透過Memory Mapped IO, 以存取記憶體的方式直接進行存取, 例如我們想要讀取一個在bus#0 dev#1 func#0的register#40~43, 透過IO的方式如下:

mov eax, (0x01 << 31 ) // Type-1 PCI Configuration
+ (0x00 << 16 ) // Bus#0
+ (0x01 << 11 ) // Device#1
+ (0x00 << 8 ) // Function#0
+ 0x40 // Register#40
mov dx, 0xCF8 // Index Port 0xCF8 ~ 0xCFB
out dx, eax
mov dx, 0xCFC // Data Port 0xCFC ~ 0xCFF
in eax, dx // Get Data in EAX

如果是透過MMIO的方式則簡單多了:

mov esi, (MMIO_BASE) + (0x00 << 20) + (0x01 << 15) + (0x00 << 12) + 0x40
mov eax, [esi]

今天我們比較有興趣的是位在0×30~0×33的register, 在PCI Spec定義中, 這裡的值存放的就是expansion rom在physical memory中被decode到IO的位址, 比如說這個位址是0xFE000000, 如果你在0×30把bit0設為0×1(io->mem decode), Command Register(0×04~0×05)的Memory Space Bit打開, 在0xFE000000的地方你就可以找到這個rom, 開頭是0×55AA(這當然也是規範之一, 用來辨視是否為一個PCI rom), BIOS在POST過程中, 會逐一掃描位於主機板上所有的PCI Device, 假如device上有rom, 就會把它給拷貝到memory中, 然後用jmp指令跳到ROM開頭offset 0×02(別忘了開頭offset 0×00是0×55AA)的地方開始執行PCI ROM, 執行完後ROM會再把控制權交回到BIOS手上, 一般而言, 在傳統記體空間中, 0xC0000~0xCFFFF是給VGA ROM用的, 0xD0000~0xEFFFF則是留給一般的PCI ROM使用, 當然各種情況下還是會有些許差異, 例如有些BIOS會保留0xE0000~0xFFFFF給自己使用, 像是BIOS的interrupt service, DMI data….等等雜七雜八的東西, 執行完的ROM仍然會保留在記憶體中, 因為有些ROM會修改IVT(Interrupt Vector Table), 將某些interrupt service導向自己的code, 像是VGA ROM可能就會hook int 0×10, 這是很合理的, 因為int 0×10是BIOS所提供用來控制螢幕的service, 聰明的你看到這裡應該就知道前面所提的那篇文章想說什麼了, 如果有個”惡意”的程式被埋在PCI ROM裡面, 只要一開機就會自動被執行, 它的運作並不是一時的, 它可以hook某個OS一定會用到的BIOS Interrupt Service, 然後在這個interrupt service被呼叫的時候動作就可以了, 而且麻煩的是即使你format你的HDD也沒用, 除非你把有問題的PCI Device從你的主機板上移除, 嗯….聽起來蠻炫的, 但是, 有可能嗎?

要回答這個問題之前, 需要知道一些基本的常識, 在保護模式下, 因為IO動作受到限制的關係, 要存取IO並不像在DOS那樣容易, 但如果想嘗試Re-flash一顆PCI ROM, 勢必得進行IO動作, 所幸在Windows下這並不是不可能的事, 有些人可能知道利用SeTcbPrivilege和使用ProcessUserModeIOPL structure呼叫undocumented Native API NtSetInformationProcess()就可以達成目的. 一旦攻擊者有辦法修改PCI ROM, 他就可以利用文章中所提到的例子: int 0×10(Windows在開機過程中會透過Ke386CallBios()呼叫int x010), 作他想作的任何事了.

可惜不論哪種攻擊方式, 最終都是要對OS kernel動手腳, 利用各種偵測工具(如: Archon Scanner), 一定可以找到有問題的地方, 如果最後的箭頭指向PCI ROM, 我們可以透過上文所述, 將存在PCI Device和memory中的ROM給dump出來, 需要dump兩邊的rom, 是因為PCI Spec規範中允許實際上所需要配置的記憶體不一定要等於原本rom的大小, 藉以節省保貴的記憶體(別忘了PCI ROM只能被配置到幾個64KB的segment裡而已), 然後向PCI Device的製造商索取正常版本的rom進行比對, 藉以得知是否為被修改的版本. 假如發現有不一樣的地方, 接下來可以朝幾個方向繼續分析是否為有問題的ROM, 我們可以檢查一下它是否修改了不必要的IVT, 像PXE ROM就不太可能hook int 0×10, 或是有保護模式相關的程式碼, 因為一般的ROM應該都是在real mode下執行, 所以應該不會切到protected mode, 如果有相關的程式碼那就非常可疑, 還有rom裡面是否有可疑的字串, 或是位在Windows Kernel位址空間裡的32-bit address, 另外ROM裡面也不太可能出現編碼過的code, 如果rom裡的code很難被disassemble, 或是充滿了一堆obfuscated code, 那也是很有問題.

除了軟體的方式, 最近興起的新技術TPM也能克制這種攻擊手法

[我所知道的BIOS]->[Shadowing] 8

[Why shadow BIOS ?]
在此之前所提的BIOS tasks,都是循著 CPU->NB->SB->SIO->ROM的路徑來達成的;意即: CPU是去BIOS ROM裡面抓code來執行 ! 明顯的一件事是: ROM access time > DRAM access time ! 且RAM access width is 32bits,而 ROM access width 通常為 16 bits. 因此,便有了所謂" BIOS Shadowing"的觀念產生 ! <- "performance consideration" !!!

[What is shadowing]
意即: 將部分BIOS CODE(in ROM)拷貝至DRAM中 ! (此後, CPU將從DRAM中抓code來執行...)這樣的動作便稱做 "Shadowing" ! 而該處的DRAM即稱為 shadow memory.

Shadowing 在BIOS 中是極為複雜的 kernel code part ! 想深入瞭解的人最好有 source code可 trace.

[When to shadow BIOS]
=> 當然是等記憶體穩定了,可以使用後,才做 ^_^

*因此,假如 DRAM sizing 有問題, BIOS shadowing必定有問題 !!!


以下是之前在其他文章中發表關於 shadow 的文章,再次節錄以供參考 !

[System Behavior before & after shadow]
說的更白話一點就是 : 當 Power On後, 跑完 system power-on sequence後, CPU會被 reset ( reset 指的是 CPU 的 內容會回到 "初始值" ); 而 CPU內部的 EIP (32-bit )的初值是 FFFFFFF0h, 所以, CPU的 第一個 code read 就是到 FFFFFFF0h fetch code...

這個 memory cycle 從 CPU發出, 先會經過 North bridge ; 此時, north bridge會說 "這不是我的"...然後,往 south bridge 丟 ; south bridge 會說, "這是我的",收下後丟給 ROM ! 所以, FFFFFFF0h 會被 ROM 接走 !!! ( 所以 前人才說 "硬體初始值要把0xFFFFFFF0 和 0x000FFFF0 要mapping到同一個地方", 這個地方就是.....ROM !!! )

之後, CPU所發的 cycle 都會照上述的方式一路抵達 ROM...由 CPU循著 fetch, decode, execute, store的順序作事情...

但到某一個階段前, BIOS的 code 會指示 " 要將 BIOS data 從 ROM 搬到 DRAM" ! 而在此階段之後, BIOS會設定 north bridge 暫存器, 告訴 north bridge "之後 CPU所發的 cycle 不可以不收而傳到 south bridge"....

自此之後, CPU 所發的 cycle 全部轉到 DRAM 中,由 CPU循著 fetch, decode, execute, store的順序作事情...

[Summary] 總而言之, BIOS 一開始是CPU讀取 ROM content來執行,之後是CPU 讀取 DRAM content 執行 ^_^

[補充][以提問的方式^_^]

[Q]: shadow memory到底是那一塊 ?
[A]: 以現在的電腦系統而言, shadow memory 是在UMA(upper memory area;傳統記憶體 640K以上至1M之間) "裡面"(part of it) ;在此UMA內可以分為 6 segments(64kB/each), total 384KB.

其中 C0000h~DFFFFh: for VGA BIOS and other devices' Option ROMs
E0000h~FFFFFh: BIOS ROM code

這兩個 blocks 本質是 memory;之後會被載以ROM content;因此,便稱為 shadow memory(好像: ROM在上面, memory在下面,是ROM的 "shadow"...)

[Q]: shadow memory 的內容 完全 "=" ROM content ?
[A]: of course NOT ! 除了 UMA不大以外,BIOS ROM因為必須support more and more functions/features,size已經越長越大;除此之外,BIOS ROM中也有部分module是經過壓縮的.(UMA不夠放...)

當BIOS shadow時,只會 copy necessary code(Ex. run-time要用的...etc)至 shadow memory ! 另外,已經執行過後的code也不會被載入. 因此,不是 1-1的copy...

[Q]: what Shadow enabled/disable mean in chipset ?
[A]: 前面提過, CPU要 fetch的code由 shadow memory 提供 or ROM提供,其根本關鍵在於 chipset's behavior ! 意即,NB必須做此決定 ! 所以,這機制一般是由NB來實現的.

NB內有所謂的 shadow registers. 當 shadow register enable時, memory cycle由 shadow memory 來回應;若 shadow disable,則由ROM回應.

意即:
000E0000h~000FFFFFh由 shadow memory replies if (shadow enabled)
000E0000h~000FFFFFh由 ROM chip replies if (shadow disabled)

若FFFE000h~FFFFFFFFh呢 ? 必定由ROM chip來回應(不然一開機怎麼辦?). 與 shadow disable/enable無關 !!!

*若使用 ru.exe 並檢視 memory space,可發現上述的情況.

[Q]: shadow有何好處 ?
[A]: 因為 slower ROM v.s faster RAM,所以放在RAM可以增加 system performance.

*各位可以發現,同樣的code放在 rom & dram中執行速度將大不相同 !

[Q]: shadow memory的 issue ?
[A]: 被 shadowed 的 memory area都會被設成 write-protected ! 因此,這塊area是不能 write 的.所以,若有程式會 "write"這塊,則會有問題 !!!

其實, BIOS shadow這段code大家不太會碰觸到...瞭解基本information即可.

有錯誤請指正 !!!

[附註]
[Q1] "BIOS的所有動作都必須先從ROM COPY至DRAM之後CPU才有辦法去執行"

Ans : 講簡單一點就是對CPU來說,他只負責提取指令,解碼指令,執行指令。至於是讀取到哪邊的指令他就不管了(IA32 架構)。例如CPU要讀取F0000h的指令來執行,那F0000h有可能是ROM也有能是DRAM。

[Q2] "那BIOS一開始的0E05BH是ROM的位置?還是DRAM的位置呢?"
==>0~4G 是指位址線的位址(因為Address bus = 32 bits=2^32),至於位址線上面是分配給DRAM還是ROM使用還是Others...就要看實際的硬體架構,例如IA32 架構就有其分配的方式。

==>所以某個位址線的位址可以被分配給ROM使用也可以分配給DRAM使用,還可以重疊一起使用,例如 F segment (000F_0000h)就是重疊使用,所謂的重疊就是就像是CPU 對F0000h存取的時候,他是存取到DRAM的資料還是ROM的資料 ? 答案就是看北橋....如果北橋在此時把這個位址是映對到ROM,那麼CPU讀取到的F0000h資料是ROM裡面的,如果北橋是映對到DRAM那麼CPU一樣是讀取F0000h但是會讀取到DRAM內的資料。

==> 所以0E05Bh 是位址線的位址。
==> 另外BIOS一開始的位址是在FFFF_FFF0 不是在0E05Bh...
==> BIOS的程式碼會去改變北橋設定,讓在不同階段的執行期間去存取到DRAM/ROM的資料,我們稱為shadow....例如從F0000h的 ROM讀資料,寫到F0000h 的DRAM中
==> 這邊說的都是IA32,其他架構要看Spec 說明。

星期一, 10月 08, 2007

[我所知道的BIOS]->[DRAM Sizing](2) 7

DRAM Sizing 的程序與chipset design息息相關,不同家chipset其DRAM sizing sequence亦不同.在此只列出 rough sequence以供參考. (真正的 detailed sequence仍須以各家的 code為準)

[假設]:
A. memory controller integrated into NB
=> NB PCI config space有一部份與DRAM相關
B. This chipset support 4 DRAM sockets(max.)
=> 存在 4 registers,分別代表此 4-socket DRAMs' information
=> 存在 4 bits ,分別控制 4-socket's CS(chip select;enable DRAM用的)

[程序]
1. Configure registers by DRAM frequency:
=> 許多DRAM相關的 registers(Ex. tRFC,tRP,tRCD,tWTR,CAS,...etc)都必須根據DRAM operating frequency來設定. (DRAM frequency 可由 CPU freq and CPU/DRAM ratio求得)

2. Execute Initial sequence for DDR(or DDR2)
=> DRAM spec都有列 initial sequence;既定的流程.

3. Set MAX-supported Size of this chipset for 4 sockets !
=> 因為"不知道"插進來的DRAM到底多大,所以假設: 每一個 socket上都有插DRAM,且size 是->此 chipset所 support的最大 size(Ex. 2G)

4. Do the followings for Socket 0/1/2/3 sequentially (<-此時,一次 "只 enable 1 socket"來做事)

1) check DRAM exist or NOT
=> 藉由簡單的 write-then-read pattern來判定 socket上是否有 installed DRAM

2) decide DRAM "TYPE" if DRAM exist
=> 假如DRAM有在socket上,則進一步去取得其 "TYPE" information
以DDR2 SDRAM為例,此步驟即:求取該記憶體的 (Bank,Row,Column,Side) information
Ex. 2x12x9's DRAM 且 double side ,則記憶體大小為 128MB

* 找DRAM information 有分 MA table & by SPD.

3) write this information to corresponding register

5. Configure registers by DRAM loading
=> 步驟4做完後,所有 installed DRAM已經找到. 此時,需要對一些與 "loading" 相關的DRAM registers做調整(因為: 不同數目的DRAM需要有不同的 driving strength !)

6. Remaining tasks:
=> disable one-page mode
enable dynamic reset
enable refresh cycle ( <- DRAM is ok to use ^_^ )

[注意事項]
1. 至此 Top memory( "可使用的記憶體上限" 已經...決定了 ^_^)
2. 在此 stage,並沒有對 DRAM 做嚴密的測試. 測試階段在後面的 tasks
3. 一般若 register config錯誤,或是 DRAM frequency不對(起因於clockgen config 錯誤),都會 hang在這個 stage ! 若系統僥倖 passed this stage並進到DOS,則可以再用 memory test utility來 verify DRAM is stable or NOT !!!

[補充]
在此stage(Sizing)完成之前, DRAM is not ready to use. 故撰寫此code時得全靠 CPU GPR(general purpose register)來傳遞參數 or return address ! 因此,得時時注意是否 register content 被改變 ( 因為 GPRs不多...)

但是,若是 activate CAR(Cache as Ram),則在此stage就可以使用 push/pop,所撰寫的code將較為模組化及彈性,也不必擔心 register content會被破壞了...
Ex. AMD Kx sizing code便是很早便使用 cache 來當作RAM,因此可以寫出 module code for sizing !!!

從前從前,Big-Endian與Little-Endian?

~轉載~

從前從前,記憶體是非常昂貴的. 資料寬度是8位元,一個記憶體位址存一個字,剛剛好.
但隨著科技進步,ALU已可做到16位元甚至32位元的運算,但記憶體實在太貴,所以只好想辦法偷機.
也就是用2個連續位址的記憶體來存16位元的資料.

問題來了,哪一個byte所存的是 b0~b7,哪個是 b8~b15呢?
所以: 低位元組放較低的位元資料,很合理阿
ex: 1234h byte0放34h,byte1放12h

或是: 低位元組放較高位數資料,也很好呀

ex: 1234h byte0放12h,byte1放34h

兩套標準就此產生.為什麼要有兩套標準?支持不同標準的廠商們異口同聲地說:
我設計處理器/電路比較好設計,不然你咬我阿.........

[我所知道的BIOS]->[DRAM Sizing] (1) 7

現在要提到的是BIOS POST中一個重要的 task : DRAM sizing !
它主要的工作是:讓我們所插的記憶體可以正常且穩定的運作 !

[Q] 為什麼穩定的記憶體這麼重要 ?
=> 因為,有太多東西需要存放其上了,例如: BIOS code(之後在 shadow部分會提到), 開機之後所需要的 device drivers and OS,etc...假如記憶體不穩,在存取 memory時無法得到需要的資料,或是根本連 read or write都不行,那麼便會發現: 不是 system hang(maybe randomly) during POST ,或是進OS後 blue screen...

所以,"BIOS DRAM sizing不好" 是系統不穩的原因之一. (地基都不穩了,怎能奢求其上的房子可以堅固 ^_^ )

在介紹 DRAM sizing之前,先稍微簡單說明 DRAM 的相關知識.(若有不足或錯誤之處,請不吝指正...)

1. DRAM chip 可看做是 square array;基本的單位 is cell. 每一個 cell 是由數個電晶體組成(depends on cell width;記得是: 1 bit 由一個電晶體組成,可以記錄 0 and 1的資訊; 1 cell 可能含 n bits;讀取DRAM cell屬於破壞性的讀取,因此讀取的同時常伴隨著 "refresh" <- 與 SRAM不同)

2. 要存取 DRAM chip 中的資料,必須提供 row and column address;這兩個 address是有先後順序的;意即: row address先送,然後再送 column address to chip.
(呼應前面所言: square array,類似 x,y 定址) 最後 指到的 data 將被放到 data bus上,CPU會讀走

*若有空可以上網查: 為什麼DRAM chip所需的 pins 是減半(half)的 ^_^

3. DRAM controller 現在一般都做在 chipset 中,Ex. NB內(AMD K8 CPU則是將 memory controller做在CPU內 ,for better performance);其工作便是 interfacing DRAM chip;充作其他 devices(Ex. CPU,DMA controller...etc) 與 DRAM chip 之間的橋樑 !

Ex. In BIOS ,寫下列的 code 欲 "read", "記憶體", "位置8處"的資料:
xor ax, ax
mov es, ax
mov edi, 00000008h
mov al, BYTE PTR es:[edi]

[00000008h] 是 programmer所"知"的 linear address,但DRAM chip 只認得 MA0~MAx,BA0~BA1..etc訊號,如何溝通 ? => 靠 DRAM controller 來轉換 !!!

4. DRAM access time => 從 DRAM chip 接到 address signals 直到 valid data 出現在 data bus上的時間(assume "read" memory),稱之;也是DRAM chip的特性之一.

5. 現今常見的 DRAM is DDR/DDR2 SDRAM. 所謂的 "S"DRAM 指的是 Synchronous DRAM,即 DRAM operation 都是參考一個 clock執行的;即與它同步(synchronous). DDR 指得是 Double Data Rate,亦即在 clock 的 rising and falling edge都可以傳資料. 而DDR2指的是: DDR的 "第二代" ! (別想成 DDR2 = DDR x 2 !!! 雖然剛好有些數值是兩倍的關係...)

Ex. DDR2 533(or called PC2 4200) =>
1. "2" means DDR2
2. 533 is transfer rate( 每一秒可做 533筆資料 transfer )
3. 4200 means: 533筆資料/s * 每筆 8 bytes( => 64 bit width) ~= 4200MB/s


下一章將介紹 DRAM sizing 的 flow/information !

[我知道的BIOS]->[系統資源] 6

在此,想先提一下所謂的系統資源(system resources). 在電腦的世界裡,所謂的系統資源約可分為四大類,亦即: DMA, Memory, IO, and Interrupts. (大家若是仔細檢視一下 [我的電腦]->[內容]->[硬體]->[裝置管理員]->[檢視]->[資源(依類型)],便可以發現我想表達的)

* DMA:
=> CPU可以說是系統的大腦;在沒有DMA時,CPU幾乎得參與所有的事情;但是有了DMA後,可以讓 DMA controller或是bus-mastering devices自己來執行data transfer from and to memory,CPU只在開始及結束時參與;如此CPU可以做其他事情. 典型的電腦系統有八個 DMA channels. 檢視 "資源(依類型)"中DMA的部分可以知道哪一個 channel被哪一個 device使用.

* Interrupt:
=> 周邊devices請求 cpu 服務的方式. 一般的電腦系統中有 16IRQs(IRQ0~15). 由兩個 cascade的8259所提供. 當有device透過 IRQx發 interrupt時, CPU會被告知;然後,CPU最後會得到對應於該IRQx的 service routine的 entry point,之後便跳到該處去執行ISR(interrupt service routine). 現在的電腦系統有發展出APIC mode(有別於原有的8259 mode),其目的在於提供更多的 interrupt inputs以及專有的interrupt機制.

在檢視 "資源(依類型)"中的interrupt可發現: IRQ 0/1/4/6/8/12/13/14/15其實是dedicated to specific devices使用的;而剩下的IRQs則是分給PCI devices共同/單獨使用. 若是APIC mode,則可以看到超過15的IRQ !

* Memory:
=>在此所謂的memory,並非指記憶體模組那種memory,而是: CPU memory addressing space ! 亦即,以32-bit CPU而言,可以定址到的 memory space is 2^32 = 4GB. 這麼大的空間就是一種資源 !因為,在此範圍內,CPU可以完全存取,而且,也是有限的 !那,要如何利用這麼大的資源呢 ? 方法就是所謂的 "Mapping(映射)".

舉FM為例,在收音機上一定有旋鈕可以調,亦即, FM的波段是有範圍的. 只有在此範圍,才能收到訊號(<-這也是資源). 然後,在此範圍內,要發射訊號的人,會先提出申請,看要使用那個頻率,經過認可後便可以使用該頻率波段了. 所以在此範圍內便可以聽到警廣,中廣,空中英語教室...等的廣播. (記住: "每個人" 都可以使用該資源中的"一部份",只要"被核准"...)

Memory space 也是一樣 ! 在廣大的 0~4G space中,也有很多人可以使用. Ex. 記憶體模組可以使用某一塊,BIOS ROM也可以使用某一塊, PCI devices也可以使用某一塊,APIC也可以使用某一塊,...etc. 常見的字眼 "Memory map" 就是闡述這樣的觀念 !

*I/O
=> 同理, IO 指的也是 CPU I/O addressing space. 也是一種資源. 現在 I/O addressing space 是 0000h~FFFFh,共64KB的範圍.

[Summary] 資源是有限的,要斟酌使用 ^_^

[補充memory部分]
假設系統插有1G記憶體,使用外部的顯示卡,且使用512KB BIOS ROM,則:

[Part1]
-memory space 0~(1G-1)被此1G記憶體所佔用;或說此1G記憶體被映射到memory space 0~(1G-1);只要CPU存取這個範圍,就會存取到此1G記憶體.

Ex. 在BIOS中寫:(前提: in big-real mode)
xor ax,ax
mov es,ax
mov esi, 00000000h
mov al, BYTE PTR es:[esi] ; access 此1G記憶體中 位置0處的資料 !

[Part2]
-memory space 1G~4G 則是由"其他人"所佔用,例如:(below are examples ONLY)
1.memory space 0xD4100000~D410FFFF是由 Ethernet card的 operational registers 所佔用
2.memory space 0xD4204000~D4204FFF是由 USB 1.1 host的 operational registers 所佔用
3.memory space 0xD4206000~D4206FFF是由 USB 2.0 host的 operational registers 所佔用
4.memory space 0xD4200000~D4203FFF是由 High Definition Audio的 Operational registers 所佔用
5.memory space 0xFEC00000~FECFFFFF是被 IO APIC 所佔用
6.memory space 0xFFF80000~FFFFFFFF是被 512KB BIOS ROM 所佔用
.....

只要CPU存取這範圍,則會access到 "對應" 的device registers.

Ex. 假設要access USB 1.1 HOST's operational register offset 0,則BIOS只要寫:
(前提: big-real mode)

xor ax,ax
mov es,ax
mov esi, 0D4204000h
add esi, 0 ; 0 means offset 0
mov al, DWORD PTR es:[esi] ; access


[Q] 那問題來了:CPU發了一個memory cycle,誰來決定/如何決定要給誰(記憶體 or USB 1.1 HOST OP registers)呢?

=>chipset一定知道系統插了1G記憶體,也會將此資訊,1G( = 40000000h ),記錄在內部register中.當cpu要存取記憶體而發 address: 00000000h 的cycle時,chipset會將 address與 1G比較;因為小於1G,則 "知道" 要發給記憶體 !

當cpu要存取USB OP而發 address: 0D4204000h 的cycle時,chipset會將 address與 1G比較;因為大於1G,則 "知道" 要發給PCI devices, Ex. USB host !

* 1G這個 information 的有一個名詞代表它: Top Memory ! 代表可用的記憶體的上限.

[結論]: 4G memory space 充斥著各式各樣的 "H/W registers"(假如把記憶體也視為 registers,ROM chip也視為registers.....)


Q:問個問題一下古時候, VGA 的memory 是被mapping 到0xA000 的一個64KB 的area,如果你的VGA card 有1MB 的memory 就無法全部mapping, 需要banking 的動作.在big-real mode 中, 如果還是1MB 的memory 會是多大 ,它會比mapping 從哪到哪?

A:就我所知,以前的VGA display會使用到 installed memory A0000h~BFFFFh間的範圍;這是屬於UMA(upper memory area)的一部份;其中A0000h~AFFFFh(64k) for VGA graphics mode memory;B0000h~B7FFFh for VGA mono text mode memory;B8000H~BFFFFh for VGA color text mode memory.

*上述的 三塊 memory 都是 video memory(or called frame buffer),用來儲存要顯示的image的記憶體

* 還記得組語練習中有 output color text to screen,其中所用到的 segment = B800h !!!
( for color text )

當program存取到這塊時,VGA card便會 read it and 負責將之顯示到 monitor !

現在的 VGA cards則是含有 built-in video memory;雖然不是使用系統記憶體,仍是被 mapped to A0000h~BFFFFh.(此時,很重要的一點是:系統記憶體的 A0000h~BFFFFh不再被VGA display用了....這點之後會提到,將被用來放 SMI code...)

你所說的1M memory,因為無法全部 mapped,所以有 bank switch來解決;那麼我的感覺是:還是一樣用 bank switch來解決 !!! 沒有變...

因為,我所知道的 memory map中在1M以下只有 A0000h~BFFFFh for Video card用的(你從 資源(依類型)中去看,也可以發現這範圍是給 display用的...) 而且,in big-real mode 也不會讓 1M memory可以完全 mapped,這是沒關係的 !

這個其實跟系統chipset無關, 跟VGA chip比較有關. 如果VGA chip不支援 linear frame buffer, 我想你只有用banking的方法做. 如果有支援, 正確方式是要透過VBIOS. VBE 2.0以後有定義linear frame buffer的地址位置.你可以呼叫 return VBE mode infomation 這個 function call 來取得位址.

星期四, 9月 27, 2007

[我所知道的BIOS]->[Chipset Config] 5

一旦正確的頻率供給給system後, BIOS便依據既定的POST table(類似行程表)來做事. 首先,與chipset最為相關的便是 [chipset config stage]. 意即,在此階段BIOS必須對 chipset registers填入正確的值,俾使其正常運作. 一般而言,要config下列的 devices:
- NB(還記得前面有提過北橋內部約可分 4 blocks; "有些" blocks 需要 在此 program...)
- SB(即 PCI-ISA bridge)
- ACPI registers
- USB registers
- SATA registers
- PATA registers
...

* 在此所謂的 registers指的是: PCI config registers(除了ACPI registers以外). PCI spec所定義的 PCI config registers 00~3Fh 為 PCI header part,這個範圍的registers並不是此階段的主要工作(當然還是會touch,只是不多),而是稍後BIOS PCI kernel的事情.此階段將 focus on :offset 40h以後的 registers,因為,這些是 device specific的部分,是 chipset自己加function/feature之處 !!!

* ACPI registers通常透過 IO 來存取
Ex. // assume ACPI Base is 800h,要 access ACPI Reg55h
mov dx, 855h
in al, dx ; <- al = ACPI Reg55h's value

* 上述所列的 devices多為 PCI device,也都有唯一的PFA(PCI Function Address,即 bus#,device#,func#),也因此,只要對 device 下 PCI configuration read/write便可以 config device registers.

Ex. 假設: set SATA register offset 52h bit3 = 1;SATA controller PFA is (0,4,3)
=> 簡單的 assembly can be:

mov eax, 80002750h
mov dx, 0cf8h
out dx, eax

mov dx, 0cfeh
in al, dx
or al, 00001000b
out dx, al

事實上每家BIOS都 maintain many "tables" 來說明: 哪一個 device 中的哪些 registers要填?值.相關的 access routine都已經寫好. Programmer只需更改表裡面的值即可 ^_^

*當有人要求改 register setting時,我們必須知道三件事:
1. why (為什麼要改?)
2. what(改成何值?)
3. when(什麼點改?)

*對於 BIOS kernel與 OEM coding的人言,這部分會是較少碰觸的,因為,這些code會由 chipset porting的人負責撰寫及維護.

[我所知道的BIOS]->[Jumpless] 4

上一篇所提到的是:BIOS entry point. 它是第一個被 CPU 所抓取並執行指令之處.每家BIOS都有其相應的 file,要牢記.

在本文中,要提及: Jumpless ! 在此要先說明Jumpless的意義.它代表:"用 s/w方式來 config clockgen generator,使之產生正確的 頻率s 來供應系統運作". 重要性在於:假如某個 frequency錯了,則將導致 system abnormal or malfunction...

有哪些 frequencies由 clockgen 提供?
- CPU clock (在此指的是 : host clock, Ex. FSB800 CPU 需被 supply 200MHz clock)
- DRAM clock( depends on chipset design; Ex. DDR 533要被 supply 266MHz clock)
更正 : DRAM clock現在都是由NB產生 . 外部的最多是 buffer而已. 並非 clock generator. 原因是為了保持較良好的 host clock & DRAM clock的同步.

補充:我舉的例子是以前的chipset design. 現在我做過的 chipset 其 DRAM clock的確由北橋來推. 北橋中有一會 register定義: CPU-DRAM ratio.
Ex. CPU clock = 100MHz. Ratio = 3:5,則可得 DRAM clock = 166MHz


- AGP clock: 標準 is 66.6MHz (= 1/2 PCI clock)
- PCI clock: 33.3MHz
- SATA clock: 100MHz(see SATA spec,似乎還要更精確...)
- Link clock: 即假如NB/SB中間有 link,亦由clockgen提供
- USB 1.1 clock: 12MHz
更正: USB 的 clock 是 48MHz
補充:我們 chipset的 design是: 由 clockgen提供 12MHz clock(或由 external crystal提供),被 PLL吃進去後,會產生 48MHz給 USB 1.1(你說的應該是這部分). 至於 USB 2.0,則是 supplied other freuencies !

- SIO clock...etc

Clock Generator的架構
=> ClockGen基本組成為 PLL(Phase Looked Loop) + Div. PLL 的輸入為 14.318MHz, 利用 迴授方式產生出 frequency F;之後 經過 Div 除頻,便得到 desired frequency,見下列圖示:

14.318MHz -> PLL -> F -> Div -> f

Ex. F = 400MHz, 為 PLL振出來的;若 Div = 4,則 f = 100MHz

ClockGen的 spec中,會列出許多 registers,好比是 Function 的 "contact window",透過這些 registers可以設定此clockgen !

更正:其實PLL 不是單純一個的 div. 鎖相迴路的參數是兩的divider 組成 M(VCO divider)/N(REF divider)
補充:觀念上來說是 PLL所振出的頻率經 div後可得到 desired frequency. 但在 clockgen中:
1. 有 2 bytes(即所謂的M,N)來決定 VCO
2. 有幾個 bits來決定 div;一些主要的 frequency都有相對應的div
3. VCO / div = frequency


How to access ClockGen
=>現今存取 clockgen的方式,都是透過 SMBUS來達成. SMBUS 有分 1.1 and 2.0,不過大同小異. SMBUS是 2-wire protocol;由 SMBUS master(即內建在南橋內部的 SMBUS controller )來下 command 給 SMBUS device(Ex. Clockgen);Command中常用的有 Block Read/Write & Byte Read/Write

不同家 chipset 的 SMBUS controller implement方式不同,自然下command的方式亦不同;主要的步驟是:
- pre-init( clear status or ...)
- put slave address(要 access 的對象)
- decide Read or Write
- put command(Protocol type, Ex. Block or Byte access)
- put offset(要存取 自 clockgen中的哪一個 Byte開始...)
- put data if (Write device)
- start transaction !!!
(... processing ...)
- get "Complete" status to check if transaction is done successfully
(...wait for system reset...)

* 當 start transaction後, SMBUS controller便會將 programmer所 prepare的資訊將之轉成 SMBUS command打給 device;完成後應該在 controller端有 status register可以檢查,看看是否 transaction ok or failed,或是有其他 error conditions.

透過這樣的方法,programmer便可以 config clockgen使之產生正確的 frequencies.

[2 Programming ways for clockgen]
=> 現今的 ClockGen有提供兩種方式 for S/W config. 一為 table mode,一為linear mode. Table mode指的是: ClockGen spec 中會有一個 table,裡面說明: 要產生 ? frequency 要填 ? 值.因此,這種方式只需program 1 byte to Clockgen就行了. 若是 linear mode,則是坊間所說的無段變頻,即可以微調frequency. Ex. table mode只提供 100/133/166/200MHz,但 linear mode則可以提供類似 102.6/136.3/168.7..等的頻率. 此時,要program的 byte數就很多了.....除此之外, linear mode也可以config 所謂的 spread spectrum(%),板廠測EMI時會用到

【Notes】
1. For NB platform power-on CPU frequency 由 2 pins來決定;DT platform會由 Jumper決定.不管如何,之後做Jumpless都可以 override掉原 clock settings

更正: 不是只有NB如此.不管DT 或server. CPU FSB 也是 pin(由於FSB頻率比NB多.所以是 3 pins )來決定的.不是jumper. 即使要做超頻設計會對這三pin做手腳, 也不會使用jumper


2. 有些 clockgen所提供的 SS 都是 center spread. 若需要 Down spread則可以由 center spread搭配 改變後的 frequency(類似直流準位)來達成

給我正確的頻率,其餘免談 ...^_^...

=====================================================================
Q&A
=====================================================================
>前輩我有幾個問題要發問!
>1. 你這邊說的Table方式頻率是指供應給CPU頻率嗎? Intel網站的CPU說明,他說是固定的不可調的? 所以這不是HW固定住(Latch)設定值了嗎?
>
>2. 可以解釋一下Down Spread/Center Spread /...所代表的意思嗎? 還有用途?
>
>3. 我目前也在嘗試Program ClcokGen(新手上路),很多專有名詞還沒搞懂,另外目前ClockGen不是可以
>輸出不同頻率嗎? 為什麼Table方式只需要Program 1 byte就好 ???還是說各家廠商設定方式不同,有些廠
>商只要利用1 Byte 就已經選擇好所有的頻率了(33MHZ/48MHz/DOT96MHZ/LCD27MHZ...)?

[For #1]
=> 我所謂的 "Table"方式指的是: 為了方便設定,clockgen designer會專門提供一個byte register(8-bit);此 byte的某些 bit fields(Ex. bit[4:0])可以讓programmer填一組值,例如 01001b. 此值 write 至 clockgen成功後, CPU/DRAM/AGP/PCI/...etc 都會是固定的頻率. Ex. 100/133/66/33/...etc. 所以才說只填 1 byte only !

此值一旦填至 clockgen, clockgen便依據該 byte(可參考 spec前幾頁,會有一張表說明: bit[4:0] <-> frequency combinations)來產生出 frequencies.

不管用哪一種方式(table or linear mode),都會 supply CPU frequency !!!不過,此時的 CPU frequency is CPU的外頻 ( CPU 總頻率(core) = (外頻)*(倍頻 <-multiplier) )

Ex. 外頻 200MHz(Merom CPU, FSB 800),倍頻 = 12 -> core freq = 2.4GHz

還有,BIOS可以調整 外頻 & 倍頻 ! 沒問題 ! 不過,若遇到鎖頻的CPU(Ex. 量產版的CPU),倍頻可能無法調整.但外頻還是可以靠 config clockgen來達到 !!!

For #2
=> 假設要達到 100MHz 且 SS = 0.5% center spread.則:
(...想像以前學的直流與交流的訊號...)

100MHz 相當於 直流的部分,其值不隨時間而變; 0.5% center spread好比弦波訊號(振幅是 100M * 0.005 * (1/2);所以,兩者的合成便是: 以 100MHz為中心,其值隨時間而變,振幅 is 100M * 0.005 * (1/2) 的訊號 !!!

我 '猜' 要這樣測試的原因是:所產生的 clock一定不會是 perfect的 100MHz,有可能因為元件或是外部干擾而被 "改變".為了模擬這樣的情況,我們會將 clock的 spread spectrum enable,產生 "隨時間而改變"的頻率,來測試系統的穩定度...等

Down spread 0.5% 指的是:距離 水平 100MHz, 最低的值是 100M-100M*0.005,最高值則是 100M本身( 等同於: 水平值 99.5 + center spread 0.5%, right ? )

For #3
=> 前面提過,只設 1 byte 就可以產生所有頻率是因為: clockgen公司已經把設定各頻率的功能"濃縮"在一個 byte裡面 ; programmer只要 config this byte 就可以了...

* 關於 EMI 測試部分,有錯請指正. ( ...我有用到 "猜" 字眼...^_^ )

[[EMI 測試的知識]]
由於 對clock信號來說. 鋒(peak)值是能量最強的地方. 所以peak的能量就會不斷累積, 發射出來. 因此在頻譜上就會出現能量很強的頻率. EMI 工程師的工作就是要想辦法屏閉這些電磁波的干擾. 

為什麼需要 spread spectrum? 由於PLL 十分精準. 也就是說 100MHz的clock, 其 peak & peak之間的距離一致(波長固定). 所以能量十分驚人. 使用展頻可以使 peak 的距離改變(由於頻率不斷的在一個範圍內改變, 這個可以用示波器累積clock信號來看) 所以累積的能量會比較低. 這樣EMI 可以比較好處理.

至於為什麼會有要求BIOS一開始就把展頻開著, 以確保板子的穩定? 很簡單.不開展頻clock很穩定.自然比較不會有問題. 展頻一開. 由於頻率是浮動的. 很多設計欠佳的板子就會不穩定. 及早發現才能及早修改.

[[補充資料~~運作原理]]
CPU本身有預設值的BSEL[2:0]值,這個值是與Clockgen所產生的BCLK是對應的,CPU本身Spec會有一個Table定義這個值,然後CPU透過BSEL[2:0](輸出pin) 連接到Clockgen與NB(這兩個都是輸入pin),所以當Power on的時候,Clcokgen會依照CPU預設的BSEL[2:0]去產生BCLK給CPU,而FSB 頻率也是經由CPU接到NB的BSEL[2:0] 來同步(一般都是1/4 CLK 提取一次資料,所以假設BCLK=200MHZ ,而Clockgen輸出到NB的Host Clk=200MHz,一般都會同步,所以 FSB=Host clk*4 = 800MHZ)

※ Clockgen 分別透過BCLK與HostClk pins輸出頻率給CPU與NB

然後CPU開始執行BIOS Code,當執行到Program Clockgen時,BIOS可以透過Clockgen內的暫存器的某個Byte 內的FSC/FSB/FSA 這3個bit 來改變BCLK輸出頻率(應該可看作override BSEL[2:0]設定),當CPU接收到新的BCLK頻率後可能的反應有:

1. CPU本身不支援這個BCLK所以系統當機或是Shutdown
2. CPU 可工作在這個BCLK頻率,依照這個頻率對照Table,然後改變BSEL[2:0]的設定

另外CPU 是否能工作在266Mhz or 200Mhz or both,這部份依照每顆CPU不同而有所不同,有些CPU只能工作在266Mhz,所以當BLCK=200Mhz時,CPU就不會工作。

假設CPU可工作在266Mhz與200Mhz,則當BCLK從200MHZ-->266Mhz時,CPU一樣可以工作。

還有CPU內部的調整ratio的MSR暫存器可分成測試版的CPU跟出貨版CPU的不同:
a)測試版CPU的Ratio可調,如何調要看Spec
b)出貨版CPU不可調,也就是鎖倍頻
---------------------------------------------------------------
1. Clockgen 會送兩組 differential-pair clocks給 CPU & NB. 目前這兩組 clocks是相同的頻率.
2. CPU有兩 pins 稱 BCLK[1:0]. Input.
NB HostClk[1:0]. Input
* bit[1:0] 是代表: differential-pair clock都會有 2 clocks,分別是 true clock and complementary clock

3. 開機時的 behavior 是:
1) CPU內部自己會產生一組 default BSEL[2:0] 給 Clockgen的 FSC/B/A (依據經驗,這組 default 值 可以由 CPU MSR 得到 ; 也等於這顆CPU所能支援的最大外頻,BCLK)
2) Clockgen據此 FSC/B/A會產生對應的頻率,送給 CPU 的 pins BCLK[1:0]
Ex. 266MHz
3) 之後,若有需要, BIOS可以透過 config FSC/B/A 或是 M/N programming(透過clockgen內部的暫存器)來 "override"掉原來的 clock setting以達到改變 BCLK的目的.
------------------------------------------------------------------

CPU 工作頻率=外頻 (BCLK)* 倍頻(MSR調整)
FSB 頻寬= CPU Spec說由 BSEL[2:0] "同步決定",但是看Clockgen電路有分成,BCLK與HostClk(兩者應該輸出同樣頻率) 所以應該是北橋拿HostClk當參考,每1/4 Clk提取一次Data,所以應該是HostClk * 4

以前FSB頻寬=FSB工作頻率,所以有人把CPU工作頻率計算方式看作 " FSB頻率 * 倍頻 ",但是由於FSB頻寬目前已經不等於FSB 工作頻率,且Clockgen 分別拉了兩組pins(BCLK與HostClk)給CPU以及北橋,所以計算CPU工作頻率的方式才會變成目前的 "BCLK * 倍頻"

加註:
1. FSB1066指的是: bandwidth ! 不是頻率. 一般不會說 FSB266,而會說 host clock = 266MHz !
2. *4 應該是因為 ( clock的上下緣都可以觸發 ) + ( differential-pair) 的關係


我先解釋一下這三pin,就我印象中除了之前 NV chip + K7 和 SiS的chipset以外.
CPU 的FS0, FS1, FS2會接到clockgen (FSA,B,C)和NB (BSEL0,1,2)的對應pin.
CPU 是負責產生準位, clockgen & NB 會在自己的powerok ready的時候把資料latch進去.
clockgen 除了利用這三pin來產生對應頻率的CPU clock外, 另外也會根據這個頻率來產生合適的PCI-E, PCI clock..., 而北橋則是利用來產生正確的 DRAM clock(host/dram ratio),對intel來說也有PCI-E的同步關係.

至於超頻跟這三pin 的關係, 我稍微解說一下.一般而言要超頻的話, MB 必須把到NB & clockgen的路徑斷開, 由另外設計的線路來取代CPU的FS0,1,2.也就是說NB & clockgen 一開機必須收到一個由這個控制線路決定的FSB. 這樣才能作所謂的 133-> 200 or 200 -> 266的大幅度超頻.因為此時NB 和 clockgen 才會正確的分配. 別忘了NB, SB, PCI-E, PCI, DRAM是沒有CPU這麼強的超頻力. 直接利用軟體改變clockgen會造成很多不該變化的頻率跟著變.

Note:此超頻線路, default是一個所有CPU都能開的安全值, 一但BIOS完成設定, 就會latch住資料
此外還必須配合watchdog 來處理過度超頻不能開機的問題. linear mode只用來處理小超頻的部份

以前遇到的 clockgen 是 CPU & DRAM clocks 由同一個 PLL決定,其餘 clocks由另一個PLL決定. 當時有寫一個OS下的小程式,可以微調 CPU頻率(以1MHz為單位). 不過,微調CPU的同時也會微調到DRAM clock(<-同一個PLL),只是微調值不同(depends on divider).

現在遇到的 clockgen + chipset design 是: CPU由一PLL供給,而DRAM clock由北橋推.同樣的,微調CPU clock一樣牽動DRAM clock.

至於其它 frequencies是否會跟著變, depends on Clockgen design;現在的clockgen幾乎都有 2以上的PLLs;所以類似PCI/AGP/SATA clocks都會有專門的 PLL控制. 而CPU則由另一個PLL控制.

有興趣及時間的人可以試試看 M/N programming(小微調). 看著 (Ex. FSB800 CPU)由200MHz 操到 210MHz,也是一種快樂 ^_^

Q: 這邊剛好有提到watchdog...想請問一下前輩這個東西在PC系統上是何時開始使用? 用途為何或是應用為何? 以前學校沒看到過這個東西,不過我在嵌入式系統常看到一些應用,另外在南橋Spec有看到過,而BIOS端需要如何去應用以及設定它? 另外使用 watchdog 來處理過度超頻不能開機是怎樣的一個方式? 頻率不對,CPU已經無法work,連BIOS code都不能執行了? 所以他是靠HW去處理嗎?

A:我曾用過關於watchdog timer 的經驗是 => 在 config clockgen完後, 需要 system發 PCI reset ! 此時會需要 watchdog timer...

因為, write clockgen完後,有可能系統當掉 !!! (<-frequency settings被改變了...)因此,我知道的作法是: 在 write clockgen之前,先 enable watchdog timer ! (當然,一定會指明要"多少時間後"才發 PCI reset ! ) 一 enable完,便匆匆去 write clockgen(當然,這段時間要夠長,以免 write clockgen沒完成,PCI reset就出來了 >_< ).

一旦 write clockgen完畢,code會寫 "jmp $"...然後等 watchdog timer 時間到,然後PCI reset出來 reset system...

你所說當機時的 watchdog timer,我沒做過 ! 不過,watchdog timer 不一定是要在 SB內啊!(或是即使做在 SB內,也是"獨立的"...)做成外部的電路就不怕 chipset hang時 timer會失效了...^_^

一般我知道的 watchdog timer control 大概有:
- enable/disable ( => 開始記時...)
- time to bomb (=>多少時間後爆炸 ?)
- event routing ( => 時間到要幹嘛 ?)


[[WatchDog]]
watchdog是個硬體機制.
watchdog 在嵌入式應用中主要就是用來做系統自動回復用的.通常用在機器處理很critical 的任務, 不能當機超過一定時間的事務. 一般的用法是, 系統的軟體要固定時間去重設timer. 例如: watchdog timer 設定10秒以後會重置系統, 那麼系統軟體可以每5秒有一個task 去重設timer. 萬一系統當機了. 由於軟體無法重設timer. timer 數完了以後就會發出reset , 讓系統重新恢復狀態. 在MB 中superI/O 有watchdog, clockgen 也有支援watchdog. 以超頻來說, 往往設定完clock generator的瞬間, 系統可能會當機. 這時有兩種狀況. 1. 因為clock 變化造成, 這種狀況只要一個PCI reset. 系統就可以繼續執行. 這時BIOS 可以借用 watchdog來發PCI reset. 只要在定clock之前先設好watchdog timer, 然後在一段時間後關掉watchdog timer. 這樣就可以解決這個問題. 一種是 clock 增加太多造成chip 死當. 這種就需要依靠 clock gen 裡面的watch dog來讓clock 回復成default value(Note:要視clock gen的設計, 有些只是發reset而已).

另外一種我知道的應用是雙 BIOS的主板. 利用watchdog來切換BIOS.

星期三, 9月 26, 2007

[我所知道的BIOS]->[PowerOn Sequence & BIOS Entry] 3

在此將以DeskTop platform來說明(Notebook platform 的 power-on sequence 牽涉到 EC,可參考討論區中 "power on sequence" 文章);還有,所敘為rough flow,詳細的時序圖依據design會有些許不同.

[Power-On sequence]

- AUX power ok
- Main power ok
- PCI reset
- SB state-machine runs...
- NB state-machine runs...
- CPU power-Good ok
- CPU reset
- 1st code-read by CPU ( <- BIOS entry point,即 CPU 會抓取 FFFFFFF0h 處的 BIOS code 來執行)

* 當然CPU一次抓取的BIOS data不僅僅是幾個Byte ! 而是一堆的data(Ex. 64 bytes);之後會從中 extract 出 FFFFFFF0h處的 data,然後來執行.

補充: 為什麼CPU 發的第一個位置不會只有FFFFFFF0h? 而是多抓很多個? 範例中為什麼是64 bytes?
原因在於 cache的支援. cache不是細分為單一byte 的, 而是以block(many bytes)為單位. 這個block的大小的名詞就是 cache line size. 也就是要填入cache一次要寫入的bytes量. 例如 cache line size 為 16 bytes. 那麼一次就要讀16 bytes到cache去.
CPU 一但發生cache miss(第一次開機一定是cache miss)的狀況時.就會通過host bus 對外取得資料. 這時就會產生burst read cycle來達成fill cache line的需求. 所以由CPU 的 cache line size的大小就可以知道CPU一次會抓多少的 bytes. P4以後CPU 的cache line size 為 64 bytes, 所以CPU 就會產生一個 burst length 為8 的 memory read cycle. 所以CPU 發出的位置就需往下減.然後在讀取的資料中可以包含到 FFFFFFF0h以後的資料.


自此,CPU便循著 CPU->NB->SB->ROM 的 path,循序地至BIOS ROM中抓code,執行 fetch->decode->calculate->store...自此開始便是BIOS POST stage starts...


[有可能遇到的問題是] debug code = "00" or "FF" (意即BIOS常用的 Port80 card所顯示出的 "code" ) !!!

* 此時,強烈建議:請在BIOS entry point 處 丟Port 80(value可自行定義),因為,即使BIOS有跑到,但因為距離 1st 丟 Port80 的 code仍有一小段程式碼;若系統 hang在此其間,Port80仍是沒有 code,因此,在BIOS一開始進來便先 out Port80 將有助於判定: system hang before BIOS entry point or NOT !!! )

# Assume system hang 'before' BIOS entry point, 可能的原因有:
- incorrect power sequence: 此 sequence 有 spec,規範訊號間的相對關係與 assert/deassert的時間,violate spec有可能導致 system hang;此時需要 H/W or board designer來量測
- incorrect power-on frequency:有發生過因為 Power-On CPU frequency錯誤而不開機者.請用 scope量測
- incorrect chipset behavior: 意即上述的 power-on sequence中有關於 NB/SB的 state-machine部分,有可能這部分的行為不正常,因此需要 H/W designer來 clarify

* 曾聽說,某家chipset需要BIOS image 中 include 幾個 bytes 來 config NB/SB的 registers;這些 settings也會影響 power-on時 chipset的 behavior;因此,這幾個 byte 若是錯的,也有可能 system hang

補充:很多chipset都(or 曾經)有這個功能, SiS, NV, VIA, Intel. 以前有個詞叫ROMSIP就是指這玩意兒... 即使在現今的intel platform也有預留這東東.

#Assume system hang 'after' BIOS entry point
=> 這就是BIOS engineer的時間了,就 debug吧...(儘管,有些 issue 是 board or H/W造成的...)

* 此時的 debug方式,若有 輔助工具的(Ex. P debug card) 就用,沒有的就用Port80 card囉 !

[Summary]: 沒進入到 BIOS entry point前....不要找我....真的沒辦法 >_<

* 關於 BIOS entry point,請參考 討論區中 "追蹤BIOS code 的進入點" 文章 !!!

[Power Button開始的動作]

一般Power Btn 都是EC 控制(或稱PCU),如果你說要知道Power Btn之後的動作就是問EC 工程師就對啦。
我印象中好像是Power btn按下後-->EC 偵測到動作(應該是KBC 發Event給EC BIOS或是EC BIOS自己每隔一段時間去檢查有沒有Event..沒K過EC Spec,純猜測...)-->EC 檢查目前系統狀態(不同時間點按下Power Btn , EC可能會做一些動作,因此要判斷),檢查的時候主要會去檢查南橋ICH接到EC的訊號線,判斷Sx state-->如果是正常開機,則開始供電--->系統上電後,CPU會從起始位址開始讀取BIOS第一條指令(至於CPU何時收到重置訊號可能要看一下其他Spec...)。

另外印象中EC BIOS有分成兩種形式,因為EC Controller可能裡面的記體器容量會不足,或是說節省成本故意做成那樣,所以 EC BIOS 會是包在SBIOS或是放在EC Controller裡面兩種格式。

如果真的要K流程,應該是去看EC Spec跟ICH Spec吧...

當EC 收到power event (指power button的動作後...), 會根據目前系統的狀態來決定是否要開某些電源...For example, 當系統處於s3(suspend)的狀態時, 當user按下power button後, EC 會發一個訊號給南橋, 然後EC 會wait for SUSB and SUSC 的訊號assert. 然後開 main power的電(我指的是非suspend的power)...但是詳細的動作應該各家都不太一樣才對. 而且這些spec都是各公司的knowhow

[我所知道的BIOS]->[系統架構] 2

任何 firmware 都有相對應的硬體;也唯有深入瞭解硬體,才能寫出好的 firmware ! (當然,對 programming language 很熟,有技巧的的人可以寫出好的 firmware,但是,另一個層面是:對硬體很瞭解,所以所寫的firmware 與 H/W 搭配良好,這也是另一種值得追求的地步)

BIOS 就是 "主機板的 F/W". 所以,它的對象就是主機板上的所有 devices. (為了後續的解說,我只列出一些,其他的坊間書上都可找到)

- [CPU]
=> central process unit. 依架構不同有分 Intel or AMD cpu. 其工作之一是: fetch code->decode it->fetch operands->calculate->store result.

- [North bridge]
=> 即俗稱的北橋. 常見的 Intel 945/P/G,965 等是其北橋晶片代號. 它可以約略區分成四個 blocks(H/W view)
1. Host interface: 即 與 CPU interface 的部分.
2. DRAM interface: 即與 DRAM interface的部分
3. Display interface: 即與 AGP/PCIe VGA interface的部分
4. Link interface: 即與 South bridge(南橋) interface的部分

簡單說,North bridge的功能是: Host block 承接 CPU 所 issue 的 cycle, 辨認(identify),並且將之 forward至 "正確的接收者" ( Ex. DRAM block, display block, or Link block )

所以,它像交通警察一樣,接收中央的命令,將來自四面八方的車流引導至正確的地方,讓整個城市的各部分各角落可以相互交流無虞.

-[South bridge]
=> 及俗稱的南橋. 常見的 Intel ICH7/8為其南橋晶片代號. 它"可能"包含以下的 blocks:
1. Link interface: 即呼應北橋的部分.
2. PCI block: 處理 PCI request部分(即 implement PCI spec H/W part)
3. ACPI block: implement ACPI H/W part
4. USB
5. Audio
6. SATA(AHCI)
7. ...

所以, 南橋裡面包含許多我們常見的名詞, USB/1394/SATA/HDA/TPM/PCIE...等

-[BIOS ROM]
=> 儲存 BIOS image的device. 常見的有 ISA ROM/LPC ROM/SPI ROM. 編譯出來的 BIOS binary file透過燒錄器或是 flash tool 燒錄到此 ROM中. 內容是0101...的 binary code

所以,總結來說: BIOS code被CPU fetch, 經過 decode後, 會轉發給NB.NB接到後也會自行decode 此 request;若是 與 DRAM有關的,則 forward to DRAM block;若與 display有關,則forward to display block;若是與 PCI有關的,則將之轉發給 SB(透過兩者中間的Link),請SB再找適合的人 Ex. USB, SATA, or 1394 controller ! ( 別忘了 PCI H/W是在SB裡...)

以上依序所講到的 CPU->NB->SB->ROM,將在後面的敘述中用來說明一些觀念及flow.

Summary:
1. BIOS可以說是 chipset's driver !!!
2. CPU所 issue 的 cycle會先抵達NB,由NB判定是給誰的;若不是 for DRAM or display,則會再轉發給SB,由它判定是要給誰的

[我所知道的BIOS]->[前言] 1

本身從事BIOS相關工作已經六年了;今年由於生涯規劃,決定暫時(?)離開BIOS而轉做其他工作;即使如此,對於 BIOS仍是有著極大的興趣以及期待. 從小就是喜歡做整理以及分享的人,所以最近有個衝動是:何不將自己所知道的整理一下,並將之POST出來,讓更多對BIOS有興趣的人可以有點深入的瞭解BIOS? 也讓版上的諸多前輩可以提出指教及批評,進而糾正我的一些觀念 ? 基於這樣的想法,我選擇了這裡來作為我這個念頭付諸實現的地方,請大家多多挹注,多多指教,多多批評 ! 歡迎任何的建議 ^_^

首先要說明的是,我覺得BIOS在台灣可以分成三類的人:(自己的分法,僅供參考)
1. 撰寫BIOS kernel的人
2. 撰寫chipset code的人
3. 撰寫 OEM code的人

我是屬於第二類的人,所以我的觀點(view point)也是從chipset出發 ! 對於第一及第三類,我的接觸不多 !
要先這樣說明的原因是:有些問題我無法回答或 comment,因此,就有賴版上諸位的幫忙了(if available)

還有,我本身是撰寫某一家BIOS(A, P, or M)的,所以會幾乎以該家的所教給我的 flow/information 來敘述事情;當然,我會盡量抽離這種成分來敘述,讓大家瞭解 "事情的本質即可 "...若有偏頗之處仍須請大家包涵...

最後,我所撰寫的幾乎是親身經歷的 information,因此,不會有天馬行空的想像(不過,做BIOS仍是需要想像力的...^_^...) 若有錯誤,那應該是我的經驗不足,或是我的經驗有誤,還是要請大家指正囉 !

就這樣...

liaoo

~轉載自程式俱樂部討論區~

PCI IRQ Routing Table Specification

~~~轉載請註明出處~~~by 小華的部落格
在x86歷史的演進中,有很多的BIOS工程師對於PCI IRQ Routing Table還是搞不清楚,我剛入行的時候也是一樣,對於這個東西一點概念都沒有,只知道是因為IRQ#不夠用,所以才需要去繞線(Routing)。

[為何要繞? 背景是什麼?]
依照我自己研究出來的歷史,我發現可能的原因是因為PCI設備越來越多,然後當時的中斷控制器是串聯的8259A,所以只有IRQ0~IRQ15可以用,且IRQ2已經連接 "僕8259" (第二顆中斷控制器),所以剩下來15支IRQ#可以用,但是又因為x86系統在早期的設計中,有些IRQ#已經分配給固定的設備使用,所以剩下來沒幾支可以用,可是設備又那麼多,所以如何去分配剩下來的IRQ就是大家討論的話題。

[IRQ繞線的歷史發展]
依照我查到的資料,早期的作業系統Windows 95年代左右,PCI設備要使用哪一支IRQ中斷線是靠PCI卡上面的"跳線"去控制,所以有可能會因為兩個PCI設備跳同一支"IRQ#"而造成衝突導致當機或是藍底白字。
後來改用BIOS Setup Menu內去控制,也就是可以進去BIOS設定畫面去設定IRQ分配。

因此為了解決PCI設備越來越多,但是IRQ不夠用的情況,所以微軟找上的晶片廠商也就是Intel合作開發PIRQ Route Controller,簡單說就是微軟想在他的作業系統上面支援"共享IRQ中斷架構"的驅動程式,但是需要硬體配合,因此定義了PCI IRQ Routing Table來規範硬體線路要怎樣繞線,且需要BIOS支援哪些資訊。

[為什麼是PCI設備而不是ISA設備?]
因為當時PCI Bus取代了傳統的周邊匯流排,所以PCI設備橫行,且設備需要服務就是透過中斷請求線IRQ#請求服務,對於OS端來說,這個服務就是驅動程式,至於CPU如何把控制權交給OS,則是靠IDT (interrupt Description Table),相關詳細資料請看Windows核心說明。

[跟DOS有關嗎?]
應該是無關,除非你在DOS下替你的PCI設備寫了一個驅動程式,或是你的PCI設備在DOS模式下要工作,不過應該也是沒啥機會這樣做吧,所以PIRQ Routing都是針對Windows作業系統而言,因為與設備驅動程式管理有關。

[Windows 作業系統的改變]
對於微軟自己定義的規範中,他最希望的就是能夠共享IRQ,所以在作業系統的改變就是要能分辨是哪個PCI Device透過IRQ發出請求,這是因為可能有好幾個PCI 設備都用同一支IRQ#中斷請求,所以OS 必須要能夠讓正確的驅動程式去服務發出中斷的設備,因此在撰寫OS 端的 Driver 時有了新的規範(針對共享IRQ的驅動程式有其規範)。

[Chipset的改變]
起先為了微軟的規範,Inetl 在南橋ICH上面多了幾支接腳(PIRQA~PIRQD),這幾支接腳又有對應的暫存器可以組態他們,例如下面範例:

PIRQA Register 60h bit3:0 <--PIRQA那隻接腳的設定暫存器在LPC Reg60h,其中bit3:0定義如下
=================================================================================
IRQ Routing — R/W. (ISA compatible.)
Value IRQ
0000b Reserved
0001b Reserved
0010b Reserved
0011b IRQ3
0100b IRQ4
0101b IRQ5
0110b IRQ6
0111b IRQ7
...
由上面範例可以看出每支PIRQ接腳都可以用"軟體"設定的方式橋接到IRQ#的任何一支。
也由於上面範例我們可以知道,OS 必須"先知道哪些IRQ可以被使用" 還有"哪些IRQ已經被使用",因為OS本身有自己配置IRQ#的演算方式,因此必須要先知道這些資訊,才有辦法去對PIRQ#繞線。

[BIOS的支援]
所以BIOS要提供"PIRQ Routing Table"給作業系統,然後OS就可以得到這些資訊,但是又因為OS版本不同(Win95/Win95/Win2000/WinXp or Acpi OS/non-ACPI OS..等分類),所以透過的傳遞管道也不同。

[後來的演變]
隨著PCI設備越來越多PIRQ只有4支接腳已經不夠用,所以後來擴充到8支,分別是PIRQA~PIRQH。

至今2007,OS 與 Intel 在這部份的演變也越來越複雜,因為後來的Intel 提出了新一代的中斷控制器APIC,所以在南橋ICH內就分成了兩種中斷控制器PIC與I/O APIC兩種,又因為OS演變成ACPI OS,所以原先PCI IRQ Routing Table Spec內所描述的方式就變成了ACPI Spec內的方式,簡單說就是BIOS傳遞PIRQ Routing方式也從Legacy OS方式演變成ACPI Mode方式(原本Table放在記憶體,現在改放在ASL Code)。

另外由於南橋ICH有兩種PIC,所以進入ACPI OS時是採用Legacy PIC mode 還是APIC mode 也會影響BIOS提供PIRQ Routing Table的方式,所以在ACPI Mode 底下又分成APIC Mode方式或是Non-APIC Mode(PIC Mode)方式。

~~~轉載請註明出處~~~by 小華的部落格

[結論]
PIRQ#是南橋上面的接腳,連接到PCI 的INTA#~INTD#,原本INTA#~INTD#應該直接接到PIC上面的IRQ#接腳,但是因為IRQ#不夠用,所以微軟才與Intel合作,多做了幾支接腳出來,然後用軟體方式去配置這些多出來的接腳PIRQ#要繞線到哪個IRQ#,且作業系統的驅動程式可支援共享IRQ中斷,所以在Chipset 端把這種機制稱之為PIRQ Route Controller (具有PIRQ繞線功能的控制器,也就是某某一代的南橋開始把這個功能整合進去南橋晶片內)。

而BIOS所扮演的角色就是提供PIRQ Routing Table,這個Table的結構如同微軟的PCI IRQ Routing Table的規範,而當系統演變到ACPI 後,BIOS也改變了提供Table的方式,也就是改遵循ACPI Spec內的規範去提供這些資訊。

上述這些資訊只是我整理的筆記的一部分概要,詳細內容可以參考相關資料說明,畢竟我也是花了一個多星期的時間才整理出整個PIRQ的歷史,是對是錯我也不清楚,畢竟過去的架構我來不及參與,只能就我收集到的資料作一個描述,如有誤請先進指教。 ~~~轉載請註明出處~~~by 小華的部落格

[後記]
1)當ACPI OS 系統處在APIC Mode的時候,PIRQA~PIRQH會直接對應在APIC 的IRQ16~IRQ23而不需要繞線。
2)APIC 目前可提供的中斷請求線有 IRQ0~IRQ255 ,目前只使用IRQ0~IRQ23
3)APIC 前面的IRQ0#~IRQ15對應到PIC的IRQ0~IRQ15
4)PIRQ Routing 是指: IRQ不夠用才需要透過PIRQ Routing Controller繞線,所以只針對PIC,而APIC模式則不需要繞線。
4)APIC Mode只需要描述哪些PCI Device共用了哪些PIRQ線。
5)non-APIC mode則需要描述哪些PIC的IRQ#可以被使用,描述的內容如同PCI IRQ Routing Table,差別在於用ASL Code描述
6)APIC有分成Local APIC與I/O APIC,這邊所提到的都是指I/O APIC。

[Reference]
http://www.microsoft.com/whdc/archive/pciirq.mspx
http://www.microsoft.com/taiwan/whdc/system/CEC/ACPI-MP.mspx
http://www.google.com

[補充資料]
VGA IRQ的配置


早期VGA卡還在ISA時代的時候, 透過BIOS啟動系統時, 會得到interrupt 第一順位, 而且有固定存取區域在UMB(Upper Memory Black)裡頭與系統相對印; 在PnP時代, BIOS系統直接在啟動系統時給予IRQ#編號, 無論你是AGP or PCI都變成由BIOS指定位置, 而不再像ISA時代需要跟其他人搶系統控制權.

順便一提的是, MS系統推出雙螢幕(Dual VGA)的概念的時候, 一些VGA-BIOS沒規劃好的顯示卡就出現問題. 因為在BIOS啟動系統的時候, 會依照設定順序尋找開機起始用的顯示卡(預設是PCI), 結果因為有兩張PCI顯示卡(或者主機板的晶片採用SIS55xx系列, VIA早期586時代晶片都有這種現象), 反而推選不出一個可以啟動的System VGA, 變成必須在佈線的時候強制規定哪邊是佔用較高優先權的現象...

到了AGP時代, 變成系統直接佔用, 不再有IRQ衝突的事情.....對廠商對BIOS規劃人員也變得比較簡單點了....

display card在古老時代是不需要IRQ的,當時diplay card本身不過是被動式的介面,顯示記憶體都是由CPU來控制寫入或讀出與搬移(如HOYO兄所說),所以那時就是單純的使用 Linear address mapping 來處理,而遇到較大而複雜的畫面記憶體處理時,就用視訊記憶體頁切換來應付,然而隨著顯示的需求越來越複雜,VGA card和CPU間的溝通需求也越來越多,VGA本身也搭載了功能強大的GPU,可算是一個小的專門處理影像的CPU了.這時候 VGA Card不再是一個只會聽CPU命令的單元了,它開始會對CPU提出要求,以進行一些需要取用北橋資源的動作,此時就需要IRQ來做"舉手發言".

目前市面上仍然有一些VGA是不需要IRQ的,但並不代表它們不是高階顯示卡,而是視廠商所使用的技術而定.
一般的 VGA IRQ 預設大都在IRQ 11,除非有佔用系統會自動轉到別的IRQ...

PIRQ配置演算法(4 支PIRQ的時候)
PCI SLOT 中斷共有四個
INT a/b/c/d
一般的接法是
Slot1 a/b/c/d 接到南橋 a/b/c/d
Slot2 a/b/c/d 接到南橋 b/c/d/a
Slot3 a/b/c/d 接到南橋 c/d/a/b
Slot4 a/b/c/d 接到南橋 d/a/b/c
而卡上 通常是從 int a 接起
所以兩片使用一個中斷的PCI 卡會變成個別接到 int a / int b, 而不會通通都接到int a 了

PCI 要求裝置的中斷要共享,唯一缺點就是 "共享的部分" 要多消耗一些程式碼去確定中斷是給誰的
另有一些ON BOARD 的裝置已經固定接到其中的一些中斷訊號線上了,看看主機板說明書上有沒有寫
這樣你就可以知道INT a/b/c/d 哪些是完全可以獨立運作的。

中斷經過IRQ Routing table 去轉成我們常說的中斷號碼 IRQ# 這個在BIOS中可以設定,也可以自動配置。如果有獨立中斷的SLOT 就可以拿來用在IDE Card/SCSI Card這種講究效能的裝置上。

另外就是給一些卡使用,因為這些卡的驅動程式不肯跟別人分享中斷,如果分享就不肯工作。
還有一種卡和別人分享就容易當機。

有一些ISA 規格的裝置一定會佔用固定的中斷,如果不用可以DISABLE,不過 PCI 還是只有四個給你的SLOT用空再多也沒用要分享的依舊要分享
irq-0 Timer
irq-1 Keyboard
irq-2 Cascade
irq-3 Com2
irq-4 Com1
irq-7 LPT1
irq-8 RTC
irq-12 PS/2
irq-13 FPU
irq-14 IDE1
irq-15 IDE2

大部分的VGA 都沒有配中斷,另外 Win2k ACPI 會把IRQ routing table 通通都設成同一個中斷號碼 沒辦法改 效能會降低 因為每個PCI卡的驅動程式都會檢查一遍 看看是不是自己的中斷,每次也只會有一個說是他的中斷這在高速裝置是很糟糕的。

星期四, 8月 30, 2007

WDM 筆記2

自己學習WDM果然還是有許多問題無法解決,也只能邊學邊做筆記,做的筆記也不知道是對是錯,只能說WDM真難搞,難怪外面的WDM課程都那麼貴。

//----------------------
// ACP CreateFile
//----------------------
CreateFile() 可以去取得某個Driver的Handle,而這個API使用的是WDM Driver中的外部名稱,但是一般Driver命名方式除了直接給名稱之外還可以使用GUID(一串數字),而這兩種差別在下面比較:
外部名稱:
(1) 直接命名 : \\DeviceDos\\MyDriver
(2) GUID : DEFINE_GUID GUID_DEVICE_MYDRIVER 1234-ABCD-7890123ABCD-....
所以如果Driver是使用GUID,則必須透過一些方式轉換成CreateFile()可以使用的格式。

//------------------------
// 外部名稱的取得
//------------------------
想要對某個Driver送IRP,首先就要利用CreateFile()取得Driver Handle,而這個函數又需要知道Driver的外部名稱,那如何知道Driver的外部名稱是什麼?
這邊又要把Driver分成兩種:
(1) Inbox Driver: 這種型態的驅動程式指的是OS本身提供的,那這些Driver幾乎都是GUID命名,而這個GUID可以在DDK中找到,找xxx.h內的宣告,或是直接搜尋關鍵字 "DEFINE_GUID" or "GUID_DEVICE_HDD"
其中"GUID_DEVICE_HDD"也可以是"GUID_DEVICE_BATTERY",看你是哪個設備的Driver你就打什麼名稱,如果找不到就要另外想辦法囉。

(2) Non-Inbox Driver : 這類的驅動程式就是指廠商自己寫的,例如ATI...etc. 而這類的驅動程式一般廠商會提供GUID,或是在廠商提供的xxx.inf 內搜尋。

//-------------------------------
// Release Driver
//-------------------------------
一般Release Driver會給3個檔案:
(1) xxx.inf : 這是類似Script的東西,用來把Driver的一些資訊告訴OS,也就是會安裝一些資訊到Registry之中(例如 : ...\Service\{ServiceName}),而INF檔案裡面有其格式,可參考MSDN說明,其中就包含Driver的GUID,另外在[DeviceList]下面宣告的就是所謂的Hardware ID。
(2) xxx.sys : Driver COnpiler好之後的檔案,也就是實際的程式碼檔案。
(3) xxx.cat : 當經過WHQL後會有這個檔案可以使用,OS就會認定這個Driver是屬於經過認證的驅動程式,當然也可以做一個假的來測試。

//--------------------------------
// Driver Stack
//--------------------------------
Filter-DO: 過濾Driver
Function-DO : 一般設備的驅動程式都稱為Function-DO
PDO: Bus Driver

一般PDO還可以看做下一層的Driver,因為OS會把Driver堆疊起來,所謂的堆疊就是利用DeviceObject內的某個LowerDeviceObject欄位指向另一個DeviceObject,而LowerDeviceObject 指過去的Driver就是PDO,也就是下一層Driver,如果剛好是Bus Driver,那麼PDO=Bus Driver...

NTSTATUS AddDevice(PDRIVER_OBJECT DriverObject, PDEVICE_OBJECT pdo)
{
NTSTATUS ntStatus = IoCreateDevice(DriverObject, sizeof(DEVICE_EXTENSION),
NULL, FILE_DEVICE_UNKNOWN, 0, FALSE, &fdo);
....
pdx->LowerDeviceObject = IoAttachDeviceToDeviceStack(fdo, pdo);
....
}

由範例程式可以看到當PnP Manager 通知Driver去執行AddDevice()函數時,會跟我們的Driver說下一層的PDO是誰(當成參數傳給AddDevice()),然後我們先去為我們的Driver建立一個DeviceObject,這個DO也就是FDO,然後把我們的FDO 連接(Attach)在某個PDO上面,這樣子是不是就長的很像Stack ?

星期三, 8月 29, 2007

WDM 筆記

最近為了撰寫ACPI Driver所以先把之前WDM Driver的筆記整理一下:
//--------------------------
// IRP 用途
//--------------------------
IRP, I/O Request Packet : 內含command, data的一組封包。系統和Driver溝通時使用。

例如AP端送出IRP,內涵IOCTL_XXXX指令,以及一些參數
對應的Driver會去分析指令然後做對應的動作:
Switch(IRP->Command)
{
case IOCTL_XXXX :
Do Somethings here
case: ...
}
//---------------------------
//WDM Driver的Entry Point
//---------------------------
NTSTATUS
DriverEntry( IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath)
{
...
//在這邊去註冊你的Routine到DriverObject資料結構內
DriverObject->DriverUnload = MyIo_Unload;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = MyIo_CreateClose;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MyIo_Dispatcher;
...
}

//---------------------------
// WDM Driver 命名方式有兩種:
//---------------------------
1.內部名稱 (在你使用IoCreateDevice會用到) : Kernel 使用
\\Devices Kernel Mode定義
2.外部名稱(symbolic link name,給User應用程式的CreateFile使用) : User端使用
\\DosDevices

#define MYIO_NT_DEVICE_NAME L"\\Device\\MyIO"
#define MYIO_DOS_DEVICE_NAME L"\\DosDevices\\MyIO"

簡單的說就是OS 會把我們寫的Driver載入到記憶體中,然後會幫我們產生一個Driver Object資料結構在記憶體中,然後把這個資料結構位址當作參數傳給我們的DriverEntry(),接著我們就會設定資料結構中的一些函數指標指向我們Driver記憶體中的函數位址。EX: DriverObject->DriverUnload = MyIo_Unload;

當Driver Object結構設定好之後(或稱註冊)我們會去呼叫IoCreateDevice()函數,這個函數會在OS管理的Namespace中插入一個名稱,然後Kernel Driver層如果要找我們Driver時候就會去找這個名稱,而這個名稱就是內部名稱。

另外當應用程式需要對我們撰寫driver送出一些IRP的時候,其實也是透過"名稱"的方式來找到我們註冊進去的函數,然後OS才會把我們的IRP送給Driver Object內指標所指向的函數來處理,因此為了不要混亂掉,所以用另一種命名方式給應用程式使用,這就是外部名稱。

//-----------------------
// WDM Driver被載入的時機
//-----------------------
1. OpenSCM 方式
我們自己寫的Driver可以不用到到Windows目錄下,當我們需要這個Driver來使用的時候,可以透過SCM所提供的一些服務,先在登入檔SYSTEM\CurrentControlSet\Services 下面替我們的Driver建立一個鍵值,然後利用SCM 的服務去把我們的Driver載入到記憶體中,接著呼叫DriverEntry(),之後我們就可以透過AP對這個Driver 送出IRP

2. PnP Driver方式
對照 Registry 和 INF 檔,找到對應的driver,因為每個PnP Device都有自己的ID,類似PCI ID或是Vendor ID...等,當系統偵測到Device時,就會去登入檔內找到對應的Driver,所以自己撰寫的Driver也要撰寫一個xxx.inf 用來安裝這些資訊。
(1) 當系統偵測到device,就會將對應的driver載入到記憶體中,接著呼叫driver的DeviceEntry()
(2) 接著系統的PnP Manager 會去呼叫driver中的AddDevice() (註冊在Driver Object中)
(3) 處理AP的IRP或是PnP Manager 的IRP,或是Device 的INT 請求...等

3.非 PnP Driver
也是對照Registry 和INF 檔找到對應的Driver,但是與PnP Driver差別在於沒有PnP ID,因此OS不會自動找到Driver且載入Driver,而是User自己要利用新增設備(Add New Hardware Wizard)來增加Device,接著OS也是對照Registry 和INF 檔找到對應的Driver來載入到記憶體中。

星期一, 7月 30, 2007

動態記憶體(3)-撰寫輔助程式

前面兩篇文章提到如何去找到動態記憶體指標的程式碼位址,然後注入一段程式碼來取得指標ESI的位址,如果你在Softice 中修改後,並且回到遊戲中去驗證沒問題後(驗證的方式就是回到遊戲中隨便更新一下血量,或是重新啟動遊戲讓他重新配置一個指標位址,接著你可以利用GameMaster直接去讀取57BF30h這個位址的內容,這個內容=ESI,所以你要自己加上228h 的位址才會存放你的血量值),你就可以開始動手寫輔助程式了。

ESI+228h=血量位址

如何撰寫輔助程式呢? 這邊會用到的工具:
VC6 or VB6 or...等 (看你自己習慣用哪一種)
Windosw API (自己翻手冊查相關說明)
Softice (用來看機器碼)

簡單說就是利用ReadProcessMemory 或是WriteProcessMemory方式去對記憶體寫入值,而寫入的值是一堆的機器碼(因為你沒辦法像Softice一樣直接修改,所以只好用機器碼的方式注入你的程式碼到修改的位址去),所以你透過SoftIce 去修改好程式碼後,要記得把機器碼抄錄下來(例如: D 44717A),然後用這兩個API寫進去你要修改的位址。

VC6的方式 :
#define MY_CODE1 0xE9
#define MY_CODE2 0x34 //這部分是要寫入的機器碼的常量定義
.........................

//-----------------------------------------------------------------------------//
DWORD A1 =MY_CODE1;
DWORD A2 =MY_CODE2;
..............

//0x0044717A
WriteProcessMemory(nOK,(LPVOID)0x0044717A+0,&A1,1,NULL);
WriteProcessMemory(nOK,(LPVOID)0x0044717A+1,&A2,1,NULL);
WriteProcessMemory(nOK,(LPVOID)0x0044717A+2,&A3,1,NULL);
WriteProcessMemory(nOK,(LPVOID)0x0044717A+3,&A4,1,NULL);
WriteProcessMemory(nOK,(LPVOID)0x0044717A+4,&A5,1,NULL);
WriteProcessMemory(nOK,(LPVOID)0x0044717A+5,&A6,1,NULL);
//0057BF00
WriteProcessMemory(nOK,(LPVOID)0x0057BF00+0,&B1,1,NULL);
WriteProcessMemory(nOK,(LPVOID)0x0057BF00+1,&B2,1,NULL);
WriteProcessMemory(nOK,(LPVOID)0x0057BF00+2,&B3,1,NULL);
WriteProcessMemory(nOK,(LPVOID)0x0057BF00+3,&B4,1,NULL);
WriteProcessMemory(nOK,(LPVOID)0x0057BF00+4,&B5,1,NULL);
....

VB6方式:
A_Start = &H0044717A '要修改成JMP的地址
A_End = &H0057BF00 '跳躍JMP到的地址

'&H0044717A
Call WriteProcessMemory(A_Start + 0, &HE9, 1)
Call WriteProcessMemory(A_Start + 1, &H3D, 1)
Call WriteProcessMemory(A_Start + 2, &H31, 1)
Call WriteProcessMemory(A_Start + 3, &H30, 1)
...

動態記憶體(2)-修改流程

前一篇文章中我們介紹了如何透過一些工具去取得你的遊戲程式碼的位址,接著就說明一下整個修改的概念:

1. 先找到程式碼存放的位址,前面我們已經找到了程式碼存放的位址是在44717Ah:
001B:44717A MOV [ESI+228],EDX
001b:44717E ....

2. 修改44717A這個位址的程式碼
001B:44717A JMP 57BF00 <--跳到我們指定的一塊記憶體位址
001B:44717D NOP <--由於修改的JMP 57BF00 指令長度不足原來的,所以增加NOP來補足長度
001b:44717E ....

3. 修改57BF00
001B:57BF00 MOV [ESI+228],EDX <--還原原來被我們破壞掉的程式碼
001B:57BFxx MOV DWORD PTR [57BF30],ESI
001B:57BFxx JMP 44717E
001B:57BFxx

由上面的範例中可以看到整個修改的流程大致上就是如此,先找到他的程式碼的位址,修改它的程式碼,然後跳躍到我們指定的記憶體中,跳躍後我們先去還原原來的程式碼以免造成原來遊戲程式當機,接著把ESI的值存放到我們指定的記憶體位址,那麼當遊戲程式重新分配記憶體位址時,他的位址會放在ESI,而ESI的值會被我們放到我們指定的記憶體中,而我們寫的遊戲輔助程式就直接去我們指定的記憶體中就可以讀到ESI的值了。

※ Softice 修改程式碼的方式鍵入 "A 44717A" ,跟DOS的Debug.com 很像

在上述的方式中,要如何找到一塊可以使用的記憶體? 下面我就提供一個方式:
進入Softice 中,找到你的遊戲視窗名稱,接著鍵入MAP32 指令,如下所示:

:map32 lineage <--lineage 是你遊戲的名稱,這邊是範例,你看的值會不一樣
Owner Obj Name Obj# Address Size Type
lineage .text 0001 017F:00401000 ....
lineage .data 0002 0187:00560000 ....
lineage .rsrc 0005 0187:0057C000 ....

你會看到他把遊戲程式的PE檔頭的一些資訊列印出來給你看,其中.data 後面的560000h代表你的遊戲會把一些資料從記憶體位置560000h 開始存放,而我的方式是從後面自己找一塊來用,例如下一個區塊是從57C000h開始,因此我找57BF00h~57BFFFh 這一塊來當我要使用的地方。

你可以利用Softice 鍵入D 57BF00的方式去看那一塊記憶體有沒有人使用,如果沒有的話你就可以自己拿來用。

動態記憶體(1)-搜尋程式碼

玩過PC遊戲的都知道市面上有一些遊戲修改器可以做一些修改遊戲記憶體內的資訊來達到你的主角能力值變強或是有用不完的金錢...等。

今天我們要探討的是網路遊戲的修改,其實不能算是修改啦,因為對於網路遊戲來說,你無法使用這種方式去修改相關資訊,因為你的主角資訊是放在伺服器端,然後透過網路傳送資訊到你的主機,接著再更新到你的主機上面的記憶體中,例如: 血量:
CMP EDX,EDX
JZ LABLE1
MOV [ESI+228],EDX
...
LABEL1:
...
例如上面的範例,他會去檢查血量有沒有變化,如果有他才會放進去[ESI+228]的記憶體位址中,因此EDX存放著伺服器端傳送過來的血量資料,然後放到你的主機的記憶體位址[ESI+228]的地方,這邊的記憶體位址指的是位址空間,所謂的位址空間是指每個行程都有4G的虛擬記憶體位址,就好比說VC寫的程式在載入記憶體的時候會被固定放在某個位址上去,然後我們就可以知道某段位址範圍放的是"程式碼"。

上面有看到一個[ESI+228]的記憶體位址,由於網路遊戲怕你很容易就找到他的記憶體位址,所以一般會把這個位址用指標方式來重新配置記憶體,簡單說就是你每次找到血量存放的位址會不一樣,所以稱為動態記憶體方式來存放人物資訊,也因此我們要討論的是如何得到這個指標指向的位址,那麼就不用怕他一直變動了,所以由上面的範例可以知道ESI會一直變動,所以我們的目標就是得到ESI的值。

※ESI+228h = 血量位址,每個遊戲不一樣,所以不見得都是用ESI來存放,另外也不見得是+228h

在進入正題前先說明如何去得知你的遊戲是如何把這些資訊放進去記憶體中,我們會使用的工具如下:
1.WinXP
2.SoftIce
3.任何遊戲修改工具 ex: FPE, GameMaster...等

(Step 1): 首先先開啟你的網路遊戲,然後利用遊戲修改工具找到你的血量的記憶體位址(找的方式跟以前一樣,就是先找一次血量,然後進入遊戲改變一下血量,然後再找一次,一直重複到剩下來的位址,或是利用人物ID去搜尋也可以),例如找到的位址是12ABAC00h, 然後按下Ctrl+D 進入SoftIce,進入SoftIce後鍵入BPM 12ABAC00 W ,意思是說設定一個中斷點,當這個記憶體位址12ABAC00 被寫入Write的時候要中斷,接著按下Ctrl+D 回到遊戲中

(Step 2): 回到遊戲後隨便改變一下你的血量(喝藥水或是放法術),然後你就會看到SoftIce 產生了一個中斷,然後進入到SoftIce 畫面(如果有攔截到,就會自動進入SoftIce,如果沒攔截到就可以能你找到的位址不對)

(Step 3): 在SoftIce 中你會看到類似下面的程式碼:
001B:44717A MOV [ESI+228],EDX
001b:44717E ....

他所代表的意思是說你的遊戲主程式載入到記憶體後負責更新血量到記憶體位址12ABAC00h的程式碼是被放在xxxx:44717A ,而程式碼是 MOV [ESI+228],EDX,
你可以在Softice 中鍵入指令 D ESI+228 ,他會把ESI+228的記憶體位址傾印出來,所以你可以在螢幕上看到你的血量值。

(Step 4) : 拿筆把44717A 這個位址抄下來,然後把你的遊戲關閉掉然後重新啟動,重新進入遊戲後重複放面的Step1~3 ,如果每次都是停在44717A,那麼就恭喜你,你已經找到他的動態指標的程式碼的地方,接著就可以想辦法取得ESI 的值了。

星期四, 7月 12, 2007

Source Insight v.s SlickEdit® 2007

============================
Program Editor and Analyzer
============================
最近為了撰寫程式碼上的方便,分別去試用了兩套軟體,這兩套軟體都是用來撰寫程式碼與分析程式碼的編輯器,下面就大致上來說一下使用心得。

Source Insight : 目前版本最新版是V3.50.0057 ,他跟一般的程式碼編輯器比較起來功能強很多,像是你可以在變數名稱的關鍵字上面按下滑鼠右鍵就可以很方便的移動到定義(Go to definition)的地方或是列舉出參考(Reference)到的地方,另外針對函數或是變數...等,都有很不錯的顏色來區分,另外它還可以執行一些外部程式,我自己就用VB6寫了一些外部程式,然後按下熱鍵就可以呼叫我的程式來輔助編輯,像是說常常我們需要寫註解,那麼你就可以用Vb6 寫一段程式碼呼叫Postmessage API 對Source Insight 編輯區插入一段字串而省去打字的時間,另外專案檔的管理與搜尋也別具特色,他有檔案瀏覽器,你可以直接輸入你要找的檔案名稱,然後會列表出來,接著你就可以用滑鼠去點擊檔案來編輯,缺點就是字型太小,如果改大一點會變亂掉,另外就是不能編輯Unicode,且編輯畫面太小,附加的功能要自己利用其他語言工具開發...等。

SlickEdit® 2007 : 基本上Source Insight 有的他都有,編輯器長的很像UltraEdit,但是有比UltraEdit更強大的功能,像是Tag的建立與搜尋速度,可以連結VC6或是VisualStudio 直接Build或是Compiler你的程式碼,程式碼的分析與Symbols 搜尋也很快,缺點就是對於顏色的區分要自己手動建立,而且函數後面的括號多空一個就不會被當成函數顯示,不過這個缺點也是優點,因為他有提供Alias ,Macro...方式去增加你自己想要的功能,而且可以全螢幕編輯,畫面字體也大很多,簡言之就是他很多東西都要自己手動調整,如果你會調,調出來的功能一定比Source Insight 強大,所以使用後的心得是如果你貪圖方便,就用Source Insight就應該夠用了,如果你沒辦法忍受Source Insight編輯器畫面,又沒辦法忍受UltraEdit龜速的分析程式碼或是搜尋程式碼相關性的地方,那麼你就一定要考慮SlickEdit® 2007。

有人會想說幹麻自己去找編輯器,一般VC6或是Visual Studio不是就已經整合到IDE中了嗎 ? 沒錯,他們都已經有了,不過只能針對C/C++ 編輯,像我這種有.asm .c .cpp .inf .xxx 混合的專案就沒辦法編輯與分析了,因此要自己找一個編輯器可以用來管理分析程式碼,因此比較來比較去,就是這兩套最好用,而試用版在他們官方網站就可以找到了。

底下是一些設定的方式:

字體:
Tools->Options->Font
個人覺得最好看的是FixedSys。

顯示改動過的行標記:
Tools->Options->File Extension Setup
Advanced-Color coding-Modified lines

自動在保存的時候清除修改標記:
Tools->Options->File Options
Save-Reset line modify

這個標籤頁還有兩個實用的選項:
Expand tabs to spaces,保存時把Tab變成空格(有的地方要求必須用空格縮進)
Strip trailing spaces,刪掉行末尾的空白

快點顯示Symbol的代碼資訊:
下面的Symbol視窗類似與Source Insight的上下文視窗,會顯示當前函數或者巨集的代碼。
Tools->Options->Tagging Options
Updtae toolbars after ms idle:
縮短顯示代碼的速度。我用150。機子夠快扛得住就弄小點。

修改該死的自動語法範本:
Tools->Options->File Extension Options
Indent-Syntax expansion-Minmum expandable keyword length:
建議設置為3,如果為默認的1的話,你根本不能用i做變數。因為只要i起頭後面一個空格就是一個if () {},吐血。

滑鼠移到函數或者識別字上面顯示資訊:
Tools->Options->File Extension Options
Tagging-Show info for symbol under mouse
這部分其他的鉤也可以選上,減輕工作量啊。

對當前檔類型都顯示行號:
Tools->Options->File Extension Setup
General-Display line numbers

修改行號顏色:
Tools->Options->Color
不是Line Number,而是Line Prefix Area~~吐血 -_-!
改GCC的設置:
必須在建立了GNU C/C++專案後才能在Build裏面找到GNU C Options

設置專案的Build之類的功能表命令:
Project->Project Properties->Tools
選中一個Configuration以後就能配置編譯器和調試器
另外,在下面的Build視窗可以直接敲命令。比如make,dir什麼的,很方便

同時啟動多個SlickEdit
使用隱藏的命令開關“+new”即可。只要把SlickEdit的快捷方式修改成這樣:
"E:\Program Files\SlickEdit\win\vs.exe" +new

解決Slickedit某些視窗字體模糊的問題
比如Open Form的窗口,
用UE打開vs.exe、vsapi.dll兩個文件,查找“OEM Fixed Font”,會找到有OEM Fixed Font..Terminal的地方。把Terminal改成Fixedsys,注意要用覆蓋模式,不能改變了檔的長度,然後存檔即可。

將Slickedit的Projects資源列表切換成檔夾模式
Slickedit的檔排列是按照Source,Head之類的分的,很不習慣,把它改成按照檔夾結構
在Projects視窗中點擊一個vpj檔,按右鍵;選擇Auto Folder→Directory View,會出來一個警告框。再點OK就好了

Workspace分類管理
Workspace多了以後分類管理會比較方便
Project->Organize all workspace,可以用目錄的形式對workspace進行管理,
注意這裏並不會改變你workspace檔的位置,只是在slickedit裏面的分類而已。

如果發現路徑的'\'顯示成了中文的'¥'字元,那麼可以設置一下字體就可以了如果在XP下,直接將所有的字體都設置成 Default Fixed Font 就不錯。

增加Alias
選取一段文字-->按滑鼠右鍵選Create Alias

編輯滑鼠右鍵的Pop Menu
按滑鼠右鍵-->選Edit this Menu
如果你要把你的設定檔給別人使用,那麼你可以直接複製C:\Documents and Settings\你的使用者名稱\My Documents\My SlickEdit Config\內的 "12.0.2"這個目錄,但是要切記編輯滑鼠右鍵的Pop Menu
中的Version Control選項不能夠刪除掉,不然你複製過去的組態會不能使用,而且會出現錯誤訊息。

[註] 破解版請自行使用Google搜尋,或是使用IDA Pro ...等反組譯工具去修正某個DLL跟EXE就可以了...
不過我還是要說一句話,請尊重智慧財產權,愛用正版....感恩喔!

Reference
Source Insight V3.50.0057
http://sourceinsight.com/

SlickEdit® 2007 Version 12.0.2.0
http://www.slickedit.com/content/view/73/60/

星期五, 7月 06, 2007

System Shutdown

有些人對作業系統關機很有興趣,我這邊就大概描述一下BIOS在關機的動作中做了什麼事情,以及如何利用軟體工具去撰寫一段關機的程式碼。

一般在NoteBook上面控制電源的部分都是EC BIOS所控制,而有時候我們會透過Send EC Cmd方式給EC BIOS來達到關機的目的(Power Off)。

像是某些BIOS中會使用到關機的地方就是在Setup Menu-->Exit-->Shutdown 選項,而這個地方的BIOS code 就可以利用EC Cmd或是PM Register來達到關機的目的。

另外在進入作業系統後我們也可以透過上面的方式來強制關閉系統,所使用的方式就是利用PM Control Register。

所以BIOS扮演的角色就是去設定PM Register IO Address Range & ACPI Table ,這樣說或許很多人搞不懂,簡單說就是BIOS提供一些資訊給OS,提供的方式是透過ACPI Table(放在記憶體中),OS自己會去記憶體中找到ACPI Table,然後得到BIOS所提供的資訊,其中這些資訊就包含PM Register的IO Address。

底下提供一個範例程式來做系統關機的動作,如果你是在DOS下可直接用Debug.com 執行
如果是在 Windows下請自行撰寫Ring 0 程式去對IO Access 或利用相關工具:

mov ax,3c00h <--請參考ICH 手冊上面的說明
mov dx,0404h <--PM Register I/O Address. (PMBase+04h)
out dx,ax

上面有提到PM Register IO Address 資訊是由BIOS所提供,因此不同的BIOS廠商所設定 PM Register 的IO Address range也會不同,所以如果想要強制關機就必須先找到這個位址,這樣才能對PM Register做Soft off (S5 satte)的動作。 一般比較常見的就是Port 0404h,所以如果你不知道BIOS工程師設定在哪個位址,那麼你可以利用ACPI Table查詢或是請教你們的BIOS工程師。

另外如果你自己在Windows下寫了一個Ring 0 層級的程式去做S5 state(Soft Off),你會發現系統會直接關機,也就是OS不會去通知Driver工作站要關機,而是直接(立即)由硬體那邊關閉系統。

Reference
ACPI Spec 3.0
ICH8 Spec

星期一, 7月 02, 2007

逆向工程-基礎知識

MASM 運算式 C++ 運算式
========================================
BYTE PTR [bx] *(unsigned char) ebx

WORD PTR [bp] *(unsigned short *) ebp

DWORD PTR [bp] *(unsigned long *) ebp

OFFSET Var &Var


;----------------------------
; 呼叫協定 test1(par1,par2,par3)
;----------------------------
__cdecl PASCAL __stdcall
push par3; push par1 push par3
push par2; push par2 push par2
push par1; push par3 push par1
call test1 call test1 call test1;//函數內回復堆疊
add esp,0ch //回復堆疊

由上面範例可以得知,_cdecl 與_stdcall的參數都是由右邊開始堆到堆疊去,也就是說par1會在堆疊頂端。
依照__stdcall 範例:
push par2
push par1
call test2;
{
push ebp //保留原來的ebp
mov ebp,esp //ebp指向目前的堆疊頂端
mov eax, [ebp+0c] //參數par2
mov ebx, [ebp+08] //參數par1
sub esp,8 //如果程式內有變數,則保留變數大小,例如int i,j
...
add esp,8 //釋放變數佔用的堆疊
pop ebp //復原ebp
ret 8 //相當於ret : add esp,8
}
;----------------------------------------------------
;堆疊示意圖 (堆疊位址是由高往下,也就是堆疊頂端位址是低位址)
;----------------------------------------------------
... <-- ESP 會一直指向堆疊的最頂端
程式內的變數j <-- EBP-8h
程式內的變數i <-- EBP-4h
保留的EBP <-- EBP + 0 (進入副程式後會mov ebp,esp)
返回位址(Offset) <-- EBP + 4h
Par1 <-- EBP + 8h
Par2 <-- EBP + 0Ch (由左至右堆入堆疊,所以第一個被堆進去)
...

C語言複習~~~指標與函數

年紀大,東西都忘東忘西的,今天複習C Primer Plus 4/e,把一些東西記錄下來。

高度技巧的宣告
* 表示一個指標
() 表示一個函數
[] 表示一個陣列
※()與[]有相同的優先順序,他們的結合姓是由左到右 →

int a[8][8]; //int陣列的陣列
int **ptr; //指向int 指標的指標
int *risks[10]; //指向int 型態的10個指標陣列
int (*risks)[10]; //指向10個int 陣列的指標
int *oof[3][4]; //指向int型態的3x4指標陣列
int (*uuf)[3][4]; //指向3x4 int 陣列的指標
int (*uof[3])[4]; //指向4個元素的int 陣列的3個元素的指標陣列

char *fump(); //回傳指向字元的指標的函數
char (*fump)(); //指向會回傳字元型態的函數的指標
char (*fump[3])(); //3個指向會回傳字元型態的函數的指標陣列

typedef int arr5[5];
typedef arr5 *P_arr5;
typedef p_arr5 arrp10[10];
arr5 togs; //togs是有5個int元素的陣列
p_arr5 P2; //p2是指向有5個int陣列的指標
arrp10 ap; //ap是10個指向5個int陣列的指標陣列

函數宣告與指標

void ToUpper(char *); //將字串轉大寫

其中ToUpper()函數型態是void,參數是char *,如果宣告為這個函數宣告一個 pf指標,就會像下面一樣:

void (*pf)(char*); //指向函數的指標,例如: pf=ToUpper,但是不能寫成pf=ToUpper();

其中括號內的(*pf)會被括號結合,所以閱讀時他會是一個指標,所以他是一個指向函數的指標,如果省略就會不同意義:

void *pf(char *) ; //pf 是回傳指標的函數

※一般如果沒有註明時,定義的名稱本身就是位址起始,例如:
int a[10];
int *p;
p=a; 或是 p=&a[0];

void ToUpper(char *);
void (*pf)(char*);
pf=ToUpper;


應用範例:
void ToUpper(char *);
void (*pf)(char*);
void Show(void (*ff)(char*), char *str);
pf=ToUpper;

Show(ToUpper, mis); //Show函數使用ToUpper函數: ff=ToUpper
Show(pf, mis); //Show函數使用ToUpper函數: ff=pf

void Show(void (*ff)(char*), char *str)
{
(*ff)(str); //呼叫ToUpper函數,參數=str
puts(str); //顯示字串
}

C語言與組合語言對應
;-------------------------------------------
; VOID Funcation_A(VOID);
;-------------------------------------------
Funcation_A PROC PUBLIC
push eax
....
pop eax
ret
Funcation_A ENDP

;------------------------------------------------------------------------------
; VOID Funcation_B (IN UINT32 RegisterInEax, OUT OEM_REGISTER *Reg);
;
;typedef struct {
; UINT32 RegEax;
; UINT32 RegEbx;
; UINT32 RegEcx;
; UINT32 RegEdx;
;}OEM_REGISTER;
;------------------------------------------------------------------------------
Funcation_B PROC PUBLIC RegisterInEax:UINT32, Reg:PTR EFI_CPUID_REGISTER
pushad
...
mov edi, DWORD PTR Reg
...
mov DWORD PTR [edi].RegEax, eax ; Reg->RegEax
...
popad
ret
Funcation_B ENDP


;------------------------------------------------------------------------------
; UINT32 OemIoPortRead32 (IN UINT64 Address); //回傳值放EAX
;------------------------------------------------------------------------------
OemIoPortRead32 PROC PUBLIC Address:UINT64
push edx
mov edx, DWORD PTR Address
in eax, dx
pop edx
ret
OemIoPortRead32 ENDP

星期四, 6月 21, 2007

S3 Resume

今天在測試EFI 的S3 Resume功能,看著C語言的寫法突然間把我原本在P廠商搞不清楚的問題都解決了,果然學BIOS還是從頭學會比較有感覺。

[學習紀錄]
1. ACPI Table的建立與放置的位址
2. 如何從F/E Segment 中找到ACPI Table
3. FACS中的Wake Vector 與OS、BIOS之間的互動
4. PM Register 與Sx State 的關係

星期二, 6月 19, 2007

海賊王~~~308話

端午節連續假期我都躲在家裡面看海賊王,總算看到308話了,雖然後面還有一些沒看完但是心裡面有夠爽的啦!

魯夫加油啦!

星期四, 5月 31, 2007

EFI v.s #define

今天把EFI 的一些心得筆記整理的差不多了,所以開始去Trace 一些Source code,在追蹤的過程中發現了Intel 在寫EFI 的時候真的把所有的東西都模組化了,以後寫BIOS的時候可以很方便的去撰寫。

而在這些EFI Code之中,因為幾乎都是C (不是C++,但是Phoenix BIOS可以使用C++方式撰寫,不過我還沒接觸到他們的EFI,所以還不確定是否真的可以!)的語法去撰寫,所以在模組化的同時他也使用了很多巨集指令,也就是使用#define 的方式去宣告了很多不同的巨集指令,在這裡面發現了"##"這個東西,例如下面的範例:
#define MyMacro(a,b,c) {a ## b ##c}
MyMacro(A,B,C)

而##是用來連結字串的,所以展開後會變成ABC,所以在Blog 留下筆記心得的紀錄。

星期二, 5月 29, 2007

EFI Protocol 觀念小筆記

C語言的宣告與定義:
1.C語言中只允許變數或函數的定義出現一次。
由於變數只有定義規則,沒有宣告規則,所以當你鍵入int a;時,代表此變數已經被定義了。我們如何宣告一個變數呢? 加上extern 變成exern int a;,告訴comipler,此變數a只是一個宣告,它的定義在別處。

2. C語言函數規則分成宣告與定義二種。當一個函數只有傳回值、名稱、傳入值, 沒有大括號{},compiler會把此種形式當成是函數宣告;如果加上了{}, 形成函數定義。所以你要宣告一個函數,只須鍵入int f(); compiler會自動把它示為函數宣告。

// EFI Protocol
------------------
EFI Protocol 可以看作是一個介面(Interface),透過這個Interface 去存取Data 或是函數,簡單說就是一個指標,指標指向的記憶體可能是Data,也可能是函數。

安裝一個 EFI Protocol 後會以 (GUID | Pointer) 的欄位格式存放在Handle DataBase中,然後就可以在其他地方利用LocateProtcol()方式去得到指標,然後使用他。

1.Data 型式的EFI Protocol
(a)宣告你的Sturcture與GUID在你的Protocol.h 與Protocol.c中
EFI_GUID gGuid=MY_GUID;
Extern gGuid;
typedef struct {
...
UINT8 Array[10];
...
} MY_PROTOCOL;

(b)在DXE Driver中使用使用MY_PROTOCOL "定義"一個變數Test
UINT16 i;
MY_PROTOCOL Test={......};

然後在呼叫InstallProtocol(&gGuid,&Test)去安裝一個Protocol到Handle Database,所以Handle Database中的Guid=gGuid,而指標Pointer=&Test。

2.Function 型式的EFI Protocol也是跟Data型式一樣作法,不同的地方在於宣告的是函數原型。

所以觀念在於如何去宣告Protocol與如何透過GUID+Pointer去做你要做的事情。

星期一, 5月 28, 2007

今天收到的新聞~~~

外傳廣達接獲新的iPhone訂單,廣達表示,廣達接單方向積極朝多元化產品發展,不會對單一客戶進行評論。據瞭解,開發一款手機約要六個月到九個月,隨著第一代iPhone推出,蘋果內部確實也在開發第二代iPhone,但訂單花落誰家,要視第一代產品銷售狀況才會決定。

看樣子我要去問問我那些做Apple代工的朋友們消息是否正確了....呵呵!

星期三, 5月 09, 2007

Direct Hardware Access Under Windows 9x/NT/2000/XP/Vista

今天又多學一樣東西,就是呼叫Undocument API 去開啟I/O 存取權限,這樣子你就可以使用Ring3的應用程式直接存取硬體,例如下面的VC++範例程式中直接使用Assembly 或是IO Funcation call去存取硬體。

EX1: Read I/O Port
_asm {
pushf
mov al, ireg.h
mov dx,0x70
out dx,al
mov dx,0x71
in al, dx
mov mbid, al
popf
}

EX2: Read CMOS
_outp(0x70,ireg.h );
mbid=_inp(0x71);

使用的技巧是先進入Ring0 (也就是掛一個Driver),然後在DriverEntry 時去呼叫API去改變IO權限。
void Ke386SetIoAccessMap(int, IOPM *);
void Ke386QueryIoAccessMap(int, IOPM *);
void Ke386IoSetAccessProcess(PEPROCESS, int);

開剛始是利用修改TSS的來開啟IO存取權限方式,不過改完後DEBUG.COM會無法執行,所以才改成呼叫上面的API的方式,而直接修改TSS方式如下所示:
__asm {
cli // 遮罩中斷
sgdt gdtr // 得到 GDT 基底位址與段界限
str TSSseg // 得到 TSS 選擇子
movzx esi,TSSseg // 擴展到 ESI 中以便計算
add esi,gdtr.dwBase // 得到 TSS 在 GDT 中描述符
mov gdt,esi
}
不管是API還是修改TSS,都是在DriverEntery進去後去執行的。

因此實作部份分成三部份:
1.WDM Driver code(DriverEntery 的地方呼叫API)
2.InstallDriver code(應用程式執行時去動態載入驅動程式)
3.應用程式Application code(直接使用Assembly或是IO Function)

星期二, 5月 08, 2007

Windows 下的I/O存取

昨天我們Leader 請我幫忙寫一個Tools給對岸同胞的生產線使用,他們需要在Vista 底下去存取Panel ID.
所以自己留下個撰寫紀錄,讓自己下次修改自己的Source Code的時候有個依據。

目前Panel ID我們是使用跳線的方式去改變GPIO值來做辨別,所以只要去存取GPIO就可以了。

1.南橋ICH8會把GPIO 值 mapped 到某個IO Range...所以只要利用組合語言的IN/OUT 去存取他映對過去的IO 位址就可以知道GPIO的值。
2.查看主機板電路圖,看實際上被當成選擇Panel ID的GPIO是哪幾支接腳。
3.開發一個DOS版的Tools直接使用IN/OUT指令去存取,例如下面範例:
byte ReadGpioStatus(word OEM_GPIO_ADDR)
{
byte mbid;
...
_asm {
mov dx, OEM_GPIO_ADDR
in ax, dx
mov mbid, al
}
...
return mbid;
}
4.開發一個Windows 版的Tools也是使用IN/OUT指令,不過是由自己撰寫的I/O Driver所提供
byte ReadGpioStatus(word OEM_GPIO_ADDR)
{
byte mbid;
mbid=IN_B(OEM_GPIO_ADDR); //IN_B 會去呼叫Windows API然後送IRP給我自己寫的Driver
return mbid;
}
5.在Source code內做一個介面IN_B() 然後呼叫API去使用自己寫的I/O Driver所提供的服務
byte IN_B (WORD port)
{
ireg.x.DX = port;
...
bRc = DeviceIoControl (...); //送IRP給我自己寫的I/O Driver...
...
return (ireg.h.AL);
}
6.整個Windows版本的Source code架構與DOS版本差異不大,只是Windows版本需要去動態載入驅動程式,而這部分寫在if (!InitializeWinIo()) return 1 ;

上述這幾個部份是比較重要的地方,而整個DOS版與Windows版的程式架構如下:
int main(int argc,char *argv[])
{
modelString[]={
"000"," Reserved. ",
"001"," Vendor1. ",
"002"," Vendor2. ",
....
}
...
if (!InitializeWinIo()) return 1 ; // For Windows version only.
...
pStatus1=ReadGpioStatus(OEM_GPIO_DEFAULT_IO1); // Read Gpio
pStatus2=ReadGpioStatus(OEM_GPIO_DEFAULT_IO2);
...
panelid=(GetBit(pStatus1,2)<<2)+(GetBit(pStatus1,6)<<1)+GetBit(pStatus2,6);
...
if (strcmp(argv[2], modelString[panelid*2]))
{
...// if match...return passed!
}
return 0;
}

從上面範例可以知道其實DOS版與Windows版的Source code 寫法幾乎一樣,只是Windows版因為無法直接存取I/O,所以我自己多加了一個I/O Driver,然後做了兩個Function (IN_B()跟InitializeWinIo())來載入與使用這個Driver 。

星期一, 5月 07, 2007

SEGMENT與Public/Extern 用法

在BIOS Source code之中常常會看到一大堆的Segment,而有時候相同的Segment內去使用同一個Segment內的程序時,會在Segment 內去把他Extern進來,如下範例所示:

Test1.asm
AAA SEGMENT <--AAA Segment 裡面有一個BBB程序被共用(Public)
....
BBB PROC NEAR PUBLIC
...
BBB ENDP
....
AAA ENDS

Test2.asm內容如下
AAA SEGMENT <--AAA Segment內有一個CCC程序要去呼叫BBB程序,但是要在AAA Segment把BBB程式用Extern方式拉進來
EXTERN BBB:NEAR
....
CCC PROC
...
CALL BBB
...
CCC ENDP
AAA ENDS

Public 是把程序共用,而Extern 可看作要使用一個共用的程序
一般來說如果BBB程序與CCC程序都在同一個檔案內,那麼CCC程序可以直接呼叫BBB程序而不需要Extern
而不同"檔案"時就要這樣做,如上面範例是Test1.asm 跟Test2.asm

Strong/Weak 語法與"::"標籤表示法

組合語言中的Strong/Weak 語法簡單說就是如果A存在就跳躍過去A執行,如果A不存在就跳到B去執行
假如我有一段程式如下面所示:

PUBLIC BBB&Return
EXTERN AAA(BBB&Return):NEAR <--Strong/weak寫法

Test1 Proc
...
Jmp AAA
...
BBB&Return:: <--如果AAA不存在會跳到這邊執行
ret
Test1 EndP

AAA Proc <--如果AAA存在會跳到這邊執行
...
ret
AAA EndP

除了上面的Strong/Weak之外,還可以看到標籤是BBB&Return:: 而"::"代表的意思有兩種:
1.PUBLIC 出來的標籤,要把它標示成"::",簡單說就是不同程序間也可以跳躍
2."::"不同程序間的跳躍,如下面範例所示:
[註]功能還是跟 ":"一樣,只是表示方式不同而已。

Public Label2
Test1 proc near

Label1:

Label2::

ret
Test1 endp

Test2 proc near

jmp Label1 ; compiler 會錯誤, 因為該 label 找不到

jmp Label2 ; 正確, 該 label2 是可以找到的,因為有把他Public

ret
Test2 endp

星期日, 5月 06, 2007

BIOS Code Entry point

前一篇說明了CPU如何去讀取第一條存在於BIOS ROM裡面的程式碼,現在就說明一下實際上BIOS Code是怎樣跑的。

這邊要重新說明一下觀念:
FFFF_FFF0 是第一條指令所存放的地方,而他是在4G頂端的地方,CPU在被重置(Reset)之後會去這邊讀取指令,而對CPU來說並不會在意Address FFFF_FFF0 是給BIOS ROM使用還是記憶體DRAM使用,而80386之後,這個位址是都給BIOS ROM使用。

000F_FFF0 (或表示成F000:FFF0),這個位址是在1M 頂端的地方,而這個位址是給BIOS ROM還是DRAM?
給誰使用則是去設定北橋,一般來說在POST 前(Power On-->BootBlock),這個地方應該是給BIOS ROM使用,而POST中途會去做Shadow動作,這個動作做完後,這個位址會給DRAM使用。

以前8088/8086 在1M頂端是給BIOS ROM使用,後來才變成BIOS ROM/DRAM 混用,所以以前CPU被重置的時候是去F_FFF0(F000:FFF0)讀取第一條指令,後來的CPU就不這樣做了。

上面觀念說明完之後我們就實際追蹤一下BIOS code的流程;在進入到OS之後,如果你去看000F_FFF0的資料,其實是看到DRAM裡面的資料,而這個資料是BIOS放進去的;而進入OS之前,這個位址的資料因為預設是給BIOS ROM使用,所以會跟FFFF_FFF0看到的資料一模一樣。

下面是實際上我們去追蹤P廠商BIOS code的範例:
1.我們先利用一些工具去在OS中去存取4G頂端的位址FFFF_FFF0 看到的資料如下所示:
FFFF_FFF0 E9 55 B2 ........... -->實際在BIOS ROM裡面的資料,因為這個位址是給BIOS ROM使用

上面看到的E9 55 B2 就是CPU讀取的第一條指令,而這個指令是一個Jmp 指令,他會跳到BootBlock Entry Point去執行,執行完畢後才會跳到POST Entry point.

2.下面也是在OS中利用工具去讀取1M頂端的資料,由於已經進入OS了,所以這些資料是BIOS填進去DRAM的資料(Shadow),也就是說這個000F_FFF0是被指向DRAM之中。

F000:FFF0 EA 5B E0 00 F0 ..... -->跳到F000:E05B
F000:E05B E9 FB A8 ........... -->跳到實際的POST Entry point....
A8FB+E05E=8959 (進位1 去掉)
F000:8959 FA ................. -->FA=CLI

這邊可以看到為了相容性問題,所以000F_FFF0是放著Jmp F000:E05B 位址,而這個位址裡面會放著POST Entry point指標(E9 FB A8),所以繼續追蹤過去後可以發現POST的第一條指令是CLI...

3.其實CPU第一條指令E9 55 B2 會跳到BootBlock去執行;而執行完畢後,BootBlock也會跳到跟F000:8959內一樣的BIOS code去執行所謂的POST。

所以簡單的描述上面所說的流程:
1.CPU去FFFF_FFF0 提取第一條指令執行(Jmp BootBlock)
2.執行BootBlock
3.執行完bootBlock後,跳到POST
4.執行POST (POST第一條指令一定是CLI)
5.POST過程中Shadow ,Shadow後000F_FFF0的資料就是Jmp F000:E05B

這樣子說應該很容易瞭解了吧! ^^Y