asm {/* See ::/Doc/Boot.DD.
ZealOS starts in real, calls some BIOS routines, switches to 32 bit, and 64 bit mode and continues in ZealC at KMain().

The boot loader jumps here in real-mode (16-bit).
It actually jumps to the CZXE header which is placed just before this by the compiler.
The header begins with a short jmp to the start of this file's code which begins with the following small jump past some data.

This file is first in the Kernel image because it is #included first.  Kernel.PRJ
*/
USE16
SYS_KERNEL:: //This must match CKernel.
        JMP     I16 CORE0_16BIT_INIT

//************************************
//  ASM Global variables required for 16-bit start-up
        ALIGN   4, OC_NOP

SYS_BOOT_SRC::              DU32    BOOT_SRC_NULL;
SYS_BOOT_BLK::              DU32    0;
SYS_BOOT_PATCH_TABLE_BASE:: DU32    0;
SYS_RUN_LEVEL::             DU32    0;

#exe
{
    StreamPrint("SYS_COMPILE_TIME:: DU64 0x%X;", Now); //See AHCIBootDVDProbeAll
};

#assert SYS_COMPILE_TIME + sizeof(CDate) + sizeof(CZXE) < DVD_BLK_SIZE

MEM_BOOT_BASE::         DU32    0;  //Offset from start used by reboot
MEM_E801::              DU16    0, 0;
MEM_E820::              DU8     MEM_E820_ENTRIES_NUM * sizeof(CMemE820) DUP (0);
MEM_PHYSICAL_SPACE::    DU64    0;
SYS_GDT_PTR::           DU16    sizeof(CGDT) - 1;
                        DU64    0;
SYS_PCI_BUSES::         DU16    0;

        ALIGN   16, OC_NOP

SYS_GDT:: //See CGDT
GDT_NULL:       DU64    0,0;
GDT_BOOT_DS:    DU64    0x00CF92000000FFFF, 0; //Gets patched.
GDT_BOOT_CS:    DU64    0x00CF9A000000FFFF, 0; //Gets patched.
GDT_CS32:       DU64    0x00CF9A000000FFFF, 0;
GDT_CS64:       DU64    0x00209A0000000000, 0; //The Charter says just ring0.
GDT_CS64_RING3: DU64    0x0020FA0000000000, 0; //Ring3, so you can play with.
GDT_DS:         DU64    0x00CF92000000FFFF, 0;
GDT_DS_RING3:   DU64    0x00CFF2000000FFFF, 0;
GDT_TR:         DU8     MP_PROCESSORS_NUM * 16 DUP(0);
GDT_TR_RING3:   DU8     MP_PROCESSORS_NUM * 16 DUP(0);
#assert $ - SYS_GDT == sizeof(CGDT)

SYS_FONT_PTR::  DU32    0;

SYS_VBE_INFO::          DU8     sizeof(CVBEInfo)                        DUP(0);
SYS_VBE_MODES::         DU8     sizeof(CVBEModeShort) * VBE_MODES_NUM   DUP(0);
SYS_VBE_FINAL_MODE::    DU8     sizeof(CVBEMode)                        DUP(0);
SYS_VBE_FINAL_MODE_NUM::DU16    0; //mode number of final mode set
#assert $ - SYS_KERNEL == sizeof(CKernel) - sizeof(CZXE)

TEMP_VBE_MODE:      DU8     sizeof(CVBEMode)        DUP(0);
MAX_VBE_MODE:       DU8     sizeof(CVBEModeShort)   DUP(0);
MAX_SCREEN_HEIGHT:  DU16    0;

#exe
{
    StreamPrint("SCREEN_WIDTH:  DU16        %d;"
                "SCREEN_HEIGHT: DU16        %d;", kernel_config->screen_width, kernel_config->screen_height);
};

//************************************
CORE0_16BIT_INIT::
//EAX is SYS_BOOT_SRC. (Value passed from boot block, BootHD, BootDVD, & BootRAM.)
        MOV     ECX, EAX
        MOV     AX, (BOOT_RAM_LIMIT - BOOT_STACK_SIZE) / 16
        MOV     SS, AX
        MOV     SP, BOOT_STACK_SIZE
        PUSH    ECX         //Will be SYS_BOOT_SRC. See BootHD, BootDVD & BootRAM.
        PUSH    EBX
        CALL    U16 GET_IP
GET_IP: POP     BX
        SUB     BX, GET_IP
        SHR     BX, 4
        MOV     AX, CS
        ADD     AX, BX
        PUSH    AX
        PUSH    U16 @@04
        RETF

@@04:   STI
        MOV     AX, CS
        MOV     DS, AX
        MOV     U32 [SYS_RUN_LEVEL], RLF_16BIT

//Our variables are on the data segment, but VBE functions require ES.
//moving DS into ES, while preserving ES
        PUSH    ES
        PUSH    DS
        POP     ES

//Get VBE implementation information
        MOV     AX, 0x4F00
        MOV     DI, SYS_VBE_INFO
        MOV     CVBEInfo.signature[DI], 'VBE2' //set to 'VBE2' to use VBE 2.0 functionality
        INT     0x10
        POP     ES
        CMP     AX, 0x004F
        JE      @@05
        JMP     $   //Freeze system if VBE not supported

@@05:

/*Loop variables:
 DI <- Temporary storage for mode information
 CX <- Mode number
 DX <- mode array
 GS <- Segment for video modes list
 SI <- Offset for video modes list
*/
//Obtain segment:offset of list of potential video modes
        MOV     AX, SYS_VBE_INFO
        MOV     SI, CVBEInfo.video_modes[AX]
        MOV     GS, CVBEInfo.video_modes+2[AX]
        MOV     DI, TEMP_VBE_MODE
        MOV     DX, SYS_VBE_MODES

@@06: //Loop through all the mode numbers
        MOV     AX, GS:[SI]
        CMP     AX, 0xFFFF //FFFF signifies the end of the list
        JE      @@08

        ADD     SI, 2 //Increment pointer to read next U16 mode

        MOV     CX, AX
        BTS     CX, 14 //Set linear framebuffer bit in the mode number we are about to pass to the BIOS below
        PUSH    ES
        PUSH    DS
        POP     ES
//Get mode information for mode number
        MOV     AX, 0x4F01
        INT     0x10
        POP     ES
        CMP     AX, 0x004F
        JNE     @@06 //if call to get mode information failed

//filter everything but 32-bit color
        MOV     AL, CVBEMode.bpp[DI]
        CMP     AL, 32
        JNE     @@06

//Check if the mode is actually supported
        MOV     AX, CVBEMode.attributes[DI]
        AND     AX, 0x91 //bit 0 = supported, bit 4 = graphics mode, bit 7 = linear framebuffer
        CMP     AX, 0x91
        JNE     @@06

//Only want memory model of packed pixel or direct color (RGB)
//      MOV     AX, CVBEMode.memory_model[DI]
//      CMP     AX, 4
//      JNE     @@06
//      CMP     AX, 6
//      JNE     @@06
//Copy information about this mode into an element of the mode array
        MOV     BX, CVBEMode.height[DI]
        MOV     CVBEModeShort.height[DX], BX
        CMP     BX, [MAX_SCREEN_HEIGHT]
        JL      @@07

        MOV     [MAX_SCREEN_HEIGHT], BX
        MOV     [MAX_VBE_MODE],      CX

@@07:   MOV     AX, CVBEMode.width[DI]
        MOV     CVBEModeShort.width[DX], AX
//      MOV     EAX, CVBEMode.max_pixel_clock[DI]
//      MOV     CVBEModeShort.max_pixel_clock[DX], EAX

        MOV     CVBEModeShort.mode_num[DX], CX
        ADD     DX, sizeof(CVBEModeShort) //next array element

//Check if width and height match
        CMP     AX, [SCREEN_WIDTH]
        JNE     @@06
        CMP     BX, [SCREEN_HEIGHT]
        JNE     @@06

//If we've made it here we have our mode
        MOV     [SYS_VBE_FINAL_MODE_NUM], CX
        JMP     @@06

@@08: //End of loop
//If there isn't a valid mode set by user through kernel config, set the mode with the biggest height.
        MOV     AX, [SYS_VBE_FINAL_MODE_NUM]
        CMP     AX, 0
        JNE     @@09
        MOV     CX, [MAX_VBE_MODE]
        MOV     [SYS_VBE_FINAL_MODE_NUM], CX

@@09:   PUSH    ES
        PUSH    DS
        POP     ES
//Get mode infomration for the mode we want
        MOV     DI, SYS_VBE_FINAL_MODE
        MOV     CX, [SYS_VBE_FINAL_MODE_NUM]
        MOV     AX, 0x4F01
        INT     0x10
        POP     ES
        CMP     AX, 0x004F
        JNE     @@10 //if called failed

//Set the mode; call takes mode number in BX
        MOV     AX, 0x4F02
        MOV     BX, CX
        INT     0x10
        CMP     AX, 0x004F
        JNE     @@10

        BTS     U32 [SYS_RUN_LEVEL], RLf_VESA
@@10:

//Get pointer to 8x8 VGA ROM font.
        MOV     AX, 0x1130
        MOV     BH, 3
        INT     0x10
        MOV     AX, ES
        SHL     EAX, 16
        MOV     AX, BP
        MOV     U32 [SYS_FONT_PTR], EAX

//Get E801 memory map.
//Output: AX = Memory between 1MiB and 16MiB in KiB (max 0x3C00 == 15 MiB)
//        BX = Memory after 16MiB until first memory hole in 64KiB blocks.
        XOR     CX, CX
        XOR     DX, DX
        MOV     AX, 0xE801
        INT     0x15
        JCXZ    @@12 //if CX and DX are zero, use AX and BX instead.
        MOV     AX, CX
        MOV     BX, DX
@@12:   MOV     U16 [MEM_E801],   AX
        MOV     U16 [MEM_E801+2], BX

//Get E820 memory map.
        MOV     CX,  MEM_E820_ENTRIES_NUM - 1 //Leave one to terminate
        XOR     EBX, EBX
        PUSH    DS
        POP     ES
        MOV     DI,  MEM_E820
@@15:   PUSH    CX
        MOV     EAX, 0xE820
        MOV     ECX, sizeof(CMemE820)
        MOV     EDX, 'PAMS'
        INT     0x15
        JC      @@20
        CMP     EAX, 'PAMS'
        JNE     @@20
        TEST    EBX, EBX
        JZ      @@20
        ADD     DI,  sizeof(CMemE820)
        POP     CX
        LOOP    @@15
        SUB     SP,  2
@@20:   ADD     SP,  2 //if called failed we want to nullify the PUSHed CX value.

//Find how much space to map, start with E801 limit.
        XOR     EAX, EAX
        MOV     AX,  [MEM_E801+2]
        SHL     EAX, 16
        ADD     EAX, SYS_16MEG_AREA_LIMIT
        XOR     EDX, EDX

//Find max of E820 to set mapped space.
        MOV     SI,  MEM_E820
@@25:   MOV     CL,  CMemE820.type[SI]
        TEST    CL,  CL
        JZ      @@35
        MOV     EBX, CMemE820.base  [SI]
        MOV     ECX, CMemE820.base+4[SI]
        ADD     EBX, CMemE820.len   [SI]
        ADC     ECX, CMemE820.len+4 [SI]
        SUB     EBX, EAX
        SBB     ECX, EDX
        JC      @@30
        MOV     EAX, CMemE820.base  [SI]
        MOV     EDX, CMemE820.base+4[SI]
        ADD     EAX, CMemE820.len   [SI]
        ADC     EDX, CMemE820.len+4 [SI]
@@30:   ADD     SI,  sizeof(CMemE820)
        JMP     @@25

@@35:   MOV     [MEM_PHYSICAL_SPACE],   EAX
        MOV     [MEM_PHYSICAL_SPACE+4], EDX

//Get PCI Bus Info
        MOV     U16 [SYS_PCI_BUSES], 256
        XOR     DX, DX
        MOV     AX, 0xB101
        INT     0x1A
        CMP     DX, 'PC'
        JNE     @@40
        MOV     CH, 0
        INC     CX
        MOV     U16 [SYS_PCI_BUSES], CX
@@40:
        CLI
//Enable A20
        IN      AL, 0x92
        OR      AL, 2
        OUT     0x92, AL

        POP     U32 [SYS_BOOT_BLK]
        POP     U32 [SYS_BOOT_SRC]  //See BootHD, BootDVD, & BootRAM.

        CLD     
        XOR     EAX, EAX
        MOV     AX,  CS
        MOV     DS,  AX
        MOV     ES,  AX
        SHL     EAX, 4

        MOV     U32 [MEM_BOOT_BASE], EAX

        MOV     DX, CS
        SUB     DX, sizeof(CZXE) / 16
#assert !(sizeof(CZXE) & 0xF)
        MOV     GS, DX

        MOV     EDX, EAX
        ADD     EDX, U32 GS:[CZXE.patch_table_offset]
        SUB     EDX, sizeof(CZXE)
        MOV     U32 [SYS_BOOT_PATCH_TABLE_BASE], EDX

        ADD     U32 [GDT_BOOT_DS+2], EAX
        ADD     U32 [GDT_BOOT_CS+2], EAX
        ADD     EAX, I32 SYS_GDT
        MOV     U32 [SYS_GDT_PTR + CSysLimitBase.base], EAX
        LGDT    U32 [SYS_GDT_PTR]

        MOV     EAX, SYS_START_CR0
        MOV_CR0_EAX

/* The assembler doesn't support far jumps so
we hand code it.    16-bit code is not important
enough to fully implement in the assembler.

To complete the switch to 32-bit mode, we have to load
the code segment with a far jump.
*/
        DU8         0x66, 0xEA; //JMP CGDT.boot_cs:CORE0_32BIT_INIT
        DU32        CORE0_32BIT_INIT;
        DU16        CGDT.boot_cs;
#assert $ + 16 <= 0xFFFF
}