1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
|
/* ==Id: atomic.S,v 1.4 2001/11/18 00:12:56 davem Exp ==
* atomic.S: These things are too big to do inline.
*
* Copyright (C) 1999 David S. Miller (davem@redhat.com)
*/
/* Recreated as
* $Id$
* for use with -Ice-.
*
* Build with
* gcc -shared -o libIceAtomic.so -mcpu=v9 -mtune=v9 IceAtomic.c -fomit-frame-pointer -O2
* for standalone use, or, better perhaps, as part of libIceUtil.so by
* gcc -c -mcpu=v9 -mtune=v9 IceAtomic.c -fomit-frame-pointer -O2
* as part of the src/IceUtil build
*
/* Narrative:
* This is the linux kernel code for atomic add, atomic subtract on sparc-v9. In fact,
* although the add/sub routines are typed -void- externally, they actually return
* the result of their operation.
*
* The exchange_and_add is like add, except that it returns the original
* value of the counter (instead of the new one), and for some reason, the
* order of its arguments is reversed.
*
* Here is how I think (all three) work.
* do {
* A. g5 <- value;
* B. g7 <- g5 [+/-] delta;
* C. if (value == g5) swap(g7, value);
* D. } while (g5 != g7); // g5 was original value, g7 is swapped from original value
* // if they are not the same, someone changed the memory
* // copy before the swap, so we start over with a new value
* E. Synchronize_memory_and_data_cache;
* F. return [value+delta | value - delta | value] depending on add/sub/exchange_add.
*
* Notice that step -C.- is an indivisible operation, so everything is good coming
* out. The entire operation can be retried of between -A.- and start of -C.- someone
* else changes the counter variable. The point is that you get a -coherent- result
* (which is to say, you don't operate on a stale counter value, and so replace it
* with something wrong for everyone). You might not be operating on the values
* at call. To do that, put global Mutex around the call itself.
*
* WARNINGS:
* Do NOT put these in a header file for inlining. You will get bus errors,
* or the results will be wrong if you don't.
*
* Do NOT use these on Sparc 2, 5, 10, 20 etc. The instructions are
* sparc-version 9.
*
* These are written for sparc(v9)-linux-gcc. I have no idea what they will do
* with Solaris or with other compilers.
*
* I do not know if these work in general; I am not a sparc architect.
*
* --
* Ferris McCormick <fmccor@inforead.com>
* 06.iii.03
*/
typedef struct {volatile int counter;} atomic_t;
int __atomic_add(int i, atomic_t* v) {
__asm__ __volatile__ (
"1: lduw [%o1], %g5\n"
" add %g5, %o0, %g7\n"
" cas [%o1], %g5, %g7\n"
" cmp %g5, %g7\n"
" bne,pn %icc, 1b\n"
" membar #StoreLoad | #StoreStore\n"
" retl\n"
" add %g7, %o0, %o0\n"
);
return; /* Not Reached */
}
int __atomic_sub(int i, atomic_t *v) {
__asm__ __volatile__ (
"1: lduw [%o1], %g5\n"
" sub %g5, %o0, %g7\n"
" cas [%o1], %g5, %g7\n"
" cmp %g5, %g7\n"
" bne,pn %icc, 1b\n"
" membar #StoreLoad | #StoreStore\n"
" retl\n"
" sub %g7, %o0, %o0\n"
);
return; /* Not Reached */
}
int __atomic_exchange_and_add(atomic_t *v, int i) {
#if 0
int t2;
t2 = v->counter;
__atomic_add(i,v);
return t2; /* Of course, this is wrong because counter might change
after the assignment to t2 but before the add */
#else /* This is what we actually do */
__asm__ __volatile__ (
"1: lduw [%o0], %g5\n"
" add %g5, %o1, %g7\n"
" cas [%o0], %g5, %g7\n"
" cmp %g5, %g7\n"
" bne,pn %icc, 1b\n"
" membar #StoreLoad | #StoreStore\n"
" retl\n"
" mov %g7, %o0\n"
);
return; /* Not Reached */
#endif
}
|