summaryrefslogtreecommitdiffstats
path: root/sys/arm/include/atomic.h
diff options
context:
space:
mode:
authorian <ian@FreeBSD.org>2014-08-01 22:28:36 +0000
committerian <ian@FreeBSD.org>2014-08-01 22:28:36 +0000
commit5cc59eee8a8d7dc40d5e6871e2dcde065a4bce83 (patch)
treecdb9d64df5775863cba3ea250ba2ec7ec97ed776 /sys/arm/include/atomic.h
parent4a0d502636a728671528394c2b7ecd90da5efd4c (diff)
downloadFreeBSD-src-5cc59eee8a8d7dc40d5e6871e2dcde065a4bce83.zip
FreeBSD-src-5cc59eee8a8d7dc40d5e6871e2dcde065a4bce83.tar.gz
Add 64-bit atomic ops for armv6. The only safe way to access a 64-bit
value shared across multiple cores is with atomic_load_64() and atomic_store_64(), because the normal 64-bit load/store instructions are not atomic on 32-bit arm. Luckily the ldrexd/strexd instructions that are atomic are fairly cheap on armv6. Because it's fairly simple to do, this implements all the ops for 64-bit, not just load/store. Reviewed by: andrew, cognet
Diffstat (limited to 'sys/arm/include/atomic.h')
-rw-r--r--sys/arm/include/atomic.h248
1 files changed, 248 insertions, 0 deletions
diff --git a/sys/arm/include/atomic.h b/sys/arm/include/atomic.h
index 02be1bd..f00600b 100644
--- a/sys/arm/include/atomic.h
+++ b/sys/arm/include/atomic.h
@@ -146,6 +146,28 @@ atomic_set_32(volatile uint32_t *address, uint32_t setmask)
}
static __inline void
+atomic_set_64(volatile uint64_t *p, uint64_t val)
+{
+ uint64_t tmp;
+ uint32_t exflag;
+
+ __asm __volatile(
+ "1: \n"
+ " ldrexd %[tmp], [%[ptr]]\n"
+ " orr %Q[tmp], %Q[val]\n"
+ " orr %R[tmp], %R[val]\n"
+ " strexd %[exf], %[tmp], [%[ptr]]\n"
+ " teq %[exf], #0\n"
+ " it ne \n"
+ " bne 1b\n"
+ : [exf] "=&r" (exflag),
+ [tmp] "=&r" (tmp)
+ : [ptr] "r" (p),
+ [val] "r" (val)
+ : "cc", "memory");
+}
+
+static __inline void
atomic_set_long(volatile u_long *address, u_long setmask)
{
u_long tmp = 0, tmp2 = 0;
@@ -177,6 +199,28 @@ atomic_clear_32(volatile uint32_t *address, uint32_t setmask)
}
static __inline void
+atomic_clear_64(volatile uint64_t *p, uint64_t val)
+{
+ uint64_t tmp;
+ uint32_t exflag;
+
+ __asm __volatile(
+ "1: \n"
+ " ldrexd %[tmp], [%[ptr]]\n"
+ " bic %Q[tmp], %Q[val]\n"
+ " bic %R[tmp], %R[val]\n"
+ " strexd %[exf], %[tmp], [%[ptr]]\n"
+ " teq %[exf], #0\n"
+ " it ne \n"
+ " bne 1b\n"
+ : [exf] "=&r" (exflag),
+ [tmp] "=&r" (tmp)
+ : [ptr] "r" (p),
+ [val] "r" (val)
+ : "cc", "memory");
+}
+
+static __inline void
atomic_clear_long(volatile u_long *address, u_long setmask)
{
u_long tmp = 0, tmp2 = 0;
@@ -213,6 +257,35 @@ atomic_cmpset_32(volatile u_int32_t *p, volatile u_int32_t cmpval, volatile u_in
return (ret);
}
+static __inline int
+atomic_cmpset_64(volatile uint64_t *p, uint64_t cmpval, uint64_t newval)
+{
+ uint64_t tmp;
+ uint32_t ret;
+
+ __asm __volatile(
+ "1: \n"
+ " ldrexd %[tmp], [%[ptr]]\n"
+ " teq %Q[tmp], %Q[cmp]\n"
+ " itee eq \n"
+ " teqeq %R[tmp], %R[cmp]\n"
+ " movne %[ret], #0\n"
+ " bne 2f\n"
+ " strexd %[ret], %[new], [%[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),
+ [cmp] "r" (cmpval),
+ [new] "r" (newval)
+ : "cc", "memory");
+ return (ret);
+}
+
static __inline u_long
atomic_cmpset_long(volatile u_long *p, volatile u_long cmpval, volatile u_long newval)
{
@@ -244,6 +317,15 @@ atomic_cmpset_acq_32(volatile u_int32_t *p, volatile u_int32_t cmpval, volatile
return (ret);
}
+static __inline uint64_t
+atomic_cmpset_acq_64(volatile uint64_t *p, volatile uint64_t cmpval, volatile uint64_t newval)
+{
+ uint64_t ret = atomic_cmpset_64(p, cmpval, newval);
+
+ __do_dmb();
+ return (ret);
+}
+
static __inline u_long
atomic_cmpset_acq_long(volatile u_long *p, volatile u_long cmpval, volatile u_long newval)
{
@@ -261,6 +343,14 @@ atomic_cmpset_rel_32(volatile u_int32_t *p, volatile u_int32_t cmpval, volatile
return (atomic_cmpset_32(p, cmpval, newval));
}
+static __inline uint64_t
+atomic_cmpset_rel_64(volatile uint64_t *p, volatile uint64_t cmpval, volatile uint64_t newval)
+{
+
+ __do_dmb();
+ return (atomic_cmpset_64(p, cmpval, newval));
+}
+
static __inline u_long
atomic_cmpset_rel_long(volatile u_long *p, volatile u_long cmpval, volatile u_long newval)
{
@@ -286,6 +376,28 @@ atomic_add_32(volatile u_int32_t *p, u_int32_t val)
}
static __inline void
+atomic_add_64(volatile uint64_t *p, uint64_t val)
+{
+ uint64_t tmp;
+ uint32_t exflag;
+
+ __asm __volatile(
+ "1: \n"
+ " ldrexd %[tmp], [%[ptr]]\n"
+ " adds %Q[tmp], %Q[val]\n"
+ " adc %R[tmp], %R[val]\n"
+ " strexd %[exf], %[tmp], [%[ptr]]\n"
+ " teq %[exf], #0\n"
+ " it ne \n"
+ " bne 1b\n"
+ : [exf] "=&r" (exflag),
+ [tmp] "=&r" (tmp)
+ : [ptr] "r" (p),
+ [val] "r" (val)
+ : "cc", "memory");
+}
+
+static __inline void
atomic_add_long(volatile u_long *p, u_long val)
{
u_long tmp = 0, tmp2 = 0;
@@ -316,6 +428,28 @@ atomic_subtract_32(volatile u_int32_t *p, u_int32_t val)
}
static __inline void
+atomic_subtract_64(volatile uint64_t *p, uint64_t val)
+{
+ uint64_t tmp;
+ uint32_t exflag;
+
+ __asm __volatile(
+ "1: \n"
+ " ldrexd %[tmp], [%[ptr]]\n"
+ " subs %Q[tmp], %Q[val]\n"
+ " sbc %R[tmp], %R[val]\n"
+ " strexd %[exf], %[tmp], [%[ptr]]\n"
+ " teq %[exf], #0\n"
+ " it ne \n"
+ " bne 1b\n"
+ : [exf] "=&r" (exflag),
+ [tmp] "=&r" (tmp)
+ : [ptr] "r" (p),
+ [val] "r" (val)
+ : "cc", "memory");
+}
+
+static __inline void
atomic_subtract_long(volatile u_long *p, u_long val)
{
u_long tmp = 0, tmp2 = 0;
@@ -334,6 +468,10 @@ ATOMIC_ACQ_REL(clear, 32)
ATOMIC_ACQ_REL(add, 32)
ATOMIC_ACQ_REL(subtract, 32)
ATOMIC_ACQ_REL(set, 32)
+ATOMIC_ACQ_REL(clear, 64)
+ATOMIC_ACQ_REL(add, 64)
+ATOMIC_ACQ_REL(subtract, 64)
+ATOMIC_ACQ_REL(set, 64)
ATOMIC_ACQ_REL_LONG(clear)
ATOMIC_ACQ_REL_LONG(add)
ATOMIC_ACQ_REL_LONG(subtract)
@@ -392,6 +530,116 @@ atomic_store_rel_32(volatile uint32_t *p, uint32_t v)
*p = v;
}
+static __inline uint64_t
+atomic_fetchadd_64(volatile uint64_t *p, uint64_t val)
+{
+ uint64_t ret, tmp;
+ uint32_t exflag;
+
+ __asm __volatile(
+ "1: \n"
+ " ldrexd %[ret], [%[ptr]]\n"
+ " adds %Q[tmp], %Q[ret], %Q[val]\n"
+ " adc %R[tmp], %R[ret], %R[val]\n"
+ " strexd %[exf], %[tmp], [%[ptr]]\n"
+ " teq %[exf], #0\n"
+ " it ne \n"
+ " bne 1b\n"
+ : [ret] "=&r" (ret),
+ [exf] "=&r" (exflag),
+ [tmp] "=&r" (tmp)
+ : [ptr] "r" (p),
+ [val] "r" (val)
+ : "cc", "memory");
+ return (ret);
+}
+
+static __inline uint64_t
+atomic_readandclear_64(volatile uint64_t *p)
+{
+ uint64_t ret, tmp;
+ uint32_t exflag;
+
+ __asm __volatile(
+ "1: \n"
+ " ldrexd %[ret], [%[ptr]]\n"
+ " mov %Q[tmp], #0\n"
+ " mov %R[tmp], #0\n"
+ " strexd %[exf], %[tmp], [%[ptr]]\n"
+ " teq %[exf], #0\n"
+ " it ne \n"
+ " bne 1b\n"
+ : [ret] "=&r" (ret),
+ [exf] "=&r" (exflag),
+ [tmp] "=&r" (tmp)
+ : [ptr] "r" (p)
+ : "cc", "memory");
+ return (ret);
+}
+
+static __inline uint64_t
+atomic_load_64(volatile uint64_t *p)
+{
+ uint64_t ret;
+
+ /*
+ * The only way to atomically load 64 bits is with LDREXD which puts the
+ * exclusive monitor into the open state, so reset it with CLREX because
+ * we don't actually need to store anything.
+ */
+ __asm __volatile(
+ "1: \n"
+ " ldrexd %[ret], [%[ptr]]\n"
+ " clrex \n"
+ : [ret] "=&r" (ret)
+ : [ptr] "r" (p)
+ : "cc", "memory");
+ return (ret);
+}
+
+static __inline uint64_t
+atomic_load_acq_64(volatile uint64_t *p)
+{
+ uint64_t ret;
+
+ ret = atomic_load_64(p);
+ __do_dmb();
+ return (ret);
+}
+
+static __inline void
+atomic_store_64(volatile uint64_t *p, uint64_t val)
+{
+ uint64_t tmp;
+ uint32_t exflag;
+
+ /*
+ * The only way to atomically store 64 bits is with STREXD, which will
+ * succeed only if paired up with a preceeding LDREXD using the same
+ * address, so we read and discard the existing value before storing.
+ */
+ __asm __volatile(
+ "1: \n"
+ " ldrexd %[tmp], [%[ptr]]\n"
+ " strexd %[exf], %[val], [%[ptr]]\n"
+ " teq %[exf], #0\n"
+ " it ne \n"
+ " bne 1b\n"
+ : [tmp] "=&r" (tmp),
+ [exf] "=&r" (exflag)
+ : [ptr] "r" (p),
+ [val] "r" (val)
+ : "cc", "memory");
+}
+
+static __inline void
+atomic_store_rel_64(volatile uint64_t *p, uint64_t val)
+{
+
+ __do_dmb();
+ atomic_store_64(p, val);
+}
+
static __inline u_long
atomic_fetchadd_long(volatile u_long *p, u_long val)
{
OpenPOWER on IntegriCloud