1/*
2 * Copyright 2009, Michael Lotz, mmlr@mlotz.ch.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include <locks.h>
8
9#include <OS.h>
10
11#include <syscalls.h>
12#include <user_thread.h>
13
14
15typedef struct rw_lock_waiter {
16	rw_lock_waiter *	next;
17	thread_id			thread;
18	bool				writer;
19} rw_lock_waiter;
20
21
22static status_t
23rw_lock_wait(rw_lock *lock, bool writer)
24{
25	rw_lock_waiter waiter;
26	waiter.thread = find_thread(NULL);
27	waiter.next = NULL;
28	waiter.writer = writer;
29
30	if (lock->waiters != NULL)
31		lock->last_waiter->next = &waiter;
32	else
33		lock->waiters = &waiter;
34
35	lock->last_waiter = &waiter;
36
37	// the rw_lock is locked when entering, release it before blocking
38	get_user_thread()->wait_status = 1;
39	mutex_unlock(&lock->lock);
40
41	status_t result;
42	do {
43		result = _kern_block_thread(0, 0);
44	} while (result == B_INTERRUPTED);
45
46	// and lock it again before returning
47	mutex_lock(&lock->lock);
48	return result;
49}
50
51
52static void
53rw_lock_unblock(rw_lock *lock)
54{
55	// this is called locked
56	if (lock->holder >= 0)
57		return;
58
59	rw_lock_waiter *waiter = lock->waiters;
60	if (waiter == NULL)
61		return;
62
63	if (waiter->writer) {
64		if (lock->reader_count > 0)
65			return;
66
67		lock->waiters = waiter->next;
68		lock->holder = waiter->thread;
69		_kern_unblock_thread(waiter->thread, B_OK);
70		return;
71	}
72
73	while (waiter != NULL && !waiter->writer) {
74		lock->reader_count++;
75		lock->waiters = waiter->next;
76		_kern_unblock_thread(waiter->thread, B_OK);
77		waiter = lock->waiters;
78	}
79}
80
81
82void
83__rw_lock_init(rw_lock *lock, const char *name)
84{
85	rw_lock_init_etc(lock, name, 0);
86}
87
88
89void
90__rw_lock_init_etc(rw_lock *lock, const char *name, uint32 flags)
91{
92	lock->waiters = NULL;
93	lock->holder = -1;
94	lock->reader_count = 0;
95	lock->writer_count = 0;
96	lock->owner_count = 0;
97	mutex_init_etc(&lock->lock, name, flags);
98}
99
100
101void
102__rw_lock_destroy(rw_lock *lock)
103{
104	mutex_lock(&lock->lock);
105
106	rw_lock_waiter *waiter = lock->waiters;
107	while (waiter != NULL) {
108		_kern_unblock_thread(waiter->thread, B_ERROR);
109		waiter = waiter->next;
110	}
111
112	mutex_destroy(&lock->lock);
113}
114
115
116status_t
117__rw_lock_read_lock(rw_lock *lock)
118{
119	MutexLocker locker(lock->lock);
120
121	if (lock->writer_count == 0) {
122		lock->reader_count++;
123		return B_OK;
124	}
125
126	if (lock->holder == find_thread(NULL)) {
127		lock->owner_count++;
128		return B_OK;
129	}
130
131	return rw_lock_wait(lock, false);
132}
133
134
135status_t
136__rw_lock_read_unlock(rw_lock *lock)
137{
138	MutexLocker locker(lock->lock);
139
140	if (lock->holder == find_thread(NULL)) {
141		if (--lock->owner_count > 0)
142			return B_OK;
143
144		// this originally has been a write lock
145		lock->writer_count--;
146		lock->holder = -1;
147
148		rw_lock_unblock(lock);
149		return B_OK;
150	}
151
152	if (lock->reader_count <= 0) {
153		debugger("rw_lock not read locked");
154		return B_ERROR;
155	}
156
157	lock->reader_count--;
158	rw_lock_unblock(lock);
159	return B_OK;
160}
161
162
163status_t
164__rw_lock_write_lock(rw_lock *lock)
165{
166	MutexLocker locker(lock->lock);
167
168	if (lock->reader_count == 0 && lock->writer_count == 0) {
169		lock->writer_count++;
170		lock->holder = find_thread(NULL);
171		lock->owner_count = 1;
172		return B_OK;
173	}
174
175	if (lock->holder == find_thread(NULL)) {
176		lock->owner_count++;
177		return B_OK;
178	}
179
180	lock->writer_count++;
181
182	status_t result = rw_lock_wait(lock, true);
183	if (result != B_OK)
184		return result;
185
186	if (lock->holder != find_thread(NULL)) {
187		debugger("write locked but holder not set");
188		return B_ERROR;
189	}
190
191	lock->owner_count = 1;
192	return B_OK;
193}
194
195
196status_t
197__rw_lock_write_unlock(rw_lock *lock)
198{
199	MutexLocker locker(lock->lock);
200
201	if (lock->holder != find_thread(NULL)) {
202		debugger("rw_lock not write locked");
203		return B_ERROR;
204	}
205
206	if (--lock->owner_count > 0)
207		return B_OK;
208
209	lock->writer_count--;
210	lock->holder = -1;
211	rw_lock_unblock(lock);
212	return B_OK;
213}
214