summaryrefslogtreecommitdiff
path: root/include/linux/seqlock.h
blob: a6de405785dd30a84227ebdfdb764903a4c7561c (plain) (blame)
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
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
#ifndef __LINUX_SEQLOCK_H
#define __LINUX_SEQLOCK_H
/*
 * Reader/writer consistent mechanism without starving writers. This type of
 * lock for data where the reader wants a consistent set of information
 * and is willing to retry if the information changes. Readers block
 * on write contention (and where applicable, pi-boost the writer).
 * Readers without contention on entry acquire the critical section
 * without any atomic operations, but they may have to retry if a writer
 * enters before the critical section ends. Writers do not wait for readers.
 *
 * This is not as cache friendly as brlock. Also, this will not work
 * for data that contains pointers, because any writer could
 * invalidate a pointer that a reader was following.
 *
 * Expected reader usage:
 * 	do {
 *	    seq = read_seqbegin(&foo);
 * 	...
 *      } while (read_seqretry(&foo, seq));
 *
 *
 * On non-SMP the spin locks disappear but the writer still needs
 * to increment the sequence variables because an interrupt routine could
 * change the state of the data.
 *
 * Based on x86_64 vsyscall gettimeofday 
 * by Keith Owens and Andrea Arcangeli
 *
 * Priority inheritance and live-lock avoidance by Gregory Haskins
 */

#include <linux/spinlock.h>
#include <linux/preempt.h>

typedef struct {
	unsigned sequence;
	raw_spinlock_t lock;
} raw_seqlock_t;

typedef struct {
	unsigned sequence;
	rwlock_t lock;
} seqlock_t;

/*
 * These macros triggered gcc-3.x compile-time problems.  We think these are
 * OK now.  Be cautious.
 */
#define __RAW_SEQLOCK_UNLOCKED(lockname) \
	{ 0, __RAW_SPIN_LOCK_UNLOCKED(lockname) }

#define raw_seqlock_init(x)				\
	do {						\
		(x)->sequence = 0;			\
		raw_spin_lock_init(&(x)->lock);	\
	} while (0)

#define DEFINE_RAW_SEQLOCK(x) \
	raw_seqlock_t x = __RAW_SEQLOCK_UNLOCKED(x)

#define __SEQLOCK_UNLOCKED(lockname) \
	{ 0, __RW_LOCK_UNLOCKED(lockname) }

#define SEQLOCK_UNLOCKED \
	__SEQLOCK_UNLOCKED(old_style_seqlock_init)

#define seqlock_init(x)					\
	do {						\
		(x)->sequence = 0;			\
		rwlock_init(&(x)->lock);		\
	} while (0)

#define DEFINE_SEQLOCK(x) \
	seqlock_t x = __SEQLOCK_UNLOCKED(x)

/* Lock out other writers and update the count.
 * Acts like a normal spin_lock/unlock.
 * Don't need preempt_disable() because that is in the spin_lock already.
 */
static inline void write_raw_seqlock(raw_seqlock_t *sl)
{
	raw_spin_lock(&sl->lock);
	++sl->sequence;
	smp_wmb();
}

static inline void write_seqlock(seqlock_t *sl)
{
	write_lock(&sl->lock);
	++sl->sequence;
	smp_wmb();
}

static inline void write_raw_sequnlock(raw_seqlock_t *sl)
{
	smp_wmb();
	sl->sequence++;
	raw_spin_unlock(&sl->lock);
}

static inline void write_sequnlock(seqlock_t *sl)
{
	smp_wmb();
	sl->sequence++;
	write_unlock(&sl->lock);
}

static inline int write_tryseqlock(seqlock_t *sl)
{
	int ret = write_trylock(&sl->lock);

	if (ret) {
		++sl->sequence;
		smp_wmb();
	}
	return ret;
}

/* Start of read calculation -- fetch last complete writer token */
static __always_inline unsigned read_raw_seqbegin(const raw_seqlock_t *sl)
{
	unsigned ret;

repeat:
	ret = sl->sequence;
	smp_rmb();
	if (unlikely(ret & 1)) {
		cpu_relax();
		goto repeat;
	}

	return ret;
}

static __always_inline unsigned read_seqbegin(seqlock_t *sl)
{
	unsigned ret;

	ret = sl->sequence;
	smp_rmb();
	if (unlikely(ret & 1)) {
		cpu_relax();
		/*
		 * Serialze with the writer which will ensure they are
		 * pi-boosted if necessary and prevent us from starving
		 * them.
		 */
		read_lock(&sl->lock);
		ret = sl->sequence;
		read_unlock(&sl->lock);
	}

	BUG_ON(ret & 1);

	return ret;
}

/*
 * Test if reader processed invalid data.
 *
 * If sequence value changed then writer changed data while in section.
 */
static __always_inline int
read_raw_seqretry(const raw_seqlock_t *sl, unsigned start)
{
	smp_rmb();

	return (sl->sequence != start);
}

static __always_inline int read_seqretry(const seqlock_t *sl, unsigned start)
{
	smp_rmb();

	return (sl->sequence != start);
}


/*
 * Version using sequence counter only.
 * This can be used when code has its own mutex protecting the
 * updating starting before the write_seqcountbeqin() and ending
 * after the write_seqcount_end().
 */

typedef struct seqcount {
	unsigned sequence;
} seqcount_t;

#define SEQCNT_ZERO { 0 }
#define seqcount_init(x)	do { *(x) = (seqcount_t) SEQCNT_ZERO; } while (0)

/* Start of read using pointer to a sequence counter only.  */
static inline unsigned read_seqcount_begin(const seqcount_t *s)
{
	unsigned ret;

repeat:
	ret = s->sequence;
	smp_rmb();
	if (unlikely(ret & 1)) {
		cpu_relax();
		goto repeat;
	}
	return ret;
}

/*
 * Test if reader processed invalid data because sequence number has changed.
 */
static inline int read_seqcount_retry(const seqcount_t *s, unsigned start)
{
	smp_rmb();

	return s->sequence != start;
}


/*
 * Sequence counter only version assumes that callers are using their
 * own mutexing.
 */
static inline void write_seqcount_begin(seqcount_t *s)
{
	s->sequence++;
	smp_wmb();
}

static inline void write_seqcount_end(seqcount_t *s)
{
	smp_wmb();
	s->sequence++;
}

/*
 * Possible sw/hw IRQ protected versions of the interfaces.
 */
#define write_raw_seqlock_irqsave(lock, flags)				\
	do { local_irq_save(flags); write_raw_seqlock(lock); } while (0)
#define write_raw_seqlock_irq(lock)					\
	do { local_irq_disable();   write_raw_seqlock(lock); } while (0)
#define write_raw_seqlock_bh(lock)					\
	do { local_bh_disable();    write_raw_seqlock(lock); } while (0)

#define write_raw_sequnlock_irqrestore(lock, flags)			\
	do { write_raw_sequnlock(lock); local_irq_restore(flags); } while(0)
#define write_raw_sequnlock_irq(lock)					\
	do { write_raw_sequnlock(lock); local_irq_enable(); } while(0)
#define write_raw_sequnlock_bh(lock)					\
	do { write_raw_sequnlock(lock); local_bh_enable(); } while(0)

#define read_raw_seqbegin_irqsave(lock, flags)				\
	({ local_irq_save(flags);   read_raw_seqbegin(lock); })

#define read_raw_seqretry_irqrestore(lock, iv, flags)			\
	({								\
		int ret = read_raw_seqretry(lock, iv);			\
		local_irq_restore(flags);				\
		ret;							\
	})

#define write_seqlock_irqsave(lock, flags)				\
	do { local_irq_save(flags); write_seqlock(lock); } while (0)
#define write_seqlock_irq(lock)						\
	do { local_irq_disable();   write_seqlock(lock); } while (0)
#define write_seqlock_bh(lock)						\
	do { local_bh_disable();    write_seqlock(lock); } while (0)

#define write_sequnlock_irqrestore(lock, flags)				\
	do { write_sequnlock(lock); local_irq_restore(flags); } while(0)
#define write_sequnlock_irq(lock)					\
	do { write_sequnlock(lock); local_irq_enable(); } while(0)
#define write_sequnlock_bh(lock)					\
	do { write_sequnlock(lock); local_bh_enable(); } while(0)

#define read_seqbegin_irqsave(lock, flags)				\
	({ local_irq_save(flags);   read_seqbegin(lock); })

#define read_seqretry_irqrestore(lock, iv, flags)			\
	({								\
		int ret = read_seqretry(lock, iv);			\
		local_irq_restore(flags);				\
		ret;							\
	})

#endif /* __LINUX_SEQLOCK_H */