aboutsummaryrefslogtreecommitdiffstats
path: root/lib/atomic64.c
diff options
context:
space:
mode:
authorPaul Mackerras <paulus@samba.org>2009-06-12 17:10:05 -0400
committerBenjamin Herrenschmidt <benh@kernel.crashing.org>2009-06-14 23:27:38 -0400
commit09d4e0edd4614e787393acc582ac701c6ec3565b (patch)
tree77f3b85e0f59a168ac78639e510ebcbd3791b3d2 /lib/atomic64.c
parent4c75f84f2c781beb230031234ed961d28771a764 (diff)
lib: Provide generic atomic64_t implementation
Many processor architectures have no 64-bit atomic instructions, but we need atomic64_t in order to support the perf_counter subsystem. This adds an implementation of 64-bit atomic operations using hashed spinlocks to provide atomicity. For each atomic operation, the address of the atomic64_t variable is hashed to an index into an array of 16 spinlocks. That spinlock is taken (with interrupts disabled) around the operation, which can then be coded non-atomically within the lock. On UP, all the spinlock manipulation goes away and we simply disable interrupts around each operation. In fact gcc eliminates the whole atomic64_lock variable as well. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Diffstat (limited to 'lib/atomic64.c')
-rw-r--r--lib/atomic64.c175
1 files changed, 175 insertions, 0 deletions
diff --git a/lib/atomic64.c b/lib/atomic64.c
new file mode 100644
index 000000000000..c5e725562416
--- /dev/null
+++ b/lib/atomic64.c
@@ -0,0 +1,175 @@
1/*
2 * Generic implementation of 64-bit atomics using spinlocks,
3 * useful on processors that don't have 64-bit atomic instructions.
4 *
5 * Copyright © 2009 Paul Mackerras, IBM Corp. <paulus@au1.ibm.com>
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version
10 * 2 of the License, or (at your option) any later version.
11 */
12#include <linux/types.h>
13#include <linux/cache.h>
14#include <linux/spinlock.h>
15#include <linux/init.h>
16#include <asm/atomic.h>
17
18/*
19 * We use a hashed array of spinlocks to provide exclusive access
20 * to each atomic64_t variable. Since this is expected to used on
21 * systems with small numbers of CPUs (<= 4 or so), we use a
22 * relatively small array of 16 spinlocks to avoid wasting too much
23 * memory on the spinlock array.
24 */
25#define NR_LOCKS 16
26
27/*
28 * Ensure each lock is in a separate cacheline.
29 */
30static union {
31 spinlock_t lock;
32 char pad[L1_CACHE_BYTES];
33} atomic64_lock[NR_LOCKS] __cacheline_aligned_in_smp;
34
35static inline spinlock_t *lock_addr(const atomic64_t *v)
36{
37 unsigned long addr = (unsigned long) v;
38
39 addr >>= L1_CACHE_SHIFT;
40 addr ^= (addr >> 8) ^ (addr >> 16);
41 return &atomic64_lock[addr & (NR_LOCKS - 1)].lock;
42}
43
44long long atomic64_read(const atomic64_t *v)
45{
46 unsigned long flags;
47 spinlock_t *lock = lock_addr(v);
48 long long val;
49
50 spin_lock_irqsave(lock, flags);
51 val = v->counter;
52 spin_unlock_irqrestore(lock, flags);
53 return val;
54}
55
56void atomic64_set(atomic64_t *v, long long i)
57{
58 unsigned long flags;
59 spinlock_t *lock = lock_addr(v);
60
61 spin_lock_irqsave(lock, flags);
62 v->counter = i;
63 spin_unlock_irqrestore(lock, flags);
64}
65
66void atomic64_add(long long a, atomic64_t *v)
67{
68 unsigned long flags;
69 spinlock_t *lock = lock_addr(v);
70
71 spin_lock_irqsave(lock, flags);
72 v->counter += a;
73 spin_unlock_irqrestore(lock, flags);
74}
75
76long long atomic64_add_return(long long a, atomic64_t *v)
77{
78 unsigned long flags;
79 spinlock_t *lock = lock_addr(v);
80 long long val;
81
82 spin_lock_irqsave(lock, flags);
83 val = v->counter += a;
84 spin_unlock_irqrestore(lock, flags);
85 return val;
86}
87
88void atomic64_sub(long long a, atomic64_t *v)
89{
90 unsigned long flags;
91 spinlock_t *lock = lock_addr(v);
92
93 spin_lock_irqsave(lock, flags);
94 v->counter -= a;
95 spin_unlock_irqrestore(lock, flags);
96}
97
98long long atomic64_sub_return(long long a, atomic64_t *v)
99{
100 unsigned long flags;
101 spinlock_t *lock = lock_addr(v);
102 long long val;
103
104 spin_lock_irqsave(lock, flags);
105 val = v->counter -= a;
106 spin_unlock_irqrestore(lock, flags);
107 return val;
108}
109
110long long atomic64_dec_if_positive(atomic64_t *v)
111{
112 unsigned long flags;
113 spinlock_t *lock = lock_addr(v);
114 long long val;
115
116 spin_lock_irqsave(lock, flags);
117 val = v->counter - 1;
118 if (val >= 0)
119 v->counter = val;
120 spin_unlock_irqrestore(lock, flags);
121 return val;
122}
123
124long long atomic64_cmpxchg(atomic64_t *v, long long o, long long n)
125{
126 unsigned long flags;
127 spinlock_t *lock = lock_addr(v);
128 long long val;
129
130 spin_lock_irqsave(lock, flags);
131 val = v->counter;
132 if (val == o)
133 v->counter = n;
134 spin_unlock_irqrestore(lock, flags);
135 return val;
136}
137
138long long atomic64_xchg(atomic64_t *v, long long new)
139{
140 unsigned long flags;
141 spinlock_t *lock = lock_addr(v);
142 long long val;
143
144 spin_lock_irqsave(lock, flags);
145 val = v->counter;
146 v->counter = new;
147 spin_unlock_irqrestore(lock, flags);
148 return val;
149}
150
151int atomic64_add_unless(atomic64_t *v, long long a, long long u)
152{
153 unsigned long flags;
154 spinlock_t *lock = lock_addr(v);
155 int ret = 1;
156
157 spin_lock_irqsave(lock, flags);
158 if (v->counter != u) {
159 v->counter += a;
160 ret = 0;
161 }
162 spin_unlock_irqrestore(lock, flags);
163 return ret;
164}
165
166static int init_atomic64_lock(void)
167{
168 int i;
169
170 for (i = 0; i < NR_LOCKS; ++i)
171 spin_lock_init(&atomic64_lock[i].lock);
172 return 0;
173}
174
175pure_initcall(init_atomic64_lock);