; 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
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
; 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

start:
    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]
.pmode:
    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
load_prog
    mov si, msg_load_str
    call print_cstr

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

.read:
    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

.donerd
    cli        ; int 13h might have enabled interrupts
    ret


; 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
empty_8042:
    push ecx
    mov ecx, 100000

.loop:
    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

.no_output:
    test al, 2
    jnz .loop

.end_loop:
    pop ecx
    ret

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

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

enable_a20:
    mov si, msg_a20
    call print_cstr

.try_loop:

.none:
    call a20_test
    jnz .done

.bios:
    mov ax, 2401h
    int 15h

    call a20_test
    jnz .done

.kbc:
    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

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

.fast:
    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

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

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

    call panic16

.done:
    mov si, msg_ok
    call print_cstr
    ret



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

a20_test:
    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
.wait:
    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
    ret


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

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

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


; 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

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

idt_ptr:
    dw 7ffh
    dd 09000h

gdt:
; 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

gdt_end:

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