天天看點

linux0.11boot之setup.s

!
! setup.s (C) 1991 Linus Torvalds
!
! setup.s is responsible for getting the system data from the BIOS,
! and putting them into the appropriate places in system memory.
! both setup.s and system has been loaded by the bootblock.
!
! This code asks the bios for memory/disk/other parameters, and
! puts them in a "safe" place: 0x90000-0x901FF, ie where the
! boot-block used to be. It is then up to the protected mode
! system to read them from there before the area is overwritten
! for buffer-blocks.
!
! setup.s 負責從BIOS 中擷取系統資料,并将這些資料放到系統記憶體的适當地方。
! 此時setup.s 和system 已經由bootsect 引導塊加載到記憶體中。
!
! 這段代碼詢問bios 有關記憶體/磁盤/其它參數,并将這些參數放到一個
! “安全的”地方:0x90000-0x901FF,也即原來bootsect 代碼塊曾經在
! 的地方,然後在被緩沖塊覆寫掉之前由保護模式的system 讀取。
!

! NOTE! These had better be the same as in bootsect.s!
! 以下這些參數最好和bootsect.s 中的相同!

INITSEG = 0x9000 ! we move boot here - out of the way ! 原來bootsect 所處的段。
SYSSEG = 0x1000 ! system loaded at 0x10000 (65536). ! system 在0x10000(64k)處。
SETUPSEG = 0x9020 ! this is the current segment ! 本程式所在的段位址。

.globl begtext, begdata, begbss, endtext, enddata, endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.text

entry start
start:

! ok, the read went well so we get current cursor position and save it for
! posterity.
! ok,整個讀磁盤過程都正常,現在将光标位置儲存以備今後使用。

mov ax,#INITSEG ! this is done in bootsect already, but...
! 将ds 置成#INITSEG(0x9000)。這已經在bootsect 程式中
! 設定過,但是現在是setup 程式,Linus 覺得需要再重新
! 設定一下。
mov ds,ax
mov ah,#0x03 ! read cursor pos
! BIOS 中斷0x10 的讀光标功能号 ah = 0x03
! 輸入:bh = 頁号
! 傳回:ch = 掃描開始線,cl = 掃描結束線,
! dh = 行号(0x00 是頂端),dl = 列号(0x00 是左邊)。
xor bh,bh
int 0x10 ! save it in known place, con_init fetches
mov [0],dx ! it from 0x90000.
! 上兩句是說将光标位置資訊存放在0x90000 處,控制台
! 初始化時會來取。

! Get memory size (extended mem, kB) ! 下面3 句取擴充記憶體的大小值(KB)。
! 是調用中斷0x15,功能号ah = 0x88
! 傳回:ax = 從0x100000(1M)處開始的擴充記憶體大小(KB)。
! 若出錯則CF 置位,ax = 出錯碼。

mov ah,#0x88
int 0x15
mov [2],ax ! 将擴充記憶體數值存在0x90002 處(1 個字)。

! Get video-card data: ! 下面這段用于取顯示卡目前顯示模式。
! 調用BIOS 中斷0x10,功能号 ah = 0x0f
! 傳回:ah = 字元列數,al = 顯示模式,bh = 目前顯示頁。
! 0x90004(1 字)存放目前頁,0x90006 顯示模式,0x90007 字元列數。

mov ah,#0x0f
int 0x10
mov [4],bx ! bh = display page
mov [6],ax ! al = video mode, ah = window width

! check for EGA/VGA and some config parameters ! 檢查顯示方式(EGA/VGA)并取參數。
! 調用BIOS 中斷0x10,附加功能選擇 -取方式資訊
! 功能号:ah = 0x12,bl = 0x10
! 傳回:bh = 顯示狀态
! (0x00 - 彩色模式,I/O 端口=0x3dX)
! (0x01 - 單色模式,I/O 端口=0x3bX)
! bl = 安裝的顯示記憶體
! (0x00 - 64k, 0x01 - 128k, 0x02 - 192k, 0x03 = 256k)
! cx = 顯示卡特性參數(參見程式後的說明)。

mov ah,#0x12
mov bl,#0x10
int 0x10
mov [8],ax ! 0x90008 = ??
mov [10],bx ! 0x9000A = 安裝的顯示記憶體,0x9000B = 顯示狀态(彩色/單色)
mov [12],cx ! 0x9000C = 顯示卡特性參數。

! Get hd0 data ! 取第一個硬碟的資訊(複制硬碟參數表)。
! 第1 個硬碟參數表的首位址竟然是中斷向量0x41 的向量值!而第2 個硬碟
! 參數表緊接第1 個表的後面,中斷向量0x46 的向量值也指向這第2 個硬碟
! 的參數表首址。表的長度是16 個位元組(0x10)。
! 下面兩段程式分别複制BIOS 有關兩個硬碟的參數表,0x90080 處存放第1 個
! 硬碟的表,0x90090 處存放第2 個硬碟的表。

mov ax,#0x0000
mov ds,ax
lds si,[4*0x41] ! 取中斷向量0x41 的值,也即hd0 參數表的位址??ds:si
mov ax,#INITSEG
mov es,ax
mov di,#0x0080 ! 傳輸的目的位址: 0x9000:0x0080 ?? es:di
mov cx,#0x10 ! 共傳輸0x10 位元組。
rep
movsb

! Get hd1 data

mov ax,#0x0000
mov ds,ax
lds si,[4*0x46] ! 取中斷向量0x46 的值,也即hd1 參數表的位址??ds:si
mov ax,#INITSEG
mov es,ax
mov di,#0x0090 ! 傳輸的目的位址: 0x9000:0x0090 ?? es:di
mov cx,#0x10
rep
movsb

! Check that there IS a hd1 :-) ! 檢查系統是否存在第2 個硬碟,如果不存在則第2 個表清零。
! 利用BIOS 中斷調用0x13 的取盤類型功能。
! 功能号 ah = 0x15;
! 輸入:dl = 驅動器号(0x8X 是硬碟:0x80 指第1 個硬碟,0x81 第2 個硬碟)
! 輸出:ah = 類型碼;00 --沒有這個盤,CF 置位; 01 --是軟驅,沒有change-line 支援;
! 02 --是軟驅(或其它可移動裝置),有change-line 支援; 03 --是硬碟。

mov ax,#0x01500
mov dl,#0x81
int 0x13
jc no_disk1
cmp ah,#3 ! 是硬碟嗎?(類型 = 3 ?)。
je is_disk1
no_disk1:
mov ax,#INITSEG ! 第2 個硬碟不存在,則對第2 個硬碟表清零。
mov es,ax
mov di,#0x0090
mov cx,#0x10
mov ax,#0x00
rep
stosb
is_disk1:

! now we want to move to protected mode ... ! 從這裡開始我們要保護模式方面的工作了。

cli ! no interrupts allowed ! ! 此時不允許中斷。

! first we move the system to it is rightful place
! 首先我們将system 子產品移到正确的位置。
! bootsect 引導程式是将system 子產品讀入到從0x10000(64k)開始的位置。由于當時假設
! system 子產品最大長度不會超過0x80000(512k),也即其末端不會超過記憶體位址0x90000,
! 是以bootsect 會将自己移動到0x90000 開始的地方,并把setup 加載到它的後面。
! 下面這段程式的用途是再把整個system 子產品移動到0x00000 位置,即把從0x10000 到0x8ffff
! 的記憶體資料塊(512k),整塊地向記憶體低端移動了0x10000(64k)的位置。

mov ax,#0x0000
cld ! 'direction'=0, movs moves forward
do_move:
mov es,ax ! destination segment ! es:di??目的位址(初始為0x0000:0x0)
add ax,#0x1000
cmp ax,#0x9000 ! 已經把從0x8000 段開始的64k 代碼移動完?
jz end_move
mov ds,ax ! source segment ! ds:si??源位址(初始為0x1000:0x0)
sub di,di
sub si,si
mov cx,#0x8000 ! 移動0x8000 字(64k 位元組)。
rep
movsw
jmp do_move

! then we load the segment descriptors
! 此後,我們加載段描述符。
! 從這裡開始會遇到32 位保護模式的操作,是以需要Intel 32 位保護模式程式設計方面的知識了,
! 有關這方面的資訊請查閱清單後的簡單介紹或附錄中的詳細說明。這裡僅作概要說明。
!
! lidt 指令用于加載中斷描述符表(idt)寄存器,它的操作數是6 個位元組,0-1 位元組是描述符表的
! 長度值(位元組);2-5 位元組是描述符表的32 位線性基位址(首位址),其形式參見下面
! 219-220 行和223-224 行的說明。中斷描述符表中的每一個表項(8 位元組)指出發生中斷時
! 需要調用的代碼的資訊,與中斷向量有些相似,但要包含更多的資訊。
!
! lgdt 指令用于加載全局描述符表(gdt)寄存器,其操作數格式與lidt 指令的相同。全局描述符
! 表中的每個描述符項(8 位元組)描述了保護模式下資料和代碼段(塊)的資訊。其中包括段的
! 最大長度限制(16 位)、段的線性基址(32 位)、段的特權級、段是否在記憶體、讀寫許可以及
! 其它一些保護模式運作的标志。參見後面205-216 行。
!

end_move:
mov ax,#SETUPSEG ! right, forgot this at first. did not work :-)
mov ds,ax ! ds 指向本程式(setup)段。
lidt idt_48 ! load idt with 0,0
! 加載中斷描述符表(idt)寄存器,idt_48 是6 位元組操作數的位置
! (見218 行)。前2 位元組表示idt 表的限長,後4 位元組表示idt 表
! 所處的基位址。
lgdt gdt_48 ! load gdt with whatever appropriate
! 加載全局描述符表(gdt)寄存器,gdt_48 是6 位元組操作數的位置
! (見222 行)。

! that was painless, now we enable A20
! 以上的操作很簡單,現在我們開啟A20 位址線。參見程式清單後有關A20 信号線的說明。

call empty_8042 ! 等待輸入緩沖器空。
! 隻有當輸入緩沖器為空時才可以對其進行寫指令。
mov al,#0xD1 ! command write ! 0xD1 指令碼-表示要寫資料到
out #0x64,al ! 8042 的P2 端口。P2 端口的位1 用于A20 線的選通。
! 資料要寫到0x60 口。
call empty_8042 ! 等待輸入緩沖器空,看指令是否被接受。
mov al,#0xDF ! A20 on ! 選通A20 位址線的參數。
out #0x60,al
call empty_8042 ! 輸入緩沖器為空,則表示A20 線已經選通。

! well, that went ok, I hope. Now we have to reprogram the interrupts :-(
! we put them right after the intel-reserved hardware interrupts, at
! int 0x20-0x2F. There they won't mess up anything. Sadly IBM really
! messed this up with the original PC, and they haven't been able to
! rectify it afterwards. Thus the bios puts interrupts at 0x08-0x0f,
! which is used for the internal hardware interrupts as well. We just
! have to reprogram the 8259's, and it isn't fun.
!! 希望以上一切正常。現在我們必須重新對中斷進行程式設計??
!! 我們将它們放在正好處于intel 保留的硬體中斷後面,在int 0x20-0x2F。
!! 在那裡它們不會引起沖突。不幸的是IBM 在原PC 機中搞糟了,以後也沒有糾正過來。
!! PC 機的bios 将中斷放在了0x08-0x0f,這些中斷也被用于内部硬體中斷。
!! 是以我們就必須重新對8259 中斷控制器進行程式設計,這一點都沒勁。

mov al,#0x11 ! initialization sequence
! 0x11 表示初始化指令開始,是ICW1 指令字,表示邊
! 沿觸發、多片8259 級連、最後要發送ICW4 指令字。
out #0x20,al ! send it to 8259A-1 ! 發送到8259A 主晶片。
.word 0x00eb,0x00eb ! jmp $+2, jmp $+2 ! $ 表示目前指令的位址,
! 兩條跳轉指令,跳到下一條指令,起延時作用。
out #0xA0,al ! and to 8259A-2 ! 再發送到8259A 從晶片。
.word 0x00eb,0x00eb
mov al,#0x20 ! start of hardware int is (0x20)
out #0x21,al ! 送主晶片ICW2 指令字,起始中斷号,要送奇位址。
.word 0x00eb,0x00eb
mov al,#0x28 ! start of hardware int is 2 (0x28)
out #0xA1,al ! 送從晶片ICW2 指令字,從晶片的起始中斷号。
.word 0x00eb,0x00eb
mov al,#0x04 ! 8259-1 is master
out #0x21,al ! 送主晶片ICW3 指令字,主晶片的IR2 連從晶片INT。
.word 0x00eb,0x00eb !參見代碼清單後的說明。
mov al,#0x02 ! 8259-2 is slave
out #0xA1,al ! 送從晶片ICW3 指令字,表示從晶片的INT 連到主芯
! 片的IR2 引腳上。
.word 0x00eb,0x00eb
mov al,#0x01 ! 8086 mode for both
out #0x21,al ! 送主晶片ICW4 指令字。8086 模式;普通EOI 方式,
! 需發送指令來複位。初始化結束,晶片就緒。
.word 0x00eb,0x00eb
out #0xA1,al !送從晶片ICW4 指令字,内容同上。
.word 0x00eb,0x00eb
mov al,#0xFF ! mask off all interrupts for now
out #0x21,al ! 屏蔽主晶片所有中斷請求。
.word 0x00eb,0x00eb
out #0xA1,al !屏蔽從晶片所有中斷請求。

! well, that certainly was not fun :-(. Hopefully it works, and we don't
! need no steenking BIOS anyway (except for the initial loading :-).
! The BIOS-routine wants lots of unnecessary data, and it is less
! "interesting" anyway. This is how REAL programmers do it.
!
! Well, now's the time to actually move into protected mode. To make
! things as simple as possible, we do no register set-up or anything,
! we let the gnu-compiled 32-bit programs do that. We just jump to
! absolute address 0x00000, in 32-bit protected mode.
!! 哼,上面這段當然沒勁??,希望這樣能工作,而且我們也不再需要乏味的BIOS 了(除了
!! 初始的加載?。BIOS 子程式要求很多不必要的資料,而且它一點都沒趣。那是“真正”的
!! 程式員所做的事。

! 這裡設定進入32 位保護模式運作。首先加載機器狀态字(lmsw - Load Machine Status Word),也稱
! 控制寄存器CR0,其比特位0 置1 将導緻CPU 工作在保護模式。
mov ax,#0x0001 ! protected mode (PE) bit ! 保護模式比特位(PE)。
lmsw ax ! This is it! ! 就這樣加載機器狀态字!
jmpi 0,8 ! jmp offset 0 of segment 8 (cs) ! 跳轉至cs 段8,偏移0 處。
! 我們已經将system 子產品移動到0x00000 開始的地方,是以這裡的偏移位址是0。這裡的段
! 值的8 已經是保護模式下的段選擇符了,用于選擇描述符表和描述符表項以及所要求的特權級。
! 段選擇符長度為16 位(2 位元組);位0-1 表示請求的特權級0-3,linux 作業系統隻
! 用到兩級:0 級(系統級)和3 級(使用者級);位2 用于選擇全局描述符表(0)還是局部描
! 述符表(1);位3-15 是描述符表項的索引,指出選擇第幾項描述符。是以段選擇符
! 8(0b0000,0000,0000,1000)表示請求特權級0、使用全局描述符表中的第1 項,該項指出
! 代碼的基位址是0(參見209 行),是以這裡的跳轉指令就會去執行system 中的代碼。

! This routine checks that the keyboard command queue is empty
! No timeout is used - if this hangs there is something wrong with
! the machine, and we probably could not proceed anyway.
! 下面這個子程式檢查鍵盤指令隊列是否為空。這裡不使用逾時方法 - 如果這裡當機,
! 則說明PC 機有問題,我們就沒有辦法再處理下去了。
! 隻有當輸入緩沖器為空時(狀态寄存器位2 = 0)才可以對其進行寫指令。
empty_8042:
.word 0x00eb,0x00eb ! 這是兩個跳轉指令的機器碼(跳轉到下一句),相當于延時空操作。
in al,#0x64 ! 8042 status port ! 讀AT 鍵盤控制器狀态寄存器。
test al,#2 ! is input buffer full? ! 測試位2,輸入緩沖器滿?
jnz empty_8042 ! yes - loop
ret

gdt: ! 全局描述符表開始處。描述符表由多個8 位元組長的描述符項組成。
! 這裡給出了3 個描述符項。第1 項無用(206 行),但須存在。第2 項是系統代碼段
! 描述符(208-211 行),第3 項是系統資料段描述符(213-216 行)。每個描述符的具體
! 含義參見清單後說明。
.word 0,0,0,0 ! dummy ! 第1 個描述符,不用。
! 這裡在gdt 表中的偏移量為0x08,當加載代碼段寄存器(段選擇符)時,使用的是這個偏移值。
.word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb)
.word 0x0000 ! base address=0
.word 0x9A00 ! code read/exec
.word 0x00C0 ! granularity=4096, 386
! 這裡在gdt 表中的偏移量是0x10,當加載資料段寄存器(如ds 等)時,使用的是這個偏移值。
.word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb)
.word 0x0000 ! base address=0
.word 0x9200 ! data read/write
.word 0x00C0 ! granularity=4096, 386

idt_48:
.word 0 ! idt limit=0
.word 0,0 ! idt base=0L

gdt_48:
.word 0x800 ! gdt limit=2048, 256 GDT entries
! 全局表長度為2k 位元組,因為每8 位元組組成一個段描述符項
! 是以表中共可有256 項。
.word 512+gdt,0x9 ! gdt base = 0X9xxxx
! 4 個位元組構成的記憶體線性位址:0x0009<<16 + 0x0200+gdt
! 也即0x90200 + gdt(即在本程式段中的偏移位址,205 行)。

.text
endtext:
.data
enddata:
.bss
endbss: