; This is the boot loader code of OSP, see details below.
; Copyright (c) 2005 John Tsiombikas <nuclear@siggraph.org>
; This program is free software; you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation; either version 2 of the License, or
; (at your option) any later version.
; This program is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; GNU General Public License for more details.
; You should have received a copy of the GNU General Public License
; along with this program; if not, write to the Free Software
; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
; -------------------------------------------------------------------------
; boot loader
; Loads the kernel, enables the A20 address line, enters 32bit protected mode
; and passes control to the C kernel startup function.
; Author: John Tsiombikas 2005
; The A20 enable code is strongly based on the relevant code from the
; Linux kernel (2.6.7) arch/i386/boot/setup.S, which is in turn taken
; from SYSLINUX by H. Peter Arvin.

[org 7c00h]
[bits 16]

%define KADDR   4000h

    mov ax, cs
    mov ds, ax
    mov es, ax
    mov gs, ax

    mov bx, 9000h  ; start 16bit stack at 4096 byte
    mov ss, bx
    xor sp, sp

    mov [boot_drv], dl

    call load_prog     ; load kernel image

    call enable_a20        ; enable the A20 line

    mov si, msg_pmode
    call print_cstr

    ; now enter 32 bit protected mode
    lidt [idt_ptr]
    lgdt [gdt_ptr]

    mov eax, cr0
    or eax, 1
    mov cr0, eax

    jmp super_code_sel:.pmode

[bits 32]
    mov ax, super_data_sel
    mov ss, ax
    mov es, ax
    mov ds, ax
    mov gs, ax
    mov fs, ax

    mov esp, 9000h

    jmp word KADDR

[bits 16]

msg_pmode       db   'Switching to protected mode...',0

; this function loads the kernel image into memory
msg_load_str    db 'Loading OSP...',13,10,0
    mov si, msg_load_str
    call print_cstr

    xor ax, ax
    mov dl, [boot_drv]
    int 13h
    jc .reset

    mov ax, 0
    mov es, ax
    mov ebx, KADDR

    mov ah, 2          ; function 2 of int 13h (read)
    mov al, [ksize]        ; read ksize sectors
    mov ch, [kcyl]     ; cylinder
    mov cl, [ksec]     ; sector
    mov dh, [khead]        ; head
    mov dl, [boot_drv] ; drive number
    int 13h                ; do the reading, data go to es:bx
    jnc .donerd

    push ax                ; save the error code
    mov si, err_msg1
    call print_cstr
    pop ax
    shr ax, 8          ; ah contains the error code, move to al
    add al, 48         ; convert to ascii digit
    call putchar
    jmp .read

    cli        ; int 13h might have enabled interrupts

; The dreaded A20 line ...
msg_a20         db    'Enabling A20 line...',0
msg_ok  db   'OK',13,10,0

; -- empty_8042() --
; empties the 8042 command queue
    push ecx
    mov ecx, 100000

    dec ecx
    jz .end_loop

    call delay

    in al, 64h     ; 8042 status port
    test al, 1     ; output buffer?
    jz .no_output

    call delay
    in al, 60h     ; read it
    jmp .loop

    test al, 2
    jnz .loop

    pop ecx

; -- delay() --
; just a short delay
    out 80h, al

; -- enable_a20() --
; tries to enable the A20 line through a variety of methods
; code taken from the Linux kernel.
%define A20_TEST_LOOPS  32
a20_tries   db    255

    mov si, msg_a20
    call print_cstr


    call a20_test
    jnz .done

    mov ax, 2401h
    int 15h

    call a20_test
    jnz .done

    call empty_8042

    call a20_test  ; in case the bios worked but took its time
    jnz .done

    mov al, 0d1h
    out 64h, al
    call empty_8042

    mov al, 0dfh
    out 60h, al
    call empty_8042

    xor cx, cx
    call a20_test
    jnz .done
    loop .wait_loop

    in al, 92h     ; configuration port A
    or al, 2       ; fast A20 version
    and al, 0feh   ; don't trigger the reset bit by accident
    out 92h, al

    xor cx, cx
    call a20_test
    jnz .done
    loop .wait_fast_loop

    ; to no avail... retry
    dec byte [a20_tries]
    jnz .try_loop

    call panic16

    mov si, msg_ok
    call print_cstr

; -- a20_test --
; tests to see if the A20 line is enabled
%define A20_TEST_ADDR   4*80h

    push cx
    push ax
    xor cx, cx
    mov fs, cx     ; low mem
    dec cx
    mov gs, cx     ; high mem
    mov cx, A20_TEST_LOOPS
    mov ax, [fs:A20_TEST_ADDR]
    push ax
    inc ax
    mov [fs:A20_TEST_ADDR], ax
    call delay
    cmp ax, [gs:A20_TEST_ADDR+10h]
    loope .wait

    pop word [fs:A20_TEST_ADDR]
    pop ax
    pop cx

; uses BIOS to output a character passed in AL
    push bx
    mov ah, 0eh
    mov bl, 4
    int 10h
    pop bx

; prints a C string pointed to by ds:si
    mov al, [si]
    mov ah, 0eh
    mov bl, 4
    int 10h
    inc si
    cmp byte [si], 0
    jnz print_cstr

msg_panic_std   db    'Aaarrggghhh!',0
    mov si, msg_panic_std
    call print_cstr

; information to locate OS kernel
ksize       db   20
kcyl        db    0
ksec        db    2
khead       db   0
boot_drv    db 0

; error messages on read error from drive
err_msg1        db    13,10,'read error: ',0

    dw gdt_end - gdt - 1
;   dw 4 * 8 - 1
    dd gdt

    dw 7ffh
    dd 09000h

; descriptor 0 (null)
    times 8 db 0

; descriptor 1 (supervisor code segment)
super_code_sel equ $-gdt
    dw 0ffffh      ; limit bits 15 - 0
    dw 0000h       ; base bits 15 - 0
    db 00h         ; base bits 23 - 16
    db 10011010b   ; present, lvl 0, code, non-conforming, readable
    db 11001111b   ; page granular, 32bit & bits 31 - 28 of limit
    db 00h         ; base 31 - 24

; descriptor 2 (supervisor data segment)
super_data_sel equ $-gdt
    dw 0ffffh      ; limit bits 15 - 0
    dw 0000h       ; base bits 15 - 0
    db 00h         ; base bits 23 - 16
    db 10010010b   ; present, lvl 0, data, expand-up, writable
    db 11001111b   ; page granular, 32bit & bits 31 - 28 of limit
    db 00h         ; base 31 - 24

; descriptor 3 (initial tss)
tss_desc_dummy equ $-gdt
    dd 0
    dd 0


times 510-($-$$) db 0
    dw 0aa55h