Code: Select all
ENTRY(init1)
OUTPUT_FORMAT("binary")
OUTPUT_ARCH(i386)
SECTIONS {
. = 0x7e00;
.text :
{
*(.text)
}
.rodata :
{
*(.rodata)
}
.data :
{
*(.data)
}
.bss :
{
*(.bss)
}
}
Code: Select all
.code16 # Produce 16-Bit Code (For Real Mode).
.section .text # Text section.
.global init0 # Make our function globally available.
.global boot0 # Make our function globally available.
.section .text
init0: # init0 function
ljmp $0, $boot0 # [CS : IP] We long jump to CS=0x0 and IP=boot0,
# where our bootloader starts. That way, we don't assume
# segments which, on some computers can result in strange errors,
# as BIOSes might set CS=0x7C00 and IP=0x0000 [ 0x7C00 = 0x0000 ]
# and cause undefined behaviours on our code.
# Starting our code with a jump instruction, also
# makes it compatible with very old Compaq computers
# that instead of searching for 0x55AA MBR signature
# at the end of the first sector of the bootable media,
# looks for a jump instruction on the first bytes of it.
boot0: # boot0 function
# Note: We'll be avoiding FS and GS registers in order to
# make our bootloader as compatible as we can until we
# reach 32-bits, where they are used and that, makes them uncompatible
# with pre-80386+ processors.
# Thoose 2 registers were added to 80386+ CPU's.
xor %ax, %ax # Xor'ing ax to ax, results in a 0, as xor'ing two registers with
# the same value always results in 0.
mov %ax, %ds # Move 0x0 to the data segment register.
# We null the data segment, because if we don't, it can overlap
# with the code segment.
mov %ax, %es # Null extra segment too.
mov $0x7C00, %bx # Move 0x7C00 to bx.
cli # Disable interrupts to circumvent a bug present on
# early 8088 CPU's.
mov %ax, %ss # Move ax (Which now is 0) to the stack segment register.
# registers that contain the same, will always result in 0.
mov %bx, %sp # Set the stack pointer register with 0x7C00.
# Stack Dissasembly:
# Top stack adress: -> 0x7C00
mov %sp, %bp # Move sp (Which now is 0x7C00) to the base pointer register.
sti # Re-enable interrupts
cld # CLD sets EFLAG's register DF flag to 0, this way, string
# operations increment the segment registers (Such as SI & DI).
# Invoking a CLD instruction, also makes us aware of the DF's flag
# contents, in order to make 0 assumptions on how the BIOS leaves this value.
mov $0x3, %ax # Use VGA Text Mode
int $0x10 # Call BIOS interrupt 13.
call .boot0Loaded # Call a function that displays a message on-screen.
mov %dl, boot0bootdrive # Store BIOS dl value containing the boot drive number.
boot0ResetDrive: # Function to reset floppy drive in order to ensure it's working correctly.
mov $0x00, %ah # Set ah to 0 (AH=0x00 -> Reset Disk Function).
mov boot0bootdrive, %dl # Move boot0bootdrive value back into dl.
int $0x13 # Call BIOS interrupt 13.
jc boot0ResetDrive # If Carry Flag is set (CF=1) an error has ocurred, run the function again.
# On this part we'll load our stage 2 bootloader onto 0x1000 and jump to it.
push %dx
mov boot0bootdrive, %dl # Move boot0bootdrive value back into dl in case INT13 messes with it.
mov $0x02, %ah # Set ah to 2 (AH=0x02 -> Disk Read Function).
mov $0x14, %al # Set al to 14 (AL=0x14) -> Sectors to be readed.
mov $0x00, %ch # Set ch to 0 (CH=0x00) -> Track 0 of the drive.
mov $0x02, %cl # Set cl to 2 (CL=0x02) -> Sector of the drive from which we start reading from.
mov $0x00, %dh # Set dh to 0 (DH=0x00) -> Head 0 of the drive.
xor %bx, %bx # Set our bx register with the adress we'll jump to.
mov %bx, %es # Move bx onto our extra segment register to prepare the jump.
mov $0x7e00, %bx # Set bx to 0.
int $0x13 # Call BIOS interrupt 13.
jc boot0diskerror
ljmp $0x0000, $0x7e00 # Long jump into our stage 2 bootloader. [0x0000:0x7e00] -> CS=0x0000; IP=0x7e00
cli
.hlt:
hlt
jmp .hlt
boot0print:
pusha
.boot0printchar:
mov (%bx), %al
cmp $0x0, %al
je .boot0printdone
mov $0x0E, %ah
int $0x10
add $0x1, %bx
jmp .boot0printchar
.boot0printdone:
popa
ret
boot0printnl:
pusha
mov $0x0E, %ah
mov $0x0A, %al
int $0x10
mov $0x0D, %al
int $0x10
popa
ret
.boot0Loaded:
mov $boot0LoadedMessage, %bx
call boot0print
call boot0printnl
ret
boot0diskerror:
mov $boot0DiskErrorMessage, %bx
call boot0print
call boot0printnl
mov %ah, %dh # TODO: Print in hex what kind of error we've got.
jmp .hlt
boot0bootdrive: .byte 0
boot0LoadedMessage: .asciz "Entered 1st Stage"
boot0DiskErrorMessage: .asciz "Disk Read Error!"
.fill 510-(.-init0), 1, 0 # Preprocessor directive from GNU as that fills 'x'
# count of bytes, with a 'y' size that has 'z' value.
# args: count, size, value
.word 0xAA55 # BIOS 2-byte magic number that enables the proper
# booting of this bootloader
Code: Select all
# Thanks to Octocontrabass, Combuster and MichaelPetch for the help @ forum.osdev.org
.code16
.global init1
.set BOOT1CODESEGMENT, 0x0000
.set BOOT1PMSTACK, 0x9c000
.set BOOT1PMVMEM, 0xb8000
.set BOOT1PMVMEMC, 0x0f
.section .data
gdt_start:
gdt_null:
.long 0
.long 0
gdt_code:
.word 0xffff
.word 0x0
.byte 0x0
.byte 0b10011010
.byte 0b11001111
.byte 0x0
gdt_data:
.word 0xffff
.word 0x0
.byte 0x0
.byte 0b10010010
.byte 0b11001111
.byte 0x0
gdt_end:
.set CODE_SEG, gdt_code - gdt_start
.set DATA_SEG, gdt_data - gdt_start
gdt:
.word (gdt_end - gdt_start - 1)
.long BOOT1CODESEGMENT << 4 + gdt_start
.boot1A20HasAlreadyBeenEnabled: .asciz "A20 Line has already been enabled"
.boot1A20HasBeenEnabled: .asciz "A20 Line Enabled"
.boot1LoadedMessage: .asciz "Entered 2nd Stage"
.boot1LoadedGDTMessage: .asciz "GDT Loaded"
.boot1LoadedPMMessage: .asciz "Entered Protected Mode"
.section .text
init1:
mov $BOOT1CODESEGMENT, %ax
mov %ax, %ds
call boot1LoadedMessage
call boot1EnableA20
call boot1LoadGDT
cli
mov %cr0, %eax
or $1, %eax
mov %eax, %cr0
cli
ljmpl $CODE_SEG, $(BOOT1CODESEGMENT << 4 + boot1Start32)
.hlt:
hlt
jmp .hlt
boot1EnableA20:
call boot1CheckA20LineState # Check if A20 Line is enabled.
jnz boot1A20HasAlreadyBeenEnabled # Jump if condition is met.
movw $0x2401, %ax # Enable A20 Line using the BIOS Method.
stc # Set carry flag.
int $0x15 # Call BIOS interrupt 15 (Enable A20 Line).
jc 1f # BIOS Method failed.
testb %ah, %ah # Compares both registers.
jne 1f # Jumps if they're not equal.
call boot1CheckA20LineState # Check if A20 Line is enabled.
jnz boot1A20HasBeenEnabled # Jump if condition is met.
1: # Enable A20 Line using Intel's 8042 Controller Method.
call .boot1_8042_wait # Wait for Intel's 8042 controller to be ready.
movb $0xd1, %al # Prepare the 8042 port write.
outb %al, $0x64 # Write to the 8042 port.
call .boot1_8042_wait # Wait for Intel's 8042 controller to be ready.
movb $0xdf, %al # Prepare the 8042 port write.
outb %al, $0x60 # Enable A20 Line.
call .boot1_8042_wait # Wait for Intel's 8042 controller to be ready.
call boot1CheckA20LineState # Check if A20 Line is enabled.
jnz boot1A20HasBeenEnabled # Jump if condition is met.
# Enable A20 Line using the 'Fast' Method.
inb $0x92, %al # Try the computer's Fast A20 Gate.
testb $0x02, %al # Compare both values.
jnz 1f # Don't enable A20 if it's already set.
orb $0x02, %al # Check wether the A20 Gate Enable Bit...
andb $0xfe, %al # ...is set or not.
outb %al, $0x92 # Enable the A20 Line using the Fast Gate.
1:
call boot1CheckA20LineState # Check if A20 Line is enabled.
jnz boot1A20HasBeenEnabled # Jump if condition is met.
jmp 1b # Check until A20 Line is set.
.boot1_8042_wait: # Function that waits for Intel's 8042 controller to be ready.
inb $0x64, %al # Read 8042's status.
testb $0x02, %al # Test if bit 1 is zero.
jnz .boot1_8042_wait # Jump if condition is met.
ret # Return to parent function.
boot1CheckA20LineState:
pushw %ds
pushw %es
xorw %ax, %ax
movw %ax, %ds
movw $0x200, %si
decw %ax
movw %ax, %es
movw $0x210, %di
movw %ax, %cx
movw (%si), %ax
pushw %ax
1:
incw %ax
movw %ax, %es:(%di)
cmpw %ax, (%si)
loope 1b
popw (%si)
popw %es
popw %ds
ret
boot1LoadGDT:
cli
pusha
lgdt gdt
sti
popa
call boot1LoadedGDTMessage
ret
boot1A20HasBeenEnabled:
mov $.boot1A20HasBeenEnabled, %bx
call boot1print
call boot1printnl
ret
boot1A20HasAlreadyBeenEnabled:
mov $.boot1A20HasAlreadyBeenEnabled, %bx
call boot1print
call boot1printnl
ret
boot1LoadedMessage:
mov $.boot1LoadedMessage, %bx
call boot1print
call boot1printnl
ret
boot1LoadedGDTMessage:
mov $.boot1LoadedGDTMessage, %bx
call boot1print
call boot1printnl
ret
boot1print:
pusha
.boot1printchar:
mov (%bx), %al
cmp $0x0, %al
je .boot1printdone
mov $0x0E, %ah
int $0x10
add $0x1, %bx
jmp .boot1printchar
.boot1printdone:
popa
ret
boot1printnl:
pusha
mov $0x0E, %ah
mov $0x0A, %al
int $0x10
mov $0x0D, %al
int $0x10
popa
ret
.code32
boot1Start32:
mov $DATA_SEG, %eax
mov %eax, %ds
mov %eax, %es
mov %eax, %fs
mov %eax, %gs
mov %eax, %ss
mov $BOOT1PMSTACK, %esp # Set the stack pointer. I place it below the EBDA
mov $.boot1LoadedPMMessage, %ebx
call boot1pmprintstring
.end_loop:
hlt
jmp .end_loop
boot1pmprintstring:
pusha
mov $BOOT1PMVMEM, %edx
.boot1pmprintstringloop:
mov (%ebx), %al
mov $BOOT1PMVMEMC, %ah
cmp $0x0, %al
je .boot1pmprintstringdone
mov %ax, (%edx)
add $0x1, %ebx
add $0x2, %edx
jmp .boot1pmprintstringloop
.boot1pmprintstringdone:
popa
ret