xref: /haiku/src/system/kernel/locks/user_mutex.cpp (revision 73ad2473e7874b3702cf5b0fdf4c81b747812ed9)
1813d4cbeSIngo Weinhold /*
2813d4cbeSIngo Weinhold  * Copyright 2010, Ingo Weinhold, ingo_weinhold@gmx.de.
3813d4cbeSIngo Weinhold  * Distributed under the terms of the MIT License.
4813d4cbeSIngo Weinhold  */
5813d4cbeSIngo Weinhold 
6813d4cbeSIngo Weinhold 
7813d4cbeSIngo Weinhold #include <user_mutex.h>
8813d4cbeSIngo Weinhold #include <user_mutex_defs.h>
9813d4cbeSIngo Weinhold 
10813d4cbeSIngo Weinhold #include <condition_variable.h>
11813d4cbeSIngo Weinhold #include <kernel.h>
12813d4cbeSIngo Weinhold #include <lock.h>
13813d4cbeSIngo Weinhold #include <smp.h>
14813d4cbeSIngo Weinhold #include <syscall_restart.h>
15813d4cbeSIngo Weinhold #include <util/AutoLock.h>
16813d4cbeSIngo Weinhold #include <util/OpenHashTable.h>
17813d4cbeSIngo Weinhold #include <vm/vm.h>
18813d4cbeSIngo Weinhold #include <vm/VMArea.h>
19813d4cbeSIngo Weinhold 
20813d4cbeSIngo Weinhold 
21813d4cbeSIngo Weinhold struct UserMutexEntry;
22813d4cbeSIngo Weinhold typedef DoublyLinkedList<UserMutexEntry> UserMutexEntryList;
23813d4cbeSIngo Weinhold 
24813d4cbeSIngo Weinhold struct UserMutexEntry : public DoublyLinkedListLinkImpl<UserMutexEntry> {
25813d4cbeSIngo Weinhold 	addr_t				address;
26813d4cbeSIngo Weinhold 	ConditionVariable	condition;
27813d4cbeSIngo Weinhold 	bool				locked;
28813d4cbeSIngo Weinhold 	UserMutexEntryList	otherEntries;
29813d4cbeSIngo Weinhold 	UserMutexEntry*		hashNext;
30813d4cbeSIngo Weinhold };
31813d4cbeSIngo Weinhold 
32813d4cbeSIngo Weinhold struct UserMutexHashDefinition {
33813d4cbeSIngo Weinhold 	typedef addr_t			KeyType;
34813d4cbeSIngo Weinhold 	typedef UserMutexEntry	ValueType;
35813d4cbeSIngo Weinhold 
36813d4cbeSIngo Weinhold 	size_t HashKey(addr_t key) const
37813d4cbeSIngo Weinhold 	{
38813d4cbeSIngo Weinhold 		return key >> 2;
39813d4cbeSIngo Weinhold 	}
40813d4cbeSIngo Weinhold 
41813d4cbeSIngo Weinhold 	size_t Hash(const UserMutexEntry* value) const
42813d4cbeSIngo Weinhold 	{
43813d4cbeSIngo Weinhold 		return HashKey(value->address);
44813d4cbeSIngo Weinhold 	}
45813d4cbeSIngo Weinhold 
46813d4cbeSIngo Weinhold 	bool Compare(addr_t key, const UserMutexEntry* value) const
47813d4cbeSIngo Weinhold 	{
48813d4cbeSIngo Weinhold 		return value->address == key;
49813d4cbeSIngo Weinhold 	}
50813d4cbeSIngo Weinhold 
51813d4cbeSIngo Weinhold 	UserMutexEntry*& GetLink(UserMutexEntry* value) const
52813d4cbeSIngo Weinhold 	{
53813d4cbeSIngo Weinhold 		return value->hashNext;
54813d4cbeSIngo Weinhold 	}
55813d4cbeSIngo Weinhold };
56813d4cbeSIngo Weinhold 
57813d4cbeSIngo Weinhold typedef BOpenHashTable<UserMutexHashDefinition> UserMutexTable;
58813d4cbeSIngo Weinhold 
59813d4cbeSIngo Weinhold 
60813d4cbeSIngo Weinhold static UserMutexTable sUserMutexTable;
61813d4cbeSIngo Weinhold static mutex sUserMutexTableLock = MUTEX_INITIALIZER("user mutex table");
62813d4cbeSIngo Weinhold 
63813d4cbeSIngo Weinhold 
64813d4cbeSIngo Weinhold static void
65813d4cbeSIngo Weinhold add_user_mutex_entry(UserMutexEntry* entry)
66813d4cbeSIngo Weinhold {
67813d4cbeSIngo Weinhold 	UserMutexEntry* firstEntry = sUserMutexTable.Lookup(entry->address);
68813d4cbeSIngo Weinhold 	if (firstEntry != NULL)
69813d4cbeSIngo Weinhold 		firstEntry->otherEntries.Add(entry);
70813d4cbeSIngo Weinhold 	else
71813d4cbeSIngo Weinhold 		sUserMutexTable.Insert(entry);
72813d4cbeSIngo Weinhold }
73813d4cbeSIngo Weinhold 
74813d4cbeSIngo Weinhold 
75813d4cbeSIngo Weinhold static bool
76813d4cbeSIngo Weinhold remove_user_mutex_entry(UserMutexEntry* entry)
77813d4cbeSIngo Weinhold {
78813d4cbeSIngo Weinhold 	UserMutexEntry* firstEntry = sUserMutexTable.Lookup(entry->address);
79813d4cbeSIngo Weinhold 	if (firstEntry != entry) {
80813d4cbeSIngo Weinhold 		// The entry is not the first entry in the table. Just remove it from
81813d4cbeSIngo Weinhold 		// the first entry's list.
82813d4cbeSIngo Weinhold 		firstEntry->otherEntries.Remove(entry);
83813d4cbeSIngo Weinhold 		return true;
84813d4cbeSIngo Weinhold 	}
85813d4cbeSIngo Weinhold 
86813d4cbeSIngo Weinhold 	// The entry is the first entry in the table. Remove it from the table and,
87813d4cbeSIngo Weinhold 	// if any, add the next entry to the table.
88813d4cbeSIngo Weinhold 	sUserMutexTable.Remove(entry);
89813d4cbeSIngo Weinhold 
900dffa8d4SIngo Weinhold 	firstEntry = entry->otherEntries.RemoveHead();
91813d4cbeSIngo Weinhold 	if (firstEntry != NULL) {
92813d4cbeSIngo Weinhold 		firstEntry->otherEntries.MoveFrom(&entry->otherEntries);
93813d4cbeSIngo Weinhold 		sUserMutexTable.Insert(firstEntry);
94813d4cbeSIngo Weinhold 		return true;
95813d4cbeSIngo Weinhold 	}
96813d4cbeSIngo Weinhold 
97813d4cbeSIngo Weinhold 	return false;
98813d4cbeSIngo Weinhold }
99813d4cbeSIngo Weinhold 
100813d4cbeSIngo Weinhold 
101813d4cbeSIngo Weinhold static status_t
102*73ad2473SPawel Dziepak user_mutex_lock_locked(int32* mutex, addr_t physicalAddress, const char* name,
103813d4cbeSIngo Weinhold 	uint32 flags, bigtime_t timeout, MutexLocker& locker)
104813d4cbeSIngo Weinhold {
105813d4cbeSIngo Weinhold 	// mark the mutex locked + waiting
106813d4cbeSIngo Weinhold 	int32 oldValue = atomic_or(mutex,
107813d4cbeSIngo Weinhold 		B_USER_MUTEX_LOCKED | B_USER_MUTEX_WAITING);
108813d4cbeSIngo Weinhold 
109813d4cbeSIngo Weinhold 	// The mutex might have been unlocked (or disabled) in the meantime.
110813d4cbeSIngo Weinhold 	if ((oldValue & (B_USER_MUTEX_LOCKED | B_USER_MUTEX_WAITING)) == 0
111813d4cbeSIngo Weinhold 			|| (oldValue & B_USER_MUTEX_DISABLED) != 0) {
112813d4cbeSIngo Weinhold 		// clear the waiting flag and be done
113813d4cbeSIngo Weinhold 		atomic_and(mutex, ~(int32)B_USER_MUTEX_WAITING);
114813d4cbeSIngo Weinhold 		return B_OK;
115813d4cbeSIngo Weinhold 	}
116813d4cbeSIngo Weinhold 
117813d4cbeSIngo Weinhold 	// we have to wait
118813d4cbeSIngo Weinhold 
119813d4cbeSIngo Weinhold 	// add the entry to the table
120813d4cbeSIngo Weinhold 	UserMutexEntry entry;
121813d4cbeSIngo Weinhold 	entry.address = physicalAddress;
122813d4cbeSIngo Weinhold 	entry.locked = false;
123813d4cbeSIngo Weinhold 	add_user_mutex_entry(&entry);
124813d4cbeSIngo Weinhold 
125813d4cbeSIngo Weinhold 	// wait
126813d4cbeSIngo Weinhold 	ConditionVariableEntry waitEntry;
127813d4cbeSIngo Weinhold 	entry.condition.Init((void*)physicalAddress, "user mutex");
128813d4cbeSIngo Weinhold 	entry.condition.Add(&waitEntry);
129813d4cbeSIngo Weinhold 
130813d4cbeSIngo Weinhold 	locker.Unlock();
131813d4cbeSIngo Weinhold 	status_t error = waitEntry.Wait(flags, timeout);
132813d4cbeSIngo Weinhold 	locker.Lock();
133813d4cbeSIngo Weinhold 
134813d4cbeSIngo Weinhold 	// dequeue
135813d4cbeSIngo Weinhold 	if (!remove_user_mutex_entry(&entry)) {
136813d4cbeSIngo Weinhold 		// no one is waiting anymore -- clear the waiting flag
137813d4cbeSIngo Weinhold 		atomic_and(mutex, ~(int32)B_USER_MUTEX_WAITING);
138813d4cbeSIngo Weinhold 	}
139813d4cbeSIngo Weinhold 
140813d4cbeSIngo Weinhold 	if (error != B_OK
141813d4cbeSIngo Weinhold 			&& (entry.locked || (*mutex & B_USER_MUTEX_DISABLED) != 0)) {
142813d4cbeSIngo Weinhold 		// timeout or interrupt, but the mutex was unlocked or disabled in time
143813d4cbeSIngo Weinhold 		error = B_OK;
144813d4cbeSIngo Weinhold 	}
145813d4cbeSIngo Weinhold 
146813d4cbeSIngo Weinhold 	return error;
147813d4cbeSIngo Weinhold }
148813d4cbeSIngo Weinhold 
149813d4cbeSIngo Weinhold 
150813d4cbeSIngo Weinhold static void
151*73ad2473SPawel Dziepak user_mutex_unlock_locked(int32* mutex, addr_t physicalAddress, uint32 flags)
152813d4cbeSIngo Weinhold {
153813d4cbeSIngo Weinhold 	if (UserMutexEntry* entry = sUserMutexTable.Lookup(physicalAddress)) {
1540dffa8d4SIngo Weinhold 		// Someone is waiting -- set the locked flag. It might still be set,
1550dffa8d4SIngo Weinhold 		// but when using userland atomic operations, the caller will usually
1560dffa8d4SIngo Weinhold 		// have cleared it already.
1570dffa8d4SIngo Weinhold 		int32 oldValue = atomic_or(mutex, B_USER_MUTEX_LOCKED);
1580dffa8d4SIngo Weinhold 
159813d4cbeSIngo Weinhold 		// unblock the first thread
160813d4cbeSIngo Weinhold 		entry->locked = true;
161813d4cbeSIngo Weinhold 		entry->condition.NotifyOne();
162813d4cbeSIngo Weinhold 
163813d4cbeSIngo Weinhold 		if ((flags & B_USER_MUTEX_UNBLOCK_ALL) != 0
1640dffa8d4SIngo Weinhold 				|| (oldValue & B_USER_MUTEX_DISABLED) != 0) {
165813d4cbeSIngo Weinhold 			// unblock all the other waiting threads as well
166813d4cbeSIngo Weinhold 			for (UserMutexEntryList::Iterator it
167813d4cbeSIngo Weinhold 					= entry->otherEntries.GetIterator();
168813d4cbeSIngo Weinhold 				UserMutexEntry* otherEntry = it.Next();) {
169813d4cbeSIngo Weinhold 				otherEntry->locked = true;
170813d4cbeSIngo Weinhold 				otherEntry->condition.NotifyOne();
171813d4cbeSIngo Weinhold 			}
172813d4cbeSIngo Weinhold 		}
1730dffa8d4SIngo Weinhold 	} else {
1740dffa8d4SIngo Weinhold 		// no one is waiting -- clear locked flag
1750dffa8d4SIngo Weinhold 		atomic_and(mutex, ~(int32)B_USER_MUTEX_LOCKED);
176813d4cbeSIngo Weinhold 	}
177813d4cbeSIngo Weinhold }
178813d4cbeSIngo Weinhold 
179813d4cbeSIngo Weinhold 
180813d4cbeSIngo Weinhold static status_t
181813d4cbeSIngo Weinhold user_mutex_lock(int32* mutex, const char* name, uint32 flags, bigtime_t timeout)
182813d4cbeSIngo Weinhold {
183813d4cbeSIngo Weinhold 	// wire the page and get the physical address
184813d4cbeSIngo Weinhold 	VMPageWiringInfo wiringInfo;
185813d4cbeSIngo Weinhold 	status_t error = vm_wire_page(B_CURRENT_TEAM, (addr_t)mutex, true,
186813d4cbeSIngo Weinhold 		&wiringInfo);
187813d4cbeSIngo Weinhold 	if (error != B_OK)
188813d4cbeSIngo Weinhold 		return error;
189813d4cbeSIngo Weinhold 
190813d4cbeSIngo Weinhold 	// get the lock
191813d4cbeSIngo Weinhold 	{
192813d4cbeSIngo Weinhold 		MutexLocker locker(sUserMutexTableLock);
193813d4cbeSIngo Weinhold 		error = user_mutex_lock_locked(mutex, wiringInfo.physicalAddress, name,
194813d4cbeSIngo Weinhold 			flags, timeout, locker);
195813d4cbeSIngo Weinhold 	}
196813d4cbeSIngo Weinhold 
197813d4cbeSIngo Weinhold 	// unwire the page
198813d4cbeSIngo Weinhold 	vm_unwire_page(&wiringInfo);
199813d4cbeSIngo Weinhold 
200813d4cbeSIngo Weinhold 	return error;
201813d4cbeSIngo Weinhold }
202813d4cbeSIngo Weinhold 
203813d4cbeSIngo Weinhold 
204813d4cbeSIngo Weinhold static status_t
205813d4cbeSIngo Weinhold user_mutex_switch_lock(int32* fromMutex, int32* toMutex, const char* name,
206813d4cbeSIngo Weinhold 	uint32 flags, bigtime_t timeout)
207813d4cbeSIngo Weinhold {
208813d4cbeSIngo Weinhold 	// wire the pages and get the physical addresses
209813d4cbeSIngo Weinhold 	VMPageWiringInfo fromWiringInfo;
210813d4cbeSIngo Weinhold 	status_t error = vm_wire_page(B_CURRENT_TEAM, (addr_t)fromMutex, true,
211813d4cbeSIngo Weinhold 		&fromWiringInfo);
212813d4cbeSIngo Weinhold 	if (error != B_OK)
213813d4cbeSIngo Weinhold 		return error;
214813d4cbeSIngo Weinhold 
215813d4cbeSIngo Weinhold 	VMPageWiringInfo toWiringInfo;
216813d4cbeSIngo Weinhold 	error = vm_wire_page(B_CURRENT_TEAM, (addr_t)toMutex, true, &toWiringInfo);
217813d4cbeSIngo Weinhold 	if (error != B_OK) {
218813d4cbeSIngo Weinhold 		vm_unwire_page(&fromWiringInfo);
219813d4cbeSIngo Weinhold 		return error;
220813d4cbeSIngo Weinhold 	}
221813d4cbeSIngo Weinhold 
222813d4cbeSIngo Weinhold 	// unlock the first mutex and lock the second one
223813d4cbeSIngo Weinhold 	{
224813d4cbeSIngo Weinhold 		MutexLocker locker(sUserMutexTableLock);
225813d4cbeSIngo Weinhold 		user_mutex_unlock_locked(fromMutex, fromWiringInfo.physicalAddress,
226813d4cbeSIngo Weinhold 			flags);
227813d4cbeSIngo Weinhold 
228813d4cbeSIngo Weinhold 		error = user_mutex_lock_locked(toMutex, toWiringInfo.physicalAddress,
229813d4cbeSIngo Weinhold 			name, flags, timeout, locker);
230813d4cbeSIngo Weinhold 	}
231813d4cbeSIngo Weinhold 
232813d4cbeSIngo Weinhold 	// unwire the pages
233813d4cbeSIngo Weinhold 	vm_unwire_page(&toWiringInfo);
234813d4cbeSIngo Weinhold 	vm_unwire_page(&fromWiringInfo);
235813d4cbeSIngo Weinhold 
236813d4cbeSIngo Weinhold 	return error;
237813d4cbeSIngo Weinhold }
238813d4cbeSIngo Weinhold 
239813d4cbeSIngo Weinhold 
240813d4cbeSIngo Weinhold // #pragma mark - kernel private
241813d4cbeSIngo Weinhold 
242813d4cbeSIngo Weinhold 
243813d4cbeSIngo Weinhold void
244813d4cbeSIngo Weinhold user_mutex_init()
245813d4cbeSIngo Weinhold {
246813d4cbeSIngo Weinhold 	if (sUserMutexTable.Init() != B_OK)
247813d4cbeSIngo Weinhold 		panic("user_mutex_init(): Failed to init table!");
248813d4cbeSIngo Weinhold }
249813d4cbeSIngo Weinhold 
250813d4cbeSIngo Weinhold 
251813d4cbeSIngo Weinhold // #pragma mark - syscalls
252813d4cbeSIngo Weinhold 
253813d4cbeSIngo Weinhold 
254813d4cbeSIngo Weinhold status_t
255813d4cbeSIngo Weinhold _user_mutex_lock(int32* mutex, const char* name, uint32 flags,
256813d4cbeSIngo Weinhold 	bigtime_t timeout)
257813d4cbeSIngo Weinhold {
258813d4cbeSIngo Weinhold 	if (mutex == NULL || !IS_USER_ADDRESS(mutex) || (addr_t)mutex % 4 != 0)
259813d4cbeSIngo Weinhold 		return B_BAD_ADDRESS;
260813d4cbeSIngo Weinhold 
261813d4cbeSIngo Weinhold 	syscall_restart_handle_timeout_pre(flags, timeout);
262813d4cbeSIngo Weinhold 
263813d4cbeSIngo Weinhold 	status_t error = user_mutex_lock(mutex, name, flags | B_CAN_INTERRUPT,
264813d4cbeSIngo Weinhold 		timeout);
265813d4cbeSIngo Weinhold 
266813d4cbeSIngo Weinhold 	return syscall_restart_handle_timeout_post(error, timeout);
267813d4cbeSIngo Weinhold }
268813d4cbeSIngo Weinhold 
269813d4cbeSIngo Weinhold 
270813d4cbeSIngo Weinhold status_t
271813d4cbeSIngo Weinhold _user_mutex_unlock(int32* mutex, uint32 flags)
272813d4cbeSIngo Weinhold {
273813d4cbeSIngo Weinhold 	if (mutex == NULL || !IS_USER_ADDRESS(mutex) || (addr_t)mutex % 4 != 0)
274813d4cbeSIngo Weinhold 		return B_BAD_ADDRESS;
275813d4cbeSIngo Weinhold 
276813d4cbeSIngo Weinhold 	// wire the page and get the physical address
277813d4cbeSIngo Weinhold 	VMPageWiringInfo wiringInfo;
278813d4cbeSIngo Weinhold 	status_t error = vm_wire_page(B_CURRENT_TEAM, (addr_t)mutex, true,
279813d4cbeSIngo Weinhold 		&wiringInfo);
280813d4cbeSIngo Weinhold 	if (error != B_OK)
281813d4cbeSIngo Weinhold 		return error;
282813d4cbeSIngo Weinhold 
283813d4cbeSIngo Weinhold 	{
284813d4cbeSIngo Weinhold 		MutexLocker locker(sUserMutexTableLock);
285813d4cbeSIngo Weinhold 		user_mutex_unlock_locked(mutex, wiringInfo.physicalAddress, flags);
286813d4cbeSIngo Weinhold 	}
287813d4cbeSIngo Weinhold 
288813d4cbeSIngo Weinhold 	vm_unwire_page(&wiringInfo);
289813d4cbeSIngo Weinhold 
290813d4cbeSIngo Weinhold 	return B_OK;
291813d4cbeSIngo Weinhold }
292813d4cbeSIngo Weinhold 
293813d4cbeSIngo Weinhold 
294813d4cbeSIngo Weinhold status_t
295813d4cbeSIngo Weinhold _user_mutex_switch_lock(int32* fromMutex, int32* toMutex, const char* name,
296813d4cbeSIngo Weinhold 	uint32 flags, bigtime_t timeout)
297813d4cbeSIngo Weinhold {
298813d4cbeSIngo Weinhold 	if (fromMutex == NULL || !IS_USER_ADDRESS(fromMutex)
299813d4cbeSIngo Weinhold 			|| (addr_t)fromMutex % 4 != 0 || toMutex == NULL
300813d4cbeSIngo Weinhold 			|| !IS_USER_ADDRESS(toMutex) || (addr_t)toMutex % 4 != 0) {
301813d4cbeSIngo Weinhold 		return B_BAD_ADDRESS;
302813d4cbeSIngo Weinhold 	}
303813d4cbeSIngo Weinhold 
3040dffa8d4SIngo Weinhold 	return user_mutex_switch_lock(fromMutex, toMutex, name,
305813d4cbeSIngo Weinhold 		flags | B_CAN_INTERRUPT, timeout);
306813d4cbeSIngo Weinhold }
307