User:Khorlane

From OSDev.wiki
Jump to navigation Jump to search

MyOS

Yep, I don't have good name, yet.

Description

Playing around with creating my very own Lino Commando Hobby OS for 32-bit x86 written in assembler using NASM. The development machine is a Microsoft Surface running Windows 10. The testing machine is Bochs.

Currently, MyOS gets itself into Protected Mode and that about it.

Credit

Most of the code comes directly from BrokenThorn Operating System Development Series

In addition, I've read through many of the OS Dev Tutorials and some of the FYSOS books Operating System Design Book Series by Benjamin David Lunt.

Tools Used

Assembler

NASM

Disk Emulator

ImDisk

Hex Editor

UltraEdit or HxD

Virtual Machine

Bochs

The Code

Stage 1

Main task is to load Stage 2.

;**************************************************************************************************
; Stage1.asm
;   A Simple Boot Sector that:
;   1. is exactly 512 bytes long
;   2. has the Magic Word at the end (0xAA55)
;   3. allows us to have a useable floppy
;      where we can put our Stage2/Stage3 code
;      by coding a proper BIOS Parameter Block
;   4. has code to load our Stage2 code
; Broken Thorn Entertainment
; Operating Systems Development Tutorial
; http://www.brokenthorn.com/Resources/OSDevIndex.html
;
; nasm -f bin Stage1.asm -o Stage1.bin -l Stage1.lst
;**************************************************************************************************

[bits 16]                               ; we are in 16 bit real mode
    ORG   0                             ; we will set regisers later
    JMP   Booter                        ; jump to start of bootloader

;--------------------------------------------------------------------------------------------------
; BIOS Parameter Block
;   and yes, this block must start at offset 0x003
;   and yes, these are the required fields
;   and yes, they must be in this order
;   you can change the names (obviously)
; Tutorial 5: Bootloaders 3
;--------------------------------------------------------------------------------------------------

; The BIOS Parameter Block begins 3 bytes from start. We do a far jump, which is 3 bytes in size.
; If you use a short jump, add a "nop" after it to offset the 3rd byte.
; See Wikipedia "Design of the FAT file system" for more info on the BIOS Parameter Block

                                        ; Hex Offset from beginning of Boot Sector
OEM                   DB "MyOs    "     ; 0x003  8 bytes padded with spaces
BytesPerSector        DW 512            ; 0x00B  2 bytes
SectorsPerCluster     DB 1              ; 0x00D  1 byte
ReservedSectors       DW 1              ; 0x00E  2 bytes
NumberOfFATs          DB 2              ; 0x010  1 bytes
RootEntries           DW 224            ; 0x011  2 bytes
TotalSectors          DW 2880           ; 0x013  2 bytes
Media                 DB 0xf0           ; 0x015  1 byte
SectorsPerFAT         DW 9              ; 0x016  2 bytes
SectorsPerTrack       DW 18             ; 0x018  2 bytes DOS 3.31 BPB
HeadsPerCylinder      DW 2              ; 0x01A  2 bytes DOS 3.31 BPB
HiddenSectors         DD 0              ; 0x01C  4 bytes DOS 3.31 BPB
TotalSectorsBig       DD 0              ; 0x020  4 bytes DOS 3.31 BPB
DriveNumber           DB 0              ; 0x024  1 byte  Extended BIOS Parameter Block
Unused                DB 0              ; 0x025  1 byte  Extended BIOS Parameter Block
ExtBootSignature      DB 0x29           ; 0x026  1 byte  Extended BIOS Parameter Block
SerialNumber          DD 0xa0a1a2a3     ; 0x027  4 bytes Extended BIOS Parameter Block
VolumeLabel           DB "MYOS FLOPPY"  ; 0x028 11 bytes Extended BIOS Parameter Block
FileSystem            DB "FAT12   "     ; 0x036  8 bytes Extended BIOS Parameter Block padded with spaces

;--------------------------------------------------------------------------------------------------
; Prints a string
; DS => SI: 0 terminated string
;--------------------------------------------------------------------------------------------------
Print:
    MOV   AH,0Eh                        ; set function code 0E (BIOS INT 10h - Teletype output) 
PrintLoop:
    LODSB                               ; Load byte at address DS:(E)SI into AL
    OR    AL,AL                         ; If AL = 0
    JZ    PrintDone                     ;   then we're done
    INT   10h                           ; Put character on the screen using bios interrupt 10
    JMP   PrintLoop                     ; Repeat until null terminator found
  PrintDone:
    RET                                 ; we are done, so return

;--------------------------------------------------------------------------------------------------
; Convert CHS to LBA
; Given: AX = Cluster to be read
; LBA = (Cluster - 2) * sectors per cluster
; Tutorial 6: Bootloaders 4
;--------------------------------------------------------------------------------------------------
ClusterLBA:
    SUB   AX,0x0002                     ; Adjust cluster to be zero based
    XOR   CX,CX                         ; CX =
    MOV   CL,BYTE [SectorsPerCluster]   ;  SectorsPerCluster
    MUL   CX                            ; AX = AX * CX
    ADD   AX,WORD [DataSector]          ; AX = AX + Data Sector
    RET

;--------------------------------------------------------------------------------------------------
; Convert LBA to CHS
; AX => LBA Address to convert
; absolute sector = (LBA %  sectors per track) + 1
; absolute head   = (LBA /  sectors per track) MOD number of heads
; absolute track  =  LBA / (sectors per track * number of heads)
; Tutorial 6: Bootloaders 4
;--------------------------------------------------------------------------------------------------
LBACHS:
    XOR   DX,DX                         ; DL = Remainder of
    DIV   WORD [SectorsPerTrack]        ;  AX \ SectorsPerTrack
    INC   DL                            ;   Plus 1
    MOV   BYTE [AbsoluteSector],DL      ;    Save DL 
    XOR   DX,DX                         ; DL = Remainder of
    DIV   WORD [HeadsPerCylinder]       ;  AX \ HeadsPerCylinder
    MOV   BYTE [AbsoluteHead],DL        ;   Save DL
    MOV   BYTE [AbsoluteTrack],AL       ; Save AL (what's left after the above dividing)
    RET

;--------------------------------------------------------------------------------------------------
; Reads a series of sectors
; CX    => Number of sectors to read
; AX    => Starting sector
; ES:BX => Buffer to read to
; Tutorial 5: Bootloaders 3
;--------------------------------------------------------------------------------------------------
ReadSector:
    MOV   DI,0x0005                     ; five retries for error
ReadSectorLoop:
    PUSH  AX
    PUSH  BX
    PUSH  CX
    CALL  LBACHS                        ; convert starting sector to CHS
    MOV   AH,0x02                       ; BIOS read sector
    MOV   AL,0x01                       ; read one sector
    MOV   CH,BYTE [AbsoluteTrack]       ; track
    MOV   CL,BYTE [AbsoluteSector]      ; sector
    MOV   DH,BYTE [AbsoluteHead]        ; head
    MOV   DL,BYTE [DriveNumber]         ; drive
    INT   0x13                          ; invoke BIOS
    JNC   ReadSectorOk                  ; test for read error
    XOR   AX,AX                         ; BIOS reset disk
    INT   0x13                          ; invoke BIOS
    DEC   DI                            ; decrement error counter
    POP   CX
    POP   BX
    POP   AX
    JNZ   ReadSectorLoop                ; attempt to read again
    INT   0x18
ReadSectorOk:
    MOV   SI,ProgressMsg
    CALL  Print                         ;
    POP   CX
    POP   BX
    POP   AX
    ADD   BX,WORD [BytesPerSector]      ; queue next buffer
    INC   AX                            ; queue next sector
    LOOP  ReadSector                    ; read next sector
    RET

;--------------------------------------------------------------------------------------------------
; Boot Loader Entry Point
;--------------------------------------------------------------------------------------------------
Booter:
    ;-------------------------------------------------------
    ;- code located at 0000:7C00, adjust segment registers
    ;-------------------------------------------------------
    CLI                                 ; disable interrupts
    MOV   AX,0x07C0                     ; setup
    MOV   DS,AX                         ;  registers
    MOV   ES,AX                         ;   to point
    MOV   FS,AX                         ;    to our
    MOV   GS,AX                         ;     segment

    ;--------------
    ;- create stack
    ;--------------
    MOV   AX,0x0000                     ; set the
    MOV   SS,AX                         ;  stack to
    MOV   SP,0xFFFF                     ;   somewhere safe
    STI                                 ; restore interrupts

    ;-------------------------
    ;- Display loading message
    ;-------------------------
    MOV   SI,LoadingMsg                 ; si points to first byte in message
    CALL  Print                         ; print message

    ;--------------------------
    ; Load root directory table 
    ; Tutorial 6: Bootloaders 4  
    ;--------------------------
    ; compute size of root directory and store in "cx"
    XOR   CX,CX                         ; zero out cx
    XOR   DX,DX                         ; zero out dx
    MOV   AX,0x0020                     ; 32 byte directory entry
    MUL   WORD [RootEntries]            ; total size of directory
    DIV   WORD [BytesPerSector]         ; sectors used by directory
    XCHG  AX,CX                         ; swap ax cx
    ; compute location of root directory and store in "ax"
    MOV   AL,BYTE [NumberOfFATs]        ; number of FATs
    MUL   WORD [SectorsPerFAT]          ; sectors used by FATs
    ADD   AX,WORD [ReservedSectors]     ; adjust for bootsector
    MOV   WORD [DataSector],AX          ; base of root directory
    ADD   WORD [DataSector],CX
    ; read root directory into memory (7C00:0200)
    MOV   BX,0x0200                     ; read root dir
    CALL  ReadSector                    ;  above bootcode

    ;-------------------------------
    ; Find Stage 2 in Root Directory
    ; Tutorial 6: Bootloaders 4
    ;-------------------------------
    MOV   CX,WORD [RootEntries]         ; load loop counter
    MOV   DI,0x0200                     ; locate first root entry
FindFat:
    PUSH  CX                            ; save loop counter on the stack
    MOV   CX,0x000B                     ; eleven character name
    MOV   SI,Stage2Name                 ; Stage2 file name to find
    PUSH  DI
    REP   CMPSB                         ; test for entry match
    POP   DI
    JE    LoadFat                       ; found our file, now load it
    POP   CX                            ; pop our loop counter
    ADD   DI,0x0020                     ; queue next directory entry
    LOOP  FindFat                       ; keep looking
    JMP   FindFatFailed                 ; file not found, this is bad!

    ;--------------------------
    ; Load FAT
    ; Tutorial 6: Bootloaders 4
    ;--------------------------
LoadFat:
    ; save starting cluster of boot image
    MOV   DX,WORD [DI + 0x001A]         ; save file's
    MOV   WORD [Cluster],DX             ;  first cluster

    ; compute size of FAT and store in "cx"
    XOR   AX,AX
    MOV   AL,BYTE [NumberOfFATs]        ; number of FATs
    MUL   WORD [SectorsPerFAT]          ; sectors used by FATs
    MOV   CX,AX

    ; compute location of FAT and store in "ax"
    MOV   AX,WORD [ReservedSectors]     ; adjust for bootsector

    ; read FAT into memory (7C00:0200)
    MOV   BX,0x0200                     ; read FAT
    CALL  ReadSector                    ;  into memory above our bootcode

;--------------------------------------------------------------------------------------------------
; Load Stage 2
; Tutorial 6: Bootloaders 4
;--------------------------------------------------------------------------------------------------
    ; read Stage2 file into memory (0050:0000)
    MOV   AX,0x0050                     ; set segment register
    MOV   ES,AX                         ;  to 50h
    MOV   BX,0x0000                     ; push our starting address (0h)
    PUSH  BX                            ;  onto the stack

LoadStage2:
    MOV   AX,WORD [Cluster]             ; cluster to read
    POP   BX                            ; buffer to read into
    CALL  ClusterLBA                    ; convert cluster to LBA
    XOR   CX,CX                         ; CL =
    MOV   CL,BYTE [SectorsPerCluster]   ;  sectors to read
    CALL  ReadSector                    ; read a sector
    PUSH  BX                            ; push buffer ptr to stack

    ; compute next cluster
    MOV   AX,WORD [Cluster]             ; identify current cluster
    MOV   CX,AX                         ; copy current cluster
    MOV   DX,AX                         ; copy current cluster
    SHR   DX,0x0001                     ; divide by two
    ADD   CX,DX                         ; sum for (3/2)
    MOV   BX,0x0200                     ; location of FAT in memory
    ADD   BX,CX                         ; index into FAT
    MOV   DX,WORD [BX]                  ; read two bytes from FAT, indexed by BX
    TEST  AX,0x0001                     ; test under mask, if cluster number is odd
    JNZ   LoadStage2OddCluster          ;  then process Odd Cluster

LoadStage2EvenCluster:
    AND   DX,0000111111111111b          ; take low  twelve bits DX x'ABCD' -> x'0BCD'
    JMP   LoadStage2CheckDone

LoadStage2OddCluster:
    SHR   DX,0x0004                     ; take high twelve bits DX x'ABCD' -> x'0ABC'

LoadStage2CheckDone:
    MOV   WORD [Cluster],DX             ; store new cluster
    CMP   DX,0x0FF0                     ; If DX is less than EOF (0x0FF0)
    JB    LoadStage2                    ;   then keep going (JB = Jump Below)

;--------------------------------------------------------------------------------------------------
; Jump to Stage 2 code
;--------------------------------------------------------------------------------------------------
    MOV   SI,Stage2Msg                  ; si points to first byte in message
    CALL  Print                         ; print message
    MOV   AH,0X00                       ; wait
    INT   0x16                          ;  for keypress
    MOV   SI,NewLineMsg                 ; print
    CALL  Print                         ;  new line
    PUSH  WORD 0x0050                   ; Jump to our Stage2 code that we put at 0050:0000
    PUSH  WORD 0x0000                   ;   by using a Far Return which pops IP(0h) then CS(50h)
    RETF                                ;   and poof, we're executing our Stage2 code!

;--------------------------------------------------------------------------------------------------
; Failed to find FAT (File Allocation Table)
;--------------------------------------------------------------------------------------------------
FindFatFailed:
    MOV   SI,FailureMsg                 ; print
    CALL  Print                         ;  failure message
    MOV   AH,0x00                       ; wait for
    INT   0x16                          ;  keypress
    INT   0x19                          ; warm boot computer

;--------------------------------------------------------------------------------------------------
; Working Storage
;--------------------------------------------------------------------------------------------------
    AbsoluteHead    DB 0x00
    AbsoluteSector  DB 0x00
    AbsoluteTrack   DB 0x00
    Cluster         DW 0x0000
    DataSector      DW 0x0000
    FailureMsg      DB 0x0D, 0x0A, "MISSING OR CURRUPT STAGE2", 0x0D, 0x0A, 0x00
    LoadingMsg      DB 0x0D, 0x0A, "MyOs v0.1.1 Stage 1", 0x00
    NewLineMsg      DB 0x0D, 0x0A, 0x00
    ProgressMsg     DB ".", 0x00
    Stage2Msg       DB 0x0D, 0x0A, " Hit Enter to Jump to Stage 2 ", 0x00
    Stage2Name      DB "STAGE2  BIN"

;--------------------------------------------------------------------------------------------------
; Make it a Boot Sector! (must be exactly 512 bytes)
;--------------------------------------------------------------------------------------------------
    TIMES 510-($-$$) DB 0               ; make boot sector exactly 512 bytes
    DW 0xAA55                           ; Magic Word that makes this a boot sector

Stage 2

Main task is to find and load the Kernel.

;**********************************************************
; Stage2.asm
;   Stage2 Bootloader
;
; Broken Thorn Entertainment
; Operating Systems Development Tutorial
; http://www.brokenthorn.com/Resources/OSDevIndex.html
;
; nasm -f bin Stage2.asm -o Stage2.bin -l Stage2.lst
;**********************************************************

; Remember the memory map-- 500h through 7BFFh is unused above the BIOS data area.
; We are loaded at 500h (50h:0h)
[bits 16]
    ORG   0500h
    JMP   Main                          ; jump to Main

;--------------------------------------------------------------------------------------------------
; Prints a null terminated string
; DS => SI: 0 terminated string
;--------------------------------------------------------------------------------------------------
[bits 16]
PutStr:
    PUSHA                               ; save registers
    MOV   AH,0Eh                        ; Nope-Print the character
PutStr1:
    LODSB                               ; load next byte from string from SI to AL
    OR    AL,AL                         ; Does AL=0?
    JZ    PutStr2                       ; Yep, null terminator found-bail out
    INT   10h                           ; invoke BIOS
    JMP   PutStr1                       ; Repeat until null terminator found
PutStr2:
    POPA                                ; restore registers
    RET                                 ; we are done, so return

;--------------------------------------------------------------------------------------------------
; Install our GDT
; Tutorial 8: Protected Mode
;--------------------------------------------------------------------------------------------------
[bits 16]
InstallGDT:
    CLI                                 ; disable interrupts
    PUSHA                               ; save registers
    LGDT  [GDT2]                        ; load GDT into GDTR
    STI                                 ; enable interrupts
    POPA                                ; restore registers
    RET                                 ; All done!

;--------------------------------------------------------------------------------------------------
; Enable A20 line through output port
;--------------------------------------------------------------------------------------------------
[bits 16]
EnableA20:
    CLI                                 ; disable interrupts
    PUSHA

    CALL  WaitInput                     ; wait for keypress
    MOV   AL,0ADh
    OUT   64h,AL                        ; disable keyboard
    CALL  WaitInput                    

    MOV   AL,0D0h
    OUT   64h,AL                        ; tell controller to read output port
    CALL  WaitOutput                   

    IN    AL,60h
    PUSH  EAX                           ; get output port data and store it
    CALL  WaitInput                    

    MOV   AL,0D1h
    OUT   64h,AL                        ; tell controller to write output port
    CALL  WaitInput                    

    POP   EAX
    OR    AL,2                          ; set bit 1 (enable a20)
    OUT   60h,AL                        ; write out data back to the output port

    CALL  WaitInput                    
    MOV   AL,0AEh                       ; enable keyboard
    OUT   64h,AL

    CALL  WaitInput                     ; wait for keypress
    POPA
    STI                                 ; enable interrupts
    RET

;------------------------------
; Helper routines for EnableA20
;------------------------------
WaitInput:
    IN    AL,64h                        ; wait for
    TEST  AL,2                          ;  input buffer
    JNZ   WaitInput                     ;   to clear
    RET

WaitOutput:
    IN    AL,64h                        ; wait for
    TEST  AL,1                          ;  output buffer
    JZ    WaitOutput                    ;   to clear
    RET

;--------------------------------------------------------------------------------------------------
; Floppy Driver Routines
;--------------------------------------------------------------------------------------------------
;------------------------------------------
; Convert CHS to LBA
; LBA = (cluster - 2) * sectors per cluster
;------------------------------------------
[bits 16]
ClusterLBA:
    SUB   AX,0002h                      ; zero base cluster number
    XOR   CX,CX
    MOV   CL,BYTE [SectorsPerCluster]   ; convert byte to word
    MUL   CX
    ADD   AX,WORD [DataSector]          ; base data sector
    RET

;---------------------------------------------------------------------------
; Convert LBA to CHS
; AX = LBA Address to convert
;
; absolute sector = (logical sector / sectors per track) + 1
; absolute head   = (logical sector / sectors per track) MOD number of heads
; absolute track  = logical sector / (sectors per track * number of heads)
;---------------------------------------------------------------------------
[bits 16]
LBACHS:                                 ;
    XOR   DX,DX                         ; prepare dx:ax for operation
    DIV   WORD [SectorsPerTrack]        ; calculate
    INC   DL                            ; adjust for sector 0
    MOV   BYTE [AbsoluteSector],DL
    XOR   DX,DX                         ; prepare dx:ax for operation
    DIV   WORD [HeadsPerCylinder]       ; calculate
    MOV   BYTE [AbsoluteHead],DL
    MOV   BYTE [AbsoluteTrack],AL
    RET

;-----------------------------------
; Read a series of sectors
; CX     = Number of sectors to read
; AX     = Starting sector
; ES:EBX = Buffer
;-----------------------------------
[bits 16]
ReadSector:
    MOV   DI,0005h                      ; five retries for error
ReadSector1:
    PUSH  AX
    PUSH  BX
    PUSH  CX
    CALL  LBACHS                        ; convert starting sector to CHS
    MOV   AH,02h                        ; BIOS read sector
    MOV   AL,01h                        ; read one sector
    MOV   CH,BYTE [AbsoluteTrack]       ; track
    MOV   CL,BYTE [AbsoluteSector]      ; sector
    MOV   DH,BYTE [AbsoluteHead]        ; head
    MOV   DL,BYTE [DriveNumber]         ; drive
    INT   13h                           ; invoke BIOS
    JNC   ReadSector2                   ; test for read error
    XOR   AX,AX                         ; BIOS reset disk
    int   13h                           ; invoke BIOS
    DEC   DI                            ; decrement error counter
    POP   CX
    POP   BX
    POP   AX
    JNZ   ReadSector1                   ; attempt to read again
    INT   18h
ReadSector2:
    POP   CX
    POP   BX
    POP   AX
    ADD   BX,WORD [BytesPerSector]      ; queue next buffer
    INC   AX                            ; queue next sector
    LOOP  ReadSector                    ; read next sector
    RET

;------------------------------------
; Load Root Directory Table to 07E00h
;------------------------------------
[bits 16]
LoadRootDir:
    PUSHA                               ; store registers
    PUSH  ES
    ; compute size of root directory and store in "CX"
    XOR   CX,CX                         ; clear registers
    XOR   DX,DX
    MOV   AX,32                         ; 32 byte directory entry
    MUL   WORD [RootEntries]            ; total size of directory
    DIV   WORD [BytesPerSector]         ; sectors used by directory
    XCHG  AX,CX                         ; move into AX
    ; compute location of root directory and store in "AX"
    MOV   AL,BYTE [NumberOfFATs]        ; number of FATs
    MUL   WORD [SectorsPerFAT]          ; sectors used by FATs
    ADD   AX,WORD [ReservedSectors]
    MOV   WORD [DataSector],AX          ; base of root directory
    ADD   WORD [DataSector],CX
    ; read root directory into 07E00h
    PUSH  WORD RootSegment
    POP   ES
    MOV   BX,0                          ; copy root dir
    CALL  ReadSector                    ; read in directory table
    POP   ES
    POPA                                ; restore registers and return
    RET

;-----------------------------
; Loads FAT table to 07C00h
; ES:DI = Root Directory Table
;-----------------------------
[bits 16]
LoadFAT:
    PUSHA                               ; store registers
    PUSH  ES
    ; compute size of FAT and store in "CX"
    XOR   AX,AX
    MOV   AL,BYTE [NumberOfFATs]        ; number of FATs
    MUL   WORD [SectorsPerFAT]          ; sectors used by FATs
    MOV   CX,AX
    ; compute location of FAT and store in "AX"
    MOV   AX,WORD [ReservedSectors]
    ; read FAT into memory (Overwrite our bootloader at 07C00h)
    PUSH  WORD FatSegment
    POP   ES
    XOR   BX,BX
    CALL  ReadSector 
    POP   ES
    POPA                                ; restore registers and return
    RET

;----------------------------------------------------------------
; Search for filename in root table
; parm DS:SI = File name
; ret  AX    = File index number in directory table. -1 if error
;----------------------------------------------------------------
[bits 16]
FindFile:
    PUSH  CX                            ; store registers
    PUSH  DX
    PUSH  BX
    MOV   BX,SI                         ; copy filename for later
    ; browse root directory for binary image
    MOV   CX,WORD [RootEntries]         ; load loop counter
    MOV   DI,RootOffset                 ; locate first root entry at 1 MB mark
    CLD                                 ; clear direction flag
FindFile1:
    PUSH  CX
    MOV   CX,11                         ; eleven character name. Image name is in SI
    MOV   SI,BX                         ; image name is in BX
    PUSH  DI
    REP   CMPSB                         ; test for entry match
    POP   DI
    JE    FindFile2
    POP   CX
    ADD   DI,32                         ; queue next directory entry
    LOOP  FindFile1
    ; Not Found
    POP   BX                            ; restore registers and return
    POP   DX
    POP   CX
    MOV   AX,-1                         ; set error code
    RET
FindFile2:
    POP   AX                            ; return value into AX contains entry of file
    POP   BX                            ; restore registers and return
    POP   DX
    POP   CX
    RET

;-----------------------------------------
; Load file
; parm ES:SI  = File to load
; parm EBX:BP = Buffer to load file to
; ret  AX     = -1 on error, 0 on success
; ret  CX     = number of sectors read
;-----------------------------------------
[bits 16]
LoadFile:
    XOR   ECX,ECX                       ; size of file in sectors
    PUSH  ECX
    PUSH  BX                            ; BX => BP points to buffer to write to; store it for later
    PUSH  BP
    CALL  FindFile                      ; find our file. ES:SI contains our filename
    CMP   AX,-1
    JNE   LoadFile1
    ; failed to find file
    POP   BP
    POP   BX
    POP   ECX
    MOV   AX,-1
    RET
LoadFile1:
    SUB   EDI,RootOffset
    SUB   EAX,RootOffset
    ; get starting cluster
    PUSH  WORD RootSegment              ; root segment loc
    POP   ES
    MOV   DX,WORD [ES:DI + 0001Ah]      ; DI points to file entry in root directory table. Refrence the table...
    MOV   WORD [Cluster],DX             ; file's first cluster
    POP   BX                            ; get location to write to so we dont screw up the stack
    POP   ES
    PUSH  BX                            ; store location for later again
    PUSH  ES
    CALL  LoadFAT
LoadFile2:
    ; load the cluster
    MOV   AX,WORD [Cluster]             ; cluster to read
    POP   ES                            ; bx:bp=es:bx
    POP   BX
    CALL  ClusterLBA
    XOR   CX,CX
    MOV   CL,BYTE [SectorsPerCluster]
    CALL  ReadSector 
    POP   ECX
    INC   ECX                           ; add one more sector to counter
    PUSH  ECX
    PUSH  BX
    PUSH  ES
    MOV   AX,FatSegment                 ;start reading from fat
    MOV   ES,AX
    XOR   BX,BX
    ; get next cluster
    MOV   AX,WORD [Cluster]             ; identify current cluster
    MOV   CX,AX                         ; copy current cluster
    MOV   DX,AX
    SHR   DX,0001h                      ; divide by two
    ADD   CX,DX                         ; sum for (3/2)
    MOV   BX,0                          ; location of fat in memory
    ADD   BX,CX
    MOV   DX,WORD [ES:BX]
    TEST  AX,0001h                      ; test for odd or even cluster
    JNZ   LoadFile3
    AND   DX,0000111111111111b          ; Even cluster - take low 12 bits
    JMP   LoadFile4
LoadFile3:
    SHR   DX,0004h                      ; Odd cluster  - take high 12 bits
LoadFile4:
    MOV   WORD [Cluster],DX
    CMP   DX,0FF0h                      ; test for end of file marker
    JB    LoadFile2
    ; We're done
    POP   ES
    POP   BX
    POP   ECX
    XOR   AX,AX
    RET

;--------------------------------------------------------------------------------------------------
; Stage 2 Entry Point
; - Set Data segment registers and stack
; - Install GDT
; - Enable A20
; - Read Stage3 into memory
; - Protected mode (pmode)
;--------------------------------------------------------------------------------------------------
[bits 16]
Main:
    ;----------------------------
    ; Set Data Segement registers
    ;----------------------------
    CLI                                 ; disable interrupts
    XOR   AX,AX                         ; null segments
    MOV   DS,AX
    MOV   ES,AX

    ;-----------------
    ; Set up our Stack
    ;-----------------
    MOV   AX,00h                        ; stack begins at 09000h-0FFFFh
    MOV   SS,AX
    MOV   SP,0FFFFh
    STI                                 ; enable interrupts

    ;----------------
    ; Install our GDT
    ;----------------
    CALL  InstallGDT

    ;-----------
    ; Enable A20
    ;-----------
    CALL  EnableA20

    ;----------------------
    ; Print loading message
    ;----------------------
    MOV   SI,LoadingMsg
    CALL  PutStr

    ;----------------------
    ; Initialize filesystem
    ;----------------------
    CALL  LoadRootDir                   ; Load root directory table

    ;----------------------
    ; Read Stage3 from disk
    ;----------------------
    MOV   EBX,0                         ; BX:BP points to buffer to load to
    MOV   BP,RModeBase
    MOV   SI,Stage3Name                 ; our file to load
    CALL  LoadFile
    MOV   DWORD [Stage3Size],ECX        ; save the size of Stage3
    CMP   AX,0                          ; Test for success
    JE    GoProtected                   ; yep--onto Stage 3!

    ;------------------
    ; This is very bad!
    ;------------------
    MOV   SI,FailureMsg                 ; Nope--print error
    CALL  PutStr                        ;
    MOV   AH,0                          ; wait
    INT   16h                           ;  for keypress
    INT   19h                           ; warm boot computer
    CLI                                 ; If we get here, something really went wrong
    HLT

GoProtected:
    MOV   SI,Stage3Msg
    CALL  PutStr
    MOV   ah,00h                        ; wait
    INT   16h                           ;  for keypress
    ;--------------
    ; Go into pmode
    ;--------------
    CLI                                 ; clear interrupts
    MOV   EAX,CR0                       ; set bit 0 in cr0--enter pmode
    OR    EAX,1
    MOV   CR0,EAX
    JMP   CodeDesc:GoStage3             ; far jump to fix CS. Remember that the code selector is 08h!

  ; Note: Do NOT re-enable interrupts! Doing so will triple fault!
  ; We will fix this in Stage 3.

;--------------------------------------------------------------------------------------------------
; Get to Stage3 - Our Kernel!
; - Set Data Segment Register
; - Set up our Stack
; - Copy Kernel to address 1 MB
; - Jump to our Kernel!!
;--------------------------------------------------------------------------------------------------
[bits 32]
GoStage3:
    ;----------------------------
    ; Set Data Segement registers
    ;----------------------------
    MOV   AX,DataDesc                   ; set data segments to data selector (10h)
    MOV   DS,AX
    MOV   SS,AX
    MOV   ES,AX

    ;-----------------
    ; Set up our Stack
    ;-----------------
    MOV   ESP,90000h                    ; stack begins from 90000h

    ;-------------------
    ; Copy kernel to 1MB
    ;-------------------
    MOV   EAX,DWORD [Stage3Size]
    MOVZX EBX,WORD [BytesPerSector]
    MUL   EBX
    MOV   EBX,4
    DIV   EBX
    CLD
    MOV   ESI,RModeBase
    MOV   EDI,PModeBase
    MOV   ECX,EAX
    REP   MOVSD                         ; copy image to its protected mode address

    ;--------------------
    ; Jump to our Kernel!
    ;--------------------
    JMP   CodeDesc:PModeBase            ; jump to our kernel! Note: This assumes Kernel's entry point is at 1 MB

    ;-------------------
    ; We never get here! 
    ;-------------------
    CLI                                 ; Stop 
    HLT                                 ;  execution

;--------------------------------------------------------------------------------------------------
; Global Descriptor Table (GDT)
; Tutorial 8: Protected Mode
;--------------------------------------------------------------------------------------------------
GDT1:
;----------------
; null descriptor
;----------------
                  DD  0
                  DD  0
NullDesc          EQU 0
;----------------
; code descriptor
;----------------
                  DW  0FFFFh            ; limit low
                  DW  0                 ; base low
                  DB  0                 ; base middle
                  DB  10011010b         ; access
                  DB  11001111b         ; granularity
                  DB  0                 ; base high
CodeDesc          EQU 8h
;----------------
; data descriptor
;----------------
                  DW  0FFFFh            ; limit low
                  DW  0                 ; base low
                  DB  0                 ; base middle
                  DB  10010010b         ; access
                  DB  11001111b         ; granularity
                  DB  0                 ; base high
DataDesc          EQU 10h
;-------------------
; pointer to our GDT
;-------------------
GDT2:
                  DW  GDT2-GDT1-1       ; limit (Size of GDT)
                  DD  GDT1              ; base of GDT

;--------------------------------------------------------------------------------------------------
; Working Storage
;--------------------------------------------------------------------------------------------------
FatSegment        EQU 2C0h
PModeBase         EQU 100000h           ; where the kernel is to be loaded to in protected mode
RModeBase         EQU 3000h             ; where the kernel is to be loaded to in real mode
RootOffset        EQU 2E00h
RootSegment       EQU 2E0h

LoadingMsg        DB  0Dh
                  DB  0Ah
                  DB  "MyOs v0.1.1 Stage 2"
                  DB  00h

Stage3Msg         DB  0Dh
                  DB  0Ah
                  DB  " Hit Enter to Jump to Stage 3"
                  DB  00h

FailureMsg        DB  0Dh
                  DB  0Ah
                  DB  "*** FATAL: MISSING OR CURRUPT STAGE3.BIN. Press Any Key to Reboot"
                  DB  0Dh
                  DB  0Ah
                  DB  0Ah
                  DB  00h


AbsoluteHead      DB  00h
AbsoluteSector    DB  00h
AbsoluteTrack     DB  00h
BytesPerSector    DW  512
Cluster           DW  0000h
DataSector        DW  0000h
DriveNumber       DB  0
HeadsPerCylinder  DW  2
Stage3Name        DB  "STAGE3  BIN"      ; kernel name (Must be 11 bytes)
Stage3Size        DB  0                  ; size of kernel image in bytes
NumberOfFATs      DB  2
ReservedSectors   DW  1
RootEntries       DW  224
SectorsPerCluster DB  1
SectorsPerFAT     DW  9
SectorsPerTrack   DW  18

Stage 3

The Kernel

Includes some primitive video code and gets a single scan code from the keyboard.

;**********************************************************
; Stage3.asm
;   A basic 32 bit binary kernel running
;
; Broken Thorn Entertainment
; Operating Systems Development Tutorial
; http://www.brokenthorn.com/Resources/OSDevIndex.html
;
; nasm -f bin Stage3.asm -o Stage3.bin -l Stage3.lst
;**********************************************************

[bits  32]                              ; 32 bit code
    ORG   100000h                       ; Kernel starts at 1 MB
    JMP   Stage3                        ; Jump to entry point

;--------------------------------------------------------------------------------------------------
; Video Routines
;--------------------------------------------------------------------------------------------------

;---------------
;- Color Codes -
;---------------
;  0 0 Black
;  1 1 Blue
;  2 2 Green
;  3 3 Cyan
;  4 4 Red
;  5 5 Purple
;  6 6 Brown
;  7 7 Gray
;  8 8 Dark Gray
;  9 9 Light Blue
; 10 A Light Green
; 11 B Light Cyan
; 12 C Light Red
; 13 D Light Purple
; 14 E Yellow
; 15 F White
; Example 3F
;         ^^
;         ||
;         ||- Foreground F = White
;         |-- Background 3 = Cyan

;------------------------------------------
; Routine to calculate video memory address
;   represented by the given Row,Col
;------------------------------------------
CalcVideoAddr:
    PUSHA                               ; Save registers
    XOR   EAX,EAX                       ; Row calculation
    MOV   AL,[Row]                      ;  row
    DEC   EAX                           ;  minus 1
    MOV   EDX,160                       ;  times
    MUL   EDX                           ;  160
    PUSH  EAX                           ;  save it
    XOR   EAX,EAX                       ; Col calculation
    MOV   AL,[Col]                      ;  col
    MOV   EDX,2                         ;  times
    MUL   EDX                           ;  2
    SUB   EAX,EDX                       ;  minus 2
    POP   EDX                           ; Add col calculation
    ADD   EAX,EDX                       ;  to row calculation
    ADD   EAX,VidMem                    ;  plus VidMem
    MOV   [VidAdr],EAX                  ; Save in VidAdr
    POPA                                ; Restore registers
    RET                                 ; Return to caller

;------------------------------
; Put a character on the screen
; EDI = address in video memory
;------------------------------
PutChar:
    PUSHA                               ; Save registers
    MOV   EDI,[VidAdr]                  ; EDI = Video Address
    MOV   DL,[Char]                     ; DL = character
    MOV   DH,[ColorAttr]                ; DH = attribute
    MOV   WORD [EDI],DX                 ; Move attribute and character to video display
    POPA                                ; Restore registers
    RET                                 ; Return to caller

;---------------------------------
; Print a null terminated string
; EBX = address of string to print
;---------------------------------
PutStr:
    PUSHA                               ; Save registers
    CALL  CalcVideoAddr                 ; Calculate video address
    XOR   ECX,ECX                       ; Clear ECX
    PUSH  EBX                           ; Copy the string address in EBX
    POP   ESI                           ;  to ESI
    MOV   CX,[ESI]                      ; Grab string length using ESI, stuff it into CX
    SUB   CX,2                          ; Subtract out 2 bytes for the length field
    ADD   ESI,2                         ; Bump past the length field to the beginning of string
PutStr1:
    MOV   BL,BYTE [ESI]                 ; Get next character
    CMP   BL,0Ah                        ; NewLine?
    JNE   PutStr2                       ;  No
    MOV   BYTE [Col],1                  ;  Yes, back to col 1
    INC   BYTE [Row]                    ;   and bump row by 1
    CALL  CalcVideoAddr                 ; Calculate video address
    JMP   PutStr3                       ; Continue
PutStr2:
    MOV   [Char],BL                     ; Stash our character
    CALL  PutChar                       ; Print it out
    ADD   DWORD [VidAdr],2              ; Bump Video Address by 2
    INC   BYTE [Col]                    ; Bump column by 1
PutStr3:
    INC   ESI                           ; Bump ESI to next character in our string
    LOOP  PutStr1                       ; Loop (Decrement CX each time until CX is zero)
    CALL  MoveCursor                    ; Update cursor (do this once after displaying the string, more efficient)
    POPA                                ; Restore registers
    RET                                 ; Return to caller

;-----------------------
; Update hardware cursor
;-----------------------
MoveCursor:
    PUSHA                               ; Save registers
    MOV   BH,BYTE [Row]                 ; BH = row
    MOV   BL,BYTE [Col]                 ; BL = col
    DEC   BH                            ; BH-- (Make row zero based)

    XOR   EAX,EAX                       ; Clear EAX
    MOV   ECX,TotCol                    ; ECX = TotCol
    MOV   AL,BH                         ; Row
    MUL   ECX                           ;  * TotCol
    ADD   AL,BL                         ;  + Col
    MOV   EBX,EAX                       ; Save result in EBX (BL,BH in particular)

    XOR   EAX,EAX                       ; Clear EAX
    MOV   DX,03D4h                      ; Set VGA port to  03D4h (Video controller register select)
    MOV   AL,0Fh                        ; Set VGA port-index 0Fh (cursor location low byte)
    OUT   DX,AL                         ; Write to the VGA port
    MOV   DX,03D5h                      ; Set VGA port to  03D5h (Video controller data)
    MOV   AL,BL                         ; Set low byte of calculated cursor position from above
    OUT   DX,AL                         ; Write to the VGA port

    XOR   EAX,EAX                       ; Clear EAX
    MOV   DX,03D4h                      ; Set VGA port to  03D4h (Video controller register select)
    MOV   AL,0Eh                        ; Set VGA port-index 0Fh (cursor location high byte)
    OUT   DX,AL                         ; Write to the VGA port
    MOV   DX,03D5h                      ; Set VGA port to  03D5h (Video controller data)
    MOV   AL,BH                         ; Set high byte of calculated cursor position from above
    OUT   DX,AL                         ; Write to the VGA port

    POPA                                ; Restore registers
    RET                                 ; Return to caller

;-------------
; Clear Screen
;-------------
ClrScr:
    PUSHA                               ; Save registers
    CLD                                 ; Clear DF Flag, REP STOSW increments EDI
    MOV   EDI,VidMem                    ; Set EDI to beginning of Video Memory
    MOV   CX,2000                       ; 2,000 'words' on the screen
    MOV   AH,[ColorAttr]                ; Set color attribute
    MOV   AL,' '                        ; We're going to 'blank' out the screen
    REP   STOSW                         ; Move AX to video memory pointed to by EDI, Repeat CX times, increment EDI each time
    MOV   BYTE [Col],1                  ; Set Col to 1
    MOV   BYTE [Row],1                  ; Set Row to 1
    POPA                                ; Restore registers
    RET                                 ; Return to caller

;-------------------
;Set Color Attribute
;-------------------
SetColorAttr:
    PUSHA                               ; Save registers
    MOV   AL,[ColorBack]                ; Background color (e.g. 3)
    SHL   AL,4                          ;  goes in highest 4 bits of AL
    MOV   BL,[ColorFore]                ; Foreground color in lowest 4 bits of BL (e.g. F)
    OR    EAX,EBX                       ; AL now has the combination of background and foreground (e.g. 3F)
    MOV   [ColorAttr],AL                ; Save result in ColorAttr
    POPA                                ; Restore registers
    RET                                 ; Return to caller

;--------------------------------------------------------------------------------------------------
; Install our IDT
;--------------------------------------------------------------------------------------------------
InstallIDT:
    CLI                                 ; Disable interrupts
    PUSHA                               ; Save registers
    LIDT  [IDT2]                        ; Load IDT into IDTR
    MOV   EDI,IDT1                      ; Set EDI to beginning of IDT
    MOV   CX,2048                       ; 2048 bytes in IDT
    XOR   EAX,EAX                       ; Set all 256 IDT entries to NULL (0h)
    REP   STOSB                         ; Move AL to IDT pointed to by EDI, Repeat CX times, increment EDI each time
    STI                                 ; Enable interrupts
    POPA                                ; Restore registers
    RET                                 ; All done!

;--------------------------------------------------------------------------------------------------
; Keyboard Routines
;--------------------------------------------------------------------------------------------------
ReadKeyboard:
    ;--------------
    ; Read scancode from the keyboard buffer and reset the keyboard
    ;--------------
    IN    AL,060h                       ;Obtain scancode form Keyboart I/O Port
    MOV   CL,AL                         ;Store the scancode in CL for now
    IN    AL,061h                       ;Parse the Keyboard Command Port
    MOV   AH,AL                         ;Store command code in AH for now
    OR    AL,080h                       ;Set AL to disable command code
    OUT   061h,AL                       ;Output disable command to Keyboard Command Port
    MOV   AL,AH                         ;Set AL to the original command code
    OUT   061h,AL                       ;Output enable command to Keyboard Command Port
    MOV   AL,CL                         ;Restore the scancode to AL
    RET

;--------------------------------------------------------------------------------------------------
; Stage3 - Our Kernel!
;--------------------------------------------------------------------------------------------------
Stage3:
    ;--------------
    ; Set registers
    ;--------------
    MOV   AX,10h                        ; Set data
    MOV   DS,AX                         ;  segments to
    MOV   SS,AX                         ;   data selector
    MOV   ES,AX                         ;    (10h)
    MOV   ESP,90000h                    ; Stack begins from 90000h

    CALL  InstallIDT                    ; Install our Interrupt Descriptor Table

    ;-------------------------------
    ; Clear screen and print success
    ;-------------------------------
    MOV   BYTE [ColorBack],Black        ; Background color
    MOV   BYTE [ColorFore],Purple       ; Foreground colr
    CALL  SetColorAttr                  ; Set color
    CALL  ClrScr                        ; Clear screen

    MOV   BYTE [Row],10                 ; Row 10
    MOV   BYTE [Col],1                  ; Col 1
    MOV   EBX,Msg1                      ; Put
    CALL  PutStr                        ;  Msg1
    MOV   EBX,NewLine                   ; Put
    CALL  PutStr                        ;  a New Line
    MOV   EBX,Msg2                      ; Put
    CALL  PutStr                        ;  Msg2

    MOV   EBX,NewLine
    CALL  PutStr
    CALL  ReadKeyboard
    MOV   [Char],AL
    CALL  PutChar

    ;---------------
    ; Stop execution
    ;---------------
    CLI
    HLT

;--------------------------------------------------------------------------------------------------
; Interrupt Descriptor Table (IDT)
;--------------------------------------------------------------------------------------------------
IDT1:
TIMES 2048  DB 0                        ; The IDT is exactly 2048 bytes - 256 entries 8 bytes each
;-------------------
; pointer to our IDT
;-------------------
IDT2:
                  DW  IDT2-IDT1-1       ; limit (Size of IDT)
                  DD  IDT1              ; base of IDT

;--------------------------------------------------------------------------------------------------
; Working Storage
;--------------------------------------------------------------------------------------------------
%macro String 2
%1          DW  %%EndStr-%1
            DB  %2
%%EndStr:
%endmacro
String  Msg1,"------   MyOs v0.1.2   -----"
String  Msg2,"------  32 Bit Kernel  -----"
String  NewLine,0Ah

ColorBack   DB  0                       ; Background color (00h - 0Fh)
ColorFore   DB  0                       ; Foreground color (00h - 0Fh)
ColorAttr   DB  0                       ; Combination of background and foreground color (e.g. 3Fh 3=cyan background,F=white text)
Char        DB  0                       ; ASCII character
Row         DB  0                       ; Row (1-25)
Col         DB  0                       ; Col (1-80)
VidAdr      DD  0                       ; Video Address

;--------------------------------------------------------------------------------------------------
; Equates
;--------------------------------------------------------------------------------------------------
VidMem      EQU 0B8000h                 ; Video Memory (Starting Address)
TotCol      EQU 80                      ; width and height of screen
Black       EQU 00h                     ; Black
Cyan        EQU 03h                     ; Cyan
Purple      EQU 05h                     ; Purple
White       EQU 0Fh                     ; White