The original code:
Code: Select all
jmp $
mov edi, 0x8000 ; basically a temp buffer to store
jmp $
call ata_lba_read; TODO: ssumption that we're booting off of 0x80
jmp $
mov si, [0x8010] ; amount of entries
mov eax, [0x500+PART_START_SECTOR] ; we need to get the lba
inc eax; load next partition
Code: Select all
=> 0x61c: jmp 0x61c
0x61e: mov $0x8000,%edi
0x623: jmp 0x623
0x625: call 0xa9e
0x62a: jmp 0x62a
0x62c: mov 0x8010,%si
0x633: mov 0xb00,%eax
0x638: inc %eax
Code: Select all
=> 0x61c: jmp 0x61c
0x61e: mov $0x8000,%edi
0x623: jmp 0x5dd
0x625: add %cl,%ah
0x627: add $0xfeeb0000,%eax
0x62c: mov 0x8010,%si
0x633: mov 0xc58,%eax
0x638: inc %eax
0x639: mov $0x1,%cl
0x63b: add $0x200,%edi
0x61f: mov edi,0x8000
become
0x61f: mov edi,0xb800
which completely broke the bootloader since edi contained the destination buffer to load partition data in. When i found the fix (move the strings from right above the subroutine to the end of the block with all the other free space), it seemed to somehow work fine. Why is this? This makes no sense to me as there is a 512 byte reserve space after the subroutine that has no effect either.
bootloader main (boot.aex.asm)
Code: Select all
; booting from AEX so we should be at 0x500
; our segments are clobbered so let's fix that
mov ax, 0x50
mov ds, ax
mov es, ax
mov bx, 0x8000
mov ss, bx
xor sp, sp
jmp 0x50:next
next:
mov [BOOT_DRIVE], dl
mov [PART_START_SECTOR], edi ; receive this from the start program
cld
KERNEL_OFFSET equ 0x10000 ; The same one we used when linking the kernel
jmp switch_to_32bit ; disable interrupts, load GDT, etc. Finally jumps to 'BEGIN_PM'
jmp $ ; Never executed
disk_error:
; mov si, disk_error_msg
; Print the error message by moving it to the video memory
mov dl, ah
mov ah, 0x0E
mov al, 'E'
int 0x10
done:
jmp $
; use the INT 0x15, eax= 0xE820 BIOS function to get a memory map
; note: initially di is 0, be sure to set it to a value so that the BIOS code will not be overwritten.
; The consequence of overwriting the BIOS code will lead to problems like getting stuck in `int 0x15`
; inputs: es:di -> destination buffer for 24 byte entries
; outputs: bp = entry count, trashes all registers except esi
[bits 16]
mmap_ent equ 0x2000 ; the number of entries will be stored at 0x2000
do_e820:
xor di, di
mov es, di
mov di, 0x2004 ; Set di to 0x8004. Otherwise this code will get stuck in `int 0x15` after some entries are fetched
xor ebx, ebx ; ebx must be 0 to start
xor bp, bp ; keep an entry count in bp
mov edx, 0x534D4150 ; Place "SMAP" into edx
mov eax, 0x0000e820
mov [es:di + 20], dword 1 ; force a valid ACPI 3.X entry
mov ecx, 24 ; ask for 24 bytes
int 0x15
jc short .failed ; carry set on first call means "unsupported function"
mov edx, 0x534D4150 ; Some BIOSes apparently trash this register?
cmp eax, edx ; on success, eax must have been reset to "SMAP"
jne short .failed
test ebx, ebx ; ebx = 0 implies list is only 1 entry long (worthless)
je short .failed
jmp short .jmpin
.e820lp:
mov eax, 0x0000e820 ; eax, ecx get trashed on every int 0x15 call
mov [es:di + 20], dword 1 ; force a valid ACPI 3.X entry
mov ecx, 24 ; ask for 24 bytes again
int 0x15
jc short .e820f ; carry set means "end of list already reached"
mov edx, 0x0534D4150 ; repair potentially trashed register
.jmpin:
jcxz .skipent ; skip any 0 length entries
cmp cl, 20 ; got a 24 byte ACPI 3.X response?
jbe short .notext
test byte [es:di + 20], 1 ; if so: is the "ignore this data" bit clear?
je short .skipent
.notext:
mov ecx, [es:di + 8] ; get lower uint32_t of memory region length
or ecx, [es:di + 12] ; "or" it with upper uint32_t to test for zero
jz .skipent ; if length uint64_t is 0, skip entry
inc bp ; got a good entry: ++count, move to next storage spot
add di, 24
.skipent:
test ebx, ebx ; if ebx resets to 0, list is complete
jne short .e820lp
.e820f:
mov [es:mmap_ent], bp ; store the entry count
clc ; there is "jc" on end of list to this point, so the carry must be cleared
ret
.failed:
mov [es:mmap_ent], bp ; store the entry count
stc ; "function unsupported" error exit
ret
[bits 16]
switch_to_32bit:
; before all that, get memory
; we want to get the memory map to store
; for the later kernel use.
call do_e820
mov di, 0x50
mov es, di
; jmp $
; jc disk_error
; jc disk_error ; if carry is set, error occurred
finished:
; if our drive isnt 0x80, then take precautionary measures
mov dl, [BOOT_DRIVE]
; cmp dl, 0x80
; je .continue
; otherwise, load via bios
; call READ_BIOS
; call READ_BIOS_KERNEL
.continue:
call choose_graphics_mode
call enable_a20 ; 0. enable A20 line
cli ; 1. disable interrupts
nop ; 1.1. some CPUs require a delay after cli
lgdt [gdt_descriptor] ; 2. load the GDT descriptor
mov eax, cr0
or eax, 0x1 ; 3. set 32-bit mode bit in cr0
mov cr0, eax
; 4. far jump by using a different segment
jmp CODE_SEG:init_32bit+0x500
[bits 16]
strcmp:
push di
push si
repe cmpsb
pop si
pop di
je .match
xor ax, ax
ret
.match:
mov ax, 1
ret
; NEVER MIX THE BLOODY 16 AND 32
; I HATE LIFE DEBUGGED TS FOR 2 HOURS
[bits 32]
strcmp32:
push edi
push esi
repe cmpsb
je .match
pop esi
pop edi
xor ax, ax
ret
.match:
pop esi
pop edi
mov ax, 1
ret
[bits 32]
init_32bit: ; we are now using 32-bit instructions
; 5. update the segment registers
mov word ax, DATA_SEG
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ebp, 0x10000 ; 6. update the stack right at the top of the free space
mov esp, ebp
mov eax, [0x500+PART_START_SECTOR] ; we need to get the lba
mov cl, 0x1 ; this command only reliably reads one sector
mov edi, 0x8000 ; basically a temp buffer to store
[bits 32]
call ata_lba_read; TODO: ssumption that we're booting off of 0x80
mov si, [0x8010] ; amount of entries
mov eax, [0x500+PART_START_SECTOR] ; we need to get the lba
inc eax; load next partition
mov cl, 0x1 ; this command only reliably reads one sector
add edi, 512
; jmp $
call ata_lba_read; TODO: ssumption that we're booting off of 0x80
found:
; the first file entry is at 0x200
mov edi, 0x8201 ; 0x8200
mov word [0x7bfe], 0x8400 ; the next sector (next place we need to load at)
mov dword [0x7bfa], 1 ; the difference in sector
check_name:
cmp si, 0 ; if the amount of files left is 0
je failed ; hang (there is no bootable file)
; check the file name (match1 is the name)
push esi
mov esi, match1+0x500
; DI should be at the aligned file name
mov ecx, 7
call strcmp32 ; compare the file name
; jmp $
cmp ax, 1
pushf
add di, 8 ; 0x209
popf
pop esi
jne tryagain; if it fails then just restart then and there
push esi
; now let's compare file extension
mov esi, match2+0x500
mov ecx, 3 ; stupid ahh code i hate programming
call strcmp32 ; compare the file extension
cmp eax, 1
pop esi
jne tryagain
; now that we've established a file, we subtract di and
; runexe
add edi, 3
xor edx, edx
mov eax, [edi + 4] ; get the size of the file
mov esi, 512
div esi ; divide by 512 to get amount of sectors it spans
inc eax ; account for the last partial sector
mov ebx, eax ; ebx now contains number of sectors (size)
xor edx, edx
mov eax, [edi]
div esi ; divide by 512 to get amount of sectors it spans
; inc eax ; part_start_sector includes the vba, so ignore that
add eax, [0x500+PART_START_SECTOR]
; we get the full dword of the partition area and add it
; eax contains LBA of the file start
; and edx contains the offset (% 512)
xor esi, esi
mov edi, 0x8000
read:
mov cl, 1
call ata_lba_read
mov [0xb8000], 'D'
cmp ebx, esi
jne .continue
jmp .done
.continue:
inc esi
inc eax
add edi, 512
mov [0xb8000], ' '
jmp read
.done:
mov [0xb8002], 'D'
jmp $
call CODE_SEG:0x8000
jmp $
tryagain:
; try again
add di, 12 ; 20 bytes later
sub si, 1 ; we went through one file
mov dx, [0x7bfe]
cmp di, dx ; make sure we aren't at the end of the sector
jge getnewvalues ; if we are then we load new files
jmp check_name
getnewvalues:
xor ah, ah
mov eax, [0x7bfa]; get the last increment in sectors
add al, 1 ; add one more to ah
mov dword [0x7bfa], eax; and put the increment back
add eax, [0x500+PART_START_SECTOR]; offset by the start
mov cl, 0x01 ; one sector as usual
mov edi, 0x8200 ; same sector
call ata_lba_read
; after this point, we only read one sector.
; hold onto the value of bx so we can read again later
jmp check_name
failed:
jmp $
%include "src/bootloader_partition/boot.aex/graphicsmode.asm"
%include "src/bootloader_partition/boot.aex/a20.asm"
%include "src/bootloader_partition/boot.aex/gdt.asm"
%include "src/bootloader_partition/boot.aex/read.asm"
BOOT_DRIVE: db 0
PART_START_SECTOR: dd 0
align 4
lba:
lbasize: db 0x10
lbaresv: db 0x00
lbamaxs: dw 0x0001
lbaoffs: dw 0x0000
lbasegs: dw 0x1000
lbalow4: dd 0x00000000
lbahigh: dd 0x00000000
align 4
lbaFIRSTPART:
db 0x10
db 0x00
dw 0x0001
dw 0x7c00
dw 0x0000
dd 0x00000000
dd 0x00000000
match1: db "LOADER", 0
match2: db "AEX", 0
os_identifier: db "AstatineOS dev build, bootloader", 0
os_callforaction: db "Choose an option for display graphics below", 0
os_textmode: db " Text mode (80x25) ", 0
os_bitmapmode: db " Display (320x200x256, 53x25) ", 0
currently_selected_graphics_mode: db 0;Code: Select all
; credit for this goes to the really really heplful people of osdev.org
[bits 16]
; out:
; ax - state (0 - disabled, 1 - enabled)
get_a20_state:
pushf
push si
push di
push ds
push es
cli
mov ax, 0x0000 ; 0x0000:0x0500(0x00000500) -> ds:si
mov ds, ax
mov si, 0x0500
not ax ; 0xffff:0x0510(0x00100500) -> es:di
mov es, ax
mov di, 0x0510
mov al, [ds:si] ; save old values
mov byte [.BufferBelowMB], al
mov al, [es:di]
mov byte [.BufferOverMB], al
mov ah, 1 ; check byte [0x00100500] == byte [0x0500]
mov byte [ds:si], 0
mov byte [es:di], 1
mov al, [ds:si]
cmp al, [es:di]
jne .exit
dec ah
.exit:
mov al, [.BufferBelowMB]
mov [ds:si], al
mov al, [.BufferOverMB]
mov [es:di], al
shr ax, 8
sti
pop es
pop ds
pop di
pop si
popf
ret
.BufferBelowMB: db 0
.BufferOverMB: db 0
; out:
; ax - a20 support bits (bit #0 - supported on keyboard controller; bit #1 - supported with bit #1 of port 0x92)
; cf - set on error
query_a20_support:
push bx
clc
mov ax, 0x2403
int 0x15
jc .error
test ah, ah
jnz .error
mov ax, bx
pop bx
ret
.error:
stc
pop bx
ret
enable_a20_keyboard_controller:
cli
call .wait_io1
mov al, 0xad
out 0x64, al
call .wait_io1
mov al, 0xd0
out 0x64, al
call .wait_io2
in al, 0x60
push eax
call .wait_io1
mov al, 0xd1
out 0x64, al
call .wait_io1
pop eax
or al, 2
out 0x60, al
call .wait_io1
mov al, 0xae
out 0x64, al
call .wait_io1
sti
ret
.wait_io1:
in al, 0x64
test al, 2
jnz .wait_io1
ret
.wait_io2:
in al, 0x64
test al, 1
jz .wait_io2
ret
; out:
; cf - set on error
enable_a20:
clc ; clear cf
pusha
mov bh, 0 ; clear bh
call get_a20_state
;jc .fast_gate
test ax, ax
jnz .done
call query_a20_support
mov bl, al
test bl, 1 ; enable A20 using keyboard controller
jnz .keybord_controller
test bl, 2 ; enable A20 using fast A20 gate
jnz .fast_gate
.bios_int:
mov ax, 0x2401
int 0x15
jc .fast_gate
test ah, ah
jnz .failed
call get_a20_state
test ax, ax
jnz .done
.fast_gate:
in al, 0x92
test al, 2
jnz .done
or al, 2
and al, 0xfe
out 0x92, al
call get_a20_state
test ax, ax
jnz .done
test bh, bh ; test if there was an attempt using the keyboard controller
jnz .failed
.keybord_controller:
call enable_a20_keyboard_controller
call get_a20_state
test ax, ax
jnz .done
mov bh, 1 ; flag enable attempt with keyboard controller
test bl, 2
jnz .fast_gate
jmp .failed
.failed:
stc
.done:
popa
retCode: Select all
ALIGN 8
gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps
; the GDT starts with a null 8-byte
dd 0x0 ; 4 byte
dd 0x0 ; 4 byte
; GDT for kernel code segment. base = 0x00000000, length = 0xfffff
; for flags, refer to os-dev.pdf document, page 36
gdt_code:
dw 0xffff ; segment length, bits 0-15 (16-bit value)
dw 0x0 ; segment base, bits 0-15 (16-bit value)
db 0x0 ; segment base, bits 16-23 (8-bit value)
db 10011010b ; flags (8 bits)
db 11001111b ; flags (4 bits) + segment length, bits 16-19
db 0x0 ; segment base, bits 24-31
; GDT for kernel data segment. base and length identical to code segment
; some flags changed, again, refer to os-dev.pdf
gdt_data:
dw 0xffff
dw 0x0
db 0x0
db 10010010b
db 11001111b
db 0x0
; Base = 0
; Limit = 0xFFFFF
; Access Byte = 0xFA
; Flags = 0xC
;gdt_user_code:
; dw 0xffff
; dw 0x0
; db 0x0
; db 10011010b
; db 11001111b
; db 0x0
; Base = 0
; Limit = 0xFFFFF
; Access Byte = 0xF2
; Flags = 0xC
;gdt_user_data:
; dw 0xffff
; dw 0x0
; db 0x0
; db 10010010b
; db 11001111b
; db 0x0
; gdt taask segment
;gdt_tss:
; dw 0x67
; dw 0x0
; db 0x0
; db 10001001b
; db 0x0
; db 0x0
gdt_end:
; GDT descriptor
gdt_descriptor:
dw gdt_end - gdt_start - 1 ; size (16 bit), always one less of its true size
dd gdt_start+0x500 ; address (32 bit)
; define some constants for later use
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_startCode: Select all
[bits 16]
; si: null terminated string
; return cx: length
[bits 16]
strlen:
push ax
push si
xor cx, cx
.loop:
lodsb
cmp al, 0
je .end
inc cx
jmp .loop
.end:
pop si
pop ax
ret
; si: null terminated string
; bl: color attribute
; dh: row
; dl: column
[bits 16]
draw_string:
push ax
push bx
push cx
push si
push bp
; we can just use bios write string
; but have to get length of string first
call strlen
xor bh, bh
mov ah, 0x13
mov al, 0x01
mov bp, si
int 0x10
.end:
pop bp
pop si
pop cx
pop bx
pop ax
ret
[bits 16]
choose_graphics_mode:
pusha
; clear the screen
mov ah, 0x06
mov al, 0x00
mov bh, 0x07
mov ch, 0x00
mov cl, 0x00
mov dh, 0x18
mov dl, 0x4F
int 0x10
; print the first line
mov dh, 8
mov dl, 16
mov bl, 0x0f
mov si, os_identifier
call draw_string
; now we print the second line
mov dh, 9
mov dl, 16
mov bl, 0x07
mov si, os_callforaction
call draw_string
; now we can start drawing options
.loop:
; draw the first option
mov dh, 11
mov dl, 16
; but now we check if its selected (0x70) or not (0x07)
movzx di, byte [currently_selected_graphics_mode]
cmp di, 0
jne .second_selected
.first_selected:
mov bl, 0x70
mov si, os_textmode
call draw_string
; second string
mov dh, 12
mov dl, 16
mov bl, 0x07
mov si, os_bitmapmode
call draw_string
jmp .keypress
.second_selected:
mov bl, 0x07
mov si, os_textmode
call draw_string
; second string
mov dh, 12
mov dl, 16
mov bl, 0x70
mov si, os_bitmapmode
call draw_string
.keypress:
mov ah, 0x00
int 0x16
; in ah is the scan code
cmp ah, 0x1C ; enter
je .done
cmp ah, 0x48 ; arrow down
je .arrow_down
cmp ah, 0x50 ; arrow up
je .arrow_up
jmp .keypress
.arrow_up:
mov dh, byte [currently_selected_graphics_mode]
cmp dh, 0
je .allow_arrow_up
jmp .keypress
.allow_arrow_up:
inc byte [currently_selected_graphics_mode]
jmp .loop
.arrow_down:
mov dh, byte [currently_selected_graphics_mode]
cmp dh, 1
je .allow_arrow_down
jmp .keypress
.allow_arrow_down:
dec byte [currently_selected_graphics_mode]
jmp .loop
.done:
movzx di, byte [currently_selected_graphics_mode]
cmp di, 1
jne .end
.set_graphics:
call set_graphics_mode
.end:
mov si, 0x1000
mov ax, di
mov byte [si], al
popa
ret
[bits 16]
set_graphics_mode:
push ax
mov ah, 0x00
mov al, 0x13
int 0x10
pop ax
ret
[bits 16]
get_vesa_info:
clc
mov ax, 0x4F00 ; VBE function 00h - Return VBE Controller Information
mov di, buffer ; ES:DI -> VBEInfoBlock
int 0x10
jne .failed
ret
.failed:
stc
ret
buffer: resb 512
failed_vbe_info_str: db "Failed to get VBE info, continuing without graphics...", 0
; =====================================================================
; VBE Controller Information Block (512 bytes total)
; Used with INT 10h, AX = 4F00h
; =====================================================================
struc VBEInfoBlock
.Signature: resb 4 ; 'VESA' after call
.Version: resw 1 ; BCD: 0x0200 = VBE 2.0
.OemStringPtr: resd 1 ; Far pointer: seg:off
.Capabilities: resb 4
.VideoModePtr: resd 1 ; Far pointer to mode list
.TotalMemory: resw 1 ; In 64 KB blocks
; VBE 2.0+ additional fields
.OemSoftwareRev: resw 1
.OemVendorNamePtr: resd 1
.OemProductNamePtr resd 1
.OemProductRevPtr: resd 1
.Reserved: resb 222 ; Must be zero
.OemData: resb 256 ; OEM data
endstruc
; =====================================================================
; VBE Mode Information Block (256 bytes total)
; Used with INT 10h, AX = 4F01h
; =====================================================================
struc VBEModeInfo
.ModeAttributes: resw 1
.WinAAttributes: resb 1
.WinBAttributes: resb 1
.WinGranularity: resw 1
.WinSize: resw 1
.WinASegment: resw 1
.WinBSegment: resw 1
.WinFuncPtr: resd 1
.BytesPerScanLine: resw 1
; VBE 1.2 fields
.XResolution: resw 1
.YResolution: resw 1
.XCharSize: resb 1
.YCharSize: resb 1
.NumberOfPlanes: resb 1
.BitsPerPixel: resb 1
.NumberOfBanks: resb 1
.MemoryModel: resb 1
.BankSize: resb 1
.NumberOfImagePages: resb 1
.Reserved1: resb 1
; Direct color fields (VBE 2.0)
.RedMaskSize: resb 1
.RedFieldPosition: resb 1
.GreenMaskSize: resb 1
.GreenFieldPosition: resb 1
.BlueMaskSize: resb 1
.BlueFieldPosition: resb 1
.RsvdMaskSize: resb 1
.RsvdFieldPosition: resb 1
.DirectColorModeInfo: resb 1
; Linear framebuffer fields
.PhysBasePtr: resd 1
.OffScreenMemOffset: resd 1
.OffScreenMemSize: resw 1
.Reserved2: resb 206
endstruc
Code: Select all
[bits 32]
;=============================================================================
; ATA read sectors (LBA mode)
;
; @param EAX Logical Block Address of sector
; @param CL Number of sectors to read
; @param EDI The address of buffer to put data obtained from disk
;
; @return None
;=============================================================================
ata_lba_read:
pushad
pushfd
and eax, 0x0FFFFFFF
mov ebx, eax ; Save LBA in RBX
mov edx, 0x01F6 ; Port to send drive and bit 24 - 27 of LBA
shr eax, 24 ; Get bit 24 - 27 in al
or al, 11100000b ; Set bit 6 in al for LBA mode
out dx, al
mov edx, 0x01F2 ; Port to send number of sectors
mov al, 1 ; Get number of sectors from CL
out dx, al
mov edx, 0x1F3 ; Port to send bit 0 - 7 of LBA
mov eax, ebx ; Get LBA from EBX
out dx, al
mov edx, 0x1F4 ; Port to send bit 8 - 15 of LBA
mov eax, ebx ; Get LBA from EBX
shr eax, 8 ; Get bit 8 - 15 in AL
out dx, al
mov edx, 0x1F5 ; Port to send bit 16 - 23 of LBA
mov eax, ebx ; Get LBA from EBX
shr eax, 16 ; Get bit 16 - 23 in AL
out dx, al
mov edx, 0x1F7 ; Command port
mov al, 0x20 ; Read with retry.
out dx, al
.still_going:
in al, dx
test al, 8 ; the sector buffer requires servicing.
jz .still_going ; until the sector buffer is ready.
mov eax, 256 ; to read 256 words = 1 sector
xor bx, bx
mov bl, cl ; read CL sectors
mul bx
mov ecx, eax ; RCX is counter for INSW
mov edx, 0x1F0 ; Data port, in and out
rep insw ; in to [RDI]
popfd
popad
ret