/*
 * Copyright (c) 2009 Corey Tabaka
 * Copyright (c) 2015 Intel Corporation
 * Copyright (c) 2016 Travis Geiselbrecht
 *
 * Use of this source code is governed by a MIT-style
 * license that can be found in the LICENSE file or at
 * https://opensource.org/licenses/MIT
 */
#include <lk/asm.h>
#include <arch/x86/descriptor.h>
#include <arch/x86/mmu.h>

/* The magic number for the Multiboot header. */
#define MULTIBOOT_HEADER_MAGIC 0x1BADB002

/* The flags for the Multiboot header. */
#if defined(__ELF__) && 0
#define MULTIBOOT_HEADER_FLAGS 0x00000002
#else
#define MULTIBOOT_HEADER_FLAGS 0x00010002
#endif

/* The magic number passed by a Multiboot-compliant boot loader. */
#define MULTIBOOT_BOOTLOADER_MAGIC 0x2BADB002

#define PHYS_LOAD_ADDRESS (MEMBASE + KERNEL_LOAD_OFFSET)
#define PHYS_ADDR_DELTA (KERNEL_BASE + KERNEL_LOAD_OFFSET - PHYS_LOAD_ADDRESS)
#define PHYS(x) ((x) - PHYS_ADDR_DELTA)

.section ".text.boot"
.global _start
_start:
    jmp real_start

.align 4

.type multiboot_header,STT_OBJECT
multiboot_header:
    /* magic */
    .int MULTIBOOT_HEADER_MAGIC
    /* flags */
    .int MULTIBOOT_HEADER_FLAGS
    /* checksum */
    .int -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS)

#if !defined(__ELF__) || 1
    /* header_addr */
    .int PHYS(multiboot_header)
    /* load_addr */
    .int PHYS(_start)
    /* load_end_addr */
    .int PHYS(__data_end)
    /* bss_end_addr */
    .int PHYS(__bss_end)
    /* entry_addr */
    .int PHYS(real_start)
#endif

real_start:
    cmpl $MULTIBOOT_BOOTLOADER_MAGIC, %eax
    jne 0f
    movl %ebx, PHYS(_multiboot_info)

0:
    /* load our new gdt by physical pointer */
    lgdt PHYS(_gdtr_phys)

    movw $DATA_SELECTOR, %ax
    movw %ax, %ds
    movw %ax, %es
    movw %ax, %fs
    movw %ax, %ss
    movw %ax, %gs
    movw %ax, %ss

    /* load initial stack pointer */
    movl $PHYS(_kstack + 4096), %esp

    /*We jumped here in protected mode in a code segment that migh not longer
      be valid , do a long jump to our code segment, we use retf instead of
      ljmp to be able to use relative labels */
    pushl $CODE_SELECTOR     /*Pushing our code segment */
    pushl $PHYS(.Lfarjump)   /*and jump address */
    retf    /*This instruction will jump to codesel:farjump */

.Lfarjump:

    /* zero the bss section */
bss_setup:
    movl $PHYS(__bss_start), %edi /* starting address of the bss */
    movl $PHYS(__bss_end), %ecx   /* find the length of the bss in bytes */
    subl %edi, %ecx
    shrl $2, %ecx       /* convert to 32 bit words, since the bss is aligned anyway */
2:
    movl $0, (%edi)
    addl $4, %edi
    loop 2b

paging_setup:
#ifdef PAE_MODE_ENABLED
#error broken for now
    /* Preparing PAE paging, we will use 2MB pages covering 1GB
    for initial bootstrap, this page table will be 1 to 1 */

    /* Setting the First PDPTE with a PD table reference*/
    movl $pdp,   %eax
    orl  $0x01, %eax
    movl %eax, (pdpt)

    movl $pdp, %esi
    movl $0x1ff, %ecx

fill_pdp:
    movl $0x1ff, %eax
    subl %ecx, %eax
    shll $21,%eax
    orl  $0x83, %eax
    movl %eax, (%esi)
    addl $8,%esi
    loop fill_pdp

    /* Set PDPT in CR3 */
    movl $pdpt, %eax
    mov %eax, %cr3

    /* Enabling PAE*/
    mov %cr4, %eax
    btsl $(5), %eax
    mov %eax, %cr4

    /* Enabling Paging and from this point we are in
    32 bit compatibility mode */
    mov %cr0,  %eax
    btsl $(31), %eax
    mov %eax,  %cr0

#elif X86_LEGACY
    /* map the first 16MB 1:1 with 4KB pages and again at 0x8000.0000 */

    /* set up 4 page tables worth of entries */
    movl $PHYS(kernel_pt), %edi
    movl $1024*4,%ecx
    movl $X86_KERNEL_PT_FLAGS, %eax

.Lfill_pt:
    movl %eax, (%edi)
    addl $4, %edi
    addl $4096, %eax
    loop .Lfill_pt

    /* set up the page dir with 4 entries at 0 and 0x8000.0000 pointing
     * to 4 page tables that will map physical address 0 - 16MB
     */
    movl $PHYS(kernel_pd), %esi
    movl $PHYS(kernel_pd) + 512*4, %edi
    movl $PHYS(kernel_pt) + X86_KERNEL_PT_FLAGS, %eax
    movl %eax, (%esi)
    movl %eax, (%edi)
    addl $4096, %eax
    movl %eax, 4(%esi)
    movl %eax, 4(%edi)
    addl $4096, %eax
    movl %eax, 8(%esi)
    movl %eax, 8(%edi)
    addl $4096, %eax
    movl %eax, 12(%esi)
    movl %eax, 12(%edi)

    /* Set PD in CR3 */
    movl $PHYS(kernel_pd), %eax
    mov %eax, %cr3

    /* Enabling Paging and from this point we are in */
    mov %cr0,  %eax
    btsl $(31), %eax
    mov %eax, %cr0
#else
    /* map the first 1GB 1:1 using 4MB pages */
    movl $PHYS(kernel_pd), %esi
    movl $0x100, %ecx
    xor  %eax, %eax

.Lfill_pd:
    mov  %eax, %edx
    orl  $X86_KERNEL_PD_LP_FLAGS, %edx
    movl %edx, (%esi)
    addl $4, %esi
    addl $0x00400000, %eax
    loop .Lfill_pd

    /* map the first 1GB to KERNEL_ASPACE_BASE */
    movl $(PHYS(kernel_pd) + 0x800), %esi
    movl $0x100, %ecx
    xor  %eax, %eax

.Lfill_pd2:
    mov  %eax, %edx
    orl  $X86_KERNEL_PD_LP_FLAGS, %edx
    movl %edx, (%esi)
    addl $4, %esi
    addl $0x00400000, %eax
    loop .Lfill_pd2

    /* Set PD in CR3 */
    movl $PHYS(kernel_pd), %eax
    mov %eax, %cr3

    /* Enabling Paging and from this point we are in */
    mov %cr4, %eax
    orl $0x10, %eax
    mov %eax, %cr4

    mov %cr0,  %eax
    btsl $(31), %eax
    mov %eax, %cr0
#endif

    /* load the high kernel stack */
    movl $(_kstack + 4096), %esp

    /* reload the high gdtr */
    lgdt PHYS(_gdtr)

    /* branch to the high address */
    movl $main_lk, %eax
    jmp *%eax

main_lk:
    /* set up the idt */
    call setup_idt

    /* call the main module */
    call lk_main
0:                          /* just sit around waiting for interrupts */
    hlt                     /* interrupts will unhalt the processor */
    pause
    jmp 0b                  /* so jump back to halt to conserve power */
