2009年10月30日 星期五

Power-On Self Test

 
這是我 N 年前對 bootsect.S 的解析。
以前的文筆充滿年輕活力啊 XD
好似宋朝晏殊《蝶戀花》之「獨上高樓,望盡天涯路」。

感覺現在程式能力減退很多,
以前一天可以 trace 完上千行 Assembly,
外加轉譯 C++ 虛擬碼,再打上一堆註解,
現在看幾行組語就要想個半天 @@...
畢竟有好幾年時間不怎麼碰程式了.....

注意,本文禁止轉載!

// 注意! 文章中 [] 用於指標取值, 例如 [xx]=9 即 C 語言的 *xx=9
// 此相當於 MASM 的慣例寫法.
// 至於一般位址, 是用16進位表示, 如 FFF0 或 0xFFF0 或 FFF0h
// 或是合成形式: (segment:offset)
// 有時可能會和常數值搞混 (雖然藉由上下文仍可辨認出2者),
// 這是因為我找不到表示位址值的好方法, 譬如: #FFF0, *FFF0,
// (FFF0), <FFF0>, |FFF0|, $FFF0... 看起來都怪怪的..

//--------------------------------------------------------------------
// 揳子: <POST: Power-On Self Test>
//
// 開機後, power supply (電源供應器) 會先測試電流和電壓是否正常,
// 就像 <星界戰旗> 中 <拉菲爾> 的突擊艦巴斯羅伊魯號要啟動時,
// 會先做壓力平衡測試一般, 若沒問題, 就送出 power good 信號給
// CPU 中的 8284 計時晶片(clock generator), 使它不會繼續對 CPU
// 發出 reset 信號, 接著, 位於主機板上 BIOS ROM 中的 BIOS 程式
// 會被映射到記憶體的 FEOOO~FFFFF, 開機時, CS 的內容全為 1,
// 而 IP 的內容全為 0, 所以 CPU 會從位址 FFFF:0000 開始執行,
// 雖然由 FFFF0 到 FFFFF 只有區區 16 Byte (在 Real mode 下),
// 但已足夠我們放入一個 jump 指令 (一般是: jmp FE05B),
// 讓她跳到實際的 ROM BIOS 起始位址去工作.
//
// 由於各家 BIOS 廠商的 ROM BIOS 程式, 其功能, 大小與起始位址,
// 都不太一樣, 所以我們聰明的祖先, 便設計了一個由統一的進入點
// FFFF0, 跳到不同位址去執行的方式, 讓各家 BIOS 都能相容並存.
//
// BIOS 程式會依序檢測並初始化主機板上的: CPU, ROM BIOS, CMOS RAM,
// 8237(DMA), 8255(鍵盤控制器), 基本的64K RAM, 8259(中斷控制器),
// 8253(中斷計時器), Cache 控制器, 然後再依序檢測週邊設備:
// CMOS RAM, 6845 (CRT控制器), 64KB以上的RAM (若是暖開機則不測試它)
// reset 鍵盤, cassette bus, diskette(軟碟機), 硬碟控制器.
//
// 檢測週邊的方式是到 UMB (Upper Memory Block, A0000h~FFFFFh)
// 中, 找找看是否存在週邊設備的 ROM 起始檢測程式, 若有則執行之.
// 例如, 它會到到 C0000~C7800 去尋找顯示卡的 ROM 起始檢測程式,
// 執行它, 對顯示卡做初始化等等. 至於, 如何測出裝置或晶片功能
// 是否正常呢? 很簡單, 咱們可以先將一段資料寫進去, 然後再讀出來,
// 看看是否相同即可.
//
// 檢查完畢後, 它會嗶一下, 告訴你電腦硬體很健康, 一切平安.
// 就像盲腸手術後一定要'那個'一樣.
// 接著將位址 00472 填入 1234h, 表示已完成冷開機程序.
// BIOS 程式的最後一步是執行 INT 19, INT 19 俗稱:
//    1. bios-startup routine (BIOS啟動常式),
//    2. 或是叫 bootstrap routine (靴帶式/鞘式啟動常式),
//    3. 或是叫 reboot interrupt (重開機中斷, 供 warm boot 叫用)
//
// 其作用是將軟碟上的 "第0軌/第0磁頭/第1磁區" 的內容 (也就是
// 底下的 bootsect.s) 讀進 RAM 的 7C00h, 然後再跳到 7C00 去執行.
//
// 軟碟上的 <第0軌/第0磁頭/第1磁區>, 就是我們常常聽到的 boot
// sector (開機磁區), 相當於硬碟上由 fdisk 造出的 MBR (Master
// Boot Record, 主開機紀錄), 不過兩者的內容不太一樣,
// MBR 啟動程式所處理的雜事會多一些 (廢話..), 但兩者都以 55AA
// 作為結束標記. (當然地, 也有不是使用 55AA 的系統)
//
//--------------------------------------------------------------------
// <bootsect.s> 內容簡介:
//
//   1. 本程式一開始被 bios-startup 常式載入到位址 7C00h 去執行,
//      執行後, 它會將自己複製一份到位址 90000, 然後跳到那兒去執行
//
//   2. 將中斷向量 1Eh 所指涉的磁碟參數表複製一份到 9000h:4000h-12
//      將其內紀錄的每軌的磁區數增加到 36, 再將向量 1Eh 指向此新表,
//      並呼叫 ah=0,int 13h, 重置磁碟, 使設定能發揮功能.
//
//   3. 載入位於啟動磁區之後的 setup-sectors (啟動程式)
//      呼叫 ah=0,int 13h, 將其載入到 bootsect 之後 (90200h)
//
//   4. 讀取磁碟裝置參數, 如 sectors 與 track 的數目,
//      然後將系統核心載入到 0x10000
//
//   5. 選擇一個 root device (這是啥?)
//--------------------------------------------------------------------
// 虛擬碼如下:

    // bootsect.s 將被 bios-startup 常式載入到位址 0x7C00 ,
    // 之後他會將自己複製一份到位址 0x90000, 然後跳到那兒去執行.
    // 為何不直接從 7C00 開始執行就好呢? 我猜測, 因為有可能咱們
    // 選用硬碟開機, 此時 MBR 中紀錄著用來開機的磁區, 位於哪個分
    // 割區(partition)中. 於是, 我們將 MBR 的資料放到 7C00 上,
    // 然後 MBR 再將自己移到 90000 去執行, 它的動作是將設定用來
    // 開機的那個磁區, 放到 7C00 上去執行.
    // 如此, 我們可載入多個磁區資料, 到特定的執行點(7C00)上去執行,
    // 最後執行到的那個, 便是最正確恰當的開機磁區(boot sector)!
    // (因此, 只要載入不同的 loader, 便可實現多系統開機)

    INITSEG = 0x9000
_main:
    DS = 0x07C0
    ES = 0x9000
    for( SI=DI=0,CX=256; CX--;)
      [ES:DI++] = [DS:SI++]     //將自己拷貝到 0x9000
    jmpi go,INITSEG             //跳到 9000h, 位移為 go 的地方開始執行
go:                             //亦即 CS= 9000h, IP= OFFSET go
    DI = 0x4000-12              //4000 是我們隨便找的一個大於 bootsect
    DS = 0x9000                 //容量的位址值, 用來暫存磁碟參數表
    SS = 0x9000                 //將堆疊指向 0x9000:0x4000-12
    SP = 0x4000-12

    // 做完 POST 後, 中斷向量 1Eh 會指向磁碟參數表,
    // 由於大多數 BIOS 預設的磁碟參數表, 每軌的磁區數平均是 7 個
    // 這將造成編號大於 7 的磁區無法被讀取, 是故, 咱們將磁碟參數
    // 表 (共 11 Byte) 先複製一份到 9000h:4000h-12, 然後將參數
    // 表第 4 Byte 的內容 (紀錄每軌的磁區數) 調到 36,
    // 再將中斷向量 1Eh 指向這個新的磁碟參數表 (9000h:4000h-12)

    FS = DS = 0                 //將 FS:BX 指向磁碟參數表
    BX = 0x78                   //78h = 中斷向量 1Eh 的起始位址
    DS:SI = *BX                 //令 DS:SI = 磁碟參數表的 seg:ofs
    for( CL=6; CL--;)           //從 [0:78h] 拷貝資料到 9000h:4000h-12
       [ES:DI++] = [DS:SI++]
     DI = 0x4000-12
    DS = 0x9000
    [DI+4] = 36                 //設定磁區數
    [1Eh] = ES:DI               //將 1Eh 號中斷指向 9000:4000-12

    // 接著將位於 bootsect 之後的 setup-sectors (啟動程式)
    // 載到位址 0x90200

load_setup:
    DL = AH = 0                 //重設一下磁碟機
    int 13h                     //讓剛剛的設定能發揮作用
    DX = 0                      //磁碟機號碼=0, 磁頭號碼=0
    CL = 2                      //起始磁區號碼=2, 磁軌號碼=0
    BX = 200h                   //將資料讀到 ES:BX (=0x90200) 中
    AH = 2
    AL = 4                      //欲讀取的磁區數
    int 13h                     //讀取磁區資料!

    // 接著, 取得磁碟裝置參數 (如 sectors 與 track 的數目)
    // 由於沒有任何 BIOS 中斷能取得磁區數目, 所以底下就用 36, 18,
    // 15 這 3 個猜測值一一去測試是否能讀取到磁區, 若都失敗就用 9.

ok_load_setup:
    Byte disksizes= {36,18,15,9} //欲測試的磁區數大小
    SI = &disksizes
    for(;AX = [DS:SI++], SI<&disksizes[4];)
    {
      CX = AX
      DX = 0                     //drive 0, head 0
      ES = 9000h
      BX = 0A00h
      AX = 0201h                 //service 2, 1 sector
      int 13h                    //嘗試讀取磁區資料
    }
    print "\nLoading"

    // 接著將系統核心載入到 0x10000 (以 ES:BX 指向載入時
    // 的緩衝區), 並確保載入的資料不會跨越 64kB 的邊界

read_it:                         //載入系統核心
    AX = ES = 0x1000
    sread = 5
    BX = 0
    U2B sread = 0                //目前磁軌上可讀取的磁區數
    U2B head  = 0                //目前的磁頭編號
    U2B track = 0                //目前的磁軌編號

    while(1){
      #ifdef __BIG_KERNEL__
      #define CALL_HIGHLOAD_KLUDGE .word 0x1eff,0x220
        CALL_HIGHLOAD_KLUDGE     //this is within setup.S
      #else                      //但是 setup.S 在哪咧?
         AX = ES - 1000h
      #endif
      if( AX>8000h ) break;
      CX = sectors - sread       //= 尚未讀取的磁區數
      CX = CX*512 + BX           //= 目前已讀到的段內偏移位置 ES:BX
      if( CX >= 0x10000)
        AX = (-BX)>>9            //????
      call read_track            //傳入 es:bx=緩衝區, AL=欲讀取的磁區數
      CX = AX                    //CX = 剛剛讀入的磁區數
      if(AX==sectors)            //若已讀完目前磁軌上的磁區
        if(head==0) head=1;      //若目前位於第 0 磁頭, 則換到第1個
        else track++;            //已讀完目前磁軌上的磁區, 換下一軌
      sread += CX
      BX += CX<<9                //調整緩衝區偏移指標
      if( BX<65536) continue     //若 BX < 64K 則繼續重複讀取
      ES += 10                   //否則就位移到下一個 64K 的開頭
    }                            //並繼續重複讀取    
    goto kill_motor

read_track:
    print '.'
    DX = track                   //DX = 目前的磁軌編號
    CX = sread+1                 //CX = 欲讀取的起始磁區編號(從1開始算)
    CH = CL                      //CH = 磁軌編號
    DH = head                    //DH = 目前的磁頭編號
    DL = 0                       //DL = 0 (磁碟機編號 A)
    AH = 2
    int 13h
    ret

    // 然後關閉磁碟馬達
kill_motor:
    DX = 0x3f2
    AL = 0
    OUTB

    // 最後, 決定要使用哪個 root-device. 若已有指定好的, 就用它.
    // 否則就依據我們前面所猜測的磁區數目, 從 /dev/fd0H2880 (2,32)
    // 或 /dev/PS0 (2,28) 或 /dev/at0 (2,8) 中選出一個.

    if(root_dev==0)                    //若並未指定 root-device
      switch(sectors){
        case 15: root_dev = 208h; break;
        csae 18: root_dev = 1ch;  break;
        case 36: root_dev = 20h;  break;
        default: root_dev = 0;
      }

    // 至此, 一切都準備好了, 便跳到位於 bootblock 後的
    // setup-routine 去執行!

    jmpi 0,SETUPSEG


//------------------------------------------------------------------------
// This is the assembly source for the boot sector used by the
// Linux free operating system. If it looks unfamiliar for intel
// assembly, that's because it gets run through the standard C
// preprocessor (CPP) and has comments starting with '!' stripped off..

! bootsect.s 將被 bios-startup 常式載入到位址 0x7c00 處,
! 之後他會將自己複製一份到位址 0x90000 處, 然後跳到 0x90200
! 去執行它的起始(setup)程式. 並呼叫中斷將系統載到 0x10000 去.
!
! 由於目前系統的大小約 (508KB = 8*65536-4096) Bytes. 這樣的大小,
! 即使是在往後的日子裡, 也已經是夠用的了! 且一個 OS 應該盡量維持
! 簡明易懂才是. 508 kB 絕對夠用, 而且這個 Liunx 核心不像 minix
! 那樣還包含了 buffer cache(快取緩衝區), 再說..她還是被壓縮過的呢.
!
! 這個 loader 已被盡可能的簡化了, 所以啦, 持續的讀取錯誤很容易就
! 會讓它死當, 然後必須煩勞你手動重開機. 本程式會盡可能一次讀取多
! 個磁軌, 以加速磁區資料的載入.

;#include <linux/config.h>     //for CONFIG_ROOT_RDONLY
;#include <asm/boot.h>

.text

SIZEBYTES = SIZEDISKB+511    ! round disk.b size for division
SETUPSECS = SIZEBYTES/512    ! 啟動磁區(setup sectors)數 (一般= 4)
BOOTSEG   = 0x07C0           ! 原始的 boot-sector 位址
INITSEG   = 0x9000           ! 我們會將啟動程式碼備份到此處
SETUPSEG  = INITSEG+0x20     ! 啟動程式從這開始執行
SYSSEG    = 0x1000           ! 系統會載入到 0x10000 (65536).
SYSSIZE   = 0x8000           ! 系統大小: 以 16 Byte 為單位做載入
ASK_VGA   = 0xfffd

! ROOT_DEV & SWAP_DEV are now written by "build".
ROOT_DEV = 0
SWAP_DEV = 0
#ifndef SVGA_MODE
# define SVGA_MODE ASK_VGA
#endif
#ifndef RAMDISK
# define RAMDISK 0
#endif
#ifndef CONFIG_ROOT_RDONLY
# define CONFIG_ROOT_RDONLY 1
#endif

! 本程序執行時, 如同一般程式, 需要一個程式進入點標示:
.globl    _main
_main:
    mov    ax,#BOOTSEG
    mov    ds,ax             ! DS= 0x07C0
    mov    ax,#INITSEG
    mov    es,ax             ! ES= 0x9000
    mov    cx,#256           ! 總共要移動 512 Bytes
    sub    si,si
    sub    di,di
    cld
    rep
    movsw                     ! 將自己拷貝到 0x9000
    jmpi    go,INITSEG        ! 跳到 0x9000

   ! 目前 ax 和 es 的內容是 INITSEG
go:
        mov   di,#0x4000-12       ! 0x4000 是一個大於 bootsect + length
                                  ! of setup + room for stack 大小的任
                                  ! 意值, 12 則是磁碟參數表的大小 
                                  ! (事實上是11)
    mov    ds,ax             ! DS = 0x9000
    mov    ss,ax             ! put stack at INITSEG:0x4000-12.
    mov    sp,di

/* 大多數 BIOS 的磁碟參數表 (disk parameter tables) 預設的每軌磁
 * 區數太少 (一般平均是 7 個), 以致於程式無法讀取到超過預設最大值
 * 之後的磁區, 而且我們的 loader 不可能一次只讀取 1 個磁區, 那樣
 * 太慢了! 使用者會因載入時間太久, 而開始幹譙.
 * 所以我們的 loader 程式, 必須另外建一個磁碟參數表, 並將每軌磁區
 * 數提升到 36 (此為 ED 2.88 上所能遇到的最大值).
 * 此處, 值太大並不是壞事, 太低才是!
 */
 ! 目前 ds=es=ss=cs= INITSEG, fs = 0, gs 未使用, cx = 0

;;;    mov    fs,cx             ! 用 ;;; 所註解掉的是舊版的碼
    mov    bx,#0x78          ! 將 fs:bx(指標) 指向 parameter table
    push    ds
;;;    seg fs
    push    cx                ! contains 0 from  rep movsw above
    pop    ds                !
    lds    si,(bx)           ! 將 ds:si(變數值) 填入磁碟參數表的所在位址

    mov    cl,#6             ! copy 12 bytes
    cld                       !
    push    di                ! DI= 0x4000-12
    rep                       !
    movsw                     ! 從 [0:78h] 拷貝 12 Byte 到 9000h:4000h-12
    pop    di                ! DI= 0x4000-12
    pop    ds                ! DS= 0x9000
    movb    4(di),*36         ! 在磁碟參數表中的相關欄位, 設定磁區數為 36

;;;    seg fs
    push    ds                ! 此處將 1Eh 號中斷指向 9000:4000-12
    push    #0
    pop    ds
    mov    (bx),di           ! [78h] = 0x4000-12
;;;    seg fs
    mov    2(bx),es          ! [7Ah] = 0x9000
    pop    ds

! 由 bootblock 載入 setup-sectors. ES=0x9020, CX=0.

load_setup:
    xor    ah,ah             ! 做 I/O 前先重設一下磁碟機
    xor    dl,dl             ! 磁碟機號碼= 0
    int     0x13              ! 讓剛剛的設定能發揮作用
                                  ! 讀取磁區資料:
    xor    dx, dx            ! 磁碟機號碼(drive)=0, 磁頭號碼(head)=0
    mov    cl,#0x02          ! 起始磁區號碼(sector)=2, 磁軌號碼(track)=0
    mov    bx,#0x0200        ! 將資料讀到 ES:BX (=0x90200) 中
    mov    ah,#0x02          !
    mov    al,setup_sects    ! nr of sectors(欲讀取的磁區數, 一般=4)
    int    0x13              ! 讀取磁區資料!
    jnc    ok_load_setup     ! ok - continue

    push    ax                ! dump error code
    call    print_nl
    mov    bp, sp
    call    print_hex
    pop    ax
    jmp    load_setup        ! 再試一次

ok_load_setup:

   ! 取得磁碟裝置參數 (如 sectors 與 track 的數目)
#if 0
    xor    dl,dl
    mov    ah,#0x08          ! AH=8 is get drive parameters
    int    0x13
    xor    ch,ch
#else
   ! 因為好像沒有任何 BIOS 中斷能取得磁區數目, 所以底下就用 36, 18,
   ! 15 這 3 個猜測值一一去測試是否能讀取到磁區, 若都失敗就用 9.

    mov    si,#disksizes     ! 欲測試的磁區數大小, 其內容={36,18,15,9}
probe_loop:
    lodsb                     ! AX = [DS:SI] = [9000h:#disksizes]
    cbw                       ! extend to word
    mov    sectors, ax
    cmp    si,#disksizes+4   ! disksizes+4 的內容是 9
    jae    got_sectors       ! if all else fails, try 9
    xchg    ax, cx            ! cx = track and sector
    xor    dx, dx            ! drive 0, head 0
    xor    bl, bl            !
    mov    bh,setup_sects    ! BH = 4
    inc    bh                ! ES:BX = 9000:0A00
    shl    bh,#1             ! address after setup (es = cs)
    mov    ax,#0x0201        ! service 2, 1 sector
    int    0x13              ! 讀取磁區資料
    jc    probe_loop           ! CF=1 表失敗, 則回頭嘗試其他值
#endif

got_sectors:

   ! Restore es
;;;    mov    ax,#INITSEG
;;;    mov    es,ax
   ! 秀出一些令人安心的訊息

    mov    ah,#0x03          ! read cursor pos
    xor    bh,bh
    int    0x10

    mov    cx,#9
    mov    bx,#0x0007        ! page 0, attribute 7 (normal)
    mov    bp,#msg1          !
    mov    ax,#0x1301        ! write string, move cursor
    int    0x10              ! 印出 "\nLoading"

   ! 接著將系統載入到 0x10000

    mov    ax,#SYSSEG
    mov    es,ax             ! segment of 0x10000
        call    read_it           ! 載入系統核心
    call    kill_motor        ! 關閉磁碟馬達
    call    print_nl          ! 換行

   ! 底下決定要使用哪個 root-device. 若已有指定好的, 就用它.
   ! 否則就從 /dev/fd0H2880 (2,32) 或 /dev/PS0 (2,28) 或 /dev/at0 (2,8),
   ! 中選出一個, 挑選的方式是依據我們前面所猜測的磁區數目, 選出最適用的.

    seg cs
    mov    ax,root_dev
    or    ax,ax
    jne    root_defined      ! 若 root_dev!=0 就跳到 root_defiend
    seg cs
    mov    bx,sectors
    mov    ax,#0x0208        ! /dev/ps0 - 1.2Mb
    cmp    bx,#15
    je    root_defined
    mov    al,#0x1c          ! /dev/PS0 - 1.44Mb
    cmp    bx,#18
    je    root_defined
    mov    al,#0x20          ! /dev/fd0H2880 - 2.88Mb
    cmp    bx,#36
    je    root_defined
    mov    al,#0             ! /dev/fd0 - autodetect
root_defined:
    seg cs
    mov    root_dev,ax

   ! 到此處, 一切都準備的差不多了, 我們便跳到位於 bootblock
   ! 之後的 setup-routine 去執行

    jmpi    0,SETUPSEG        ! 跳到 90200h 去執行

   ! 下面的程式碼會將系統載到位址 10000h, 並且確保載入的資料不會
   ! 跨越 64kB 的邊界, 並盡可能一次讀取多個磁軌, 以加速載入動作.
   !
   ! 輸入參數: es = 起始區段位址 (正常值為 0x1000)

sread:   .word 0                  ! 目前磁軌上可讀取的磁區數
head:    .word 0                  ! 目前的磁頭編號
track:   .word 0                  ! 目前的磁軌編號

read_it:
    mov   al,setup_sects      ! AL = 4
    inc   al                  ! AL = 5
    mov   sread,al            !
    mov   ax,es               ! AX = 1000h
    test  ax,#0x0fff          ! (1000h AND 0fffh) -> ZF=1
die:                              !
        jne die                   ! ES 必須位於 64K 邊界, 否則不給通過
    xor bx,bx                 ! bx is starting address within segment
rp_read:
#ifdef __BIG_KERNEL__
#define \                      ! 叫用far* bootsect_kludge(as86無法組譯這段碼)
        CALL_HIGHLOAD_KLUDGE .word 0x1eff,0x220
    CALL_HIGHLOAD_KLUDGE      ! this is within setup.S
#else
    mov ax,es
    sub ax,#SYSSEG            ! AX = ES - 1000h
#endif
    cmp ax,syssize            ! have we loaded all yet?
    jbe ok1_read              ! 若 AX<=8000h 則繼續讀取
    ret
ok1_read:
    mov ax,sectors            ! AX = 磁軌上的磁區數
    sub ax,sread              !
    mov cx,ax                 ! CX = 尚未讀取的磁區數
    shl cx,#9                 ! CX *= 512
    add cx,bx                 ! CX += 目前已讀到的段內偏移位置 ES:BX
    jnc ok2_read              !
    je  ok2_read              ! 若 CX<=64K 則繼續讀取
    xor ax,ax 
    sub ax,bx
    shr ax,#9
ok2_read:
    call read_track           ! 傳入 es:bx=緩衝區, AL=欲讀取的磁區數
    mov cx,ax                 ! CX = 已讀入的磁區數
    add ax,sread              ! AX = 目前磁軌上的磁區數
    cmp ax,sectors            !
    jne ok3_read              ! 若尚未讀完目前磁軌上的磁區, 則跳到 ok3_read
    mov ax,#1
    sub ax,head
    jne ok4_read              ! 若目前位於第 0 磁頭, 則跳到 ok4_read
    inc track                 ! 已讀完目前磁軌上的磁區, 換下一個
ok4_read:
    mov head,ax               ! 切換磁頭
    xor ax,ax                 ! AX = 0 (因為尚未開始讀取)
ok3_read:
    mov sread,ax              ! sread = 目前磁軌上的尚未讀取的磁區數
    shl cx,#9                 ! 算出之前讀入的資料量
    add bx,cx                 ! 調整緩衝區偏移指標
    jnc rp_read               ! 若 BX < 64K 則繼續重複讀取
    mov ax,es                 !
    add ah,#0x10              ! 否則將 ES +=10,
    mov es,ax                 ! 也就是位移到下一個 64K 的開頭
    xor bx,bx                 !
    jmp rp_read               ! 並繼續重複讀取

read_track:
    pusha
    pusha
    mov    ax, #0xe2e        ! 在螢幕上印出 '.'
    mov    bx, #7
     int    0x10
    popa

    mov    dx,track          ! DX = 目前的磁軌編號
    mov    cx,sread          !
    inc    cx                ! CX = 欲讀取的起始磁區編號 (從1開始算)
    mov    ch,dl             ! CH = 磁軌編號
    mov    dx,head           !
    mov    dh,dl             ! DH = 目前的磁頭編號
    and    dx,#0x0100        ! DL = 0 (磁碟機編號 A)
    mov    ah,#2

    push    dx                ! save for error dump
    push    cx
    push    bx
    push    ax

    int    0x13              ! 載入磁區
    jc    bad_rt            ! 成功了嗎?
    add    sp, #8            ! 取消上面推入的 DX,CX,BX,AX
    popa
    ret

bad_rt:
   push    ax                        ! save error code
    call print_all            ! ah = error, al = read
    xor ah,ah
    xor dl,dl
    int 0x13
    add sp, #10
    popa
    jmp read_track

/* print_all 用於除錯, 他可被副程式叫用, 印出所有的暫存器內容,
 * 當叫用她時, 堆疊內應具有如下的結構:
 *   dx
 *   cx
 *   bx
 *   ax
 *   error
 *   ret <- sp
*/

print_all:
    mov    cx, #5            ! error code + 4 registers
    mov    bp, sp
print_loop:
    push    cx                ! 保存剩餘的計數值
    call    print_nl          ! 換行顯示, 以加強可讀性
    cmp    cl, #5
    jae    no_reg            ! see if register name is needed

    mov    ax, #0xe05+'A-1   ! 依序印 D,C,B,A
    sub    al, cl
    int    0x10
    mov    al, #'X
    int    0x10
    mov    al, #':
    int    0x10
no_reg:
    add    bp, #2            ! next register
    call    print_hex         ! print it
    pop    cx
    loop    print_loop
    ret

print_nl:
    mov    ax, #0xe0d        ! CR
    int    0x10
    mov    al, #0xa          ! LF
    int     0x10
    ret

// print_hex 用於除錯, 會以 16 進位數值印出 [ss:bp]

print_hex:
    mov    cx, #4            ! 4 位數的 hex
    mov    dx, (bp)          ! load word into dx
print_digit:
    rol    dx, #4            ! rotate so that lowest 4 bits are used
    mov    ax, #0xe0f        ! ah = request, al = mask for nybble
    and    al, dl
    add    al, #0x90         ! convert al to ASCII hex (four instructions)
    daa                       ! 轉成 10 進制
    adc    al, #0x40
    daa
    int    0x10
    loop    print_digit
    ret

// 這段程式可關閉軟碟機的馬達, 使得我們進入核心後,
// 其狀態是已知的. (若不關的話會怎樣呢?)

kill_motor:
    push dx
    mov dx,#0x3f2
    xor al, al
    outb
    pop dx
    ret

// 底下是變數群:

sectors:
    .word 0

disksizes:
    .byte 36,18,15,9

msg1:
    .byte 13,10
    .ascii "Loading"
    .byte 0

.org 497         //底下變數由本程式的 497 Bytes 偏移處開始存放
setup_sects:
    .byte SETUPSECS
root_flags:
    .word CONFIG_ROOT_RDONLY
syssize:
    .word SYSSIZE
swap_dev:
    .word SWAP_DEV
ram_size:
    .word RAMDISK
vid_mode:
    .word SVGA_MODE
root_dev:
    .word ROOT_DEV
boot_flag:
    .word 0xAA55

沒有留言:

張貼留言