/*
 * Copyright 2013, winocm. <winocm@icloud.com>
 * 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.
 * 
 *   If you are going to use this software in any form that does not involve
 *   releasing the source to this project or improving it, let me know beforehand.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER OR CONTRIBUTORS 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.
 */
/*
 * ARM machine routines
 */

#include <arm/arch.h>
#include <arm/asm_help.h>
#include <assym.s>
#include <mach/arm/asm.h>

/*
 * This is ARMv6 and later ONLY.
 */

#if __ARM_ARCH == 7
#undef EnterThumb
#define EnterThumb EnterARM
#endif

/**
 * lck_mtx_ilk_unlock
 */
EnterARM(lck_mtx_ilk_unlock)
    ldr     r2, [r0]
    bic     r3, r2, #1
    str     r3, [r0]
    b       __enable_preemption
    
/**
 * machine_idle
 *
 * Idle loop.
 */
EnterARM(machine_idle)
    /* See if first we have power saving enabled. */
    LOAD_ADDR(r0, do_power_save)
    ldr     r0, [r0]

    /* Disabled? We leave. */
    teq     r0, #0
    beq     .Lmachine_idle_return

    /* Disable all interrupts and go into wfi mode. */
.Lmachine_idle_wfi:
    cpsid   if
    wfi
    cpsie   if

.Lmachine_idle_return:
    bx      lr

/**
 * machine_signal_idle
 */
EnterThumb(machine_signal_idle)
    bx      lr

/**
 * machine_callstack
 */
EnterThumb(machine_callstack)
    bx      lr


/**
 * machine_trace_thread
 */
EnterARM(machine_trace_thread)
    movs    r0, #0
    bx      lr

/**
 * machine_trace_thread64
 */
EnterARM(machine_trace_thread64)
    movs    r0, #0
    bx      lr

/**
 * ml_set_interrupts_enabled
 *
 * Disable or enable interrupts based on input boolean.
 */
EnterARM(ml_set_interrupts_enabled)
    mrs     r2, cpsr

    cmp     r0, #0
    beq     disable_interrupts

astloop:
    mrc     p15, 0, r0, c13, c0, 4
    ldr     r1, [r0, MACHINE_THREAD_CPU_DATA]
    ldr     r0, [r1, CPU_PENDING_AST]
    
    stmfd   sp!,{r0,r2,r7,lr}
    bl      _get_preemption_level
    cmp     r0, #0
    ldmfd   sp!,{r0,r2,r7,lr}

    bne     enable_interrupts

    and     r0, r0, #4
    cmp     r0, #0
    beq     enable_interrupts

    stmfd   sp!,{r0-r7,lr}
    movs    r0, #7
    movs    r1, #0
    blx     _ast_taken
    ldmfd   sp!,{r0-r7,lr}

    b       astloop

enable_interrupts:
    cpsie   i
    b       out

disable_interrupts:
    cpsid   i
    b       out

out:
    mov     r0, #1
    bic     r0, r0, r2, lsr#7
    bx      lr

/**
 * ml_get_interrupts_enabled
 *
 * Get current interrupt state.
 */
EnterARM(ml_get_interrupts_enabled)
    mrs     r1, cpsr
    mov     r0, #1
    bic     r0, r0, r1, lsr#7
    bx      lr

/**
 * __disable_preemption
 *
 * Disable preemption for a specified thread.
 */
EnterARM(__disable_preemption)
EnterARM(_disable_preemption)
EnterARM(disable_preemption)
    LoadThreadRegister(r12)
    IncrementPreemptLevel(r12, r2)
    bx      lr

/**
 * get_preemption_level
 *
 * Get current thread's preemption level.
 */
EnterARM(get_preemption_level)
    LoadThreadRegister(r12)
    ldr     r0, [r12, MACHINE_THREAD_PREEMPT_COUNT]
    bx      lr

/**
 * __enable_preemption
 *
 * Enable preemption for a specified thread.
 */
EnterARM(__enable_preemption)
EnterARM(_enable_preemption)
EnterARM(enable_preemption)
    /* Get thread ID */
    LoadThreadRegister(r12)
    ldr     r2, [r12, MACHINE_THREAD_PREEMPT_COUNT]
    subs    r2, r2, #1
    strne   r2, [r12, MACHINE_THREAD_PREEMPT_COUNT]
    bxne    lr

    /* Preempt. */
    cpsid   if

    /* Check for interrupts */
    mrs     r3, cpsr
    tst     r3, #0x80
    strne   r2, [r12, MACHINE_THREAD_PREEMPT_COUNT]
    bxne    lr

    /* Get CPU data and add an AST. */
    ldr     r1, [r12, MACHINE_THREAD_CPU_DATA]
    ldr     r0, [r1, CPU_PENDING_AST]
    str     r2, [r12, MACHINE_THREAD_PREEMPT_COUNT]

    ands    r1, r0, #4
    bne     __preempt
    msr     cpsr_cf, r3
    bx      lr

__preempt:
    /* Reenable interrupts */
    cpsie   f
    stmfd   sp!,{r7,lr}
    add     r7, sp, #8
    mov     r0, #7
    mov     r1, #1
    blx     _ast_taken
    ldmfd   sp!,{r7,lr}
    bx      lr

/**
 * current_thread
 *
 * Return the core thread structure of the currently executing thread.
 * The reason this doesn't use the thread register is because the current
 * "executing" thread may not be the one in the register. Just get the one
 * saved from machine_set_current_thread.
 */
EnterARM(current_thread)
    mrc     p15, 0, r0, c13, c0, 4
    bx      lr

/**
 * set_mmu_ttb/ttb_alt/ttbcr
 *
 * Set the translation table base register (and alternate TTBR) to point
 * to the physical address of a translation-table.
 *
 * Set the current TTB control register for a split.
 */
EnterARM(set_mmu_ttb)
    orr     r0, r0, #0x18
    mcr     p15, 0, r0, c2, c0, 0
    bx      lr
EnterARM(set_mmu_ttb_alt)
    orr     r0, r0, #0x18
    mcr     p15, 0, r0, c2, c0, 1
    bx      lr
EnterARM(set_mmu_ttbcr)
    mcr     p15, 0, r0, c2, c0, 2
    bx      lr

/**
 * flush_mmu_single
 *
 * Flush a MVA specific entry from the TLB.
 */
 EnterARM(flush_mmu_single)
    /* Gated. */
    mov     r0, #0
    mcr     p15, 0, r0, c8, c7, 0
    mcr     p15, 0, r0, c7, c5, 0
#if __ARM_ARCH == 7
    isb     sy
    dsb     sy
#else
    mcr     p15, 0, r0, c7, c5, 4
    mcr     p15, 0, r0, c7, c10, 4
#endif
    bx      lr

    /* Shove the lowest 12-bits off the VA */
    mov     r0, r0, lsr #12
    mov     r0, r0, lsl #12
    /* Clean it */
    mcr     p15, 0, r0, c8, c7, 1
#if __ARM_ARCH == 7
    isb     sy
    dsb     sy
#else
    mcr     p15, 0, r0, c7, c5, 4
    mcr     p15, 0, r0, c7, c10, 4
#endif
    bx      lr

/**
 * ml_cause_interrupt
 */
EnterThumb(ml_cause_interrupt)
    bx      lr

/**
 * Halt_system
 *
 * Halt the system entirely.
 */
EnterARM(Halt_system) 
    /* Disable interruptions. */
    cpsid    if

.L_deadloop:
    /* Drain write buffer. */
#if __ARM_ARCH == 7
    dsb      sy
#else
    mov      r0, #0
    mcr      p15, 0, r0, c7, c10, 4
#endif

    /* Wait for interrupts, reduce voltage and power state. */
#if __ARM_ARCH == 7
    wfi
#else
    mov      r0, #0
    mcr      p15, 0, r0, c7, c0, 4
#endif

    /* Try again for a halt. */
    b        .L_deadloop

LOAD_ADDR_GEN_DEF(do_power_save)
