/*
 * Copyright (c) 2001 Jakub Jermar
 * Copyright (c) 2005 Martin Decky
 * Copyright (c) 2011 Martin Sucha
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include <abi/asmtool.h>
#include <arch/boot/boot.h>
#include <arch/boot/memmap.h>
#include <arch/mm/page.h>
#include <arch/pm.h>
#include <genarch/multiboot/multiboot.h>
#include <arch/cpuid.h>
#include <arch/cpu.h>
#define START_STACK  (BOOT_OFFSET - BOOT_STACK_SIZE)
.section K_TEXT_START, "ax"
.code32
.macro pm_error msg
        movl \msg, %esi
        jmp pm_error_halt
.endm
.macro pm_status msg
#ifdef CONFIG_EGA
        pushl %esi
        movl \msg, %esi
        call pm_early_puts
        popl %esi
#endif
.endm
.macro pm2_status msg
        pushl \msg
        call early_puts
.endm
.align 4
multiboot_header:
        .long MULTIBOOT_HEADER_MAGIC
        .long MULTIBOOT_HEADER_FLAGS
        .long -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS)  /* checksum */
        .long multiboot_header
        .long unmapped_start
        .long 0
        .long 0
        .long multiboot_image_start
SYMBOL(multiboot_image_start)
        cli
        cld
        /* Initialize stack pointer */
        movl $START_STACK, %esp
        /*
         * Initialize Global Descriptor Table and
         * Interrupt Descriptor Table registers
         */
        lgdtl bootstrap_gdtr
        lidtl bootstrap_idtr
        /* Kernel data + stack */
        movw $GDT_SELECTOR(KDATA_DES), %cx
        movw %cx, %es
        movw %cx, %fs
        movw %cx, %gs
        movw %cx, %ds
        movw %cx, %ss
        jmpl $GDT_SELECTOR(KTEXT_DES), $multiboot_meeting_point
        multiboot_meeting_point:
        /* Save multiboot arguments */
        movl %eax, multiboot_eax
        movl %ebx, multiboot_ebx
        pm_status $status_prot
#include "vesa_prot.inc"
#ifndef PROCESSOR_i486
        pm_status $status_prot2
        movl $(INTEL_CPUID_LEVEL), %eax
        cpuid
        cmp $0x0, %eax  /* any function > 0? */
        jbe pse_unsupported
        movl $(INTEL_CPUID_STANDARD), %eax
        cpuid
        bt $(INTEL_PSE), %edx
        jnc pse_unsupported
                /* Map kernel and turn paging on */
                pm_status $status_pse
                call map_kernel_pse
                jmp stack_init
#endif /* PROCESSOR_i486 */
        pse_unsupported:
                /* Map kernel and turn paging on */
                pm_status $status_non_pse
                call map_kernel_non_pse
        stack_init:
        /* Create the first stack frame */
        pushl $0
        movl %esp, %ebp
        pm2_status $status_prot3
        /* Call ia32_pre_main(multiboot_eax, multiboot_ebx) */
        pushl multiboot_ebx
        pushl multiboot_eax
        call ia32_pre_main
        pm2_status $status_main
        /* Call main_bsp() */
        call main_bsp
        /* Not reached */
        cli
        hlt0:
                hlt
                jmp hlt0
/** Setup mapping for the kernel (PSE variant)
 *
 * Setup mapping for both the unmapped and mapped sections
 * of the kernel. For simplicity, we map the entire 4G space.
 *
 */
FUNCTION_BEGIN(map_kernel_pse)
        /* Paging features */
        movl %cr4, %ecx
        orl $CR4_PSE, %ecx      /* PSE on */
        andl $~CR4_PAE, %ecx    /* PAE off */
        movl %ecx, %cr4
        movl $(page_directory + 0), %esi
        movl $(page_directory + 2048), %edi
        xorl %ecx, %ecx
        xorl %ebx, %ebx
        floop_pse:
                movl $(PDE_4M | PDE_RW | PDE_P), %eax
                orl %ebx, %eax
                /* Mapping 0x00000000 + %ecx * 4M => 0x00000000 + %ecx * 4M */
                movl %eax, (%esi, %ecx, 4)
                /* Mapping 0x80000000 + %ecx * 4M => 0x00000000 + %ecx * 4M */
                movl %eax, (%edi, %ecx, 4)
                addl $(4 * 1024 * 1024), %ebx
                incl %ecx
                cmpl $512, %ecx
                jl floop_pse
        movl %esi, %cr3
        movl %cr0, %ebx
        orl $CR0_PG, %ebx       /* paging on */
        movl %ebx, %cr0
        ret
FUNCTION_END(map_kernel_pse)
/** Setup mapping for the kernel (non-PSE variant).
 *
 * Setup mapping for both the unmapped and mapped sections
 * of the kernel. For simplicity, we map the entire 4G space.
 *
 */
FUNCTION_BEGIN(map_kernel_non_pse)
        /* Paging features */
        movl %cr4, %ecx
        andl $~CR4_PAE, %ecx  /* PAE off */
        movl %ecx, %cr4
        call calc_kernel_end
        call find_mem_for_pt
        mov kernel_end, %esi
        mov free_area, %ecx
        cmpl %esi, %ecx
        jbe use_kernel_end
                mov %ecx, %esi
                /* Align address down to 4k */
                andl $(~(PAGE_SIZE - 1)), %esi
        use_kernel_end:
                /* Align address to 4k */
                addl $(PAGE_SIZE - 1), %esi
                andl $(~(PAGE_SIZE - 1)), %esi
                /* Allocate space for page tables */
                movl %esi, pt_loc
                movl $KA2PA(ballocs), %edi
                movl %esi, (%edi)
                addl $4, %edi
                movl $(2 * 1024 * 1024), (%edi)
                /* Fill page tables */
                xorl %ecx, %ecx
                xorl %ebx, %ebx
                floop_pt:
                        movl $(PTE_RW | PTE_P), %eax
                        orl %ebx, %eax
                        movl %eax, (%esi, %ecx, 4)
                        addl $PAGE_SIZE, %ebx
                        incl %ecx
                        cmpl $(512 * 1024), %ecx
                        jl floop_pt
                /* Fill page directory */
                movl $(page_directory + 0), %esi
                movl $(page_directory + 2048), %edi
                xorl %ecx, %ecx
                movl pt_loc, %ebx
                floop:
                        movl $(PDE_RW | PDE_P), %eax
                        orl %ebx, %eax
                        /* Mapping 0x00000000 + %ecx * 4M => 0x00000000 + %ecx * 4M */
                        movl %eax, (%esi, %ecx, 4)
                        /* Mapping 0x80000000 + %ecx * 4M => 0x00000000 + %ecx * 4M */
                        movl %eax, (%edi, %ecx, 4)
                        addl $PAGE_SIZE, %ebx
                        incl %ecx
                        cmpl $512, %ecx
                        jl floop
                movl %esi, %cr3
                movl %cr0, %ebx
                orl $CR0_PG, %ebx  /* paging on */
                movl %ebx, %cr0
                ret
FUNCTION_END(map_kernel_non_pse)
/** Calculate unmapped address of the end of the kernel. */
calc_kernel_end:
        movl $KA2PA(kdata_end), %edi
        movl %edi, kernel_end
        ret
/** Find free 2M (+4k for alignment) region where to store page tables */
find_mem_for_pt:
        /* Check if multiboot info is present */
        cmpl $MULTIBOOT_LOADER_MAGIC, multiboot_eax
        je check_multiboot_map
                ret
        check_multiboot_map:
                /* Copy address of the multiboot info to ebx */
                movl multiboot_ebx, %ebx
                /* Check if memory map flag is present */
                movl (%ebx), %edx
                andl $MULTIBOOT_INFO_FLAGS_MMAP, %edx
                jnz use_multiboot_map
                        ret
        use_multiboot_map:
                /* Copy address of the memory map to edx */
                movl MULTIBOOT_INFO_OFFSET_MMAP_ADDR(%ebx), %edx
                movl %edx, %ecx
                addl MULTIBOOT_INFO_OFFSET_MMAP_LENGTH(%ebx), %ecx
                /* Find a free region at least 2M in size */
                check_memmap_loop:
                        /* Is this a free region? */
                        cmpl $MEMMAP_MEMORY_AVAILABLE, MULTIBOOT_MEMMAP_OFFSET_MM_INFO + E820MEMMAP_OFFSET_TYPE(%edx)
                        jnz next_region
                        /* Check size */
                        cmpl $0, MULTIBOOT_MEMMAP_OFFSET_MM_INFO + E820MEMMAP_OFFSET_SIZE + 4(%edx)
                        jnz next_region
                        cmpl $(2 * 1024 * 1024 + PAGE_SIZE), MULTIBOOT_MEMMAP_OFFSET_MM_INFO + E820MEMMAP_OFFSET_SIZE(%edx)
                        jbe next_region
                        cmpl $0, MULTIBOOT_MEMMAP_OFFSET_MM_INFO + E820MEMMAP_OFFSET_BASE_ADDRESS + 4(%edx)
                        jz found_region
                next_region:
                        cmp %ecx, %edx
                        jbe next_region_do
                                ret
                next_region_do:
                        addl MULTIBOOT_MEMMAP_OFFSET_SIZE(%edx), %edx
                        addl $MULTIBOOT_MEMMAP_SIZE_SIZE, %edx
                        jmp check_memmap_loop
                found_region:
                        /* Use end of the found region */
                        mov MULTIBOOT_MEMMAP_OFFSET_MM_INFO + E820MEMMAP_OFFSET_BASE_ADDRESS(%edx), %ecx
                        add MULTIBOOT_MEMMAP_OFFSET_MM_INFO + E820MEMMAP_OFFSET_SIZE(%edx), %ecx
                        sub $(2 * 1024 * 1024), %ecx
                        mov %ecx, free_area
                        ret
/** Print string to EGA display (in light red) and halt.
 *
 * Should be executed from 32 bit protected mode with paging
 * turned off. Stack is not required. This routine is used even
 * if CONFIG_EGA is not enabled. Since we are going to halt the
 * CPU anyway, it is always better to at least try to print
 * some hints.
 *
 * @param %esi NULL-terminated string to print.
 *
 */
pm_error_halt:
        movl $0xb8000, %edi  /* base of EGA text mode memory */
        xorl %eax, %eax
        /* Read bits 8 - 15 of the cursor address */
        movw $0x3d4, %dx
        movb $0xe, %al
        outb %al, %dx
        movw $0x3d5, %dx
        inb %dx, %al
        shl $8, %ax
        /* Read bits 0 - 7 of the cursor address */
        movw $0x3d4, %dx
        movb $0xf, %al
        outb %al, %dx
        movw $0x3d5, %dx
        inb %dx, %al
        /* Sanity check for the cursor on screen */
        cmp $2000, %ax
        jb err_cursor_ok
                movw $1998, %ax
        err_cursor_ok:
        movw %ax, %bx
        shl $1, %eax
        addl %eax, %edi
        err_ploop:
                lodsb
                cmp $0, %al
                je err_ploop_end
                movb $0x0c, %ah  /* black background, light red foreground */
                stosw
                /* Sanity check for the cursor on the last line */
                inc %bx
                cmp $2000, %bx
                jb err_ploop
                /* Scroll the screen (24 rows) */
                movl %esi, %edx
                movl $0xb80a0, %esi
                movl $0xb8000, %edi
                movl $960, %ecx
                rep movsl
                /* Clear the 24th row */
                xorl %eax, %eax
                movl $40, %ecx
                rep stosl
                /* Go to row 24 */
                movl %edx, %esi
                movl $0xb8f00, %edi
                movw $1920, %bx
                jmp err_ploop
        err_ploop_end:
        /* Write bits 8 - 15 of the cursor address */
        movw $0x3d4, %dx
        movb $0xe, %al
        outb %al, %dx
        movw $0x3d5, %dx
        movb %bh, %al
        outb %al, %dx
        /* Write bits 0 - 7 of the cursor address */
        movw $0x3d4, %dx
        movb $0xf, %al
        outb %al, %dx
        movw $0x3d5, %dx
        movb %bl, %al
        outb %al, %dx
        cli
        hlt1:
                hlt
                jmp hlt1
/** Print string to EGA display (in light green).
 *
 * Should be called from 32 bit protected mode with paging
 * turned off. A stack space of at least 24 bytes is required,
 * but the function does not establish a stack frame.
 *
 * Macros such as pm_status take care that this function
 * is used only when CONFIG_EGA is enabled.
 *
 * @param %esi NULL-terminated string to print.
 *
 */
pm_early_puts:
        pushl %eax
        pushl %ebx
        pushl %ecx
        pushl %edx
        pushl %edi
        movl $0xb8000, %edi  /* base of EGA text mode memory */
        xorl %eax, %eax
        /* Read bits 8 - 15 of the cursor address */
        movw $0x3d4, %dx
        movb $0xe, %al
        outb %al, %dx
        movw $0x3d5, %dx
        inb %dx, %al
        shl $8, %ax
        /* Read bits 0 - 7 of the cursor address */
        movw $0x3d4, %dx
        movb $0xf, %al
        outb %al, %dx
        movw $0x3d5, %dx
        inb %dx, %al
        /* Sanity check for the cursor on screen */
        cmp $2000, %ax
        jb pm_puts_cursor_ok
                movw $1998, %ax
        pm_puts_cursor_ok:
        movw %ax, %bx
        shl $1, %eax
        addl %eax, %edi
        pm_puts_ploop:
                lodsb
                cmp $0, %al
                je pm_puts_ploop_end
                movb $0x0a, %ah  /* black background, light green foreground */
                stosw
                /* Sanity check for the cursor on the last line */
                inc %bx
                cmp $2000, %bx
                jb pm_puts_ploop
                /* Scroll the screen (24 rows) */
                movl %esi, %edx
                movl $0xb80a0, %esi
                movl $0xb8000, %edi
                movl $960, %ecx
                rep movsl
                /* Clear the 24th row */
                xorl %eax, %eax
                movl $40, %ecx
                rep stosl
                /* Go to row 24 */
                movl %edx, %esi
                movl $0xb8f00, %edi
                movw $1920, %bx
                jmp pm_puts_ploop
        pm_puts_ploop_end:
        /* Write bits 8 - 15 of the cursor address */
        movw $0x3d4, %dx
        movb $0xe, %al
        outb %al, %dx
        movw $0x3d5, %dx
        movb %bh, %al
        outb %al, %dx
        /* Write bits 0 - 7 of the cursor address */
        movw $0x3d4, %dx
        movb $0xf, %al
        outb %al, %dx
        movw $0x3d5, %dx
        movb %bl, %al
        outb %al, %dx
        popl %edi
        popl %edx
        popl %ecx
        popl %ebx
        popl %eax
        ret
/** Print string to EGA display.
 *
 * Should be called from 32 bit protected mode (with paging
 * enabled and stack established). This function is ABI compliant.
 *
 * If CONFIG_EGA is undefined or CONFIG_FB is defined
 * then this function does nothing.
 *
 * @param %ebp+0x08 NULL-terminated string to print.
 *
 */
early_puts:
#if ((defined(CONFIG_EGA)) && (!defined(CONFIG_FB)))
        /* Prologue, save preserved registers */
        pushl %ebp
        movl %esp, %ebp
        pushl %ebx
        pushl %esi
        pushl %edi
        movl 0x08(%ebp), %esi
        movl $(PA2KA(0xb8000)), %edi  /* base of EGA text mode memory */
        xorl %eax, %eax
        /* Read bits 8 - 15 of the cursor address */
        movw $0x3d4, %dx
        movb $0xe, %al
        outb %al, %dx
        movw $0x3d5, %dx
        inb %dx, %al
        shl $8, %ax
        /* Read bits 0 - 7 of the cursor address */
        movw $0x3d4, %dx
        movb $0xf, %al
        outb %al, %dx
        movw $0x3d5, %dx
        inb %dx, %al
        /* Sanity check for the cursor on screen */
        cmp $2000, %ax
        jb early_puts_cursor_ok
                movw $1998, %ax
        early_puts_cursor_ok:
        movw %ax, %bx
        shl $1, %eax
        addl %eax, %edi
        early_puts_ploop:
                lodsb
                cmp $0, %al
                je early_puts_ploop_end
                movb $0x0e, %ah  /* black background, yellow foreground */
                stosw
                /* Sanity check for the cursor on the last line */
                inc %bx
                cmp $2000, %bx
                jb early_puts_ploop
                /* Scroll the screen (24 rows) */
                movl %esi, %edx
                movl $(PA2KA(0xb80a0)), %esi
                movl $(PA2KA(0xb8000)), %edi
                movl $960, %ecx
                rep movsl
                /* Clear the 24th row */
                xorl %eax, %eax
                movl $40, %ecx
                rep stosl
                /* Go to row 24 */
                movl %edx, %esi
                movl $(PA2KA(0xb8f00)), %edi
                movw $1920, %bx
                jmp early_puts_ploop
        early_puts_ploop_end:
        /* Write bits 8 - 15 of the cursor address */
        movw $0x3d4, %dx
        movb $0xe, %al
        outb %al, %dx
        movw $0x3d5, %dx
        movb %bh, %al
        outb %al, %dx
        /* Write bits 0 - 7 of the cursor address */
        movw $0x3d4, %dx
        movb $0xf, %al
        outb %al, %dx
        movw $0x3d5, %dx
        movb %bl, %al
        outb %al, %dx
        /* Epilogue, restore preserved registers */
        popl %edi
        popl %esi
        popl %ebx
        leave
#endif
        ret
#include "vesa_real.inc"
.section K_DATA_START, "aw", @progbits
.align 4096
page_directory:
        .space 4096, 0
SYMBOL(bootstrap_idtr)
        .word 0
        .long 0
SYMBOL(bootstrap_gdtr)
        .word GDT_SELECTOR(GDT_ITEMS)
        .long KA2PA(gdt)
SYMBOL(multiboot_eax)
        .long 0
SYMBOL(multiboot_ebx)
        .long 0
pt_loc:
        .long 0
kernel_end:
        .long 0
free_area:
        .long 0
status_prot:
        .asciz "[prot] "
status_pse:
        .asciz "[pse] "
status_non_pse:
        .asciz "[non_pse] "
status_vesa_copy:
        .asciz "[vesa_copy] "
status_multiboot_cmdline:
        .asciz "[multiboot_cmdline] "
status_vesa_real:
        .asciz "[vesa_real] "
status_prot2:
        .asciz "[prot2] "
status_prot3:
        .asciz "[prot3] "
status_main:
        .asciz "[main] "