Changeset View
Standalone View
sys/arm/include/atomic-v6.h
| Show First 20 Lines • Show All 184 Lines • ▼ Show 20 Lines | atomic_clear_long(volatile u_long *address, u_long setmask) | ||||
| atomic_clear_32((volatile uint32_t *)address, setmask); | atomic_clear_32((volatile uint32_t *)address, setmask); | ||||
| } | } | ||||
| ATOMIC_ACQ_REL(clear, 32) | ATOMIC_ACQ_REL(clear, 32) | ||||
| ATOMIC_ACQ_REL(clear, 64) | ATOMIC_ACQ_REL(clear, 64) | ||||
| ATOMIC_ACQ_REL_LONG(clear) | ATOMIC_ACQ_REL_LONG(clear) | ||||
| #define ATOMIC_FCMPSET_CODE(RET, TYPE, SUF) \ | |||||
| { \ | |||||
| TYPE tmp; \ | |||||
| \ | |||||
| __asm __volatile( \ | |||||
| "1: ldrex" SUF " %[tmp], [%[ptr]] \n" \ | |||||
| " ldr %[ret], [%[oldv]] \n" \ | |||||
| " teq %[tmp], %[ret] \n" \ | |||||
| " ittee ne \n" \ | |||||
| " str" SUF "ne %[tmp], [%[oldv]] \n" \ | |||||
| " movne %[ret], #0 \n" \ | |||||
| " strex" SUF "eq %[ret], %[newv], [%[ptr]] \n" \ | |||||
| " eorseq %[ret], #1 \n" \ | |||||
| " beq 1b \n" \ | |||||
andrew: Isn't the point of fcmpset that we don't loop? They can return false when the store fails and… | |||||
ianAuthorUnsubmitted Done Inline ActionsAn outer loop might handle failure because of an oldval mismatch, but why should it have to handle failure due to the internal implementation details of atomics on a given platform? The strex can fail for reasons such as an interrupt or fault happening between the ldrex and strex. We can loop more efficiently internally than an outer loop can (it costs literally 0 cycles unless the strex actually fails). The manpage for fcmpset says that some platforms "might" return false even when old==new, and I think that's a documentation bug (and implementation bug if it's actually true on any platform) that should be fixed. ian: An outer loop might handle failure because of an oldval mismatch, but why should it have to… | |||||
mjgUnsubmitted Not Done Inline ActionsThe point of fcmpset (over cmpset) is that it returns the found value instead of throwing it away and forcing common consumers to read it again. The c11 standard provides cmpxchg with two variants (weak and strong) to let you pick what behavior you want and if there is a bug it's that we don't have fcmpset_weak and fcmpset_strong (with whatever names). Another consideration is that fcmpset is inlined all over the kernel due to locking primitives, so adding the loop makes the call site bigger (but I don't know the impact on arm). On the other hand these suckers seem so big already that perhaps deinline locking routines would be faster on this arch. tl;dr looping or not looping is up to you, if it's faster to loop then do it, but apart from it perhaps you just want to reduce the spread of fcmpset over the kernel mjg: The point of fcmpset (over cmpset) is that it returns the found value instead of throwing it… | |||||
ianAuthorUnsubmitted Done Inline ActionsAdding the loop on arm makes the call site bigger by one instruction, which will take 0 cycles in the usual case. I guess it's a cisc-centric background that would make someone declare this code as "large". The biggest one, fcmpset64, is 10 instructions, of which only 6 execute in the usual uncontested case (I wonder if x86 lock cmpxchg is able to run in just 6 cycles without impacting any core other than the one it runs on?). ARM's ability to conditionally execute (or not) most instructions makes for really fast branch-free code in which you get to coast through some instructions at a cost of zero cycles, and most of the ones that aren't skipped are 1 cycle to execute. The point about returning the found value is why I think it's important to loop internally on a strex access conflict. If you don't do so, then the value returned is the original value you read from there, even though you know (because of the strex failure) that there is now a new value there. If the code returns false on strex failure instead of looping, the caller cannot know whether *oldval is valid or not, and would have to re-read it to be sure. ian: Adding the loop on arm makes the call site bigger by one instruction, which will take 0 cycles… | |||||
mjgUnsubmitted Not Done Inline ActionsEven for cache-hot atomics the cost drastically differs between microarchitectures (in part because of how they react to it with the surrounding code). Stale value is fine. For example a loop trying to replace the non-0 counter with a value 1 bigger worst case will get a counter which is already bigger (or lower) and will try to replace it, which is going to fail. Problems could only stem from the read not being preceded with a memory barrier or the value being invaild (e.g. a torn store or straight up garbage), none of which is the case here. Anything which can legitimately be there has to already be accounted for by the consumer. Whether relooping internally helps on this arch I cannot comment on. mjg: Even for cache-hot atomics the cost drastically differs between microarchitectures (in part… | |||||
| : [ret] "=&r" (RET), \ | |||||
| [tmp] "=&r" (tmp) \ | |||||
| : [ptr] "r" (_ptr), \ | |||||
| [oldv] "r" (_old), \ | |||||
| [newv] "r" (_new) \ | |||||
| : "cc", "memory"); \ | |||||
| } | |||||
| #define ATOMIC_FCMPSET_CODE64(RET) \ | |||||
| { \ | |||||
| uint64_t cmp, tmp; \ | |||||
| \ | |||||
| __asm __volatile( \ | |||||
| "1: ldrexd %Q[tmp], %R[tmp], [%[ptr]] \n" \ | |||||
| " ldrd %Q[cmp], %R[cmp], [%[oldv]] \n" \ | |||||
| " teq %Q[tmp], %Q[cmp] \n" \ | |||||
| " it eq \n" \ | |||||
| " teqeq %R[tmp], %R[cmp] \n" \ | |||||
| " ittee ne \n" \ | |||||
| " movne %[ret], #0 \n" \ | |||||
| " strdne %[cmp], [%[oldv]] \n" \ | |||||
| " strexdeq %[ret], %Q[newv], %R[newv], [%[ptr]] \n" \ | |||||
| " eorseq %[ret], #1 \n" \ | |||||
| " beq 1b \n" \ | |||||
| : [ret] "=&r" (RET), \ | |||||
| [cmp] "=&r" (cmp), \ | |||||
| [tmp] "=&r" (tmp) \ | |||||
| : [ptr] "r" (_ptr), \ | |||||
| [oldv] "r" (_old), \ | |||||
| [newv] "r" (_new) \ | |||||
| : "cc", "memory"); \ | |||||
| } | |||||
| static __inline int | static __inline int | ||||
| atomic_fcmpset_32(volatile uint32_t *p, uint32_t *cmpval, uint32_t newval) | atomic_fcmpset_8(volatile uint8_t *_ptr, uint8_t *_old, uint8_t _new) | ||||
| { | { | ||||
| uint32_t tmp; | |||||
| uint32_t _cmpval = *cmpval; | |||||
| int ret; | int ret; | ||||
| __asm __volatile( | ATOMIC_FCMPSET_CODE(ret, uint8_t, "b"); | ||||
| " mov %0, #1 \n" | return (ret); | ||||
| " ldrex %1, [%2] \n" | |||||
| " cmp %1, %3 \n" | |||||
| " it eq \n" | |||||
| " strexeq %0, %4, [%2] \n" | |||||
| : "=&r" (ret), "=&r" (tmp), "+r" (p), "+r" (_cmpval), "+r" (newval) | |||||
| : : "cc", "memory"); | |||||
| *cmpval = tmp; | |||||
| return (!ret); | |||||
| } | } | ||||
| static __inline int | static __inline int | ||||
| atomic_fcmpset_64(volatile uint64_t *p, uint64_t *cmpval, uint64_t newval) | atomic_fcmpset_acq_8(volatile uint8_t *_ptr, uint8_t *_old, uint8_t _new) | ||||
| { | { | ||||
| uint64_t tmp; | |||||
| uint64_t _cmpval = *cmpval; | |||||
| int ret; | int ret; | ||||
| __asm __volatile( | ATOMIC_FCMPSET_CODE(ret, uint8_t, "b"); | ||||
| "1: mov %[ret], #1 \n" | dmb(); | ||||
| " ldrexd %Q[tmp], %R[tmp], [%[ptr]] \n" | return (ret); | ||||
| " teq %Q[tmp], %Q[_cmpval] \n" | |||||
| " ite eq \n" | |||||
| " teqeq %R[tmp], %R[_cmpval] \n" | |||||
| " bne 2f \n" | |||||
| " strexd %[ret], %Q[newval], %R[newval], [%[ptr]]\n" | |||||
| "2: \n" | |||||
| : [ret] "=&r" (ret), | |||||
| [tmp] "=&r" (tmp) | |||||
| : [ptr] "r" (p), | |||||
| [_cmpval] "r" (_cmpval), | |||||
| [newval] "r" (newval) | |||||
| : "cc", "memory"); | |||||
| *cmpval = tmp; | |||||
| return (!ret); | |||||
| } | } | ||||
| static __inline int | static __inline int | ||||
| atomic_fcmpset_long(volatile u_long *p, u_long *cmpval, u_long newval) | atomic_fcmpset_rel_8(volatile uint8_t *_ptr, uint8_t *_old, uint8_t _new) | ||||
| { | { | ||||
| int ret; | |||||
| return (atomic_fcmpset_32((volatile uint32_t *)p, | dmb(); | ||||
| (uint32_t *)cmpval, newval)); | ATOMIC_FCMPSET_CODE(ret, uint8_t, "b"); | ||||
| return (ret); | |||||
| } | } | ||||
| static __inline int | static __inline int | ||||
| atomic_fcmpset_acq_64(volatile uint64_t *p, uint64_t *cmpval, uint64_t newval) | atomic_fcmpset_16(volatile uint16_t *_ptr, uint16_t *_old, uint16_t _new) | ||||
| { | { | ||||
| int ret; | int ret; | ||||
| ret = atomic_fcmpset_64(p, cmpval, newval); | ATOMIC_FCMPSET_CODE(ret, uint16_t, "h"); | ||||
| return (ret); | |||||
| } | |||||
| static __inline int | |||||
| atomic_fcmpset_acq_16(volatile uint16_t *_ptr, uint16_t *_old, uint16_t _new) | |||||
| { | |||||
| int ret; | |||||
| ATOMIC_FCMPSET_CODE(ret, uint16_t, "h"); | |||||
| dmb(); | dmb(); | ||||
| return (ret); | return (ret); | ||||
| } | } | ||||
| static __inline int | static __inline int | ||||
| atomic_fcmpset_acq_long(volatile u_long *p, u_long *cmpval, u_long newval) | atomic_fcmpset_rel_16(volatile uint16_t *_ptr, uint16_t *_old, uint16_t _new) | ||||
| { | { | ||||
| int ret; | int ret; | ||||
| ret = atomic_fcmpset_long(p, cmpval, newval); | |||||
| dmb(); | dmb(); | ||||
| ATOMIC_FCMPSET_CODE(ret, uint16_t, "h"); | |||||
| return (ret); | return (ret); | ||||
| } | } | ||||
| static __inline int | static __inline int | ||||
| atomic_fcmpset_acq_32(volatile uint32_t *p, uint32_t *cmpval, uint32_t newval) | atomic_fcmpset_32(volatile uint32_t *_ptr, uint32_t *_old, uint32_t _new) | ||||
| { | { | ||||
| int ret; | |||||
| ATOMIC_FCMPSET_CODE(ret, uint32_t, ""); | |||||
| return (ret); | |||||
| } | |||||
| static __inline int | |||||
| atomic_fcmpset_acq_32(volatile uint32_t *_ptr, uint32_t *_old, uint32_t _new) | |||||
| { | |||||
| int ret; | int ret; | ||||
| ret = atomic_fcmpset_32(p, cmpval, newval); | ATOMIC_FCMPSET_CODE(ret, uint32_t, ""); | ||||
| dmb(); | dmb(); | ||||
| return (ret); | return (ret); | ||||
| } | } | ||||
| static __inline int | static __inline int | ||||
| atomic_fcmpset_rel_32(volatile uint32_t *p, uint32_t *cmpval, uint32_t newval) | atomic_fcmpset_rel_32(volatile uint32_t *_ptr, uint32_t *_old, uint32_t _new) | ||||
| { | { | ||||
| int ret; | |||||
| dmb(); | dmb(); | ||||
| return (atomic_fcmpset_32(p, cmpval, newval)); | ATOMIC_FCMPSET_CODE(ret, uint32_t, ""); | ||||
| return (ret); | |||||
| } | } | ||||
| static __inline int | static __inline int | ||||
| atomic_fcmpset_rel_64(volatile uint64_t *p, uint64_t *cmpval, uint64_t newval) | atomic_fcmpset_long(volatile long *_ptr, long *_old, long _new) | ||||
| { | { | ||||
| int ret; | |||||
| ATOMIC_FCMPSET_CODE(ret, long, ""); | |||||
| return (ret); | |||||
| } | |||||
| static __inline int | |||||
| atomic_fcmpset_acq_long(volatile long *_ptr, long *_old, long _new) | |||||
| { | |||||
| int ret; | |||||
| ATOMIC_FCMPSET_CODE(ret, long, ""); | |||||
| dmb(); | dmb(); | ||||
| return (atomic_fcmpset_64(p, cmpval, newval)); | return (ret); | ||||
| } | } | ||||
| static __inline int | static __inline int | ||||
| atomic_fcmpset_rel_long(volatile u_long *p, u_long *cmpval, u_long newval) | atomic_fcmpset_rel_long(volatile long *_ptr, long *_old, long _new) | ||||
| { | { | ||||
| int ret; | |||||
| dmb(); | dmb(); | ||||
| return (atomic_fcmpset_long(p, cmpval, newval)); | ATOMIC_FCMPSET_CODE(ret, long, ""); | ||||
| return (ret); | |||||
| } | } | ||||
| static __inline int | static __inline int | ||||
| atomic_cmpset_32(volatile uint32_t *p, uint32_t cmpval, uint32_t newval) | atomic_fcmpset_64(volatile uint64_t *_ptr, uint64_t *_old, uint64_t _new) | ||||
| { | { | ||||
| int ret; | int ret; | ||||
| __asm __volatile( | ATOMIC_FCMPSET_CODE64(ret); | ||||
| "1: ldrex %0, [%1] \n" | |||||
| " cmp %0, %2 \n" | |||||
| " itt ne \n" | |||||
| " movne %0, #0 \n" | |||||
| " bne 2f \n" | |||||
| " strex %0, %3, [%1] \n" | |||||
| " cmp %0, #0 \n" | |||||
| " ite eq \n" | |||||
| " moveq %0, #1 \n" | |||||
| " bne 1b \n" | |||||
| "2:" | |||||
| : "=&r" (ret), "+r" (p), "+r" (cmpval), "+r" (newval) | |||||
| : : "cc", "memory"); | |||||
| return (ret); | return (ret); | ||||
| } | } | ||||
| static __inline int | static __inline int | ||||
| atomic_cmpset_64(volatile uint64_t *p, uint64_t cmpval, uint64_t newval) | atomic_fcmpset_acq_64(volatile uint64_t *_ptr, uint64_t *_old, uint64_t _new) | ||||
| { | { | ||||
| uint64_t tmp; | int ret; | ||||
| uint32_t ret; | |||||
| __asm __volatile( | ATOMIC_FCMPSET_CODE64(ret); | ||||
| "1: \n" | dmb(); | ||||
| " ldrexd %Q[tmp], %R[tmp], [%[ptr]] \n" | |||||
| " teq %Q[tmp], %Q[cmpval] \n" | |||||
| " itee eq \n" | |||||
| " teqeq %R[tmp], %R[cmpval] \n" | |||||
| " movne %[ret], #0 \n" | |||||
| " bne 2f \n" | |||||
| " strexd %[ret], %Q[newval], %R[newval], [%[ptr]]\n" | |||||
| " teq %[ret], #0 \n" | |||||
| " it ne \n" | |||||
| " bne 1b \n" | |||||
| " mov %[ret], #1 \n" | |||||
| "2: \n" | |||||
| : [ret] "=&r" (ret), | |||||
| [tmp] "=&r" (tmp) | |||||
| : [ptr] "r" (p), | |||||
| [cmpval] "r" (cmpval), | |||||
| [newval] "r" (newval) | |||||
| : "cc", "memory"); | |||||
| return (ret); | return (ret); | ||||
| } | } | ||||
| static __inline int | static __inline int | ||||
| atomic_cmpset_long(volatile u_long *p, u_long cmpval, u_long newval) | atomic_fcmpset_rel_64(volatile uint64_t *_ptr, uint64_t *_old, uint64_t _new) | ||||
| { | { | ||||
| int ret; | |||||
| return (atomic_cmpset_32((volatile uint32_t *)p, cmpval, newval)); | dmb(); | ||||
| ATOMIC_FCMPSET_CODE64(ret); | |||||
| return (ret); | |||||
| } | } | ||||
| #define ATOMIC_CMPSET_CODE(RET, SUF) \ | |||||
| { \ | |||||
| __asm __volatile( \ | |||||
| "1: ldrex" SUF " %[ret], [%[ptr]] \n" \ | |||||
| " teq %[ret], %[oldv] \n" \ | |||||
| " itee ne \n" \ | |||||
| " movne %[ret], #0 \n" \ | |||||
| " strex" SUF "eq %[ret], %[newv], [%[ptr]] \n" \ | |||||
| " eorseq %[ret], #1 \n" \ | |||||
| " beq 1b \n" \ | |||||
| : [ret] "=&r" (RET) \ | |||||
| : [ptr] "r" (_ptr), \ | |||||
| [oldv] "r" (_old), \ | |||||
| [newv] "r" (_new) \ | |||||
| : "cc", "memory"); \ | |||||
| } | |||||
| #define ATOMIC_CMPSET_CODE64(RET) \ | |||||
| { \ | |||||
| uint64_t tmp; \ | |||||
| \ | |||||
| __asm __volatile( \ | |||||
| "1: ldrexd %Q[tmp], %R[tmp], [%[ptr]] \n" \ | |||||
| " teq %Q[tmp], %Q[oldv] \n" \ | |||||
| " it eq \n" \ | |||||
| " teqeq %R[tmp], %R[oldv] \n" \ | |||||
| " itee ne \n" \ | |||||
| " movne %[ret], #0 \n" \ | |||||
| " strexdeq %[ret], %Q[newv], %R[newv], [%[ptr]] \n" \ | |||||
| " eorseq %[ret], #1 \n" \ | |||||
| " beq 1b \n" \ | |||||
| : [ret] "=&r" (RET), \ | |||||
| [tmp] "=&r" (tmp) \ | |||||
| : [ptr] "r" (_ptr), \ | |||||
| [oldv] "r" (_old), \ | |||||
| [newv] "r" (_new) \ | |||||
| : "cc", "memory"); \ | |||||
| } | |||||
| static __inline int | static __inline int | ||||
| atomic_cmpset_acq_32(volatile uint32_t *p, uint32_t cmpval, uint32_t newval) | atomic_cmpset_8(volatile uint8_t *_ptr, uint8_t _old, uint8_t _new) | ||||
| { | { | ||||
| int ret; | int ret; | ||||
| ret = atomic_cmpset_32(p, cmpval, newval); | ATOMIC_CMPSET_CODE(ret, "b"); | ||||
| return (ret); | |||||
| } | |||||
| static __inline int | |||||
| atomic_cmpset_acq_8(volatile uint8_t *_ptr, uint8_t _old, uint8_t _new) | |||||
| { | |||||
| int ret; | |||||
| ATOMIC_CMPSET_CODE(ret, "b"); | |||||
| dmb(); | dmb(); | ||||
| return (ret); | return (ret); | ||||
| } | } | ||||
| static __inline int | static __inline int | ||||
| atomic_cmpset_acq_64(volatile uint64_t *p, uint64_t cmpval, uint64_t newval) | atomic_cmpset_rel_8(volatile uint8_t *_ptr, uint8_t _old, uint8_t _new) | ||||
| { | { | ||||
| int ret; | int ret; | ||||
| ret = atomic_cmpset_64(p, cmpval, newval); | |||||
| dmb(); | dmb(); | ||||
| ATOMIC_CMPSET_CODE(ret, "b"); | |||||
| return (ret); | return (ret); | ||||
| } | } | ||||
| static __inline int | static __inline int | ||||
| atomic_cmpset_acq_long(volatile u_long *p, u_long cmpval, u_long newval) | atomic_cmpset_16(volatile uint16_t *_ptr, uint16_t _old, uint16_t _new) | ||||
| { | { | ||||
| int ret; | int ret; | ||||
| ret = atomic_cmpset_long(p, cmpval, newval); | ATOMIC_CMPSET_CODE(ret, "h"); | ||||
| return (ret); | |||||
| } | |||||
| static __inline int | |||||
| atomic_cmpset_acq_16(volatile uint16_t *_ptr, uint16_t _old, uint16_t _new) | |||||
| { | |||||
| int ret; | |||||
| ATOMIC_CMPSET_CODE(ret, "h"); | |||||
| dmb(); | dmb(); | ||||
| return (ret); | return (ret); | ||||
| } | } | ||||
| static __inline int | static __inline int | ||||
| atomic_cmpset_rel_32(volatile uint32_t *p, uint32_t cmpval, uint32_t newval) | atomic_cmpset_rel_16(volatile uint16_t *_ptr, uint16_t _old, uint16_t _new) | ||||
| { | { | ||||
| int ret; | |||||
| dmb(); | dmb(); | ||||
| return (atomic_cmpset_32(p, cmpval, newval)); | ATOMIC_CMPSET_CODE(ret, "h"); | ||||
| return (ret); | |||||
| } | } | ||||
| static __inline int | static __inline int | ||||
| atomic_cmpset_rel_64(volatile uint64_t *p, uint64_t cmpval, uint64_t newval) | atomic_cmpset_32(volatile uint32_t *_ptr, uint32_t _old, uint32_t _new) | ||||
| { | { | ||||
| int ret; | |||||
| ATOMIC_CMPSET_CODE(ret, ""); | |||||
| return (ret); | |||||
| } | |||||
| static __inline int | |||||
| atomic_cmpset_acq_32(volatile uint32_t *_ptr, uint32_t _old, uint32_t _new) | |||||
| { | |||||
| int ret; | |||||
| ATOMIC_CMPSET_CODE(ret, ""); | |||||
| dmb(); | dmb(); | ||||
| return (atomic_cmpset_64(p, cmpval, newval)); | return (ret); | ||||
| } | } | ||||
| static __inline int | static __inline int | ||||
| atomic_cmpset_rel_long(volatile u_long *p, u_long cmpval, u_long newval) | atomic_cmpset_rel_32(volatile uint32_t *_ptr, uint32_t _old, uint32_t _new) | ||||
| { | { | ||||
| int ret; | |||||
| dmb(); | dmb(); | ||||
| return (atomic_cmpset_long(p, cmpval, newval)); | ATOMIC_CMPSET_CODE(ret, ""); | ||||
| return (ret); | |||||
| } | |||||
| static __inline int | |||||
| atomic_cmpset_long(volatile long *_ptr, long _old, long _new) | |||||
| { | |||||
| int ret; | |||||
| ATOMIC_CMPSET_CODE(ret, ""); | |||||
| return (ret); | |||||
| } | |||||
| static __inline int | |||||
| atomic_cmpset_acq_long(volatile long *_ptr, long _old, long _new) | |||||
| { | |||||
| int ret; | |||||
| ATOMIC_CMPSET_CODE(ret, ""); | |||||
| dmb(); | |||||
| return (ret); | |||||
| } | |||||
| static __inline int | |||||
| atomic_cmpset_rel_long(volatile long *_ptr, long _old, long _new) | |||||
| { | |||||
| int ret; | |||||
| dmb(); | |||||
| ATOMIC_CMPSET_CODE(ret, ""); | |||||
| return (ret); | |||||
| } | |||||
| static __inline int | |||||
| atomic_cmpset_64(volatile uint64_t *_ptr, uint64_t _old, uint64_t _new) | |||||
| { | |||||
| int ret; | |||||
| ATOMIC_CMPSET_CODE64(ret); | |||||
| return (ret); | |||||
| } | |||||
| static __inline int | |||||
| atomic_cmpset_acq_64(volatile uint64_t *_ptr, uint64_t _old, uint64_t _new) | |||||
| { | |||||
| int ret; | |||||
| ATOMIC_CMPSET_CODE64(ret); | |||||
| dmb(); | |||||
| return (ret); | |||||
| } | |||||
| static __inline int | |||||
| atomic_cmpset_rel_64(volatile uint64_t *_ptr, uint64_t _old, uint64_t _new) | |||||
| { | |||||
| int ret; | |||||
| dmb(); | |||||
| ATOMIC_CMPSET_CODE64(ret); | |||||
| return (ret); | |||||
| } | } | ||||
| static __inline uint32_t | static __inline uint32_t | ||||
| atomic_fetchadd_32(volatile uint32_t *p, uint32_t val) | atomic_fetchadd_32(volatile uint32_t *p, uint32_t val) | ||||
| { | { | ||||
| uint32_t tmp = 0, tmp2 = 0, ret = 0; | uint32_t tmp = 0, tmp2 = 0, ret = 0; | ||||
| __asm __volatile( | __asm __volatile( | ||||
| ▲ Show 20 Lines • Show All 404 Lines • Show Last 20 Lines | |||||
Isn't the point of fcmpset that we don't loop? They can return false when the store fails and the calling code has to handle this, e.g. it will be in a larger loop.