Feature/bootloader (#2), closes #1

* Added bootloader files

* Changed to spaces from tabs
This commit is contained in:
Edward Dean 2019-03-27 21:33:59 +00:00 committed by Sam Tebbs
parent 915330287f
commit 7678019e03
11 changed files with 1090 additions and 0 deletions

4
.gitignore vendored
View file

@ -33,3 +33,7 @@
# Images
*.iso
*.bin
# Intellij
.idea/

153
src/bootloader/2ndstage.asm Normal file
View file

@ -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

View file

@ -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

29
src/bootloader/Makefile Normal file
View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
; ------------------------------------------

View file

@ -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

235
src/bootloader/macros.asm Normal file
View file

@ -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

117
src/bootloader/memory.asm Normal file
View file

@ -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