小華的部落格: 2007/12/16 - 2007/12/23

搜尋此網誌

星期四, 12月 20, 2007

A20 開關 v.s 回繞

很多剛入行的朋友問我A20開關的知識,我這邊就整理一下相關資訊,讓大家查閱的時候可以有個參考。

首先先說明相關的CPU的工作模式:在目前x86下面工作模式與A20有關的就是保護模式跟真實模式。

真實模式下,它允許定址到1MB記憶體,所以超過1MB的位址(FFFFFh)要繞回去00000h,這就是所謂的回繞

保護模式下則分不同時間點來談,在80286 時,位址線增加到24 pin,所以可以定址到16M,而為了向下相容,所以設計了一個開關A20 Switch來控制A20~A23,當A20=0時,強制把位址線歸零,當A20=1時,可進位

後來因為這個開關要Reset才能回復狀態,因此出現一個新的問題就是進入保護模式後,要重新開機才能回到真實模式。

於是有人就想說找一個設備,然後控制這個設備就可以做A20的開與關,因此找上了8042 KBC,所以以後開關A20 Switch時,只要去設定8042就可以了,因此解決了每次回真實模式都要重新開機的問題。

後來80386 之後,CPU的設計可以直接從保護模式切換回去真實模式,但是為了向下相容,所以還是一值保留這個設計。

慢慢的這個留下來的設計又出現新的問題,x86設計師可能想說每次都透過8042去開關速度有點慢,因此後來又提出了Fast A20 的設計,簡單說就是透過Port 92h 直接設定A20 switch開關。

至於這個被保留下來的設計,還有沒有當初的功能我也沒去測試,不過已經變成一種習慣,就是進入保護模式要去開關A20 switch。

以上大致上就是x86對於 A20的歷史,有興趣的人可以多去找找ㄧ些資料來看,或是做做小實驗,看看A20 switch不開的時候,會發生什麼事情 ^^.

星期三, 12月 19, 2007

純手工打造你自己的x86 BIOS(4)

上ㄧ篇文章我已經針對我的實驗做了敘述,這裡我就針對實際上我的程式碼撰寫的內容做一個介紹。

在程式碼的撰寫中,其實我只有使用了簡單的C語言跟組合語言語法,重點是要讓大家知道,其實BIOS跟一般的Boot Loader寫法沒什麼不同,只是PC上面的BIOS需要考慮的事情比一般的Boot Loader還多很多,因此程式碼 size可以大 到1MB甚至是2MB (ㄧ般Boot Loader不可能這麼大),所以我有機會玩一個這麼大的Boot Loader也真是很榮幸的啦!

廢話不多說,我就先針對我前面提到的Build.exe 內的程式碼說明;

底下是我的Build.c 內的程式碼片段,其實我就只有使用到fopen() 、fputc() ...等基本的函數去讀寫一個檔案,所以可以很容易的把我組譯好的MyBIOS.bin 跟EC.bin 塞進去同一個檔案內,做法其實很簡單,就是像我下面做法一樣,先利用fopen()開啟檔案,然後在把你要的資料寫進去檔案,只是寫的時候你要考慮file offset 位置的問題,因為當你燒錄到BIOS part中的時候,CPU是會固定重FFFF_FFF0h的位址讀取第一條指令,因此你要像我前面說的一樣,把MyBios.bin放在固定的位址中。

//建立一個空白的MyBIOS.ROM , 裡面資料都是00h
void show_help(void)
{
printf("Build.exe v1.0.0 by Harrison Hsieh \n");
printf("===========================================\n");
printf("/C Init MyBIOS.ROM \n");
printf("/B [EC] [BIOS] Add Rom \n");
printf("Output : MyBios.ROM \n");
}
void InitBiosROM(char *argv[])

{
FILE *fo;
long i;
if ((fo = fopen (BiosRom, "wb")) == (FILE *) NULL)
{
exit(1);
}

for(i=0 ; i<= BIOSSIZE ; i++) //1MB
{
fputc(0x00,fo);
}
/* All done, close the file */
fclose (fo);
}

在說明完Build.c內的做法後,接著說明MyBIOS.bin 內的程式碼撰寫;
其實在MyBios.asm 中,我只有做4 件事情:

1. 設定好FFFF_FFF0h的第一條指令
2.開啟BigReal Mode (因為我要設定ICH9的RCRB內的暫存器,所以要開啟)
3.設定ICH9內的暫存器,把所有Port 80h的訊號轉送到LPC介面(我的Post card走LPC界面,所以要設定)
4.輸出99h 到Port 80h(所以LPC介面上面的Post card就會顯示99h)

底下是我的MyBIOS.asm 內的程式碼片段:

COLDBOOT:
CLI
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 1. Enable big real mode
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
JMPREG di,Make4GBSegmentDI

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 2. Set RCRB base address
;; 3. Config ICH9 Register
;; 4. Out 99h to Port 80h
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
....
mov dx, 0cfch
mov eax,RCRB_BaseAddr
out dx, eax
....
and BYTE PTR es:[esi], NOT (04h) ; RCRB+xxxxh bit 2=0 Output to LPC
....
fPostCode:
mov al,099h
mov dx,80h
out dx,al
jmp fPostCode ;無窮回圈ㄧ直顯示99h
...
...
wbinvd ; ...begins here on power up
PUBLIC POWER
POWER:
JMP COLDBOOT ; first jump
DB '11/14/07',00,00,00 ; My release marker

以上就是我撰寫的程式碼內容的說明,其實沒有用到什麼特別的東西,如果說比較難的部份大概就是如何把程式碼塞到正確的位址吧。

總結:
純手工打造你自己的x86 BIOS 文章(1)~(4) 在這邊就做一個結束,在這幾篇文章中的實驗我主要是要幫助剛入門的BIOS新手去了解整個BIOS vendor提供的BIOS環境架構以及實際上BIOS code撰寫的第一步,因為很多東西都是入門的第一步比較難。

還記得ㄧ年前我剛入行的時候,我們學長跟我說寫BIOS最簡單的方式就是自己把一個BIOS寫到能開機你大概就已經學會了,雖然我離能自己寫到開機還有一段距離,不過在學習的過程中也學到了很多東西,因此當初會想自己純手工寫一個能讓x86 CPU執行一段BIOS code的環境也是希望能幫助更多BIOS入門時遇到挫折的朋友 ^^Y。

BIOS Boot Specification

最近有些朋友問我有關BEV以及BCV的相關資訊,這邊轉貼一篇之前收集的文章中的部分內容,希望對大家有幫助。

原文章出處:http://tw.myblog.yahoo.com/jw!T20aSgeaCQdebnoX0CAb9Xk-/article?mid=17

~以下是轉貼內容~
BIOS Boot Specification
完整的文件可以參考
http://www.phoenix.com/NR/rdonlyres/56E38DE2-3E6F-4743-835F-B4A53726ABED/0/specsbbs101.pdf
以下為一些重點整理
BBS (BIOS Boot Specification) 是用來規範 BIOS 如何選擇啟動裝置。它包含了
1. 辨識系統中的 IPL (Initial Program Load) 裝置
2. 根據使用者的選擇,尋訪每個裝置並檢視它是否能夠啟動系統

IPL
IPL 指的是可以啟動載入並執行作業系統的裝置。他包含了像是 Floppy, Hard drives, CD-ROM, PCMCIA conrtollers/cards, PnP Cards, Legacy cards 甚至像是 Network, Serial port, Parallel port 等等可開機的實體或虛擬裝置。
所有的 IPL 可以被歸類成下列三種
1. BAID
2. PnP Card (可再細分為 BCV 和 BEV 兩種裝置)
3. Legacy IPL Device

BAID (BIOS Aware IPL Device):
此類 IPL 需要 BIOS 的程式碼支援,來提供它啟動系統的能力。通常啟動的程式碼內建於 INT 19h (BIOS Bootstrap loader) 的服務之中。常見的裝置如下:
First floppy drive, First ATA Hard drive, PCI ATA card drive, ATAPI CD-ROM drive, PCMCIA controller bootable card, Ethernet controller code embedded in BIOS
PnP Cards:

此類 IPL 裝置,必須附加 option ROM 於 C0000h-EFFFFh (2K boundary)。而且在 Option ROM 中必須有 PnP Option ROM Header (Table 2)。另外,開機相關資訊會被記錄在 PnP Expansion Header (Table 3),在此表格中,包含了 BCV 或 BEV 的指標。

BCV (Boot Connection Vector):
BCV 是一個指標,指向 Option ROM 中的某一段程式碼。這段程式碼負責執行裝置的初始化、偵測硬體 (例如是否有 SCSI 裝置連接到系統) 或者在必要時 Hook INT 13h 的服務 (Disk I/O)。常見的有:
PnP SCSI card drive, NoN-PnP card PnP Expansion Header
BEV (Bootstrap Entry Vector):
BEV 是一個指標,指向 Option ROM 中負責載入作業系統的一段程式碼,並在必要時 Hook INT 18h 或 INT 19h 的服務。通常於網路卡裝置的 Network Remote Boot 時使用。常見的有:
PnP Token Ring card, PnP Ethernet card, NoN-PnP card PnP Expansion Header

Legacy IPL Devices:
此類裝置為標準的 ISA Card,其包含了一個 Option ROM 於 C0000h-EFFFFh (2K boundary)。此類型的裝置於 Option ROM 並沒有 PnP Expansion Header 的相關資訊。在它的 Option ROM 被 BIOS 找到時,會先執行一段初始化的程式。這段程式執行期間,會根據需要來 Hook INT 19h, INT 18h 以及 INT 13h。

IPL Table
每個 BAID 以及 BEV 裝置必須在 IPL Table 中有一個相對應的欄位
範例
0: Floppy A:
1: Hard Drive C:
2: CD-ROM
3: BEV #1
4: BEV #2

IPL Priority
IPL Priority 決定 IPL 開機的順序。它存在於非揮發性記憶體中,並且可以讓使用者修改。在 INT 19h (載入作業系統) 呼叫中,它必須能夠被取用,並且根據表格中的順序來進行開機的程序。

BCV Priority
在 BIOS INT 13h (Disk I/O) 的服務之中,磁碟機代號 00-7Fh 為 Floppy Disk, 而 80-FFh 為 Fixed Disk。而這些代號和實體磁碟的對應必須在 BIOS 中完成。另外值得注意的一點就是,由於只有第一台 Floppy 和第一台 Fixed Disk 可以用來啟動 (代號 00h 以及 80h),所以根據不同的啟動設定,也必須將 INT 13h Hook 的順序作調整才能夠順利開機。
舉例來說,如果 ATA 硬碟占用掉 80h,而 SCSI 只能占用 81h 之後的磁碟機代號的話,那麼 SCSI 硬碟就不能作為開機的硬碟了。
範例
BCV Table
0: ATA Drives
1: Legacy Cards
2: BCV #1
3: BCV #2
BCV Priority
0: 2 (BCV #1)
1: 0 (ATA Drives)
2: 1 (Legacy Cards)
3: 3 (BCV #2)

INT 13h 支援的裝置有下列幾種
1. ATA Drive
2. PnP Cards with BCVs
3. Legacy Cards with Option ROMs
4. Hard Drive BAID

關於 INT 13h 的幾個重點
1. 當 INT 13h 被 Hook 時,舊的 INT 13h Vector 必須被保存
2. 已經安裝的硬碟數目必須被保存在 BDA 0040:0075
3. 第一個安裝的硬碟會得到 80h 的代號,這也代表著它是開機硬碟
4. 一旦安裝到 INT 13h 之後,就不能被解除安裝

INT 19h
在這個服務呼叫時,所有的 IPL 已經被辨識,並且 INT 13h 的裝置也都已安裝完成。在呼叫之後,它會根據 IPL Priority 中的裝置,呼叫其 Boot handler。第一個呼叫成功的裝置會負責載入作業系統。如果全部的裝置都已呼叫過後還沒有成功載入作業系統,它會顯示一個錯誤訊息,並且等待重新開始。

INT 18h
原本的 INT 18h 的動作是將控制權交給 BIOS,顯示一個錯誤訊息並且等待使用者按下按鍵後進行下一個動作。而在 BBS 中重新定義 INT 18h 的功能為錯誤回復的中斷向量。這裡要注意的是 INT 18h 並不會返回至呼叫它的程序,並且在一開始就將堆疊重新設定。

Boot Menu (Optional)
在 POST 期間,部份 BIOS 充許使用者使用一個特定的 Hot Key 來呼叫 Boot Menu,並用它來改變 INT 19h 所使用的啟動裝置。這裡要注意的是,這個動作並不會改變 IPL Priority 的內容,它只是單純地選擇啟動的裝置。

一些相關的表格
Table 1 - IPL Table and BCV Table Entry Data Structure
Name Offset Size Description
deviceType 00h WORD See definitions below
statusFlags 02h WORD See bit definitions below
bootHandler 04h FAR PTR Far pointer to address of boot handler
descString 08h FAR PTR Far pointer to ASCIIZ description string
expansion 0Ch DWORD Reserved for future expansion

deviceType:
00h = Reserved
01h = Floppy
02h = Hard disk
03h = CD-ROM
04h = PCMCIA
05h = USB device
06h = Embedded network
07h..7Fh = Reserved
80h = BEV device
81h..FEh = Reserved
FFh = Unknown

Table 2 - PnP Option ROM Header
Offset Size Value Description
00h BYTE 55h Signature byte 1
01h BYTE AAh Signature byte 2
02h BYTE Varies Option ROM length in 512-byte blocks
03h DWORD Varies Initialization entry point
07h 17BYTES Varies Reserved.
18h WORD Varies Offset to PCI data structure
1Ah WORD Varies Offset to expansion header structure

Table 3 - PnP Expansion Header
0ffset Size Value Description
00h BYTE '$' Signature byte 1
01h BYTE 'P' Signature byte 2
02h BYTE 'n' Signature byte 3
03h BYTE 'P' Signature byte 4
04h BYTE 01h Structure revision
05h BYTE Varies Length (in 16 byte increments)
06h WORD Varies Offset of next header (0000h if none).
08h BYTE 00h Reserved
09h BYTE Varies Checksum
0Ah DWORD Varies Device identifier
0Eh WORD Varies Pointer to manufacturer string (Optional)
10h WORD Varies Pointer to product name string (Optional)
12h 3BYTES Varies Device type code
15h BYTE Varies Device indicators
16h WORD Varies Boot Connection Vector (BCV), 0000h if none
18h WORD Varies Disconnect Vector (DV), 0000h if none
1Ah WORD Varies Bootstrap Entry Vector (BEV), 0000h if none
1Ch WORD 0000h Reserved
1Eh WORD Varies Static resource information vector, 0000h if none

Table 4 - PCI Data Structure
00h BYTE 'P' Signature byte 1
01h BYTE 'C' Signature byte 2
02h BYTE 'I' Signature byte 3
03h BYTE 'R' Signature byte 4
04h WORD Varies Vendor Identification
06h WORD Varies Device Identification
08h WORD Varies Pointer to Vital Product Data
0Ah WORD Varies PCI Data Structure Length
0Ch BYTE Varies PCI Data Structure Revision
0Dh 3BYTES Varies Class Code
10h WORD Varies Image Length
12h WORD Varies Revision Level of Code/Data
14h BYTE Varies Code type
15h BYTE Varies Indicator
16h WORD Reserved

Flat Memory Mode

最近在逛ㄧ些網站看到下面這篇文章,在講所謂的Flat Memory Mode,由於資料說明的很詳細,所以就收集文章在自己部落格中,有興趣的朋友可以去原作者部落格看這篇的完整文章,以下是轉貼部份內容。

原文章出處:http://w3tony.blogspot.com/2006/04/flat-real-mode_114619442075796383.html

~以下是轉貼內容~
Flat real mode 或者是 unreal Mode,名詞很多,不過主要的用處都一樣,在 Real Mode 存取超過 1MB 以上的記憶體空間,下面就來介紹怎麼進入 Flat Real Mode。

要能夠使用 32bit 的 segment,首先需要進入保護模式,最簡單的方法是:

cli
mov eax,cr0
or al,1
mov cr0,eax
sti

cli 的目的是將中斷遮蔽,避免臨時的中斷服務打斷我們的工作,透過設定 CR0 的 PE bit 就可以進入 Protected Mode,接下來我們需要 descriptor table 才能夠將 segment limit 從 64K 換成 4G,

DataSel = 8
GDT dw 4 dup(0) ; NULL descriptor
dw 0ffffh,0ffh,9200h,8fh ;Data segment descriptor
GDT_ptr label fword
dw offset GDTptr-1-offset GDT
dd offset GDT

DataSel 指 Data segment entry 的 selector,設定為 8 表示我們的 entry 是在 NULL Descriptor 的下一個位置,GDT_ptr 用來存放 GDT Table 的長度以及 GDT Table 的 Linear Address,再來我們要將 GDT Table 載入到 gdt 暫存器,方法如下

mov ax,cs
mov ds,ax
movzx eax,ax
shl eax,4
add dword ptr ds:GDT_ptr+2,eax ;將 GDT 的 Linear Address 存入 GDT_ptr
lgdt fword ptr ds:GDT_ptr ;載入 GDT table

cli
mov eax,cr0
or al,1
mov cr0,eax
sti

然後需要一個 jump 的動作,目的是清除 instruction queue 的 real mode instruction:

mov ax,cs
mov ds,ax
movzx eax,ax
shl eax,4
add dword ptr ds:GDT_ptr+2,eax ;將 GDT 的 Linear Address 存入 GDT_ptr
lgdt fword ptr ds:GDT_ptr ;載入 GDT table
cli
mov eax,cr0
or al,1
mov cr0,eax
sti
jmp short pmode ; Clear the execution pipe
pmode:
mov ax,DataSel ; 進入保護模式
mov ds,ax ; 設定 selector limits 為 4 GB
mov es,ax
mov fs,ax
mov gs,ax

現在我們已經進入 Protected Mode,不過這樣只是單純的保護模式,還不是 Flat Real Mode,所以我們還需要一個步驟,返回 Real Mode,方法如下:

mov ax,cs
mov ds,ax
movzx eax,ax
shl eax,4
add dword ptr ds:GDT_ptr+2,eax ;將 GDT 的 Linear Address 存入 GDT_ptr
lgdt fword ptr ds:GDT_ptr ;載入 GDT table
cli
mov eax,cr0
or al,1
mov cr0,eax
sti
jmp short pmode ; Clear the execution pipe
pmode:
mov ax,DataSel ; 進入保護模式
mov ds,ax ; 設定 selector limits 為 4 GB
mov es,ax
mov fs,ax
mov gs,ax
jmp short Real_mode
Real_mode:
clc;我們已經返回 real mode了並且將 carry flag 清除,通常這代表正確執行

sti ;解除中斷遮蔽
ret

進行到這,我們已經將 fs 與 gs 設定成 4G 範圍的 segment,試試看使用
mov eax, 6400000H
mov edi, eax
mov eax, dword ptr gs:[edi]

能不能讀取到 100MB 的記憶體內容,是不是很有趣?Flat real mode 的應用很多,不過有個很大的缺點,執行的 code 還是只能放在 1MB 的範圍,只有 data 才能存取 4GB 的空間。往後我還會繼續介紹怎麼返回真實模式,有許多注意事項是常常被人乎略的,尤其是當我們需要重複進入跟退出保護模式時,很容易破壞暫存器的設定,例如 SS:SP 就是最常忘記的地方,下次有機會再繼續討論這個部份。

延伸閱讀:
Flat real mode
http://www.df.lth.se/~john_e/gems/gem0022.html
Flat real mode interface http://www.programmersheaven.com/zone5/cat19/1365.htm