Firstly, here is my multi-boot MBR so I can boot from more than a single partition, i actually use this on this machine, and it works great:
Code: Select all
[bits 16]
[org 0]
jmp short Start
Choice
db '0' ;Default partition, set by fdisk type program!
Start:
;Setup our stack
xor eax, eax ;This is where we got loaded
mov ds, ax
mov es, ax
mov ax, 0x7c0 ;Right before boot sector!
mov sp, ax
;Lets copy ourself to another free location
mov si, 0x7c00 ;Were we are now
mov di, Location ;Move to our new location
mov ecx, 512/4 ;Total bytes to move
rep movsd
jmp 0:Reloaded+Location
Reloaded:
mov [BootDisk+Location], dl ;Store boot disk
mov si, BootMessage+Location
call PrintText
.AskAgain
xor edx, edx ;Partition zero
mov eax, Location+PartEntry+8 ;Start of partitions + LBA
mov [Location+PartEntry], dl
mov [Location+PartEntry+16], dl
mov [Location+PartEntry+32], dl
mov [Location+PartEntry+48], dl
.Again
mov ebx, [eax] ;LBA
cmp ebx, 0
je .NoPartition
call PrintNum
mov si, BootPart+Location
call PrintText
pusha
mov edx, ebx
call PrintNum
mov si, PartSep+Location
call PrintText
mov edx, [eax+4] ;Size in sectors
shr edx, 11 ;Divide by 2048 (*1024*1024/512, mb)
call PrintNum
mov si, PartEnd+Location
call PrintText
popa
.NoPartition
add eax, 16 ;Next...
inc dx
cmp dx, 4
jne .Again
mov bx, 182 ;10 seconds
; mov bx, 91 ;5 seconds
; mov bx, 9 ;1/2 second, just enough to see boot menu ;)
; mov bx, 46 ;2.5 seconds, just enough to see boot menu ;)
add bx, [0x46c] ;Bios clock!
.KeyWait
mov ah, 1
int 0x16
jnz .GetKey
cmp bx, [0x46c]
jge .KeyWait ;Still waiting...
mov al, [Choice+Location] ;Default partition
jmp .Default ;Default
.GetKey
xor ax, ax
int 0x16
call PrintChar
.Default
;Lets boot the partition now!
xor ecx, ecx
mov cl, al ;Get our choice
mov eax, Location+PartEntry
sub cl, '0' ;Now go from character to drive!
shl ecx, 4 ;Multiply by 16
add eax, ecx ;Move to the correct table
add eax, 8 ;Go to LBA of partition
mov ebx, [eax]
cmp ebx, 0
je .AskAgain
;Update our _lba to start of partition
mov [_lba+Location], ebx ;Put LBA of boot sector into _lba
;Write our MBR back out before reading the new one :)
sub eax, 8
mov dl, [BootDisk+Location]
mov [eax], dl ;Store our boot disk back into this
mov dh, 0 ;Head
mov cx, 1 ;Sector 1/Cylinder 0
mov ax, 0x0301 ;Write, 1 sector
mov bx, Location
int 0x13
;Reset drive
; mov ah, 0
; int 0x13
.ReadBootSector
;Lets use LBA
mov dl, [BootDisk+Location]
mov ah, 0x42 ;LBA read
mov si, _boot_lba+Location ;information block!
int 0x13
jc .ReadBootSector
mov dl, [BootDisk+Location] ;Let the bootsector know our boot drive
jmp 0:0x7c00 ;Jump to the boot sector where it thinks the bios loaded it ;)
;Inputs
; edx = value
PrintNum:
pusha
mov eax, edx
mov ebx, 10
xor ecx, ecx
.ValueToStack
xor edx, edx ;Zero out dx
div ebx
add dx, '0'
push dx
inc ecx ;Add to counter
cmp eax, 0
jne .ValueToStack
.PrintDecNext
pop ax
call PrintChar
loop .PrintDecNext
popa
ret
;Print char in al
PrintChar:
push ax
push bx
mov ah, 0x0e
mov bx, 0x07
int 0x10
pop bx
pop ax
ret
;si = message
PrintText:
push ax
.PrintNext
lodsb ;Load es:si into al
test al, al
jz .Done
call PrintChar
jmp .PrintNext ;Repeat
.Done
pop ax
ret
_boot_lba
db 0x10
db 0
_length
dw 1 ;1 sector
_location
dw 0x7c00
_segment
dw 0
_lba
dd 0 ;Sector to read, 4 bytes!
dd 0 ;Upper part of lba (8 bytes total!)
BootMessage
db 'PhatOS Multi Booter',13,10,0
BootPart
db '.) LBA ',0
PartSep
db ' Size : ',0
PartEnd
db 'mb',13,10,0
BootDisk ;Disk we booted from
db 0
Location equ 0x8000 ;Some random place out of the way
tick equ 0x46c ;Location of bios tick count!
PartEntry equ 446 ;Start of partition entries
times 446-($-$$) db 0 ;Up to boot sector info
times 510-($-$$) db 0 ;Up to boot signature
dw 0xaa55
Ok, so now my boot loader has more than one part... the first part I replace depending on the file system/boot device,the second part doesn't change. First my Fat32 standard HD boot sector.
Code: Select all
[bits 16]
[org 0x7c00]
jmp short Start ;0-2
nop
;Lets make our BPB
OEMName db 'PhatOS '
BytesPerSector dw 512 ;512 bytes is normal ;)
SectorsPerClustor db 1 ;Sector == cluster
ReservedSectors dw 1 ;Reserve the boot sector
FatCount db 1 ;1 copy of the fat (some things require this)
RootEntries dw 224 ;# of root entries
TotalSectors16 dw 0 ;# of sectors on the disk
MediaType db 0xF8 ;Ignored for most things
FatSize16 dw 0 ;Size of a single fat in sectors
SectorsPerTrack dw 0 ;Sectors Per Track
Heads dw 0 ;Head count
HiddenSectors dd 0 ;# of hidden sectors
TotalSectors32 dd 0 ;# of sectors, 32-bit!
;Fat32 section
FatSize32 dd 0 ;Size of a single Fat in sectors
ExtFlags dw 0 ;Used for mirroring, leave this alone
FSVersion dw 0 ;Our version is 0.0 :)
RootCluster dd 2 ;Cluster # of root, normally 2
FSInfo dw 0 ;Sector # of FS Info Block
BkBootSector dw 0 ;Backup boot sector... unused for us ;)
Reserved1 times 12 db 0 ;Reserved
DriveNum db 0 ;We don't care for this
Reserved2 db 0 ;Reserved
BootSig db 0x29 ;0x29 - next 3 things exist
VolumeID dd 0 ;Volume's ID, we don't care for it ;)
VolumeLabel db 'PhatOS ' ;11 bytes
FileSystem db 'FAT32 ' ;File system
Start:
cld
mov [BootDisk], dl ;Store boot disk
;Set up our stack at 0x07c0:0x0000 (goes downwards!)
xor ax, ax
mov ss, ax
mov es, ax
mov ax, 0x07c0
mov sp, ax
;Read our MBR to see what partition we booted from
mov eax, 0 ;MBR
mov bx, 0x1000 ;Random free address
mov cx, 1 ;One sector
call ReadSector
xor ecx, ecx
mov eax, 0x1000+446
;Lets determine boot partition + LBA
.CheckNext
cmp byte [eax], 0 ;Would be 0x80
jne .FoundBootPartition
inc cx
cmp cx, 4
je .DidntFindMBR
add eax, 16
jmp .CheckNext
.FoundBootPartition
mov [BootPartition], cl ;Store boot partition here...
mov ebx, [eax+8] ;Store into ebx
mov [PartitionLBA], ebx ;Store this here...
.DidntFindMBR
;Lets determine our Data Start now... based on params above :)
xor edx, edx
mov ebx, edx
mov eax, [FatSize32]
mov dl, [FatCount]
mov bx, [ReservedSectors]
or ax, [FatSize16]
mul dx
add eax, ebx
inc eax
mov [DataStart], eax ;This is our start sector of our kernel :)
;Lets load our second stage :)
mov eax, 1 ;Start of 2nd stage
mov bx, 0x7c00+512 ;Right after boot sector!
mov cx, 3 ;3 sectors
call ReadSector
jmp SecondStage ;Go to our second stage loader
BootDisk ;Disk we booted from
db 0
BootPartition ;Partition on disk we booted from (0xFF = no partitions!)
db 0xFF
PartitionLBA ;LBA of the boot partition
dd 0
SystemMemory ;Memory in bytes,4gb max
dd 0
;Read a sector from the boot disk/partition
;eax - sector to read
;bx - dest
;cx - length
ReadSector:
db 0x66
pusha
mov dl, [BootDisk] ;Use boot disk
add eax, [PartitionLBA] ;Start of partition
mov [_lba], eax ;Store linear block address
mov [_location], bx ;Store location
mov [_length], cx ;Length in sectors
mov ah, 0x42 ;Read function
mov si, _boot_lba ;Location of LBA block
int 0x13
db 0x66
popa
ret
_boot_lba
db 0x10 ;Size of lba block structure :)
db 0
_length
dw 0 ;Filled in from above!
_location
dw 0 ;4k page bounary
_segment
dw 0
_lba
dd 0 ;Sector to read, 8 bytes total!
dd 0
DataStart dd 0
times 502-($-$$) db 0 ;Last, enough for 4 entries :)
LoadEntries: ;Stores size of each file in sectors
times 510-($-$$) db 0
dw 0xaa55
%include '2ndStage.inc'
And, here is one for fat12/fat16 floppy disk or hard drive
Code: Select all
[bits 16]
[org 0x7c00]
jmp short Start ;0-2
nop
;Lets make our BPB
OEMName db 'PhatOS '
BytesPerSector dw 512 ;512 bytes is normal ;)
SectorsPerClustor db 1 ;Sector == cluster
ReservedSectors dw 1 ;Reserve the boot sector
FatCount db 1 ;1 copy of the fat (some things require this)
RootEntries dw 224 ;# of root entries
TotalSectors16 dw 0 ;# of sectors on the disk
MediaType db 0xF8 ;Ignored for most things
FatSize16 dw 0 ;Size of a single fat in sectors
SectorsPerTrack dw 0 ;Sectors Per Track
Heads dw 0 ;Head count
HiddenSectors dd 0 ;# of hidden sectors
TotalSectors32 dd 0 ;# of sectors, 32-bit!
;Start of our Fat12/16 info
DriveNum db 0 ;Drive number (0x0x - floppy, 0x8x - hd
Reserved db 0 ;Reserved, set to 0
BootSig db 0x29 ;Set to 0x29 if next 3 values are present
VolumeID dd 0 ;Volume's ID, we don't care for it ;)
VolumeLabel db 'PhatOS ' ;11 bytes
FileSystem db 'FAT ' ;File system
Start:
cld
mov [BootDisk], dl ;Store boot disk
;Set up our stack at 0x07c0:0x0000 (goes downwards!)
xor ax, ax
mov ss, ax
mov es, ax
mov ax, 0x07c0
mov sp, ax
;Determine which read function to use...
test dl, 0x80 ;Hard Disk Bit?
jnz .KernelHD ;Lets use LBA if bit set
mov dword [ReadSectorFunc], ReadSectorFD
jmp DidntFindMBR
.KernelHD
mov dword [ReadSectorFunc], ReadSectorHD ;Use HD function
;Read our MBR to see what partition we booted from
mov eax, 0 ;MBR
mov bx, 0x1000 ;Random free address
mov cx, 1 ;One sector
call ReadSector
xor ecx, ecx
mov eax, 0x1000+446
;Lets determine boot partition + LBA
.CheckNext
cmp byte [eax], 0 ;Would be 0x80
jne .FoundBootPartition
inc cx
cmp cx, 4
je DidntFindMBR
add eax, 16
jmp .CheckNext
.FoundBootPartition
mov [BootPartition], cl ;Store boot partition here...
mov ebx, [eax+8] ;Store into ebx
mov [PartitionLBA], ebx ;Store this here...
DidntFindMBR:
;Lets determine our Data Start now... based on params above :)
xor edx, edx
mov ebx, edx
mov eax, edx
mov ecx, edx
mov cx, [RootEntries]
shr cx, 4 ;Divide by 16 to get # of sectors
mov dl, [FatCount]
mov bx, [ReservedSectors]
mov ax, [FatSize16]
mul dx
add eax, ebx
add eax, ecx
mov [DataStart], eax ;This is our start sector of our kernel :)
;Lets load our second stage :)
mov eax, 1 ;Start of 2nd stage
mov bx, 0x7c00+512 ;Right after boot sector!
mov cx, 1 ;1 sector
call ReadSector
jmp SecondStage ;Go to our second stage loader
BootDisk ;Disk we booted from
db 0
BootPartition ;Partition on disk we booted from (0xFF = no partitions!)
db 0xFF
PartitionLBA ;LBA of boot partition
dd 0
SystemMemory ;Memory in bytes,4gb max
dd 0
ReadSectorFunc
dd 0 ;Which read function to use
ResetDrive:
pusha
.Retry
mov ax, 0
mov dl, [BootDisk] ;Drive [BootDisk]
int 0x13
jc .Retry ;Didn't reset, lets try again
popa
ret
;Convert LBA -> CHS
;Inputs:
; eax - sector
; cx - length
; bx - destination
;Outputs: Standard CHS format
GetCHS:
xor edx, edx ;Zero out edx
div word [SectorsPerTrack]
mov cl, dl ;Sector # ?
inc cl
xor edx, edx
div word [Heads]
mov dh, dl ;Mov dl into dh (dh=head)
mov ch, al ;Mov cylinder into ch
;//??
shl ah, 6
or cl, ah
ret
;eax - sector to read
;bx - dest
ReadSingleSectorFD:
db 0x66
pusha
call GetCHS ;Grab our CHS
.Retry
call ResetDrive ;Get drive ready..
mov dl, [BootDisk] ;Grab our boot disk
mov ax, 0x0201 ;Read function, one sector
int 0x13
jc .Retry
db 0x66
popa
ret
;Floppy disk code
;eax - sector to read
;bx - dest
;cx - length
ReadSectorFD:
db 0x66
pusha
.ReadNext
call ReadSingleSectorFD
dec cx
jz .Done
add bx, 512 ;Next location..
inc eax ;Next LBA
jmp .ReadNext ;Keep reading
.Done
db 0x66
popa
ret
;Hard disk code
;eax - sector to read
;bx - dest
;cx - length
ReadSectorHD:
db 0x66
pusha
mov dl, [BootDisk] ;Use boot disk
add eax, [PartitionLBA] ;Start of partition
mov [_lba], eax ;Store linear block address
mov [_location], bx ;Store location
mov [_length], cx ;Length in sectors
mov ah, 0x42 ;Read function
mov si, _boot_lba ;Location of LBA block
int 0x13
db 0x66
popa
ret
ReadSector:
jmp [ReadSectorFunc] ;Jump to the correct function
_boot_lba
db 0x10
db 0
_length
dw 0 ;Filled in from above!
_location
dw 0x8000 ;4k page bounary
_segment
dw 0
_lba
dd 0 ;Sector to read, 8 bytes total!
dd 0
DataStart dd 0
times 502-($-$$) db 0 ;Last, enough for 4 entries :)
LoadEntries:
times 510-($-$$) db 0
dw 0xaa55
%include '2ndStage.inc'
And lastly is the include file that these both include (2ndStage.inc)
Code: Select all
;Second Stage, completely I/O independant besides a few calls to first stage
[bits 16]
;Our 16-bit functions
A20Loop:
mov cx, 0x2000 ;8k tries!
A20Looper:
in al, 0x64
test al, 2
loopne A20Looper
ret
;Lets setup our new text mode before going to far... (80x50)
SetTextMode:
mov ax, 0x1112
xor bx, bx
int 0x10
mov word [0x60], 0x07 ;cursor to blank!
ret
EnableA20:
;Enable the A20 line
call A20Loop
jnz .EnableA20Done
mov al, 0xd1
out 0x64, al
call A20Loop
jnz .EnableA20Done
mov al, 0xdf
out 0x60, al
call A20Loop
.EnableA20Done
ret
;Print char in al
PrintChar:
push ax
push bx
mov ah, 0x0e
mov bx, 0x07
int 0x10
pop bx
pop ax
ret
PrintHex:
db 0x66
pusha
mov ecx, 8 ;8 values!
push word 13
push word 10
.ValueToStack
mov eax, edx
and eax, 15
shr edx, 4
cmp ax, 10
jl .Number
add al, 'A'-10-'0'
.Number
add al, '0'
push ax
loop .ValueToStack
push word 'x'
push word '0'
mov ecx, 12 ;8 values + CrLf + 0x
.PrintHexNext
pop ax
call PrintChar
loop .PrintHexNext
;This runs once ecx hits 0
db 0x66
popa
ret
;Create a memory mapping of the system!
FindMemory:
mov ax, 0xe801 ;Read system ram :)
xor ecx, ecx
xor edx, edx
int 0x15
shl ecx, 10 ;Multiply by 1024 to get MB's
;Returns 64kb blocks above 16mb into bx
shl edx, 16 ;Multiply by 16 to get MB's
add ecx, edx
cmp ecx, 0 ;Check if we have 0 bytes!
jne .MemGood
;Fallback?
mov ecx, 3*1024*1024 ;Assume 3mb + 1mb below = 4mb!
.MemGood
add ecx, 1024*1024 ;Tack on one extra meg since it only shows 15mb under 16mb!
mov [SystemMemory], ecx
ret
SecondStage:
call EnableA20 ;Enable our A20 line
mov ecx, 4
mov ebx, 0x9000
mov edx, LoadEntries
mov eax, [DataStart] ;Start sector of our kernel
.EntryLoop
push cx
mov cx, [edx] ;Sector count
cmp cx, 0
je .NothingThere
call ReadSector
.NothingThere
push edx
mov edx, eax ;LBA
call PrintHex
mov edx, ecx ;Sector Count
call PrintHex
pop edx
add eax, ecx ;Increment LBA...
shl cx, 9 ;Multiply by 512 to get byte count!
add bx, cx ;Move to next memory address
add edx, 2 ;Next entry
pop cx
loop .EntryLoop
;All 4 sections loaded...
;1 - Kernel
;2 - Memory Manager
;3 - Disk Driver
;4 - File System Driver
call SetTextMode ;Set our new text mode
call FindMemory ;Find the amount of memory we have in the system
;Lets get into 32-bit mode now
;Next thing is get into 32-bit mode
lgdt [gdt_desc]
cli ;Clear interrupts before we switch to 32-bit mode
mov eax, cr0
inc eax
mov cr0, eax
jmp 0x8:Go32 ;Jump to 32-bit code
[bits 32]
Go32:
mov eax, 0x10
mov ds, eax
mov es, eax
mov gs, eax
mov fs, eax
mov ss, eax
mov esp, 0x7000 ;Put stack right before some stuff :)
;Now lets enable paging...
;We're currently at 0x9000, we want to first map the bottom 1mb of memory...
;Mark each of these as completely available, but not loaded yet...
mov edi, Kern_PDE ;Kernel base :)
call SetAllAvailable
mov edi, Kern_PTE ;Kernel space :)
call SetAllAvailable
xor eax, eax ;Virtual Address
mov ebx, eax ;Physical Address
mov ecx, 256 ;Map only first 1mb
mov edx, Tmp_PTE ;Used to map lower 1mb
call MemoryMap ;Map this area :)
mov eax, 0xF0000000 ;Virtual address
mov ebx, 0x7000 ;Kernel address - 4k for functions + 4k for IDT
mov ecx, 64 ;First 256kb memory mapped
mov edx, Kern_PTE ;PTE to map kernel space
call MemoryMap
;Now lets map ourself
mov dword [Kern_PDE+0xDFC], Kern_PDE+3 ;Set it to itself!
mov eax, Kern_PDE
mov cr3, eax
mov eax, cr0
or eax, 0x80000000 ;Enable paging
mov cr0, eax
mov dl, [BootDisk]
mov dh, [BootPartition]
mov eax, [SystemMemory]
jmp 0xF0002000 ;Jump to kernel code at 0xF0000000
;edi = PDE/PTE to set!
SetAllAvailable:
mov eax, 0x200 ;Available, but not loaded
mov ecx, 1024 ;1024*4 = 4k
rep stosd
ret
;Memory map a 4mb region (not present)
;eax - virtual location, must be 4mb aligned
;ebx - physical memory to map, must be 4kb aligned
;ecx - number currently present
;edx - location of PTE
MemoryMap:
pusha
shr eax, 20 ;Divide by 1mb (divide by 4mb and mult index by 4)
add eax, Kern_PDE ;Add base address of PDE
mov [eax], edx ;Set the PDE entry to point to PTE
or byte [eax], 3 ;Set up for supervisor r/w
or ebx, 3 ;Set it up for supervisor : read/write present
.Loop
mov [edx], ebx ;Store the address here
add ebx, 4096 ;Next physical address
add edx, 4 ;Next entry
loop .Loop
popa
ret
Kern_PDE equ 0xC000 ;Location of first PDE
Kern_PTE equ 0xD000 ;First 4mb of kernel
Tmp_PTE equ 0xE000 ;PTE to map bottom 4mb
KernelPages dd 0 ;# of pages used for kernel/base drivers
gdt: ; Address for the GDT
gdt_null: ; Null Segment
dw 0
dw 0
db 0
db 0
db 0
db 0
gdt_code: ; Code segment, read/execute, nonconforming
dw 0xFFFF
dw 0
db 0
db 10011010b
db 11001111b
db 0
gdt_data: ; Data segment, read/write, expand down
dw 0xFFFF
dw 0
db 0
db 10010010b
db 11001111b
db 0
gdt_end: ; Used to calculate the size of the GDT
gdt_desc: ; The GDT descriptor
dw gdt_end - gdt - 1 ; Limit (size)
dd gdt ; Address of the GDT
times 1024-($-$$) db 0
So, that's it, mbr is 512 bytes, boot loader is 1k no matter what boot medium (well, so far). It's still a work in progress, but works fine right now for what I'm doing. I need to do more work on the memory detection stuff, but other than that it is pretty much done.