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_FRAMEBUFFER_ADDR::      DU64    0;
SYS_FRAMEBUFFER_WIDTH::     DU64    0;
SYS_FRAMEBUFFER_HEIGHT::    DU64    0;
SYS_FRAMEBUFFER_PITCH::     DU64    0;
SYS_FRAMEBUFFER_BPP::       DU8     0;

SYS_SMBIOS_ENTRY::  DU64    0;

SYS_DISK_UUID::         DU64    0, 0;

SYS_BOOT_STACK::    DU32    BOOT_RAM_LIMIT;

SYS_IS_UEFI_BOOTED::    DU8    0;

SYS_FRAMEBUFFER_LIST::  DU8 sizeof(CVideoInfo) * VBE_MODES_NUM  DUP(0);

#assert $ - SYS_KERNEL == sizeof(CKernel) - sizeof(CZXE)

REQUESTED_SCREEN_WIDTH:     DU16    1024;
REQUESTED_SCREEN_HEIGHT:    DU16    768;

VBE_TEMP_MODE:      DU8     sizeof(CVBEMode)    DUP(0);
VBE_INFO:           DU8     sizeof(CVBEInfo)    DUP(0);
VBE_FINAL_MODE_NUM: DU16    0; // gets set to final result mode number
VBE_STD_MODE_NUM:   DU16    0; // gets set to mode number for standard 1024x768

//************************************
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 @@05
        RETF

@@05:   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, VBE_INFO
        MOV     CVBEInfo.signature[DI], 'VBE2' //set to 'VBE2' to use VBE 2.0 functionality
        INT     0x10
        POP     ES
        CMP     AX, 0x004F
        JE      @@10
        JMP     $   //Freeze system if VBE not supported

@@10:

/*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, VBE_INFO
        MOV     SI, CVBEInfo.video_modes[AX]
        MOV     GS, CVBEInfo.video_modes+2[AX]
        MOV     DX, SYS_FRAMEBUFFER_LIST
        MOV     DI, VBE_TEMP_MODE

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

        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     @@15 //if call to get mode information failed

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

//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     @@15

//Only want memory model of packed pixel or direct color (RGB)
        MOV     AX, CVBEMode.memory_model[DI]
        CMP     AX, 4
        JE      @@18
        CMP     AX, 6
        JNE     @@15

//Copy information about this mode into AX and BX
@@18:   MOV     BX, CVBEMode.height[DI]
@@20:   MOV     AX, CVBEMode.width[DI]

// Copy info into DX SYS_FRAMEBUFFER_LIST

        MOV     CVideoInfo.height[DX], BX
        MOV     CVideoInfo.width[DX], AX
        ADD     DX, sizeof(CVideoInfo) // next array element

//Check if width and height match standard 1024x768
//If they match, set VBE_STD_MODE_NUM to this mode num
        CMP     AX, 1024
        JNE     @@23
        CMP     BX, 768
        JNE     @@23
        MOV     [VBE_STD_MODE_NUM], CX

//Check if width and height match requested resolution
@@23:   CMP     AX, [REQUESTED_SCREEN_WIDTH]
        JNE     @@15
        CMP     BX, [REQUESTED_SCREEN_HEIGHT]
        JNE     @@15

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

@@25: //End of loop

//If requested resolution wasn't found in VBE mode list,
//use the standard 1024x768 mode as fallback
        MOV     AX, [VBE_FINAL_MODE_NUM]
        CMP     AX, 0
        JNE     @@30

        MOV     CX, [VBE_STD_MODE_NUM]
        MOV     [VBE_FINAL_MODE_NUM], CX

@@30:   PUSH    ES
        PUSH    DS
        POP     ES
//Get mode information for the mode we want
        MOV     DI, VBE_TEMP_MODE
        MOV     CX, [VBE_FINAL_MODE_NUM]
        MOV     AX, 0x4F01
        INT     0x10
        POP     ES
        CMP     AX, 0x004F
        JNE     @@35 //if called failed

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

//Give mode information to kernel
        MOV     EAX, CVBEMode.framebuffer[DI]
        MOV     U32 [SYS_FRAMEBUFFER_ADDR], EAX
        MOV     AX, CVBEMode.height[DI]
        MOV     U16 [SYS_FRAMEBUFFER_HEIGHT], AX
        MOV     AX, CVBEMode.width[DI]
        MOV     U16 [SYS_FRAMEBUFFER_WIDTH], AX
        MOV     AX, CVBEMode.pitch[DI]
        MOV     U16 [SYS_FRAMEBUFFER_PITCH], AX
        MOV     AL, CVBEMode.bpp[DI]
        MOV     U8 [SYS_FRAMEBUFFER_BPP], AL

        BTS     U32 [SYS_RUN_LEVEL], RLf_VESA
@@35:

//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    @@40 //if CX and DX are zero, use AX and BX instead.
        MOV     AX, CX
        MOV     BX, DX
@@40:   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
@@45:   PUSH    CX
        MOV     EAX, 0xE820
        MOV     ECX, sizeof(CMemE820)
        MOV     EDX, 'PAMS'
        INT     0x15
        JC      @@50
        CMP     EAX, 'PAMS'
        JNE     @@50
        TEST    EBX, EBX
        JZ      @@50
        ADD     DI,  sizeof(CMemE820)
        POP     CX
        LOOP    @@45
        SUB     SP,  2
@@50:   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
@@55:   MOV     CL,  CMemE820.type[SI]
        TEST    CL,  CL
        JZ      @@65
        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      @@60
        MOV     EAX, CMemE820.base  [SI]
        MOV     EDX, CMemE820.base+4[SI]
        ADD     EAX, CMemE820.len   [SI]
        ADC     EDX, CMemE820.len+4 [SI]
@@60:   ADD     SI,  sizeof(CMemE820)
        JMP     @@55

@@65:   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     @@70
        MOV     CH, 0
        INC     CX
        MOV     U16 [SYS_PCI_BUSES], CX
@@70:
        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
}