From 14c0596d40f5b02153ce379ea6060bd82120f4f9 Mon Sep 17 00:00:00 2001 From: Joursoir Date: Tue, 27 Sep 2022 18:59:00 +0300 Subject: move our bootloader to special directory --- arch/x86/boot/Makefile | 39 ------ arch/x86/boot/bios.inc | 20 --- arch/x86/boot/bootloader/Makefile | 39 ++++++ arch/x86/boot/bootloader/bios.inc | 20 +++ arch/x86/boot/bootloader/bootsect.s | 57 ++++++++ arch/x86/boot/bootloader/head.s | 35 +++++ arch/x86/boot/bootloader/linker.ld | 10 ++ arch/x86/boot/bootloader/rm_seg.inc | 14 ++ arch/x86/boot/bootloader/setup.s | 268 ++++++++++++++++++++++++++++++++++++ arch/x86/boot/bootsect.s | 57 -------- arch/x86/boot/head.s | 35 ----- arch/x86/boot/linker.ld | 10 -- arch/x86/boot/rm_seg.inc | 14 -- arch/x86/boot/setup.s | 268 ------------------------------------ 14 files changed, 443 insertions(+), 443 deletions(-) delete mode 100644 arch/x86/boot/Makefile delete mode 100644 arch/x86/boot/bios.inc create mode 100644 arch/x86/boot/bootloader/Makefile create mode 100644 arch/x86/boot/bootloader/bios.inc create mode 100644 arch/x86/boot/bootloader/bootsect.s create mode 100644 arch/x86/boot/bootloader/head.s create mode 100644 arch/x86/boot/bootloader/linker.ld create mode 100644 arch/x86/boot/bootloader/rm_seg.inc create mode 100644 arch/x86/boot/bootloader/setup.s delete mode 100644 arch/x86/boot/bootsect.s delete mode 100644 arch/x86/boot/head.s delete mode 100644 arch/x86/boot/linker.ld delete mode 100644 arch/x86/boot/rm_seg.inc delete mode 100644 arch/x86/boot/setup.s (limited to 'arch/x86/boot') diff --git a/arch/x86/boot/Makefile b/arch/x86/boot/Makefile deleted file mode 100644 index e026e24..0000000 --- a/arch/x86/boot/Makefile +++ /dev/null @@ -1,39 +0,0 @@ -# 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/bios.inc b/arch/x86/boot/bios.inc deleted file mode 100644 index 19cd9a0..0000000 --- a/arch/x86/boot/bios.inc +++ /dev/null @@ -1,20 +0,0 @@ -# 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/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 diff --git a/arch/x86/boot/bootsect.s b/arch/x86/boot/bootsect.s deleted file mode 100644 index a6e274e..0000000 --- a/arch/x86/boot/bootsect.s +++ /dev/null @@ -1,57 +0,0 @@ -/* - 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/head.s b/arch/x86/boot/head.s deleted file mode 100644 index d24e36c..0000000 --- a/arch/x86/boot/head.s +++ /dev/null @@ -1,35 +0,0 @@ -/* - 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/linker.ld b/arch/x86/boot/linker.ld deleted file mode 100644 index 5e8dfd6..0000000 --- a/arch/x86/boot/linker.ld +++ /dev/null @@ -1,10 +0,0 @@ -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/rm_seg.inc b/arch/x86/boot/rm_seg.inc deleted file mode 100644 index f6d4750..0000000 --- a/arch/x86/boot/rm_seg.inc +++ /dev/null @@ -1,14 +0,0 @@ -/* - 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/setup.s b/arch/x86/boot/setup.s deleted file mode 100644 index 2afc974..0000000 --- a/arch/x86/boot/setup.s +++ /dev/null @@ -1,268 +0,0 @@ -/* - 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 -- cgit v1.2.3-18-g5258