From 7678019e03f8f904fb72b55e54cca7bb8d970704 Mon Sep 17 00:00:00 2001 From: Edward Dean Date: Wed, 27 Mar 2019 21:33:59 +0000 Subject: [PATCH] Feature/bootloader (#2), closes #1 * Added bootloader files * Changed to spaces from tabs --- .gitignore | 4 + src/bootloader/2ndstage.asm | 153 +++++++++++++++++ src/bootloader/32bit_functions.asm | 102 +++++++++++ src/bootloader/Makefile | 29 ++++ src/bootloader/bootloader.asm | 109 ++++++++++++ src/bootloader/descriptors_macros.asm | 47 ++++++ src/bootloader/enabling_a20.asm | 161 ++++++++++++++++++ src/bootloader/fat_descripter.asm | 30 ++++ src/bootloader/functions.asm | 103 +++++++++++ src/bootloader/macros.asm | 235 ++++++++++++++++++++++++++ src/bootloader/memory.asm | 117 +++++++++++++ 11 files changed, 1090 insertions(+) create mode 100644 src/bootloader/2ndstage.asm create mode 100644 src/bootloader/32bit_functions.asm create mode 100644 src/bootloader/Makefile create mode 100644 src/bootloader/bootloader.asm create mode 100644 src/bootloader/descriptors_macros.asm create mode 100644 src/bootloader/enabling_a20.asm create mode 100644 src/bootloader/fat_descripter.asm create mode 100644 src/bootloader/functions.asm create mode 100644 src/bootloader/macros.asm create mode 100644 src/bootloader/memory.asm diff --git a/.gitignore b/.gitignore index 1cea5de..18e9d69 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,7 @@ # Images *.iso +*.bin + +# Intellij +.idea/ \ No newline at end of file diff --git a/src/bootloader/2ndstage.asm b/src/bootloader/2ndstage.asm new file mode 100644 index 0000000..e98b811 --- /dev/null +++ b/src/bootloader/2ndstage.asm @@ -0,0 +1,153 @@ +; ------------------------------------------------------------ +; Stage 2 of the bootloader +; ------------------------------------------------------------ +%define boot_sector_location (0x7C03) ; The location of the boot sector +%define fat_segment (0x0050) ; The memory location to load the FAT into memory +%define stage_2_location (0x7E00) ; The location of the second stage bootloader + +%define kernel_stack (0x9FBFF) ; The location of the start of the bottom of the stack + +%define kernel_load_segment (0x3000) ; The segment when the kernel is loaded by the bootloader before A20 is enabled so can access above 1MB +%define kernel_load_location (0x30000) ; The location when the kernel is loaded by the bootloader before A20 is enabled so can access above 1MB +%define kernel_target_location (0x100000) ; The target location for the kernel to be loaded. Above 1MB. + +%define memory_map_location (0x20000) ; This is where the memory map is loaded into. At bottom of 2nd stage bootloader +%define memory_map_segment (0x02000) + +%define boot_params_location (0x7000) ; The location where the boot parameters are save to for the kernel +%define SIGNATURE (0x8A3C) ; The signature of the parameters to test for valid parameters + + [bits 16] ; Tell the assembler that this is a 16bit program not 32bit + [org stage_2_location] + + jmp stage_2_bootload_start + +times (3 - ($ - $$)) db 0 + +boot_sector: +%include 'fat_descripter.asm' + +; Macros to make code more readable. This doesn't take up memory here as they are copied where they are used + +%include 'macros.asm' +%include 'descriptors_macros.asm' +%include 'enabling_a20.asm' +%include 'memory.asm' + +stage_2_bootload_start: + cli + xor ax, ax + mov ds, ax + mov es, ax + sti + + ; Copy the boot sector + m_copy_boot_sector + + ; Find the kernel file + m_find_file kernel_filename, kernel_load_segment + + ; Read the kernel into the load segment + m_read_file kernel_load_segment, fat_segment + + ; Save The size read, stored in BX, may change to number of sectors as kernel size gets bigger + ; kernel_size is in bytes + mov word [kernel_size], bx + mov word [boot_parameters.kernel_len], bx + + ; Reset the disk + m_reset_disk + + ; Write the loading message for the second stage + m_write_line loading_msg + + ; Save the the boot parameters so the kernel can access them + m_save_cursor + + ; Enable the a20 line + m_enable_a20 + + ; Read the size of the memory and store at 'boot_info' + m_get_memory_size + + ; Set up and write into memory the interrupt descriptor table + m_setup_idt + + ; Set up and write into memory the global descriptor table + m_setup_gdt + + ; Load the tables + m_load_gdt_and_idt + + ; Enable protected mode + m_enable_protected + + jmp 0x08:stage_2_bootloader_32 ; Set CS to the code segment of the kernel in the GDT + +; If there is a floppy disk error or a boot error, the call this function +boot_error: + m_reboot_with_msg disk_error_msg ; Print the disk error message + ; Reboot + +%include 'functions.asm' + +idt_descriptor: + dw 0x0000 ; 256 entries of 8 bytes for the interrupt table + dd 0x0000 ; The location of the table, at 0x0000:0x0000 + +gdt_descriptor: + dw 0x0017 ; 3 tables of 8 bytes total (minus 1) + dd 0x0800 ; The location of the 3 tables, at 0x0000:0x0800, just bellow the IDT + +kernel_filename db "KERNEL BIN", 0 +disk_error_msg db "Disk error", 0 +loading_msg db "Loading: 2nd stage bootloader", 0 +reboot_msg db "Press any key to reboot", 0 +a20_error db "a20 line not initialised", 0 + +loading_kernel db "Loading: Kernel", 0x0A, 0 + +memory_map_error db "Error getting memory map from BIOS INT 0x15 0xE820", 0 + +; Data storage +root_sectors db 0,0 +root_start db 0,0 +file_start db 0,0 + +kernel_size db 0,0 + +; Now in 32bit mode + [bits 32] + +stage_2_bootloader_32: + m_set_up_segments + + lea esi, [loading_kernel] + call print_string_32 + + ; Move kernel to target location + mov esi, kernel_load_location + mov edi, kernel_target_location + xor ecx, ecx ; Zero out ECX for the kernel size + mov cx, word [kernel_size] + shr cx, 2 ; Divide by 4 as now copying 4 bytes at a time + cld + rep movsd + + jmp kernel_target_location ; Jump to the kernel, shouldn't return + +%include '32bit_functions.asm' + +times (3 * 512) - ($ - $$) db 0 + + [absolute boot_params_location] + +boot_parameters: + .signature resw 1 + .cursor_pos_x resb 1 + .cursor_pos_y resb 1 + .memory_lower resw 1 + .memory_upper resw 1 + .memory_map_address resd 1 + .memory_map_length resw 1 + .kernel_len resd 1 diff --git a/src/bootloader/32bit_functions.asm b/src/bootloader/32bit_functions.asm new file mode 100644 index 0000000..2387db0 --- /dev/null +++ b/src/bootloader/32bit_functions.asm @@ -0,0 +1,102 @@ + [bits 32] + +%define VIDMEM 0xB8000 ; Video memory mapped +%define COLUMNS 80 ; Height of screen +%define ROWS 25 ; Width of screen +%define CHAR_ATTRIB 0x02 ; Character attribute + +; Prints a character +; Input: +; BL - The character to print +print_char_32: + pusha + mov edi, VIDMEM ; Let EDI point to the start of video memory + xor eax, eax ; Clear EAX + + mov ecx, COLUMNS + mov al, byte [boot_parameters.cursor_pos_y] ; Get the y position + mul ecx ; Multiply by the number of columns (EAX = y * COLUMNS) + + xor ecx, ecx + mov cl, byte [boot_parameters.cursor_pos_x] ; Get the x position + add eax, ecx ; Add the x position to (y * COLUMS) + shl eax, 1 ; Multiply by 2 as 2 bytes per character + + add edi, eax ; Add the offset to the video memory address + + cmp bl, 0x0A ; Is it a new line + je short .new_line + + mov dl, bl ; Get the character to print + mov dh, CHAR_ATTRIB ; Add the character attribute + mov word [edi], dx ; Write to the video memory + + inc byte [boot_parameters.cursor_pos_x] ; Increment the x position + jmp short .print_done + +.new_line: + mov byte [boot_parameters.cursor_pos_x], 0 ; Set cursor to beginning of line + inc byte [boot_parameters.cursor_pos_y] ; Increment new line + +.print_done: + popa + ret + +; Print a null terminated string to the screen +; Input: +; ESI - Pointer to the string to print +print_string_32: + pusha + +.print_loop: + mov bl, byte [esi] ; Get the character to print + cmp bl, 0 ; Is it null + je short .print_end ; Then finish + + call print_char_32 ; Print the character + + inc esi ; Increment to next character + jmp short .print_loop + +.print_end: + mov bh, byte [boot_parameters.cursor_pos_y] + mov bl, byte [boot_parameters.cursor_pos_x] + call update_cursor ; Update the cursor + + popa + ret + +; Update the cursors new position +; Input: +; BH - y position +; BL - x position +update_cursor: + pusha + + xor eax, eax + mov ecx, COLUMNS + mov al, bh + mul ecx + add al, bl + mov ebx, eax + + mov al, 0x0f + mov dx, 0x03D4 + out dx, al + + mov al, bl + mov dx, 0x03D5 + out dx, al + + xor eax, eax + + mov al, 0x0e + mov dx, 0x03D4 + out dx, al + + mov al, bh + mov dx, 0x03D5 + out dx, al + + popa + ret diff --git a/src/bootloader/Makefile b/src/bootloader/Makefile new file mode 100644 index 0000000..340c427 --- /dev/null +++ b/src/bootloader/Makefile @@ -0,0 +1,29 @@ +BIN = ../../bin/boot + +INCLUDES = ./ + +1stSTAGE_SOURCE = bootloader.asm +1stSTAGE_BINARY = bootloader.bin + +2ndSTAGE_SOURCE = 2ndstage.asm +2ndSTAGE_BINARY = 2ndstage.bin + +ASM = nasm +ASMFLAGS = -f bin + +all: clean $(BIN)/$(2ndSTAGE_BINARY) $(BIN)/$(1stSTAGE_BINARY) | $(BIN) + +$(BIN): + mkdir -p $(BIN) + +$(BIN)/$(2ndSTAGE_BINARY): $(2ndSTAGE_SOURCE) | $(BIN) + @echo "\nMaking 2nd stage bootloader\n" + $(ASM) $< -o $@ $(ASMFLAGS) + +$(BIN)/$(1stSTAGE_BINARY): $(1stSTAGE_SOURCE) | $(BIN) + @echo "\nMaking bootloader\n" + $(ASM) $< -o $@ $(ASMFLAGS) + +clean: + @echo "\nCleanning the workspace\n" + rm -f $(BIN)/*.bin diff --git a/src/bootloader/bootloader.asm b/src/bootloader/bootloader.asm new file mode 100644 index 0000000..56ff5fb --- /dev/null +++ b/src/bootloader/bootloader.asm @@ -0,0 +1,109 @@ +; --------------------------------------------------------------------- +; Here is some information for you so can understand the whole thing :) +; Using a 1440KB 3.5" Floppy for the bootloader +; --------------------------------------------------------------------- + +%define boot0_location (0x7C00) ; The location that BOOT0 is load to by the BIOS +%define boot_signature (0xAA55) ; The boot signature that is needed at the end of the 512 bytes so that it is recognized as a boot device. +%define fat_segment (0x0050) ; The memory location to load the FAT into memory +;%define stage_2_load_segment (0x0200) ; The location of the second stage bootloader +%define stage_2_load_segment (0x07E0) + + [bits 16] ; Tell the assembler that this is a 16bit program not 32bit + [org boot0_location] ; As the bootloader is loaded at 0x7C00, all addressing will be relative to this location + + ; Will need to jump over the FAT16 header. 3 Bytes are allowed before the header + ; so can only use a relative/short jump. + jmp short bootloader_start + +; Need to pad to a total of 3 bytes so far. +; This is so to comply with the FAT16 header. +; We could use a NOP instruction as the previous instruction (JMP) is 2 bytes and NOP is 1 byte +; See https://www.win.tue.nl/~aeb/linux/fs/fat/fat-1.html +; Bytes 0-2 +times (3 - ($ - $$)) db 0 + +%include 'fat_descripter.asm' + +; Bytes 62-509 + +%include 'macros.asm' + +bootloader_start: + ; The BIOS can load us (the bootloader) at address 0x000:7C00 or 0x7C00:0000 + ; This is in fact the same address + ; So we need to normalise this by using a long jump + jmp long 0x0000:start_boot0_16bit + +start_boot0_16bit: + ; ------------------------------------------------------------------------ + ; Set up the memory segment for accessing memory the old way. + ; ------------------------------------------------------------------------ + + cli ; Disable interrupts so not mess up the declarations of the segments + + mov byte [Logical_drive_number], dl ; Save what drive we booted from (should be 0x00) into our boot parameter block above + + mov ax, cs ; Set all the sectors to start to begin with. Can get this from the code segment. Should be 0x00 + mov ds, ax ; Set the data segment at the beginning of the bootloader location + mov es, ax ; Set the extra1 segment at the beginning of the bootloader location + mov ss, ax ; Set the stack segment at the beginning of the bootloader location + + mov sp, boot0_location ; Set the stack pointer to the bootloader and grows down to 0x0. + + sti ; Enable interrupts + + ; ------------------------------------------------------------------------ + ; Finished setting up the memory segment for accessing memory the old way. + ; ------------------------------------------------------------------------ + + ; Reset the floppy disk + ; Now need to reset the floppy drive so that we can get information from it + m_reset_disk + + ; Print the loading message + m_write_line loading_msg + + ; Find the 2ndstage bootloader in the root directory + m_find_file stage_2_filename, stage_2_load_segment + + ; Load the FAT table into memory + m_read_fat fat_segment + + ; Read the 2ndstage bootloader into memory + m_read_file stage_2_load_segment, fat_segment + + ; ------------------------------------------------------------------------ + ; Start the second stage bootloader + ; ------------------------------------------------------------------------ + + ; Jump to second stage start of code: + jmp long stage_2_load_segment:0000h + + +; If there is a floppy disk error or a boot error, the call this function +boot_error: + m_reboot_with_msg disk_error_msg ; Print the disk error message + ; Reboot + +; Include the functions that can be called +%include 'functions.asm' + +; Messages +disk_error_msg db "Disk error", 0 +loading_msg db "Loading: 1st stage bootloader", 0 +reboot_msg db "Press any key to reboot", 0 + +; Stage 2 bootloader file name to find +stage_2_filename db "2NDSTAGEBIN", 0 + +; Data storage +root_sectors db 0,0 +root_start db 0,0 +file_start db 0,0 + +; Pad the rest of the file with zeros +; Bytes 510-511 +times 510 - ($ - $$) db 0 +; Add the boot signature at the end +dw boot_signature diff --git a/src/bootloader/descriptors_macros.asm b/src/bootloader/descriptors_macros.asm new file mode 100644 index 0000000..20f1504 --- /dev/null +++ b/src/bootloader/descriptors_macros.asm @@ -0,0 +1,47 @@ +; Set up the interrupt descriptor table +; This is to be stored at 0x0000:0x0000 +%macro m_setup_idt 0 + push es + push di + xor ax, ax + mov es, ax + mov di, 0x0000 + mov cx, 2048 ; Write 2048 byte of zero so now into the IDT + cld + rep stosb +%endmacro + +; Set up the global descriptor table +; This is to be stored at 0x0000:0x0800 +%macro m_setup_gdt 0 + + ; NULL Descriptor: + mov cx, 4 ; Write the NULL descriptor, + rep stosw ; which is 4 zero-words. + + ; Code segment descriptor: + mov [es:di], word 0xFFFF ; limit = 0xFFFF (since granularity bit is set, this is 4 GB) + mov [es:di + 2], word 0x0000 ; base = 0x0000 + mov [es:di + 4], byte 0x00 ; base + mov [es:di + 5], byte 0x9A ; access = 1001 1010; segment present, ring 0, S=code/data, type=0xA (code, execute/read) + mov [es:di + 6], byte 0xCF ; granularity = 1100 1111; limit = 0xf, AVL=0, L=0, 32bit, G=1 + mov [es:di + 7], byte 0x00 ; base + add di, 8 + + ; Data segment descriptor: + mov [es:di], word 0xFFFF ; limit = 0xFFFF (since granularity bit is set, this is 4 GB) + mov [es:di + 2], word 0x0000 ; base = 0x0000 + mov [es:di + 4], byte 0x00 ; base + mov [es:di + 5], byte 0x92 ; access = 1001 0010; segment present, ring 0, S=code/data, type=0x2 (data, read/write) + mov [es:di + 6], byte 0xCF ; granularity = 1100 1111; limit = 0xf, AVL=0, L=0, 32bit, G=1 + mov [es:di + 7], byte 0x00 ; base + pop di + pop es +%endmacro + +; Tell the CPU of the GDT and IDT +%macro m_load_gdt_and_idt 0 + cli + lgdt [gdt_descriptor] + lidt [idt_descriptor] +%endmacro diff --git a/src/bootloader/enabling_a20.asm b/src/bootloader/enabling_a20.asm new file mode 100644 index 0000000..e28141e --- /dev/null +++ b/src/bootloader/enabling_a20.asm @@ -0,0 +1,161 @@ +; Purpose: To check the status of the a20 line in a completely self-contained state-preserving way. +; The function can be modified as necessary by removing pushes at the beginning and their +; respective pop's at the end if complete self-containment is not required. +; +; Returns: 0 in AX if the a20 line is disabled (memory wraps around) +; 1 in AX if the a20 line is enabled (memory does not wrap around) +test_a20: + pushf + push ds + push es + push di + push si + + cli + + xor ax, ax + mov es, ax + + mov ax, 0xFFFF + mov ds, ax + + mov di, 0x0500 + mov si, 0x0510 + + mov al, byte [es:di] + push ax + + mov al, byte [ds:si] + push ax + + mov byte [es:di], 0x00 + mov byte [ds:si], 0xFF + + cmp byte [es:di], 0xFF + + pop ax + mov byte [ds:si], al + + pop ax + mov byte [es:di], al + + mov ax, 0 + je .a20_enabled + + mov ax, 1 + +.a20_enabled: + pop si + pop di + pop es + pop ds + popf + ret + +; Try to enable the a20 line using the BIOS, need to test after this +%macro m_enable_a20_via_bios 0 + pusha + mov ax, 0x2403 ; Test if BIOS supports enabling a20 line + int 0x15 + jb short .m_enable_a20_via_bios_done + cmp ah, 0 + jnz short .m_enable_a20_via_bios_done + + mov ax, 0x2402 ; Test the status of the a20 line + int 0x15 + jb short .m_enable_a20_via_bios_done + cmp ah, 0 + jnz short .m_enable_a20_via_bios_done + cmp al, 1 + jz short .m_enable_a20_via_bios_done ; Already enabled + + mov ax, 0x2401 ; Enable the a20 line + int 0x15 +.m_enable_a20_via_bios_done: + popa +%endmacro + +; Can enable the a20 line by using the keyboard +a20_wait_command: ; But need to check if the keyboard is ready to receive commands + in al, 0x64 + test al, 0x02 + jnz a20_wait_command + ret + +a20_wait_data: ; But need to check if the keyboard is ready to receive data + in al, 0x64 + test al, 0x01 + jz a20_wait_data + ret + +%macro m_enable_a20_via_keyboard 0 + cli + + call a20_wait_command + mov al, 0xAD ; Disable keyboard + out 0x64, al + + call a20_wait_command + mov al, 0xD0 ; Send command 0xd0 (read from input) + out 0x64, al + + call a20_wait_data + in al, 0x60 ; Read input + push eax ; Save input + + call a20_wait_command + mov al, 0xD1 ; Send command 0xd1 (Write to output) + out 0x64, al + + call a20_wait_command + pop eax ; Write input back + or al, 2 + out 0x60, al + + call a20_wait_command + mov al, 0xAE ; Enable keyboard + out 0x64, al + + call a20_wait_command + sti +%endmacro + +%macro m_enable_a20_fast 0 + in al, 0x92 + test al, 0x02 + jnz short .m_enable_a20_fast_done + + or al, 0x02 + and al, 0xFE + out 0x92, al +.m_enable_a20_fast_done: +%endmacro + +%macro m_enable_a20 0 + call test_a20 + cmp ax, 0 + jne .a20_enabled + + m_enable_a20_via_keyboard + + call test_a20 + cmp ax, 0 + jne .a20_enabled + + m_enable_a20_via_bios + + call test_a20 + cmp ax, 0 + jne .a20_enabled + + m_enable_a20_fast + + call test_a20 + cmp ax, 0 + jne .a20_enabled + +.a20_enabled_failed: + m_reboot_with_msg a20_error + +.a20_enabled: +%endmacro diff --git a/src/bootloader/fat_descripter.asm b/src/bootloader/fat_descripter.asm new file mode 100644 index 0000000..50573b4 --- /dev/null +++ b/src/bootloader/fat_descripter.asm @@ -0,0 +1,30 @@ +; ------------------------------------------ +; This will be where the floppy FAT12 header +; Bytes 3-61 +; https://technet.microsoft.com/en-us/library/cc976796.aspx +; Values are those used by IBM for 1.44 MB, 3.5" diskette +; ------------------------------------------ + +OEM_name db "DeanOS " ; Bytes 03-10 - OEM name (Original Equipment Manufacturer) The name for the bootloader/OS +Bytes_per_sector dw 512 ; Bytes 11-12 - Number of bytes per sector (usually 512 bytes) +Sectors_per_cluster db 1 ; Bytes 13 - Number of sectors per cluster, is 1 because in FAT12 a cluster is the same as a sector +Reserved_sectors dw 1 ; Bytes 14-15 - For FAT12 is 1 +FAT_tables db 2 ; Bytes 16 - Number of FAT tables (usually 2) +Root_directory_size dw 224 ; Bytes 17-18 - Size of root directory entries 224 for FAT12 +Sectors_in_filesystem dw 2880 ; Bytes 19-20 - Total number of sectors in the file system (usually 2880) +Media_descriptor_type db 0xF0 ; Bytes 21 - Media descriptor: 3.5" floppy 1440KB +Sectors_per_FAT dw 9 ; Bytes 22-23 - Number of sectors per FAT is 9 +Sectors_per_track dw 18 ; Bytes 24-25 - Number of sectors per track is 12 but found to be 9 +Head_count dw 2 ; Bytes 26-27 - Number of heads/sides of the floppy (usually 2) +Hidden_sectors dd 0 ; Bytes 28-31 - Number of hidden sectors (usually 0) +Total_sectors dd 0 ; Bytes 32-35 - Total number of sectors in file system +Logical_drive_number db 0 ; Bytes 36 - Logical drive number (0) +Reserved db 0 ; Bytes 37 - Reserved sectors +Extended_signature db 0x29 ; Bytes 38 - Indicates that there 3 more fields +Serial_number dd 0xA1B2C3D4 ; Bytes 39-42 - Serial number, can be anything +Volume_lable db "OS bootdisk" ; Bytes 43-53 - Name of the volume, 11 characters +Filesystem_type db "FAT12 " ; Bytes 54-61 - File system type (FAT12), 8 characters + +; ------------------------------------------ +; End of FAT12 header +; ------------------------------------------ diff --git a/src/bootloader/functions.asm b/src/bootloader/functions.asm new file mode 100644 index 0000000..d76d901 --- /dev/null +++ b/src/bootloader/functions.asm @@ -0,0 +1,103 @@ +; Print a null terminated string to the screen +; DS:SI is the location of the string +; Input: +; SI - pointer to the string to be printed in register SI +print_string_with_new_line: + pusha ; Push all registers onto the stack + mov ah, 0x0E ; Specify the teletype output function + xor bx, bx +.loop: + lodsb ; Load byte at address SI into AL and increment SI + cmp al, 0 ; If it the end of the null-terminated string + je .done ; Then exit + int 0x10 ; Else print the character in AL as an interrupt into the BIOS + jmp short .loop ; Repeat for next character +.done: + ; Print the line feed and carriage return + mov al, 0x0A ; Teletype print sub function(0x0E), Line feed (0x0A) + int 0x10 + mov al, 0x0D ; Carriage return + int 0x10 + popa ; Pop the register of the stack + ret ; And return to caller + +; Reboot the computer if there was an error +reboot: + m_write_line reboot_msg + + xor ah, ah ; Sub function for reading a character + int 0x16 ; Wait for key press + int 0x19 ; Warm reboot + + cli ; If failed to reboot, halt + hlt ; Halt + +; Read a sector from the disk +; ES:BX is the location of the buffer that the data is read into +; As reads often fail, it will try 4 times to read from the disk. The counter is stored in CX. +; With the data buffer at ES:BX +; Input: +; AX - The logical block address (LBA) +; ES:BX - The buffer location which the sector will be read into +read_sector: + xor cx, cx ; Set the counter to 0 +.read: + push ax ; Save the logical block address + push cx ; Save the counter + +; Convert the logical block address into the head-cylinder/track-sector values +; The conversions are: +; (1) Sector = (LBA mod SectorsPerTrack) + 1 +; (2) Cylinder = (LBA / SectorsPerTrack) / NumHeads +; (3) Head = (LBA / SectorsPerTrack) mod NumHeads +; +; Input: +; AX - the logical block address +; Output: These are used for the 0x13 BIOS interrupt to read from the disk along with ES:BX and ax +; CH - Lower 8 bits of cylinder +; CL - Upper 2 bits of cylinder and 6 bits for the sector +; DH - The head number +; DL - The drive number/ID +.lba_to_hcs: + push bx ; Save the buffer location + + ;mov bx, word [Sectors_per_track] ; Get the sectors per track + xor dx, dx ; Set DX to 0x0 (part of operand for DIV instruction and needs to be 0x00) + div word [Sectors_per_track] ; Divide (DX:AX / Sectors_per_track) + ; Quotient (AX) - LBA / SectorsPerTrack + ; Remainder (DX) - LBA mod SectorsPerTrack + + inc dx ; (1) Sector = (LBA mod SectorsPerTrack) + 1 + mov cl, dl ; Store sector in cl as defined for the output and for the 0x13 BIOS interrupt + + ;mov bx, word [Head_count] ; Get the number of heads + xor dx, dx + div word [Head_count] ; Quotient (AX) - Cylinder = (LBA / SectorsPerTrack) / NumHeads + ; Remainder (DX) - Head = (LBA / SectorsPerTrack) mod NumHeads + + mov ch, al ; Store cylinder in ch as defined for the output and for the 0x13 BIOS interrupt + mov dh, dl ; Store head in DH as defined for the output and for the 0x13 BIOS interrupt + + mov dl, byte [Logical_drive_number] ; Store drive number in DL as defined for the output and for the 0x13 BIOS interrupt + + pop bx ; Restore the buffer location + + ; Using the values above, read off the drive + + mov ax, 0x0201 ; Sub function 2 to read from the disk, Read 1 (0x01) sector + int 0x13 ; Call BIOS interrupt 13h + jc short .read_fail ; If fails to read (carry bit set) + + pop cx + pop ax ; Restore the logical block address + ret ; If read successful, then return to caller +.read_fail: ; If failed to read, try again, if tried 4 times, the reboot + pop cx ; Restore the counter + inc cx ; Increment the counter + cmp cx, 4 ; Compare if counter is equal to 4 + je boot_error ; If equal, then error reading 4 times so reboot + xor ah, ah ; Reset the disk to try again + int 0x13 + + pop ax ; Restore the logical block address + jmp .read ; Try to read again diff --git a/src/bootloader/macros.asm b/src/bootloader/macros.asm new file mode 100644 index 0000000..0cba30b --- /dev/null +++ b/src/bootloader/macros.asm @@ -0,0 +1,235 @@ +; The reboot macro +%macro m_reboot_with_msg 1 + m_write_line %1 + call reboot +%endmacro + +; m_write_line str_to_write +%macro m_write_line 1 + lea si, [%1] + call print_string_with_new_line +%endmacro + +%macro m_reset_disk 0 + mov dl, byte [Logical_drive_number] ; The drive to reset + xor ah, ah ; The sub function to reset the floppy (0) + int 0x13 ; Call BIOS interrupt 13h + jc boot_error ; If there was an error resetting the floppy, then the carry bit will be set + ; If so, jump to failure function and reboot +%endmacro + +; Fine a file on the disk +; m_find_file filename, load_segment +%macro m_find_file 2 + mov ax, %2 + mov es, ax + ; Calculate the number of sectors for the root directory + ; root_sectors = (root_size * 32) / 512 + mov ax, 32 + xor dx, dx ; Must be 0 initially for the multiplication + mul word [Root_directory_size] + div word [Bytes_per_sector] + mov cx, ax ; The number of root entries to read in CX for the loop + mov word [root_sectors], ax ; Save this value + + ; Calculate the start of the root directory + ; root_start = (FAT tables) * (sectors per FAT) + (reserved sectors) + (hidden sectors) + xor ax, ax + mov al, byte [FAT_tables] + mul word [Sectors_per_FAT] + add ax, word [Hidden_sectors] ; Add the top half of hidden sectors + adc ax, word [Hidden_sectors + 2] ; Add the bottom half with carry of hidden sectors + add ax, word [Reserved_sectors] + mov word [root_start], ax ; Save this value + +.read_next_sector: ; Read a sector from the root directory + ; If the reading fails, will reboot + ; CX is a loop counter for how many sector to read until failure + push cx ; Save the number of sector for the root directory on the stack + push ax ; Save the start of the root directory on the stack + xor bx, bx + call read_sector ; Read the root directory stored in ax + +.check_entry: ; Check that the loaded sector contain the file we want + mov cx, 11 ; Directory entry's are 11 bytes long (file name + file extension = 8 + 3) + mov di, bx ; ES:DI is the directory entry address + lea si, [%1] ; Load the file name the we are checking for into the SI register (DS:SI) + repz cmpsb ; Compare the filename in SI to memory (for 11 bytes) + je short .found_file ; If the string matches, the found file + ; Else try the next entry + add bx, 32 + cmp bx, word [Bytes_per_sector] ; Have we move out of the sector + jne short .check_entry ; If not, then try the next entry + + pop ax + inc ax ; Increment to the next logical block address + pop cx + loopnz .read_next_sector ; Decrement CX counter and if not 0 then read the next sector + jmp boot_error ; Else if 0, then the file wasn't found so reboot +.found_file: ; The stage 2 bootloader file has been found + mov ax, word [es:(bx + 0x1A)] ; The directory entry stores the first cluster number of the file + ; at byte 26 (0x1A). BX is pointing to the address of the start of + ; the directory entry, so go from there + mov word [file_start], ax ; Save the start of the stage 2 bootloader file + + pop ax + pop cx +%endmacro + +; Read a FAT into memory +; m_read_fat FAT_segment +%macro m_read_fat 1 + mov ax, %1 ; Load the memory location for the FAT + mov es, ax ; The FAT will be loaded into the extra segment + ; Calculate the offset of FAT12 + mov ax, word [Reserved_sectors] ; Add the reserved sectors + add ax, word [Hidden_sectors] ; Add the hidden sectors + adc ax, word [Hidden_sectors + 2] + ;Read all FAT sectors into memory + mov cx, word [Sectors_per_FAT] ; Read the number of sectors per FAT + xor bx, bx ; Set the start offset to be 0x00 +.read_next_fat_sector: + push cx ; Save the loop counter + push ax ; Save the start of the FAT + call read_sector ; Read the FAT sector + pop ax + pop cx + inc ax ; Increment the logical block address for the next sector + add bx, word [Bytes_per_sector] ; Increment by the size of the sector sector + loopnz .read_next_fat_sector ; Repeatedly read each FAT into memory (CX of them) +%endmacro + +; Read a file into memory +; m_read_file load_segment FAT_segment +; AX - Is used for the logical block address when reading the sector from the floppy using the FAT table +; - The is used for calculating the next FAT entry to read +; ES:BX - Is used for the buffer location which the sectors will be read into +; CX - Is used for calculating the next FAT entry +; - Then storing the next FAT entry +; DX - Is used for testing whether to mask the upper bits or shift out the 4 bits +; DS:SI - Is used for the location for the FAT table and SI to index into the FAT table +%macro m_read_file 2 + ; The root directory will be loaded in a higher segment. + ; Set ES to this segment. + ; Here AX is used to set up the extra segment to point to the load segment where the 2nd stage is loaded + mov ax, %1 ; Set up the memory segment that will receive the file + mov es, ax + xor bx, bx ; Set the start offset to be 0x00 + mov cx, word [file_start] ; Load the start of the file +.read_next_file_sector: + ; Locate the sector to read + mov ax, cx ; Sector to read is equal to the current FAT entry + add ax, word [root_start] ; Add the start location of the root directory + add ax, word [root_sectors] ; Add the size of the root directory + sub ax, 2 ; minus 2? + + ; Read the sector + push cx + call read_sector + pop cx + add bx, word [Bytes_per_sector] + + ; Get the next sector to read + push ds ; Save the previous data segment value + mov dx, %2 ; Make DS:SI (the data segment) point to the FAT + mov ds, dx + + push bx ; Save BX as is used for multiply and divide and contains the load location + + mov ax, cx ; Get the start of the FAT entry + xor dx, dx ; Set DX to 0 for the multiply and divide + mov bx, 3 ; For multiply by 3 -| + mul bx ; | + mov bx, 2 ; For divide by 2 |- This is for AX * 1.5 + div bx ; AX - (AX * 3) / 2 -| + ; DX - (AX * 3) mod 2 - This to check which 4 bits of a byte is needed + mov si, ax ; Set the location for the next FAT entry at DS:SI + + pop bx ; Restore BX for the buffer location + + mov cx, word [ds:si] ; Read the FAT entry + + or dx, dx ; If is even, then drop last 4 bits of word + ; Else is odd, shift the first 4 bits of word + + jnz .read_next_cluster_old ; If is even + and cx, 0x0FFF ; Then mask the upper 4 bits + jmp .read_next_file_cluster_done +.read_next_cluster_old: ; If odd + shr cx, 4 ; Shift the 4 bits to the right +.read_next_file_cluster_done: + pop ds ; Restore DS segment to normal + cmp cx, 0xFF8 ; If FAT entry is 0x0FF8, then is the end of file (for FAT12) + jl .read_next_file_sector ; If not end of file, read the next sector +%endmacro + +; Need to copy the boot sector from the first stage bootloader as contains information thats might have changed +; during the first stage +%macro m_copy_boot_sector 0 + push ds ; Need to set DS:SI to 0x0000:0x7C03 + xor ax, ax + mov ds, ax + mov si, boot_sector_location + mov di, boot_sector ; Set ES:DI to location of second stage boot sector + mov cx, 34 ; The fist 34 bytes to copy as the rest isn't changed + rep movsb + pop ds +%endmacro + +; Enable protected mode +%macro m_enable_protected 0 + mov eax, cr0 + or eax, 1 + mov cr0, eax +%endmacro + +%macro m_save_cursor 0 + mov word [boot_parameters.signature], SIGNATURE + + mov ah, 0x03 + xor bh, bh + int 0x10 + + mov byte [boot_parameters.cursor_pos_x], dl + mov byte [boot_parameters.cursor_pos_y], dh +%endmacro + +; Set up segments to point to the data GDT table (0x10) +%macro m_set_up_segments 0 + mov ax, 0x10 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + mov esp, kernel_stack + mov ebp, esp +%endmacro + +; Parameter: the memory map table memory segment to be stores at +%macro m_get_memory_size 0 + xor eax, eax + xor ebx, ebx + + m_bios_get_memory_size_E801 + + mov word [boot_parameters.memory_lower], ax + mov word [boot_parameters.memory_upper], bx + + push es + push di + + xor ax, ax + mov ax, memory_map_segment ; Set up the memory segment for where the memory map table will be loaded into + mov es, ax + + xor di, di + + m_bios_get_memory_map + + pop di + pop es + + mov word [boot_parameters.memory_map_length], si + mov dword [boot_parameters.memory_map_address], memory_map_location +%endmacro diff --git a/src/bootloader/memory.asm b/src/bootloader/memory.asm new file mode 100644 index 0000000..71375cd --- /dev/null +++ b/src/bootloader/memory.asm @@ -0,0 +1,117 @@ + [bits 16] + +; Get the number of KB of conventional memory up to 64KB. +; Output: +; AX - The number of KB of conventional memory or -1 if error. +%macro m_bios_get_conventional_memory_size 0 + int 0x12 + jc short .error_1 + test ax, ax ; If size=0 + je short .error_1 + cmp ah, 0x86 ; Unsupported function + je short .error_1 + cmp ah, 0x80 ; Invalid command + je short .error_1 + ret +.error_1: + mov ax, -1 +%endmacro + +; Get the number of contiguous KB starting at 1MB of extended memory up to 64MB. +; Output: +; AX - The number of contiguous KB starting at 1MB of extended memory or -1 if error. +%macro bios_get_extended_memory_size 0 + mov ax, 0x88 + int 0x15 + jc short .error_2 + test ax, ax ; If size = 0 + je short .error_2 + cmp ah, 0x86 ; Unsupported function + je short .error_2 + cmp ah, 0x80 ; Invalid command + je short .error_2 + ret +.error_2: + mov ax, -1 +%endmacro + +; Get the memory size for above 64MB. +; Output: +; AX - KB between 1MB and 16MB. If error, then returns -1. +; BX - Number of 64KB blocks above 16MB +%macro m_bios_get_memory_size_E801 0 + push ecx + push edx + xor ecx, ecx ; Clear all registers. This is needed for testing later + xor edx, edx + mov eax, 0x0000E801 + int 0x15 + jc short .error_3 + cmp ah, 0x86 ; Unsupported function + je short .error_3 + cmp ah, 0x80 ; Invalid command + je short .error_3 + jcxz .use_ax ; BIOS may have stored it in AX, BX or CX, DX. Test if CX is 0 + mov ax, cx ; It's not, so it should contain memory size; store it + mov bx, dx + +.use_ax: + pop edx ; Memory size is in ax and bx already, return it + pop ecx + jmp short .end + +.error_3: + mov ax, -1 ; Return -1 as there is an error + xor bx, bx ; Zero out BX + pop edx + pop ecx +.end: +%endmacro + +; Get the memory map from the BIOS saying which areas of memory are reserved or available. +; Input: +; ES:DI - The memory segment where the memory map table will be saved to. +; Output: +; ESI - The number of memory map entries. +%macro m_bios_get_memory_map 0 + xor ebx, ebx ; Start as 0, must preserve value after INT 0x15 + xor esi, esi ; Number of entries + mov eax, 0x0000E820 ; INT 0x15 sub function 0xE820 + mov ecx, 24 ; Memory map entry structure is 24 bytes + mov edx, 0x534D4150 ; SMAP + mov [es:di + 20], dword 0x00000001 ; Force a valid ACPI 3.x entry + int 0x15 ; Get first entry + jc short .error_4 ; If carry is set, then there was and error + cmp eax, 0x534D4150 ; BIOS returns SMAP in EAX + jne short .error_4 + test ebx, ebx ; If EBX = 0 then list is one entry + je short .error_4 ; Then is worthless, so error. + jmp short .start +.next_entry: + mov eax, 0x0000E820 + mov ecx, 24 + mov edx, 0x534D4150 + mov [es:di + 20], dword 0x00000001 + int 0x15 ; Get next entry + jc short .done ; Carry set if end of list already reached +.start: + jcxz .skip_entry ; If actual returned bytes is 0, skip entry + cmp cl, 20 ; Has it returned a a 24 byte ACPI 3.x response + jbe short .notext + test byte [es:di + 20], 0x01 ; If so, is the 'ignore this data' bit set + jc short .skip_entry +.notext: + mov ecx, dword [es:di + 8] ; Save the lower 32 bit memory region lengths + or ecx, dword [es:di + 12] ; OR with upper region to test for zero + jz short .skip_entry ; If zero, the skip entry + inc esi ; Good entry so increment entry count and buffer offset + add di, 24 +.skip_entry: + cmp ebx, 0 ; If EBX is 0, list is done + jne short .next_entry ; Get next entry if not zero + jmp short .done ; If zero then finish +.error_4: + m_reboot_with_msg memory_map_error +.done: + clc ; Clear the carry as was set before this point because of the JC. +%endmacro