summaryrefslogtreecommitdiffstats
path: root/arch/x86/boot/bootloader
diff options
context:
space:
mode:
authorJoursoir <chat@joursoir.net>2022-09-27 18:59:00 +0300
committerJoursoir <chat@joursoir.net>2022-10-04 02:34:12 +0300
commit14c0596d40f5b02153ce379ea6060bd82120f4f9 (patch)
tree58d633b4aa883c7e9a52cbdf6431263a77458eeb /arch/x86/boot/bootloader
parent3955de98fa26ca847041f5fcb50268927b44a208 (diff)
downloadmfsos-14c0596d40f5b02153ce379ea6060bd82120f4f9.tar.gz
mfsos-14c0596d40f5b02153ce379ea6060bd82120f4f9.tar.bz2
mfsos-14c0596d40f5b02153ce379ea6060bd82120f4f9.zip
move our bootloader to special directory
Diffstat (limited to 'arch/x86/boot/bootloader')
-rw-r--r--arch/x86/boot/bootloader/Makefile39
-rw-r--r--arch/x86/boot/bootloader/bios.inc20
-rw-r--r--arch/x86/boot/bootloader/bootsect.s57
-rw-r--r--arch/x86/boot/bootloader/head.s35
-rw-r--r--arch/x86/boot/bootloader/linker.ld10
-rw-r--r--arch/x86/boot/bootloader/rm_seg.inc14
-rw-r--r--arch/x86/boot/bootloader/setup.s268
7 files changed, 443 insertions, 0 deletions
diff --git a/arch/x86/boot/bootloader/Makefile b/arch/x86/boot/bootloader/Makefile
new file mode 100644
index 0000000..e026e24
--- /dev/null
+++ b/arch/x86/boot/bootloader/Makefile
@@ -0,0 +1,39 @@
+# If you want run this makefile immediately, then set environment
+# variables (CC, LD, AS, OBJDUMP) to path of your cross-compiler.
+#
+# The best way to compile $BOOTBIN is to run the corresponding
+# target in the main Makefile, which is in the root of the
+# project (.../path/to/os/Makefile)
+
+BOOTBIN = bootloader.bin
+
+.PHONY: all objdump clean
+
+all: $(BOOTBIN)
+
+$(BOOTBIN): bootsect.bin setup.bin
+ cat $^ > $@
+
+bootsect.bin: bootsect.o
+ $(CC) -Wl,--oformat binary -Ttext 0x7c00 -o $@ \
+ -ffreestanding -nostdlib \
+ $^ -lgcc
+
+setup.bin: setup.o
+ $(CC) -Wl,--oformat binary -Ttext 0x0200 -o $@ \
+ -ffreestanding -nostdlib \
+ $^ -lgcc
+
+%.o: %.s
+ $(AS) $< -o $@
+
+objdump-bootsect:
+ $(OBJDUMP) -D -m i386 -b binary \
+ --adjust-vma=0x7c00 -Maddr16,data16 bootsect.bin
+
+objdump-setup:
+ $(OBJDUMP) -D -m i386 -b binary \
+ --adjust-vma=0x2000 -Maddr16,data16 setup.bin
+
+clean:
+ rm -rf bootsect.o setup.o $(BOOTBIN)
diff --git a/arch/x86/boot/bootloader/bios.inc b/arch/x86/boot/bootloader/bios.inc
new file mode 100644
index 0000000..19cd9a0
--- /dev/null
+++ b/arch/x86/boot/bootloader/bios.inc
@@ -0,0 +1,20 @@
+# CONVENTION: macro only uses %AX, %SI registers
+.macro BIOS_PRINT string
+ mov $0x0e, %ah # Set writing char in TTY mode routine
+ mov \string, %si # Set in Source Index reg the beginning
+ # address of a string
+print_loop\@:
+ lodsb # Increase SI by 1 byte => get next char
+ or %al, %al # Check for a '\0'
+ jz print_done\@
+
+ int $0x10 # Print a char in al register
+ jmp print_loop\@
+print_done\@:
+.endm
+
+.macro PUTCHAR char
+ mov $0x0e, %ah # Set writing char in TTY mode routine
+ mov \char, %al
+ int $0x10 # Print a char in al register
+.endm
diff --git a/arch/x86/boot/bootloader/bootsect.s b/arch/x86/boot/bootloader/bootsect.s
new file mode 100644
index 0000000..a6e274e
--- /dev/null
+++ b/arch/x86/boot/bootloader/bootsect.s
@@ -0,0 +1,57 @@
+/*
+ bootsect.s is loaded at 0x7c00 (BIOS likes always to load the boot
+ sector to this address). It is first stage bootloader, it has one
+ task - loads setup.s (second stage) to 0x90000 and jumps there.
+*/
+
+.code16 # Tell GAS to generate 16 bit code
+
+.include "bios.inc"
+.include "rm_seg.inc"
+
+.global _start # Make the symbol visible to ld
+_start:
+ jmp $0x0, $_start2 # Normalize the start address
+ # CS = 0 and IP = _start2
+_start2:
+ mov $SDATASEG, %ax # We will store the boot drive at
+ mov %ax, %ds # $SDATASEG in the first byte
+ mov $SETUPSEG, %ax # Do it for disk read routine (see below)
+ mov %ax, %es
+
+ cld # Set direction flag for incrementing
+ mov %dl, (0) # BIOS stores our boot drive in DL,
+ # so we remember it
+ mov %cs, %ax # AX = CS = 0 (see above)
+ mov %ax, %ds # Zero data segment register
+
+load_setup:
+ BIOS_PRINT $load_setup_msg # The routine uses only AX register
+
+ mov $0x02, %ah # Set BIOS read sector routine
+ mov $0x00, %ch # Select cylinder 0
+ mov $0x00, %dh # Select head 0 [has a base of 0]
+ mov $0x02, %cl # Select sector 2 (next after the
+ # boot sector) [has a base of 1]
+ mov $SETUPLEN, %al # Read $SETUPLEN sectors
+ mov $0x0, %bx # Load sectors to ES:BX ($SETUPSEG:0)
+ int $0x13 # Start reading from drive
+ jc disk_error # If carry flag set, bios failed to read
+
+ # Make a far jump to our setup code
+ jmp $SETUPSEG, $0x0
+
+disk_error:
+ BIOS_PRINT $disk_error_msg
+ jmp .
+
+load_setup_msg:
+ .asciz "Loading setup sectors\r\n"
+
+disk_error_msg:
+ .asciz "Disk read error!"
+
+# Bootsector padding
+.space 512 - 2 - (. - _start), 0
+boot_flag:
+ .word 0xAA55
diff --git a/arch/x86/boot/bootloader/head.s b/arch/x86/boot/bootloader/head.s
new file mode 100644
index 0000000..d24e36c
--- /dev/null
+++ b/arch/x86/boot/bootloader/head.s
@@ -0,0 +1,35 @@
+/*
+ head.s is loaded at 0x1000 (by second stage), its main goal
+ is run 32-bit startup code.
+
+ After that manipulations it jumps to kernel written by C
+*/
+
+.code32 # Tell GAS to generate 32 bit code
+.extern kernel_main
+
+.set CODESEG, 0x08
+.set DATASEG, 0x10
+
+.global _start
+_start:
+ mov $DATASEG, %ax # Point segment registers to the
+ mov %ax, %ds # data selector we defined in our GDT
+ mov %ax, %es
+ mov %ax, %ss
+ mov %ax, %fs
+ mov %ax, %gs
+
+ mov $0x90000, %ebp # Update stack position so it is right
+ mov %ebp, %esp # at the top of the free space.
+
+check_a20:
+ mov $0x112345, %edi # Odd megabyte address
+ mov $0x012345, %esi # Even megabyte address
+ mov %esi, (%esi) # If A20 line is disabled two pointers
+ mov %edi, (%edi) # would point to the address 0x012345
+ cmpsd # Compare values at addresses
+ je . # If equivalent, loop forever
+
+ call kernel_main
+ jmp . # infinite loop
diff --git a/arch/x86/boot/bootloader/linker.ld b/arch/x86/boot/bootloader/linker.ld
new file mode 100644
index 0000000..5e8dfd6
--- /dev/null
+++ b/arch/x86/boot/bootloader/linker.ld
@@ -0,0 +1,10 @@
+OUTPUT_FORMAT("binary")
+OUTPUT_ARCH(i386)
+ENTRY(_start)
+
+SECTIONS
+{
+ /* BIOS likes always to load the boot sector to the address 0x7c00,
+ where it is sure will not be occupied by important routines. */
+ . = 0x7c00;
+}
diff --git a/arch/x86/boot/bootloader/rm_seg.inc b/arch/x86/boot/bootloader/rm_seg.inc
new file mode 100644
index 0000000..f6d4750
--- /dev/null
+++ b/arch/x86/boot/bootloader/rm_seg.inc
@@ -0,0 +1,14 @@
+/*
+ Real mode segment constants.
+
+ NOTE: Header file only for assembler
+*/
+
+# Addresses of RAM:
+.set BOOTSEG, 0x07c0 # Address of boot sector
+.set SDATASEG, 0x9000 # System data
+.set SETUPLEN, 2 # Number of setup sectors
+.set SETUPSEG, 0x9020 # Setup address
+.set KERNSEG, 0x0100 # Historical load address
+.set KERNSIZE, 0x1000 # Kernel size, interpret as a segment
+.set KERNADDR, KERNSEG * 0x10
diff --git a/arch/x86/boot/bootloader/setup.s b/arch/x86/boot/bootloader/setup.s
new file mode 100644
index 0000000..2afc974
--- /dev/null
+++ b/arch/x86/boot/bootloader/setup.s
@@ -0,0 +1,268 @@
+/*
+ setup.s is second stage bootloader loaded at 0x90200 (by
+ first stage), is responsible for getting the system data
+ from the BIOS.
+
+ System data puts at special place: 0x90000-0x901FF.
+*/
+
+.code16 # Tell GAS to generate 16 bit code
+
+.include "bios.inc"
+.include "rm_seg.inc"
+
+.set ENDSEG, KERNSEG + KERNSIZE # Where to stop loading kernel
+
+# Define some constants for the GDT segment descriptor offsets
+.set CODESEG, gdt_code - gdt_start
+.set DATASEG, gdt_data - gdt_start
+
+# Keyboard Controller commands:
+.set READ_OUTP, 0xD0 # Read Output Port
+.set WRITE_OUTP, 0xD1 # Write Output Port
+
+.global _start # Make the symbol visible to ld
+_start:
+ mov $SDATASEG, %ax
+ mov %ax, %ds
+ mov %ax, %ss
+ mov $KERNSEG, %ax
+ mov %ax, %es
+
+ mov $0xFF00, %bp # Set up the stack at 0x9ff00
+ mov %bp, %sp
+
+ BIOS_PRINT $get_data_msg
+
+ # Get current cursor position
+ mov $0x3, %ah
+ xor %bh, %bh # Page Number = 0
+ int $0x10
+ add $3, %dh # Add number of next BIOS_PRINTs
+ mov %dx, (2) # Save it
+
+ # Get disk drive parameters
+ xor %ax, %ax
+ mov %ax, %es # ES:DI = 0x0000:0x0000 to guard
+ mov %ax, %di # against BIOS bugs
+ mov (0), %dl # Set drive boot
+ mov $0x8, %ah
+ int $0x13
+ jc disk_error
+
+ # Interrupt return:
+ # - CH = low eight bits of maximum cylinder number
+ # - CL = maximum sector number (bits 5-0)
+ # high two bits of maximum cylinder number (bits 7-6)
+ # - DH = maximum head number
+ xor %ch, %ch
+ and $0b00111111, %cl
+ xor %dl, %dl
+ mov %dx, heads
+ mov %cx, sectors
+
+ # TODO: get memory size
+ # TODO: get video infos
+
+load_kernel: # Load our kernel
+ BIOS_PRINT $boot_load_kern_msg
+
+ # Load the system at $KERNSEG address:
+ mov $KERNSEG, %ax
+ mov %ax, %es # ES - starting address segment
+ xor %bx, %bx # BX is offset within segment
+
+ # A few words about the algorithm:
+ # We read 0x10000 bytes (64 kB) and overflow BX (16 bytes register),
+ # then add 0x1000 to ES reg, after that compare with $ENDSEG
+ #
+ # If KERNSIZE != 0x10000 * N we read some unnecessary data, but
+ # i think it's not a problem
+repeat_read:
+ mov %es, %ax
+ cmp $ENDSEG, %ax
+ jae enable_a20 # Jump if AX >= $ENDSEG
+get_sects_for_read:
+ mov sectors, %ax # AX = amount of sectors - current sector
+ sub csect, %ax # AX has 6 significant bytes
+ mov %ax, %cx # Calculate how many bytes we get by
+ # reading AX sectors
+ shl $9, %cx # One sector = 2^9 = 512
+ add %bx, %cx # CX = 0@@@.@@@0.0000.0000 + BX
+ jnc read_sects # if not overflow, then jump
+ jz read_sects # if CX = 0, then jump
+ xor %ax, %ax # AX = 0
+ sub %bx, %ax # AX = amount of sectors that we must
+ shr $9, %ax # read for overflow BX
+read_sects:
+ call read_track # INPUT: AX
+ mov %ax, %cx # CX = amount of sectors that we read
+ add csect, %ax
+ cmp sectors, %ax # Current sector = amount of sectors?
+ jne check_read # If not equal, jump
+ mov chead, %ax
+ cmp heads, %ax # Current head = amount of heads?
+ jne inc_chead # If not equal, jump
+ movw $0xffff, chead # Current head will overflow and equal 0
+ # after INC instuction in inc_chead
+ incw ctrack # Go to next cylinder
+ # We don't check cylinder overflow
+ # because it makes no sense
+inc_chead:
+ incw chead
+ xor %ax, %ax
+check_read:
+ mov %ax, csect # Calculate how many bytes we get by
+ shl $9, %cx # reading AX sectors
+ add %cx, %bx # Add it to BX
+ jnc repeat_read # If BX not overflow, jjmp
+ mov %es, %ax
+ add $0x1000, %ax # We read 0x10000 = 65536 bytes
+ mov %ax, %es
+ xor %bx, %bx
+ jmp repeat_read
+
+# INPUT:
+# AX - amount of sectors that we want to read
+read_track:
+ push %ax
+ push %bx
+ push %cx
+ push %dx
+ mov ctrack, %dx
+ mov csect, %cx # Set sector
+ inc %cx # Add +1 because sector has a base of 1
+ mov %dl, %ch # Set cylinder
+ mov chead, %dh # Set head
+ mov %dl, %dh
+ mov (0), %dl # Set boot
+ mov $0x02, %ah # Set BIOS read sector routine
+ int $0x13
+ jc . # Error :(
+ pop %dx
+ pop %cx
+ pop %bx
+ pop %ax
+ ret
+
+enable_a20:
+ BIOS_PRINT $enable_a20_msg
+
+ cli # Switch of interrupt until we have set
+ # up the protected mode interrupt vector
+
+ call wait_input
+ mov $READ_OUTP, %al
+ out %al, $0x64
+ call wait_output
+
+ in $0x60, %al # Read input buffer and store on stack
+ push %ax
+ call wait_input
+
+ mov $WRITE_OUTP, %al
+ out %al, $0x64
+ call wait_input
+
+ pop %ax # Pop the output port data from stack
+ or $2, %al # Set bit 1 (A20) to enable
+ out %al, $0x60 # Write the data to the output port
+ call wait_input
+
+switch_to_pm:
+ BIOS_PRINT $boot_prot_mode_msg
+ lgdt gdt_descriptor # Load our global descriptor table
+
+ mov %cr0, %eax # Set the first bit of CR0
+ or $0x01, %eax # to make the switch to protected mode
+ mov %eax, %cr0
+
+ # Make a far jump to our 32-bit code.
+ # This also forces the CPU to flush its cache of pre-fetched
+ # and real-mode decoded instructions, which can cause problems
+ jmp $CODESEG, $KERNADDR
+
+disk_error:
+ BIOS_PRINT $disk_error_msg
+ jmp .
+
+wait_input:
+ in $0x64, %al # Read status
+ test $2, %al # Is input buffer full?
+ jnz wait_input # yes - continue waiting
+ ret
+
+wait_output:
+ in $0x64, %al
+ test $1, %al # Is output buffer full?
+ jz wait_output # no - continue waiting
+ ret
+
+# Global Descriptor Table (contains 8-byte entries)
+gdt_start:
+gdt_null: # The mandatory null descriptor
+ .quad 0x0
+
+gdt_code: # The code segment descriptor
+ # Base = 0x0, limit = 0xfffff
+ # 1st flags: (present)1 (privilege)00 (descriptor type)1 -> b1001
+ # Type flags: (code)1 (conforming)0 (readable)1 (accessed)0 -> b1010
+ # 2nd flags: (granularity)1 (size)1 (64-bit seg)0 (AVL)0 -> b1100
+ .word 0xffff # Limit (bits 0-15)
+ .word 0x0 # Base (bits 0-15)
+ .byte 0x0 # Base (bits 16-23)
+ .byte 0b10011010 # 1st flags, type flags
+ .byte 0b11001111 # 2nd flags, limit (bits 16-19)
+ .byte 0x0 # Base (bits 24-31)
+
+gdt_data: # the data segment descriptor
+ # Same as code segment except for the type flags:
+ # Type flags: (code)0 (direction)0 (writable)1, (accessed)0 -> b0010
+ # P.S: direction bit: 0 the segment grows up
+ .word 0xffff # Limit (bits 0-15)
+ .word 0x0 # Base (bits 0-15)
+ .byte 0x0 # Base (bits 16-23)
+ .byte 0b10010010 # 1st flags, type flags
+ .byte 0b11001111 # 2nd flags, limit (bits 16-19)
+ .byte 0x0 # Base (bits 24-31)
+gdt_end:
+
+# Global variables
+
+# Total amount of HDD components:
+heads: # 8 significant bytes
+ .word 0x0
+sectors: # 6 significant bytes
+ .word 0x0
+
+# The number of the current component with which we interact:
+ctrack: # track/cylinder
+ .word 0x0
+chead:
+ .word 0x0
+csect:
+ .word 1 + SETUPLEN
+
+gdt_descriptor:
+ # The 6-byte GDT structure containing:
+ # - GDT size, 2 bytes (size always less one of the real size):
+ .word gdt_end - gdt_start - 1
+ # - GDT address, 4 bytes:
+ .word gdt_start, 0x9
+
+get_data_msg:
+ .asciz "Getting the system data from the BIOS\r\n"
+
+boot_prot_mode_msg:
+ .asciz "Entering 32-bit protected mode\r\n"
+
+boot_load_kern_msg:
+ .asciz "Loading kernel into memory\r\n"
+
+enable_a20_msg:
+ .asciz "Enabling A20 line\r\n"
+
+disk_error_msg:
+ .asciz "Disk read error!"
+
+.space (512 * SETUPLEN) - (. - _start), 0