// Rom layout and bios assembler to C interface.
//
// Copyright (C) 2008-2012  Kevin O'Connor <kevin@koconnor.net>
// Copyright (C) 2002  MandrakeSoft S.A.
//
// This file may be distributed under the terms of the GNU LGPLv3 license.

#include "asm-offsets.h" // BREGS_*
#include "config.h" // CONFIG_*
#include "entryfuncs.S" // ENTRY_*
#include "hw/rtc.h" // CMOS_RESET_CODE
#include "x86.h" // CR0_*

        .code16


/****************************************************************
 * 16bit / 32bit call trampolines
 ****************************************************************/

// Place CPU into 32bit mode from 16bit mode.
// %edx = return location (in 32bit mode)
// Clobbers: ecx, flags, segment registers, cr0, idt/gdt
        DECLFUNC transition32
        .global transition32_nmi_off
transition32:
        // Disable irqs (and clear direction flag)
        cli
        cld

        // Disable nmi
        movl %eax, %ecx
        movl $CMOS_RESET_CODE|NMI_DISABLE_BIT, %eax
        outb %al, $PORT_CMOS_INDEX
        inb $PORT_CMOS_DATA, %al

        // enable a20
        inb $PORT_A20, %al
        orb $A20_ENABLE_BIT, %al
        outb %al, $PORT_A20
        movl %ecx, %eax

transition32_nmi_off:
        // Set segment descriptors
        lidtw %cs:pmode_IDT_info
        lgdtw %cs:rombios32_gdt_48

        // Enable protected mode
        movl %cr0, %ecx
        andl $~(CR0_PG|CR0_CD|CR0_NW), %ecx
        orl $CR0_PE, %ecx
        movl %ecx, %cr0

        // start 32bit protected mode code
        ljmpl $SEG32_MODE32_CS, $(BUILD_BIOS_ADDR + 1f)

        .code32
        // init data segments
1:      movl $SEG32_MODE32_DS, %ecx
        movw %cx, %ds
        movw %cx, %es
        movw %cx, %ss
        movw %cx, %fs
        movw %cx, %gs

        jmpl *%edx
        .code16

// Place CPU into 16bit mode from 32bit mode.
// %edx = return location (in 16bit mode)
// Clobbers: ecx, flags, segment registers, cr0, idt/gdt
        DECLFUNC transition16
        .global transition16big
        .code32
transition16:
        // Reset data segment limits
        movl $SEG32_MODE16_DS, %ecx
        movw %cx, %ds
        movw %cx, %es
        movw %cx, %ss
        movw %cx, %fs
        movw %cx, %gs

        // Jump to 16bit mode
        ljmpw $SEG32_MODE16_CS, $1f

transition16big:
        movl $SEG32_MODE16BIG_DS, %ecx
        movw %cx, %ds
        movw %cx, %es
        movw %cx, %ss
        movw %cx, %fs
        movw %cx, %gs

        ljmpw $SEG32_MODE16BIG_CS, $1f

        .code16
        // Disable protected mode
1:      movl %cr0, %ecx
        andl $~CR0_PE, %ecx
        movl %ecx, %cr0

        // far jump to flush CPU queue after transition to real mode
        ljmpw $SEG_BIOS, $2f

        // restore IDT to normal real-mode defaults
2:      lidtw %cs:rmode_IDT_info

        // Clear segment registers
        xorw %cx, %cx
        movw %cx, %fs
        movw %cx, %gs
        movw %cx, %es
        movw %cx, %ds
        movw %cx, %ss  // Assume stack is in segment 0

        jmpl *%edx


/****************************************************************
 * External calling trampolines
 ****************************************************************/

// Far call a 16bit function from 16bit mode with a specified cpu register state
// %eax = address of struct bregs, %edx = segment of struct bregs
// Clobbers: %e[bc]x, %e[ds]i, flags
        DECLFUNC __farcall16
__farcall16:
        // Save %edx/%eax, %ebp
        pushl %ebp
        pushl %eax
        pushl %edx

        // Setup for iretw call
        movl %edx, %ds
        pushw %cs
        pushw $1f                       // return point
        pushw BREGS_flags(%eax)         // flags
        pushl BREGS_code(%eax)          // CS:IP

        // Load calling registers and invoke call
        RESTOREBREGS_DSEAX
        iretw                           // XXX - just do a lcalll
1:
        // Store flags, es, eax
        pushfw
        cli
        cld
        pushw %ds
        pushl %eax
        movw 0x08(%esp), %ds
        movl 0x0c(%esp), %eax
        SAVEBREGS_POP_DSEAX
        popw BREGS_flags(%eax)
        movw %ss, %cx
        movw %cx, %ds                   // Restore %ds == %ss

        // Remove %edx/%eax, restore %ebp
        popl %edx
        popl %eax
        popl %ebp

        retl

// IRQ trampolines
        .macro IRQ_TRAMPOLINE num
        DECLFUNC irq_trampoline_0x\num
        irq_trampoline_0x\num :
        int $0x\num
        lretw
        .endm

        IRQ_TRAMPOLINE 02
        IRQ_TRAMPOLINE 05
        IRQ_TRAMPOLINE 10
        IRQ_TRAMPOLINE 13
        IRQ_TRAMPOLINE 15
        IRQ_TRAMPOLINE 16
        IRQ_TRAMPOLINE 18
        IRQ_TRAMPOLINE 19
        IRQ_TRAMPOLINE 1b
        IRQ_TRAMPOLINE 1c
        IRQ_TRAMPOLINE 4a


/****************************************************************
 * Misc. entry points.
 ****************************************************************/

// Entry point for QEMU smi interrupts.
        DECLFUNC entry_smi
entry_smi:
        // Transition to 32bit mode.
        movl $1f + BUILD_BIOS_ADDR, %edx
        jmp transition32_nmi_off
        .code32
1:      movl $BUILD_SMM_ADDR + 0x8000, %esp
        calll _cfunc32flat_handle_smi - BUILD_BIOS_ADDR
        rsm
        .code16

// Entry point for QEMU smp sipi interrupts.
        DECLFUNC entry_smp
entry_smp:
        // Transition to 32bit mode.
        cli
        cld
        movl $2f + BUILD_BIOS_ADDR, %edx
        jmp transition32_nmi_off
        .code32
        // Acquire lock and take ownership of shared stack
1:      rep ; nop
2:      lock btsl $0, SMPLock
        jc 1b
        movl SMPStack, %esp
        // Call handle_smp
        calll _cfunc32flat_handle_smp - BUILD_BIOS_ADDR
        // Release lock and halt processor.
        movl $0, SMPLock
3:      hlt
        jmp 3b
        .code16

// Resume (and reboot) entry point - called from entry_post
        DECLFUNC entry_resume
entry_resume:
        // Disable interrupts
        cli
        cld
        // Use the ExtraStack in low mem.
        movl $_zonelow_seg, %eax
        movw %ax, %ds
        movw %ax, %ss
        movl $ExtraStack + BUILD_EXTRA_STACK_SIZE, %esp
        // Call handler.
        jmp handle_resume

// PMM entry point
        DECLFUNC entry_pmm
entry_pmm:
        pushl %esp              // Backup %esp, then clear high bits
        movzwl %sp, %esp
        pushfl                  // Save registers clobbered by C code
        cli
        cld
        PUSHBREGS
        movl %ss, %ecx          // Move %ss to %ds
        movw %cx, %ds
        shll $4, %ecx
        movl $_cfunc32flat_handle_pmm, %eax // Setup: call32(handle_pmm, args, -1)
        leal PUSHBREGS_size+12(%esp, %ecx), %edx // %edx points to start of args
        movl $-1, %ecx
        calll __call32
        movw %ax, BREGS_eax(%esp)       // Modify %ax:%dx to return %eax
        shrl $16, %eax
        movw %ax, BREGS_edx(%esp)
        POPBREGS
        popfl
        popl %esp
        lretw

// PnP entry points
        DECLFUNC entry_pnp_real
        .global entry_pnp_prot
entry_pnp_prot:
        pushl %esp
        jmp 1f
entry_pnp_real:
        pushl %esp              // Backup %esp, then clear high bits
        movzwl %sp, %esp
1:
        pushfl                  // Save registers clobbered by C code
        cli
        cld
        PUSHBREGS
        movw %ss, %cx           // Move %ss to %ds
        movw %cx, %ds
        leal PUSHBREGS_size+12(%esp), %eax  // %eax points to start of u16 args
        calll handle_pnp
        movw %ax, BREGS_eax(%esp)   // Modify %eax to return %ax
        POPBREGS
        popfl
        popl %esp
        lretw

// APM entry points
        DECLFUNC entry_apm16
entry_apm16:
        pushfw          // save flags
        pushl %eax      // dummy
        ENTRY_ARG handle_apm
        addw $4, %sp    // pop dummy
        popfw           // restore flags
        lretw

        DECLFUNC entry_apm32
        .code32
entry_apm32:
        pushfl
        pushl %gs
        pushl %cs               // Move second descriptor after %cs to %gs
        addl $16, (%esp)
        popl %gs
        ENTRY_ARG_ESP _cfunc32seg_handle_apm
        popl %gs
        popfl
        lretl
        .code16

// PCI-BIOS entry points
        DECLFUNC entry_pcibios32
        .code32
entry_pcibios32:
        pushfl
        pushl %gs               // Backup %gs and set %gs=%ds
        pushl %ds
        popl %gs
        ENTRY_ARG_ESP _cfunc32seg_handle_pcibios
        popl %gs
        popfl
        lretl
        .code16

        DECLFUNC entry_pcibios16
entry_pcibios16:
        ENTRY_ARG handle_pcibios
        iretw

// int 1589 entry point
        DECLFUNC entry_1589
entry_1589:
        ENTRY_ARG handle_1589
        iretw

// BIOS32 support
        DECLFUNC entry_bios32
        .code32
entry_bios32:
        pushfl
#if CONFIG_PCIBIOS
        // Check for PCI-BIOS request
        cmpl $0x49435024, %eax // $PCI
        jne 1f
        movl $BUILD_BIOS_ADDR, %ebx
        movl $BUILD_BIOS_SIZE, %ecx
        movl $entry_pcibios32, %edx
        xorb %al, %al
        jmp 2f
#endif
        // Unknown request
1:      movb $0x80, %al
        // Return to caller
2:      popfl
        lretl
        .code16

// 32bit elf entry point
        DECLFUNC entry_elf
        .code32
entry_elf:
        cli
        cld
        movl %eax, entry_elf_eax
        movl %ebx, entry_elf_ebx
        lidtl (BUILD_BIOS_ADDR + pmode_IDT_info)
        lgdtl (BUILD_BIOS_ADDR + rombios32_gdt_48)
        movl $SEG32_MODE32_DS, %eax
        movw %ax, %ds
        movw %ax, %es
        movw %ax, %fs
        movw %ax, %gs
        movw %ax, %ss
        movl $BUILD_STACK_ADDR, %esp
        ljmpl $SEG32_MODE32_CS, $_cfunc32flat_handle_post
        .code16

// UEFI Compatibility Support Module (CSM) entry point
        DECLFUNC entry_csm
entry_csm:
        // Backup register state
        pushfw
        cli
        cld
        pushl %eax                      // dummy
        PUSHBREGS

        // Backup stack location and convert to a "flat pointer"
        movl %ss, %eax
        movw %ax, BREGS_code+2(%esp)    // Store %ss in bregs->code.seg
        shll $4, %eax
        addl %esp, %eax

        // Change to BUILD_STACK_ADDR stack and call handle_csm(bregs)
        ENTRY_INTO32 _cfunc32flat_handle_csm

        DECLFUNC __csm_return
        .code32
__csm_return:
        movl $1f, %edx
        jmp transition16big
        .code16

        // Switch back to original stack
1:      movzwl BREGS_code+2(%eax), %edx
        movl %edx, %ecx
        shll $4, %ecx
        subl %ecx, %eax
        movl %edx, %ss
        movl %eax, %esp

        // Restore register state and return.
        POPBREGS
        addw $4, %sp                    // pop dummy
        popfw
        lretw

// Serial console "hooked vga" entry point
        DECLFUNC entry_sercon
entry_sercon:
        // Setup for chain loading to real vga handler
        pushfw
        pushl %cs:sercon_real_vga_handler

        // Set %ds to varlow segment
        cli
        cld
        pushw %ds
        pushl %eax
        movl $_zonelow_seg, %eax
        movl %eax, %ds

        // Test if the sercon handler can be called
        movl %esp, %eax         // Test for broken x86emu
        pushl $1f
        retl
1:      cmpl %esp, %eax
        jne 4f
        cmpb $0, sercon_enable  // Test that sercon is enabled
        je 3f

        // call handle_sercon
        popl %eax
        popw %ds
2:      pushl $handle_sercon
#if CONFIG_ENTRY_EXTRASTACK
        jmp irqentry_arg_extrastack
#else
        jmp irqentry_arg
#endif

        // sercon disabled - check for legacy text modeset and otherwise exit
3:      popl %eax
        popw %ds
        cmpw $0x0007, %ax
        jle 2b
        iretw

        // Running on broken x86emu - restore stack and exit
4:      movl %eax, %esp
        popl %eax
        popw %ds
        iretw


/****************************************************************
 * Interrupt entry points
 ****************************************************************/

        // Main entry point for hardware interrupts handled on extra stack
        DECLFUNC irqentry_extrastack
irqentry_extrastack:
        cli
        cld
        pushw %ds               // Set %ds:%eax to space on ExtraStack
        pushl %eax
        movl $_zonelow_seg, %eax
        movl %eax, %ds
        movl StackPos, %eax
        subl $PUSHBREGS_size+8, %eax
        SAVEBREGS_POP_DSEAX
        popl %ecx
        movl %esp, PUSHBREGS_size(%eax)
        movw %ss, PUSHBREGS_size+4(%eax)

        movw %ds, %dx           // Setup %ss/%esp and call function
        movw %dx, %ss
        movl %eax, %esp
        calll *%ecx

        movl %esp, %eax         // Restore registers and return
        movw PUSHBREGS_size+4(%eax), %ss
        movl PUSHBREGS_size(%eax), %esp
        RESTOREBREGS_DSEAX
        iretw

        // Main entry point for software interrupts handled on extra stack
        DECLFUNC irqentry_arg_extrastack
irqentry_arg_extrastack:
        cli
        cld
        pushw %ds               // Set %ds:%eax to space on ExtraStack
        pushl %eax
        movl $_zonelow_seg, %eax
        movl %eax, %ds
        movl StackPos, %eax
        subl $PUSHBREGS_size+16, %eax
        SAVEBREGS_POP_DSEAX     // Save registers on extra stack
        popl %ecx
        movl %esp, PUSHBREGS_size+8(%eax)
        movw %ss, PUSHBREGS_size+12(%eax)
        popl BREGS_code(%eax)
        popw BREGS_flags(%eax)

        movw %ds, %dx           // Setup %ss/%esp and call function
        movw %dx, %ss
        movl %eax, %esp
        calll *%ecx

        movl %esp, %eax         // Restore registers and return
        movw PUSHBREGS_size+12(%eax), %ss
        movl PUSHBREGS_size+8(%eax), %esp
        popl %edx
        popw %dx
        pushw BREGS_flags(%eax)
        pushl BREGS_code(%eax)
        RESTOREBREGS_DSEAX
        iretw

        // Main entry point for software interrupts (using caller's stack)
        DECLFUNC irqentry_arg
irqentry_arg:
        ENTRY_ARG_ST
        iretw

        // Helper macros for hardware interrupt declaration
        .macro IRQ_ENTRY num
        .global entry_\num
        entry_\num :
        pushl $ handle_\num
        jmp irqentry_extrastack
        .endm

        .macro DECL_IRQ_ENTRY num
        DECLFUNC entry_\num
        IRQ_ENTRY \num
        .endm

        // Helper macros for software interrupt declaration
        .macro IRQ_ENTRY_ARG num
        .global entry_\num
        entry_\num :
        pushl $ handle_\num
#if CONFIG_ENTRY_EXTRASTACK
        jmp irqentry_arg_extrastack
#else
        jmp irqentry_arg
#endif
        .endm

        .macro DECL_IRQ_ENTRY_ARG num
        DECLFUNC entry_\num
        IRQ_ENTRY_ARG \num
        .endm

        // Various entry points (that don't require a fixed location).
        DECL_IRQ_ENTRY_ARG 13
        DECL_IRQ_ENTRY 76
        DECL_IRQ_ENTRY 70
        DECL_IRQ_ENTRY 74
        DECL_IRQ_ENTRY 75
        DECL_IRQ_ENTRY hwpic1
        DECL_IRQ_ENTRY hwpic2

        // int 18/19 are special - they reset stack and call into 32bit mode.
        DECLFUNC entry_19
entry_19:
        ENTRY_INTO32 _cfunc32flat_handle_19

        DECLFUNC entry_18
entry_18:
        ENTRY_INTO32 _cfunc32flat_handle_18


/****************************************************************
 * Fixed position entry points
 ****************************************************************/

        // Specify a location in the fixed part of bios area.
        .macro ORG addr
        .section .fixedaddr.\addr
        .endm

        ORG 0xe05b
entry_post:
        cmpl $0, %cs:HaveRunPost                // Check for resume/reboot
        jnz entry_resume
        ENTRY_INTO32 _cfunc32flat_handle_post   // Normal entry point

        ORG 0xe2c3
        .global entry_02
entry_02:
        ENTRY handle_02  // NMI handler does not switch onto extra stack
        iretw

        ORG 0xe3fe
        .global entry_13_official
entry_13_official:
        jmp entry_13

        // 0xe401 - OldFDPT in misc.c

        ORG 0xe6f2
        .global entry_19_official
entry_19_official:
        jmp entry_19

        // 0xe6f5 - BIOS_CONFIG_TABLE in misc.c

        // 0xe729 - BaudTable in misc.c

        ORG 0xe739
        IRQ_ENTRY_ARG 14

        ORG 0xe82e
        IRQ_ENTRY_ARG 16

        ORG 0xe987
        IRQ_ENTRY 09

        ORG 0xec59
        IRQ_ENTRY_ARG 40

        ORG 0xef57
        IRQ_ENTRY 0e

        // 0xefc7 - diskette_param_table in misc.c

        ORG 0xefd2
        IRQ_ENTRY_ARG 17

        ORG 0xf045
entry_10_0x0f:
        // XXX - INT 10 Functions 0-Fh Entry Point
        iretw

        ORG 0xf065
entry_10:
        iretw

        // 0xf0a4 - VideoParams in misc.c

        ORG 0xf841
        IRQ_ENTRY_ARG 12

        ORG 0xf84d
        IRQ_ENTRY_ARG 11

        ORG 0xf859
        .global entry_15_official
entry_15_official:
        cmpb $0x89, %ah
        je entry_1589           // 1589 calls return in protected mode
        IRQ_ENTRY_ARG 15

        // 0xfa6e - vgafont8 in font.c

        ORG 0xfe6e
        .global entry_1a_official
entry_1a_official:
        cmpb $0xb1, %ah
        je entry_pcibios16      // PCIBIOS calls can be in protected mode
        IRQ_ENTRY_ARG 1a

        ORG 0xfea5
        IRQ_ENTRY 08

        // 0xfef3 - InitVectors in misc.c

        ORG 0xff53
        .global entry_iret_official
entry_iret_official:
        iretw

        ORG 0xff54
        IRQ_ENTRY_ARG 05

        ORG 0xfff0 // Power-up Entry Point
        .global reset_vector
reset_vector:
        ljmpw $SEG_BIOS, $entry_post

        // 0xfff5 - BiosDate in misc.c

        // 0xfffe - BiosModelId in misc.c

        // 0xffff - BiosChecksum in misc.c

        .end
