summaryrefslogtreecommitdiff
path: root/src/sync/atomic/asm_linux_arm.s
blob: 944758441a84c5bb373934287332763d0f74d666 (plain)
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
// Copyright 2011 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build !race

#include "textflag.h"

// Linux/ARM atomic operations.

// Because there is so much variation in ARM devices,
// the Linux kernel provides an appropriate compare-and-swap
// implementation at address 0xffff0fc0.  Caller sets:
//	R0 = old value
//	R1 = new value
//	R2 = addr
//	LR = return address
// The function returns with CS true if the swap happened.
// http://lxr.linux.no/linux+v2.6.37.2/arch/arm/kernel/entry-armv.S#L850
// On older kernels (before 2.6.24) the function can incorrectly
// report a conflict, so we have to double-check the compare ourselves
// and retry if necessary.
//
// http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=b49c0f24cf6744a3f4fd09289fe7cade349dead5
//
TEXT cas<>(SB),NOSPLIT,$0
	MOVW	$0xffff0fc0, PC

TEXT ·CompareAndSwapInt32(SB),NOSPLIT,$0
	B	·CompareAndSwapUint32(SB)

// Implement using kernel cas for portability.
TEXT ·CompareAndSwapUint32(SB),NOSPLIT,$0-13
	MOVW	addr+0(FP), R2
	// trigger potential paging fault here,
	// because we don't know how to traceback through __kuser_cmpxchg
	MOVW	(R2), R0
	MOVW	old+4(FP), R0
casagain:
	MOVW	new+8(FP), R1
	BL	cas<>(SB)
	BCC	cascheck
	MOVW	$1, R0
casret:
	MOVB	R0, swapped+12(FP)
	RET
cascheck:
	// Kernel lies; double-check.
	MOVW	addr+0(FP), R2
	MOVW	old+4(FP), R0
	MOVW	0(R2), R3
	CMP	R0, R3
	BEQ	casagain
	MOVW	$0, R0
	B	casret

TEXT ·CompareAndSwapUintptr(SB),NOSPLIT,$0
	B	·CompareAndSwapUint32(SB)

TEXT ·CompareAndSwapPointer(SB),NOSPLIT,$0
	B	·CompareAndSwapUint32(SB)

TEXT ·AddInt32(SB),NOSPLIT,$0
	B	·AddUint32(SB)

// Implement using kernel cas for portability.
TEXT ·AddUint32(SB),NOSPLIT,$0-12
	MOVW	addr+0(FP), R2
	MOVW	delta+4(FP), R4
addloop1:
	MOVW	0(R2), R0
	MOVW	R0, R1
	ADD	R4, R1
	BL	cas<>(SB)
	BCC	addloop1
	MOVW	R1, new+8(FP)
	RET

TEXT ·AddUintptr(SB),NOSPLIT,$0
	B	·AddUint32(SB)

TEXT ·SwapInt32(SB),NOSPLIT,$0
	B	·SwapUint32(SB)

// Implement using kernel cas for portability.
TEXT ·SwapUint32(SB),NOSPLIT,$0-12
	MOVW	addr+0(FP), R2
	MOVW	new+4(FP), R1
swaploop1:
	MOVW	0(R2), R0
	MOVW	R0, R4 // cas smashes R0
	BL	cas<>(SB)
	BCC	swaploop1
	MOVW	R4, old+8(FP)
	RET

TEXT ·SwapUintptr(SB),NOSPLIT,$0
	B	·SwapUint32(SB)

TEXT ·SwapPointer(SB),NOSPLIT,$0
	B	·SwapUint32(SB)

TEXT cas64<>(SB),NOSPLIT,$0
	MOVW	$0xffff0f60, PC // __kuser_cmpxchg64: Linux-3.1 and above

TEXT kernelCAS64<>(SB),NOSPLIT,$0-21
	// int (*__kuser_cmpxchg64_t)(const int64_t *oldval, const int64_t *newval, volatile int64_t *ptr);
	MOVW	addr+0(FP), R2 // ptr
	// trigger potential paging fault here,
	// because we don't know how to traceback through __kuser_cmpxchg64
	MOVW	(R2), R0
	// make unaligned atomic access panic
	AND.S	$7, R2, R1
	BEQ 	2(PC)
	MOVW	R1, (R1)
	MOVW	$4(FP), R0 // oldval
	MOVW	$12(FP), R1 // newval
	BL	cas64<>(SB)
	MOVW.CS	$1, R0 // C is set if the kernel has changed *ptr
	MOVW.CC	$0, R0
	MOVW	R0, 20(FP)
	RET

TEXT ·generalCAS64(SB),NOSPLIT,$0-21
	B  	runtime·cas64(SB)

GLOBL armCAS64(SB), NOPTR, $4

TEXT setupAndCallCAS64<>(SB),NOSPLIT,$-4-21
	MOVW	$0xffff0ffc, R0 // __kuser_helper_version
	MOVW	(R0), R0
	// __kuser_cmpxchg64 only present if helper version >= 5
	CMP 	$5, R0
	MOVW.CS	$kernelCAS64<>(SB), R1
	MOVW.CS	R1, armCAS64(SB)
	MOVW.CS	R1, PC
	MOVB	runtime·armArch(SB), R0
	// LDREXD, STREXD only present on ARMv6K or higher
	CMP	$6, R0 // TODO(minux): how to differentiate ARMv6 with ARMv6K?
	MOVW.CS	$·armCompareAndSwapUint64(SB), R1
	MOVW.CS	R1, armCAS64(SB)
	MOVW.CS	R1, PC
	// we are out of luck, can only use runtime's emulated 64-bit cas
	MOVW	$·generalCAS64(SB), R1
	MOVW	R1, armCAS64(SB)
	MOVW	R1, PC

TEXT ·CompareAndSwapInt64(SB),NOSPLIT,$0
	B   	·CompareAndSwapUint64(SB)

TEXT ·CompareAndSwapUint64(SB),NOSPLIT,$-4-21
	MOVW	armCAS64(SB), R0
	CMP 	$0, R0
	MOVW.NE	R0, PC
	B	setupAndCallCAS64<>(SB)

TEXT ·AddInt64(SB),NOSPLIT,$0
	B	·addUint64(SB)

TEXT ·AddUint64(SB),NOSPLIT,$0
	B	·addUint64(SB)

TEXT ·SwapInt64(SB),NOSPLIT,$0
	B	·swapUint64(SB)

TEXT ·SwapUint64(SB),NOSPLIT,$0
	B	·swapUint64(SB)

TEXT ·LoadInt32(SB),NOSPLIT,$0
	B	·LoadUint32(SB)

TEXT ·LoadUint32(SB),NOSPLIT,$0-8
	MOVW	addr+0(FP), R2
loadloop1:
	MOVW	0(R2), R0
	MOVW	R0, R1
	BL	cas<>(SB)
	BCC	loadloop1
	MOVW	R1, val+4(FP)
	RET

TEXT ·LoadInt64(SB),NOSPLIT,$0
	B	·loadUint64(SB)

TEXT ·LoadUint64(SB),NOSPLIT,$0
	B	·loadUint64(SB)

TEXT ·LoadUintptr(SB),NOSPLIT,$0
	B	·LoadUint32(SB)

TEXT ·LoadPointer(SB),NOSPLIT,$0
	B	·LoadUint32(SB)

TEXT ·StoreInt32(SB),NOSPLIT,$0
	B	·StoreUint32(SB)

TEXT ·StoreUint32(SB),NOSPLIT,$0-8
	MOVW	addr+0(FP), R2
	MOVW	val+4(FP), R1
storeloop1:
	MOVW	0(R2), R0
	BL	cas<>(SB)
	BCC	storeloop1
	RET

TEXT ·StoreInt64(SB),NOSPLIT,$0
	B	·storeUint64(SB)

TEXT ·StoreUint64(SB),NOSPLIT,$0
	B	·storeUint64(SB)

TEXT ·StoreUintptr(SB),NOSPLIT,$0
	B	·StoreUint32(SB)

TEXT ·StorePointer(SB),NOSPLIT,$0
	B	·StoreUint32(SB)