Home | History | Annotate | Download | only in private
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 #ifndef BIONIC_ATOMIC_ARM_H
     17 #define BIONIC_ATOMIC_ARM_H
     18 
     19 #include <machine/cpu-features.h>
     20 
     21 /* Some of the harware instructions used below are not available in Thumb-1
     22  * mode (they are if you build in ARM or Thumb-2 mode though). To solve this
     23  * problem, we're going to use the same technique than libatomics_ops,
     24  * which is to temporarily switch to ARM, do the operation, then switch
     25  * back to Thumb-1.
     26  *
     27  * This results in two 'bx' jumps, just like a normal function call, but
     28  * everything is kept inlined, avoids loading or computing the function's
     29  * address, and prevents a little I-cache trashing too.
     30  *
     31  * However, it is highly recommended to avoid compiling any C library source
     32  * file that use these functions in Thumb-1 mode.
     33  *
     34  * Define three helper macros to implement this:
     35  */
     36 #if defined(__thumb__) && !defined(__thumb2__)
     37 #  define  __ATOMIC_SWITCH_TO_ARM \
     38             "adr r3, 5f\n" \
     39             "bx  r3\n" \
     40             ".align\n" \
     41             ".arm\n" \
     42         "5:\n"
     43 /* note: the leading \n below is intentional */
     44 #  define __ATOMIC_SWITCH_TO_THUMB \
     45             "\n" \
     46             "adr r3, 6f\n" \
     47             "bx  r3\n" \
     48             ".thumb" \
     49         "6:\n"
     50 
     51 #  define __ATOMIC_CLOBBERS   "r3"  /* list of clobbered registers */
     52 
     53 /* Warn the user that ARM mode should really be preferred! */
     54 #  warning Rebuilding this source file in ARM mode is highly recommended for performance!!
     55 
     56 #else
     57 #  define  __ATOMIC_SWITCH_TO_ARM   /* nothing */
     58 #  define  __ATOMIC_SWITCH_TO_THUMB /* nothing */
     59 #  define  __ATOMIC_CLOBBERS        /* nothing */
     60 #endif
     61 
     62 
     63 /* Define a full memory barrier, this is only needed if we build the
     64  * platform for a multi-core device. For the record, using a 'dmb'
     65  * instruction on a Nexus One device can take up to 180 ns even if
     66  * it is completely un-necessary on this device.
     67  *
     68  * NOTE: This is where the platform and NDK headers atomic headers are
     69  *        going to diverge. With the NDK, we don't know if the generated
     70  *        code is going to run on a single or multi-core device, so we
     71  *        need to be cautious.
     72  *
     73  *        I.e. on single-core devices, the helper immediately returns,
     74  *        on multi-core devices, it uses "dmb" or any other means to
     75  *        perform a full-memory barrier.
     76  *
     77  * There are three cases to consider for the platform:
     78  *
     79  *    - multi-core ARMv7-A       => use the 'dmb' hardware instruction
     80  *    - multi-core ARMv6         => use the coprocessor
     81  *    - single core ARMv6+       => do not use any hardware barrier
     82  */
     83 #if defined(ANDROID_SMP) && ANDROID_SMP == 1
     84 
     85 /* Sanity check, multi-core is only supported starting from ARMv6 */
     86 #  if __ARM_ARCH__ < 6
     87 #    error ANDROID_SMP should not be set to 1 for an ARM architecture less than 6
     88 #  endif
     89 
     90 #  ifdef __ARM_HAVE_DMB
     91 /* For ARMv7-A, we can use the 'dmb' instruction directly */
     92 __ATOMIC_INLINE__ void
     93 __bionic_memory_barrier(void)
     94 {
     95     /* Note: we always build in ARM or Thumb-2 on ARMv7-A, so don't
     96      * bother with __ATOMIC_SWITCH_TO_ARM */
     97     __asm__ __volatile__ ( "dmb" : : : "memory" );
     98 }
     99 #  else /* !__ARM_HAVE_DMB */
    100 /* Otherwise, i.e. for multi-core ARMv6, we need to use the coprocessor,
    101  * which requires the use of a general-purpose register, which is slightly
    102  * less efficient.
    103  */
    104 __ATOMIC_INLINE__ void
    105 __bionic_memory_barrier(void)
    106 {
    107     __asm__ __volatile__ (
    108         __SWITCH_TO_ARM
    109         "mcr p15, 0, %0, c7, c10, 5"
    110         __SWITCH_TO_THUMB
    111         : : "r" (0) : __ATOMIC_CLOBBERS "memory");
    112 }
    113 #  endif /* !__ARM_HAVE_DMB */
    114 #else /* !ANDROID_SMP */
    115 __ATOMIC_INLINE__ void
    116 __bionic_memory_barrier(void)
    117 {
    118     /* A simple compiler barrier */
    119     __asm__ __volatile__ ( "" : : : "memory" );
    120 }
    121 #endif /* !ANDROID_SMP */
    122 
    123 #ifndef __ARM_HAVE_LDREX_STREX
    124 #error Only ARM devices which have LDREX / STREX are supported
    125 #endif
    126 
    127 /* Compare-and-swap, without any explicit barriers. Note that this functions
    128  * returns 0 on success, and 1 on failure. The opposite convention is typically
    129  * used on other platforms.
    130  */
    131 __ATOMIC_INLINE__ int
    132 __bionic_cmpxchg(int32_t old_value, int32_t new_value, volatile int32_t* ptr)
    133 {
    134     int32_t prev, status;
    135     do {
    136         __asm__ __volatile__ (
    137             __ATOMIC_SWITCH_TO_ARM
    138             "ldrex %0, [%3]\n"
    139             "mov %1, #0\n"
    140             "teq %0, %4\n"
    141 #ifdef __thumb2__
    142             "it eq\n"
    143 #endif
    144             "strexeq %1, %5, [%3]"
    145             __ATOMIC_SWITCH_TO_THUMB
    146             : "=&r" (prev), "=&r" (status), "+m"(*ptr)
    147             : "r" (ptr), "Ir" (old_value), "r" (new_value)
    148             : __ATOMIC_CLOBBERS "cc");
    149     } while (__builtin_expect(status != 0, 0));
    150     return prev != old_value;
    151 }
    152 
    153 /* Swap operation, without any explicit barriers. */
    154 __ATOMIC_INLINE__ int32_t
    155 __bionic_swap(int32_t new_value, volatile int32_t* ptr)
    156 {
    157     int32_t prev, status;
    158     do {
    159         __asm__ __volatile__ (
    160             __ATOMIC_SWITCH_TO_ARM
    161             "ldrex %0, [%3]\n"
    162             "strex %1, %4, [%3]"
    163             __ATOMIC_SWITCH_TO_THUMB
    164             : "=&r" (prev), "=&r" (status), "+m" (*ptr)
    165             : "r" (ptr), "r" (new_value)
    166             : __ATOMIC_CLOBBERS "cc");
    167     } while (__builtin_expect(status != 0, 0));
    168     return prev;
    169 }
    170 
    171 /* Atomic increment - without any barriers
    172  * This returns the old value
    173  */
    174 __ATOMIC_INLINE__ int32_t
    175 __bionic_atomic_inc(volatile int32_t* ptr)
    176 {
    177     int32_t prev, tmp, status;
    178     do {
    179         __asm__ __volatile__ (
    180             __ATOMIC_SWITCH_TO_ARM
    181             "ldrex %0, [%4]\n"
    182             "add %1, %0, #1\n"
    183             "strex %2, %1, [%4]"
    184             __ATOMIC_SWITCH_TO_THUMB
    185             : "=&r" (prev), "=&r" (tmp), "=&r" (status), "+m"(*ptr)
    186             : "r" (ptr)
    187             : __ATOMIC_CLOBBERS "cc");
    188     } while (__builtin_expect(status != 0, 0));
    189     return prev;
    190 }
    191 
    192 /* Atomic decrement - without any barriers
    193  * This returns the old value.
    194  */
    195 __ATOMIC_INLINE__ int32_t
    196 __bionic_atomic_dec(volatile int32_t* ptr)
    197 {
    198     int32_t prev, tmp, status;
    199     do {
    200         __asm__ __volatile__ (
    201             __ATOMIC_SWITCH_TO_ARM
    202             "ldrex %0, [%4]\n"
    203             "sub %1, %0, #1\n"
    204             "strex %2, %1, [%4]"
    205             __ATOMIC_SWITCH_TO_THUMB
    206             : "=&r" (prev), "=&r" (tmp), "=&r" (status), "+m"(*ptr)
    207             : "r" (ptr)
    208             : __ATOMIC_CLOBBERS "cc");
    209     } while (__builtin_expect(status != 0, 0));
    210     return prev;
    211 }
    212 
    213 #endif /* SYS_ATOMICS_ARM_H */
    214