diff options
Diffstat (limited to 'arch/x86/boot/bootloader')
-rw-r--r-- | arch/x86/boot/bootloader/Makefile | 39 | ||||
-rw-r--r-- | arch/x86/boot/bootloader/bios.inc | 20 | ||||
-rw-r--r-- | arch/x86/boot/bootloader/bootsect.s | 57 | ||||
-rw-r--r-- | arch/x86/boot/bootloader/head.s | 35 | ||||
-rw-r--r-- | arch/x86/boot/bootloader/linker.ld | 10 | ||||
-rw-r--r-- | arch/x86/boot/bootloader/rm_seg.inc | 14 | ||||
-rw-r--r-- | arch/x86/boot/bootloader/setup.s | 268 |
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 |