mirror of
https://github.com/gbowne1/ClassicOS.git
synced 2025-10-13 13:15:07 -07:00
286 lines
6.9 KiB
NASM
286 lines
6.9 KiB
NASM
; ==============================================================================
|
||
; boot.asm - First Stage Bootloader (CHS Based)
|
||
; ==============================================================================
|
||
|
||
[BITS 16]
|
||
[ORG 0x7C00]
|
||
|
||
start:
|
||
cli ; Disable interrupts
|
||
|
||
mov [bootdev], dl ; Save boot device number (from BIOS in DL)
|
||
|
||
; Setup stack safely below EBDA area (choose 0x0000:0x7A00)
|
||
xor ax, ax ; AX = 0
|
||
mov ss, ax ; Stack segment = 0x0000
|
||
mov sp, 0x7A00 ; Stack offset = 0x7A00
|
||
|
||
; Initialize DS, ES for zero-based segments
|
||
xor ax, ax
|
||
mov ds, ax
|
||
mov es, ax
|
||
|
||
; Load second-stage bootloader (boot1.asm) to 0x7E00
|
||
mov ax, 1 ; LBA of boot1.asm (starts at sector 1)
|
||
call lba_to_chs
|
||
mov si, 0x7E00
|
||
mov al, 4 ; Number of sectors to read
|
||
call read_chs
|
||
|
||
; Load kernel to 0x100000 (1 MB)
|
||
mov si, 0x0000 ; Destination offset
|
||
mov ax, 0x1000 ; ES = 0x1000 (0x1000:0x0000 = 1 MB)
|
||
mov es, ax
|
||
xor bx, bx
|
||
mov ax, 5 ; LBA of kernel start (boot1 is 4 sectors: LBA 1–4 → kernel at LBA 5)
|
||
call lba_to_chs
|
||
mov al, 16 ; Number of sectors for kernel
|
||
call read_chs
|
||
jc disk_error
|
||
|
||
; Memory Validation: Verify checksum of second stage bootloader
|
||
mov si, 0x7E00 ; Start of second stage
|
||
mov cx, 512 * 4 ; Size in bytes (adjust if more sectors loaded)
|
||
call verify_checksum
|
||
jc disk_error ; Jump if checksum fails
|
||
|
||
; Enable A20 line
|
||
call enable_a20
|
||
jc a20_error ; Jump if A20 enable fails
|
||
|
||
; Setup Global Descriptor Table
|
||
call setup_gdt
|
||
|
||
; Switch to protected mode and jump to second stage at 0x08:0x7E00
|
||
call switch_to_pm
|
||
|
||
disk_error:
|
||
mov si, disk_error_msg
|
||
call print_string_16
|
||
jmp halt
|
||
|
||
a20_error:
|
||
mov si, a20_error_msg
|
||
call print_string_16
|
||
jmp halt
|
||
|
||
; ----------------------------------------------------------------
|
||
; Verify Checksum Routine
|
||
; Uses SI = start address, CX = byte count
|
||
; Simple XOR checksum over bytes, expects result 0
|
||
|
||
verify_checksum:
|
||
push ax
|
||
push bx
|
||
push di
|
||
mov di, si
|
||
xor al, al
|
||
xor bx, bx
|
||
|
||
.verify_loop:
|
||
lodsb
|
||
xor bl, al
|
||
loop .verify_loop
|
||
|
||
test bl, bl
|
||
jz .checksum_ok
|
||
stc ; Set carry on checksum error
|
||
jmp .done
|
||
|
||
.checksum_ok:
|
||
clc ; Clear carry on success
|
||
|
||
.done:
|
||
pop di
|
||
pop bx
|
||
pop ax
|
||
ret
|
||
|
||
; ----------------------------------------------------------------
|
||
; CHS Disk Read Routine
|
||
; AL = number of sectors
|
||
; CL = starting sector (1-based)
|
||
; SI = destination offset (Segment:ES already set)
|
||
; Inputs:
|
||
; AL = sector count
|
||
; CH = cylinder
|
||
; DH = head
|
||
; CL = sector (1–63, with top 2 bits as high cylinder bits)
|
||
; SI = destination offset (segment ES must be set)
|
||
|
||
; ----------------------------------------------------------------
|
||
; Convert LBA to CHS
|
||
; Inputs:
|
||
; AX = LBA sector number (0-based)
|
||
; Outputs:
|
||
; CH = cylinder
|
||
; DH = head
|
||
; CL = sector (1-63, top 2 bits are upper cylinder bits)
|
||
|
||
lba_to_chs:
|
||
pusha
|
||
xor dx, dx
|
||
mov bx, [sectors_per_track]
|
||
div bx ; AX = LBA / sectors_per_track, DX = LBA % spt
|
||
mov si, ax ; temp quotient
|
||
mov cx, [heads_per_cylinder]
|
||
xor dx, dx
|
||
div cx ; AX = cylinder, DX = head
|
||
mov ch, al ; CH = cylinder low
|
||
mov dh, dl ; DH = head
|
||
mov cl, sil ; CL = sector number (0-based)
|
||
inc cl ; Sector is 1-based
|
||
mov ah, al
|
||
and ah, 0xC0 ; upper 2 bits of cylinder
|
||
or cl, ah ; insert upper cylinder bits into CL
|
||
popa
|
||
ret
|
||
|
||
read_chs:
|
||
pusha
|
||
push dx
|
||
|
||
mov cx, 5
|
||
.retry:
|
||
mov ah, 0x02 ; BIOS: Read sectors
|
||
mov dl, [bootdev] ; Boot device
|
||
; Assume CH, DH, CL already set before this call
|
||
int 0x13
|
||
jc .error
|
||
pop dx
|
||
popa
|
||
ret
|
||
|
||
.error:
|
||
dec cx
|
||
jz disk_error
|
||
xor ah, ah
|
||
int 0x13
|
||
jmp .retry
|
||
|
||
; ----------------------------------------------------------------
|
||
enable_a20:
|
||
; Try fast A20 gate method
|
||
in al, 0x92
|
||
or al, 0x02
|
||
and al, 0xFE ; Clear bit 0 to avoid fast A20 bugs
|
||
out 0x92, al
|
||
|
||
; Verify A20
|
||
call check_a20
|
||
jnc .done ; Success
|
||
|
||
; Fallback: use keyboard controller method
|
||
call .fallback
|
||
|
||
.done:
|
||
ret
|
||
|
||
.fallback:
|
||
mov al, 0xAD ; Disable keyboard
|
||
out 0x64, al
|
||
call .wait_input_clear
|
||
|
||
mov al, 0xD0 ; Command: read output port
|
||
out 0x64, al
|
||
call .wait_output_full
|
||
in al, 0x60
|
||
or al, 0x02 ; Set A20 enable bit
|
||
mov bl, al
|
||
|
||
call .wait_input_clear
|
||
mov al, 0xD1 ; Command: write output port
|
||
out 0x64, al
|
||
call .wait_input_clear
|
||
mov al, bl
|
||
out 0x60, al
|
||
|
||
call .wait_input_clear
|
||
mov al, 0xAE ; Enable keyboard
|
||
out 0x64, al
|
||
ret
|
||
|
||
.wait_input_clear:
|
||
in al, 0x64
|
||
test al, 0x02
|
||
jnz .wait_input_clear
|
||
ret
|
||
|
||
.wait_output_full:
|
||
in al, 0x64
|
||
test al, 0x01
|
||
jz .wait_output_full
|
||
ret
|
||
|
||
check_a20:
|
||
in al, 0x64 ; Read keyboard controller status
|
||
test al, 0x02 ; Check if input buffer is full
|
||
jnz check_a20 ; Wait until empty
|
||
|
||
mov al, 0xD0 ; Command: read output port
|
||
out 0x64, al
|
||
|
||
.wait_output:
|
||
in al, 0x60 ; Read output port value
|
||
test al, 0x02 ; Check A20 gate bit (bit 1)
|
||
jnz .a20_enabled ; If set, A20 enabled
|
||
|
||
xor al, al ; Clear carry to indicate failure
|
||
stc ; Set carry for failure, jc will jump
|
||
|
||
ret
|
||
|
||
.a20_enabled:
|
||
clc ; Clear carry flag to indicate success
|
||
ret
|
||
|
||
; ----------------------------------------------------------------
|
||
gdt_start:
|
||
dq 0x0000000000000000 ; Null descriptor
|
||
dq 0x00CF9A000000FFFF ; 32-bit code segment (selector 0x08)
|
||
dq 0x00CF92000000FFFF ; 32-bit data segment (selector 0x10)
|
||
dq 0x00009A000000FFFF ; 16-bit code segment for real mode (selector 0x18)
|
||
|
||
gdt_descriptor:
|
||
dw gdt_end - gdt_start - 1
|
||
dd gdt_start
|
||
gdt_end:
|
||
|
||
setup_gdt:
|
||
lgdt [gdt_descriptor]
|
||
ret
|
||
|
||
; ----------------------------------------------------------------
|
||
switch_to_pm:
|
||
cli
|
||
mov eax, cr0
|
||
or eax, 1
|
||
mov cr0, eax
|
||
jmp 0x08:0x7E00
|
||
|
||
; ----------------------------------------------------------------
|
||
print_string_16:
|
||
.loop:
|
||
lodsb
|
||
or al, al
|
||
jz .done
|
||
mov ah, 0x0E
|
||
int 0x10
|
||
jmp .loop
|
||
.done:
|
||
ret
|
||
|
||
disk_error_msg db "Disk error!", 0
|
||
a20_error_msg db "A20 error!", 0
|
||
|
||
halt:
|
||
cli
|
||
hlt
|
||
|
||
bootdev db 0
|
||
sectors_per_track dw 63
|
||
heads_per_cylinder dw 255
|
||
|
||
times 510 - ($ - $$) db 0
|
||
dw 0xAA55
|