1/*
2 * PowerPC spinlock operations.
3 * With the help of various source: Darwin, Linux kernel...
4 *
5 * $Id: spinlock.h.in,v 1.1 2007/05/24 12:54:10 mbuna Exp $
6 */
7#ifndef INCLUDED_PEAK_SPINLOCK_H_
8#define INCLUDED_PEAK_SPINLOCK_H_
9
10#define PEAK_SPINLOCK_ITERATIONS 1000
11
12#ifdef HAVE_CONFIG_H
13#include "config.h"
14#endif
15#include <sys/types.h>
16#include <sys/time.h>
17#include <sys/signal.h>
18#include <pthread.h>
19#include <unistd.h>
20#ifdef HAVE_SCHED_H
21#include <sched.h>
22#endif
23#ifdef HAVE_ATOMIC_OP_H
24#include <sys/atomic_op.h> /* AIX */
25#endif
26
27#include <peak/stdint.h>
28
29/* Erratum #77 on the 405 means we need a sync or dcbt before every stwcx.
30 * The old ATOMIC_SYNC_FIX covered some but not all of this.
31 */
32#ifdef CONFIG_IBM405_ERR77
33#define PPC405_ERR77(ra,rb)	"dcbt " #ra "," #rb ";"
34#else
35#define PPC405_ERR77(ra,rb)
36#endif
37
38/* PEAK defaults to SMP
39 */
40#ifdef PEAK_CONFIG_UNIPROCESSOR
41#define SMP_WMB
42#define SMP_MB
43#else
44#define SMP_WMB "eieio\n"
45#define SMP_MB "\nsync"
46#endif /* PEAK_CONFIG_UNIPROCESSOR */
47
48
49#if defined(__cplusplus)
50extern "C" {
51#endif
52
53/* PEAK INTERNAL SPIN LOCK
54 *
55 * _peak_spinlock_lock(lockp)
56 * _peak_spinlock_lock_try(lockp) - returns 0 (busy) or 1 (got the lock)
57 * _peak_spinlock_unlock(lockp)
58 *
59 */
60
61extern int _peak_is_threaded;
62
63#if defined(HAVE__SPIN_LOCK) && defined(HAVE__SPIN_LOCK_TRY) && defined(HAVE__SPIN_UNLOCK)
64#define USE_DARWIN_SPINLOCK 1
65#else
66#define USE_DARWIN_SPINLOCK 0
67#endif
68
69/* Type and initializer for this architecture.
70 */
71#if USE_DARWIN_SPINLOCK
72typedef volatile long peak_spinlock_t;
73#define PEAK_SPINLOCK_INITIALIZER (0)
74#elif defined(__GNUC__)
75typedef struct { volatile unsigned long lock; } peak_spinlock_t;
76#define PEAK_SPINLOCK_INITIALIZER ((peak_spinlock_t){ (0) })
77#elif defined(_AIX)
78typedef int peak_spinlock_t; /* AIX: typedef int     *atomic_p; */
79#define PEAK_SPINLOCK_INITIALIZER (0)
80#endif
81
82#if USE_DARWIN_SPINLOCK
83extern void _spin_lock(peak_spinlock_t *lockp);
84extern int  _spin_lock_try(peak_spinlock_t *lockp);
85extern void _spin_unlock(peak_spinlock_t *lockp);
86#endif
87
88static inline void
89_peak_spinlock_lock(peak_spinlock_t *lockp)
90  {
91#if USE_DARWIN_SPINLOCK
92
93  if (!_peak_is_threaded) /* set only if peak uses several threads */
94    return;
95
96  _spin_lock(lockp);   /* Lucky, we have a system function for that.
97                        * I checked on Darwin 7.0 and it deals properly
98                        * with 32 or 64 bit, UP (always depress) or
99                        * MP (1000 spin tries before relinquish).
100                        * On Darwin 6.x, however, the kernel use generic
101                        * PPC MP code, but well.                 --mbuna
102                        */
103
104#elif defined(__GNUC__)
105
106  unsigned long tmp;
107
108  if (!_peak_is_threaded) /* set only if peak uses several threads */
109    return;
110
111  __asm__ __volatile__(
112        "b	1f		# spin_lock\n\
1132:	lwzx	%0,0,%1\n\
114	cmpwi	0,%0,0\n\
115	bne+	2b\n\
1161:	lwarx	%0,0,%1\n\
117	cmpwi	0,%0,0\n\
118	bne-	2b\n"
119	PPC405_ERR77(0,%1)
120"	stwcx.	%2,0,%1\n\
121	bne-	2b\n\
122	isync"
123	: "=&r"(tmp)
124	: "r"(&lockp->lock), "r"(1)
125	: "cr0", "memory");
126
127#elif defined(_AIX)
128
129  unsigned int tries = PEAK_SPINLOCK_ITERATIONS;
130
131  if (!_peak_is_threaded) /* set only if peak uses several threads */
132    return;
133
134  while (!_check_lock(lockp, 0, 1))
135    {
136    if (--tries > 0)
137      {
138      sched_yield();
139      tries = PEAK_SPINLOCK_ITERATIONS;
140      }
141    }
142
143#else
144#error _peak_spinlock_lock not supported
145#endif /* USE_DARWIN_SPINLOCK */
146  }
147
148static inline int
149_peak_spinlock_lock_try(peak_spinlock_t *lockp)
150  {
151#if USE_DARWIN_SPINLOCK
152  if (!_peak_is_threaded) /* set only if peak uses several threads */
153    return 1; /* always succeed */
154
155  return _spin_lock_try(lockp);
156#elif defined(__GNUC__)
157  unsigned int old, t;
158  unsigned int mask = 1;
159  volatile unsigned int *p = ((volatile unsigned int *)lockp->lock);
160
161  if (!_peak_is_threaded) /* set only if peak uses several threads */
162    return 1; /* always succeed */
163
164  __asm__ __volatile__(SMP_WMB "\n\
1651:      lwarx   %0,0,%4 \n\
166        or      %1,%0,%3 \n"
167        PPC405_ERR77(0,%4)
168"       stwcx.  %1,0,%4 \n\
169        bne     1b"
170        SMP_MB
171        : "=&r" (old), "=&r" (t), "=m" (*p)
172        : "r" (mask), "r" (p), "m" (*p)
173        : "cc", "memory");
174
175  return (old & mask) == 0;
176#elif defined(_AIX)
177  return !_check_lock(lockp, 0, 1);
178#else
179#error _peak_spinlock_lock_try not supported
180#endif
181  }
182
183static inline void
184_peak_spinlock_unlock(peak_spinlock_t *lockp)
185  {
186  if (!_peak_is_threaded) /* set only if peak uses several threads */
187    return;
188
189#if USE_DARWIN_SPINLOCK
190  if (!_peak_is_threaded) /* set only if peak uses several threads */
191    return;
192
193  _spin_unlock(lockp);
194
195#elif defined(__GNUC__)
196  /* The eieio instruction is a faster sync: a barrier providing an ordering
197   * (separately) for (a) cacheable stores and (b) loads and stores to
198   * non-cacheable memory (e.g. I/O devices).
199   */
200  __asm__ __volatile__("eieio		# spin_unlock": : :"memory");
201  lockp->lock = 0;
202#elif defined(_AIX)
203  _clear_lock(lockp, 0);
204#else
205#error _peak_spinlock_unlock not supported
206#endif
207  }
208
209#if defined(__cplusplus)
210}
211#endif
212
213#endif /* INCLUDED_PEAK_SPINLOCK_H_ */
214