/*
 * Copyright (C) 2005 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <machine/cpu-features.h>

/*
 * NOTE: these atomic operations are SMP safe on all architectures, 
 * except swap(), see below.
 */

	.text
	.align
	
    .global android_atomic_write

	.global android_atomic_inc
	.global android_atomic_dec
    
	.global android_atomic_add
	.global android_atomic_and
	.global android_atomic_or
    
    .global android_atomic_swap
	
	.global android_atomic_cmpxchg

/*
 * ----------------------------------------------------------------------------
 * int __kernel_cmpxchg(int oldval, int newval, int *ptr)
 * clobbered: r3, ip, flags
 * return 0 if a swap was made, non-zero otherwise.
 */ 

   .equ     kernel_cmpxchg, 0xFFFF0FC0
   .equ     kernel_atomic_base, 0xFFFF0FFF

/*
 * ----------------------------------------------------------------------------
 * android_atomic_write
 * input: r0=value, r1=address
 * output: void
 */
 
android_atomic_write:
    stmdb   sp!, {r4, lr}
    mov     r2, r1
    mov     r1, r0
1: @ android_atomic_write
    ldr     r0, [r2]
    mov     r3, #kernel_atomic_base
#ifdef __ARM_HAVE_PC_INTERWORK
    add     lr, pc, #4
    add     pc, r3, #(kernel_cmpxchg - kernel_atomic_base)
#else
    add     r3, r3, #(kernel_cmpxchg - kernel_atomic_base)
    mov     lr, pc
    bx      r3
#endif
    bcc     1b
    ldmia   sp!, {r4, lr}
    bx      lr

/*
 * ----------------------------------------------------------------------------
 * android_atomic_inc
 * input: r0 = address
 * output: r0 = old value
 */
 
android_atomic_inc:
    stmdb   sp!, {r4, lr}
    mov     r2, r0
1: @ android_atomic_inc
    ldr     r0, [r2]
    mov     r3, #kernel_atomic_base
#ifdef __ARM_HAVE_PC_INTERWORK
    add     lr, pc, #4
    add     r1, r0, #1
    add     pc, r3, #(kernel_cmpxchg - kernel_atomic_base)
#else
    add     r1, r0, #1
    add     r3, r3, #(kernel_cmpxchg - kernel_atomic_base)
    mov     lr, pc
    bx      r3
#endif
    bcc     1b
    sub     r0, r1, #1
    ldmia   sp!, {r4, lr}
    bx      lr
  
/*
 * ----------------------------------------------------------------------------
 * android_atomic_dec
 * input: r0=address
 * output: r0 = old value
 */
 
android_atomic_dec:
    stmdb   sp!, {r4, lr}
    mov     r2, r0
1: @ android_atomic_dec
    ldr     r0, [r2]
    mov     r3, #kernel_atomic_base
#ifdef __ARM_HAVE_PC_INTERWORK
    add     lr, pc, #4
    sub     r1, r0, #1
    add     pc, r3, #(kernel_cmpxchg - kernel_atomic_base)
#else
    sub     r1, r0, #1
    add     r3, r3, #(kernel_cmpxchg - kernel_atomic_base)
    mov     lr, pc
    bx      r3
#endif
    bcc     1b
    add     r0, r1, #1
    ldmia   sp!, {r4, lr}
    bx      lr
    
/*
 * ----------------------------------------------------------------------------
 * android_atomic_add
 * input: r0=value, r1=address
 * output: r0 = old value
 */

android_atomic_add:
    stmdb   sp!, {r4, lr}
    mov     r2, r1
    mov     r4, r0
1: @ android_atomic_add
    ldr     r0, [r2]
    mov     r3, #kernel_atomic_base
#ifdef __ARM_HAVE_PC_INTERWORK
    add     lr, pc, #4
    add     r1, r0, r4
    add     pc, r3, #(kernel_cmpxchg - kernel_atomic_base)
#else
    add     r1, r0, r4
    add     r3, r3, #(kernel_cmpxchg - kernel_atomic_base)
    mov     lr, pc
    bx      r3
#endif
    bcc     1b
    sub     r0, r1, r4
    ldmia   sp!, {r4, lr}
    bx      lr
    
    
/*
 * ----------------------------------------------------------------------------
 * android_atomic_and
 * input: r0=value, r1=address
 * output: r0 = old value
 */

android_atomic_and:
    stmdb   sp!, {r4, r5, lr}   
    mov     r2, r1              /* r2 = address */
    mov     r4, r0              /* r4 = the value */
1: @ android_atomic_and
    ldr     r0, [r2]            /* r0 = address[0] */
    mov     r3, #kernel_atomic_base
#ifdef __ARM_HAVE_PC_INTERWORK
    add     lr, pc, #8
    mov     r5, r0              /* r5 = save address[0] */
    and     r1, r0, r4          /* r1 = new value */
    add     pc, r3, #(kernel_cmpxchg - kernel_atomic_base)  /* call cmpxchg() */
#else
    mov     r5, r0              /* r5 = save address[0] */
    and     r1, r0, r4          /* r1 = new value */
    add     r3, r3, #(kernel_cmpxchg - kernel_atomic_base)  /* call cmpxchg() */
    mov     lr, pc
    bx      r3
#endif
    bcc     1b
    mov     r0, r5
    ldmia   sp!, {r4, r5, lr}
    bx      lr
    
/*
 * ----------------------------------------------------------------------------
 * android_atomic_or
 * input: r0=value, r1=address
 * output: r0 = old value
 */

android_atomic_or:
    stmdb   sp!, {r4, r5, lr}   
    mov     r2, r1              /* r2 = address */
    mov     r4, r0              /* r4 = the value */
1: @ android_atomic_or
    ldr     r0, [r2]            /* r0 = address[0] */
    mov     r3, #kernel_atomic_base
#ifdef __ARM_HAVE_PC_INTERWORK
    add     lr, pc, #8
    mov     r5, r0              /* r5 = save address[0] */
    orr     r1, r0, r4          /* r1 = new value */
    add     pc, r3, #(kernel_cmpxchg - kernel_atomic_base)  /* call cmpxchg() */
#else
    mov     r5, r0              /* r5 = save address[0] */
    orr     r1, r0, r4          /* r1 = new value */
    add     r3, r3, #(kernel_cmpxchg - kernel_atomic_base)  /* call cmpxchg() */
    mov     lr, pc
    bx      r3
#endif
    bcc     1b
    mov     r0, r5
    ldmia   sp!, {r4, r5, lr}
    bx      lr

/*
 * ----------------------------------------------------------------------------
 * android_atomic_swap
 * input: r0=value, r1=address
 * output: r0 = old value
 */

/* FIXME: this is not safe on SMP systems 
 * a general way to do it is to use kernel_cmpxchg */

android_atomic_swap:
    swp     r0, r0, [r1]
    bx      lr

/*
 * ----------------------------------------------------------------------------
 * android_atomic_cmpxchg
 * input: r0=oldvalue, r1=newvalue, r2=address
 * output: r0 = 0 (xchg done) or non-zero (xchg not done)
 */

android_atomic_cmpxchg:
    stmdb   sp!, {r4, lr}
    mov     r4, r0          /* r4 = save oldvalue */
1: @ android_atomic_cmpxchg
    mov     r3, #kernel_atomic_base
#ifdef __ARM_HAVE_PC_INTERWORK
    add     lr, pc, #4
    mov     r0, r4          /* r0 = oldvalue */
    add     pc, r3, #(kernel_cmpxchg - kernel_atomic_base)
#else
    mov     r0, r4          /* r0 = oldvalue */
    add     r3, r3, #(kernel_cmpxchg - kernel_atomic_base)
    mov     lr, pc
    bx      r3
#endif
    bcs     2f              /* swap was made. we're good, return. */
    ldr     r3, [r2]        /* swap not made, see if it's because *ptr!=oldvalue */
    cmp     r3, r4
    beq     1b
2: @ android_atomic_cmpxchg
    ldmia   sp!, {r4, lr}
    bx      lr

/*
 * ----------------------------------------------------------------------------
 * android_atomic_cmpxchg_64
 * input: r0-r1=oldvalue, r2-r3=newvalue, arg4 (on stack)=address
 * output: r0 = 0 (xchg done) or non-zero (xchg not done)
 */
/* TODO: NEED IMPLEMENTATION FOR THIS ARCHITECTURE */
