User:Khorlane
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
Disk Emulator
Hex Editor
Virtual Machine
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