2 * Copyright 2015, Hamish Morrison, hamishm53@gmail.com.
3 * Copyright 2010, Ingo Weinhold, ingo_weinhold@gmx.de.
4 * Distributed under the terms of the MIT License.
8 #include <user_mutex.h>
9 #include <user_mutex_defs.h>
11 #include <condition_variable.h>
15 #include <syscall_restart.h>
16 #include <util/AutoLock.h>
17 #include <util/OpenHashTable.h>
19 #include <vm/VMArea.h>
22 struct UserMutexEntry
;
23 typedef DoublyLinkedList
<UserMutexEntry
> UserMutexEntryList
;
25 struct UserMutexEntry
: public DoublyLinkedListLinkImpl
<UserMutexEntry
> {
27 ConditionVariable condition
;
29 UserMutexEntryList otherEntries
;
30 UserMutexEntry
* hashNext
;
33 struct UserMutexHashDefinition
{
34 typedef addr_t KeyType
;
35 typedef UserMutexEntry ValueType
;
37 size_t HashKey(addr_t key
) const
42 size_t Hash(const UserMutexEntry
* value
) const
44 return HashKey(value
->address
);
47 bool Compare(addr_t key
, const UserMutexEntry
* value
) const
49 return value
->address
== key
;
52 UserMutexEntry
*& GetLink(UserMutexEntry
* value
) const
54 return value
->hashNext
;
58 typedef BOpenHashTable
<UserMutexHashDefinition
> UserMutexTable
;
61 static UserMutexTable sUserMutexTable
;
62 static mutex sUserMutexTableLock
= MUTEX_INITIALIZER("user mutex table");
66 add_user_mutex_entry(UserMutexEntry
* entry
)
68 UserMutexEntry
* firstEntry
= sUserMutexTable
.Lookup(entry
->address
);
69 if (firstEntry
!= NULL
)
70 firstEntry
->otherEntries
.Add(entry
);
72 sUserMutexTable
.Insert(entry
);
77 remove_user_mutex_entry(UserMutexEntry
* entry
)
79 UserMutexEntry
* firstEntry
= sUserMutexTable
.Lookup(entry
->address
);
80 if (firstEntry
!= entry
) {
81 // The entry is not the first entry in the table. Just remove it from
82 // the first entry's list.
83 firstEntry
->otherEntries
.Remove(entry
);
87 // The entry is the first entry in the table. Remove it from the table and,
88 // if any, add the next entry to the table.
89 sUserMutexTable
.Remove(entry
);
91 firstEntry
= entry
->otherEntries
.RemoveHead();
92 if (firstEntry
!= NULL
) {
93 firstEntry
->otherEntries
.MoveFrom(&entry
->otherEntries
);
94 sUserMutexTable
.Insert(firstEntry
);
103 user_mutex_wait_locked(int32
* mutex
, addr_t physicalAddress
, const char* name
,
104 uint32 flags
, bigtime_t timeout
, MutexLocker
& locker
, bool& lastWaiter
)
106 // add the entry to the table
107 UserMutexEntry entry
;
108 entry
.address
= physicalAddress
;
109 entry
.locked
= false;
110 add_user_mutex_entry(&entry
);
113 ConditionVariableEntry waitEntry
;
114 entry
.condition
.Init((void*)physicalAddress
, "user mutex");
115 entry
.condition
.Add(&waitEntry
);
118 status_t error
= waitEntry
.Wait(flags
, timeout
);
121 if (error
!= B_OK
&& entry
.locked
)
125 // if nobody woke us up, we have to dequeue ourselves
126 lastWaiter
= !remove_user_mutex_entry(&entry
);
128 // otherwise the waker has done the work of marking the
129 // mutex or semaphore uncontended
138 user_mutex_lock_locked(int32
* mutex
, addr_t physicalAddress
,
139 const char* name
, uint32 flags
, bigtime_t timeout
, MutexLocker
& locker
)
141 // mark the mutex locked + waiting
142 int32 oldValue
= atomic_or(mutex
,
143 B_USER_MUTEX_LOCKED
| B_USER_MUTEX_WAITING
);
145 if ((oldValue
& (B_USER_MUTEX_LOCKED
| B_USER_MUTEX_WAITING
)) == 0
146 || (oldValue
& B_USER_MUTEX_DISABLED
) != 0) {
147 // clear the waiting flag and be done
148 atomic_and(mutex
, ~(int32
)B_USER_MUTEX_WAITING
);
153 status_t error
= user_mutex_wait_locked(mutex
, physicalAddress
, name
,
154 flags
, timeout
, locker
, lastWaiter
);
157 atomic_and(mutex
, ~(int32
)B_USER_MUTEX_WAITING
);
164 user_mutex_unlock_locked(int32
* mutex
, addr_t physicalAddress
, uint32 flags
)
166 UserMutexEntry
* entry
= sUserMutexTable
.Lookup(physicalAddress
);
168 // no one is waiting -- clear locked flag
169 atomic_and(mutex
, ~(int32
)B_USER_MUTEX_LOCKED
);
173 // Someone is waiting -- set the locked flag. It might still be set,
174 // but when using userland atomic operations, the caller will usually
175 // have cleared it already.
176 int32 oldValue
= atomic_or(mutex
, B_USER_MUTEX_LOCKED
);
178 // unblock the first thread
179 entry
->locked
= true;
180 entry
->condition
.NotifyOne();
182 if ((flags
& B_USER_MUTEX_UNBLOCK_ALL
) != 0
183 || (oldValue
& B_USER_MUTEX_DISABLED
) != 0) {
184 // unblock and dequeue all the other waiting threads as well
185 while (UserMutexEntry
* otherEntry
= entry
->otherEntries
.RemoveHead()) {
186 otherEntry
->locked
= true;
187 otherEntry
->condition
.NotifyOne();
190 // dequeue the first thread and mark the mutex uncontended
191 sUserMutexTable
.Remove(entry
);
192 atomic_and(mutex
, ~(int32
)B_USER_MUTEX_WAITING
);
194 bool otherWaiters
= remove_user_mutex_entry(entry
);
196 atomic_and(mutex
, ~(int32
)B_USER_MUTEX_WAITING
);
202 user_mutex_sem_acquire_locked(int32
* sem
, addr_t physicalAddress
,
203 const char* name
, uint32 flags
, bigtime_t timeout
, MutexLocker
& locker
)
205 // The semaphore may have been released in the meantime, and we also
206 // need to mark it as contended if it isn't already.
207 int32 oldValue
= atomic_get(sem
);
208 while (oldValue
> -1) {
209 int32 value
= atomic_test_and_set(sem
, oldValue
- 1, oldValue
);
210 if (value
== oldValue
&& value
> 0)
216 status_t error
= user_mutex_wait_locked(sem
, physicalAddress
, name
, flags
,
217 timeout
, locker
, lastWaiter
);
220 atomic_test_and_set(sem
, 0, -1);
227 user_mutex_sem_release_locked(int32
* sem
, addr_t physicalAddress
)
229 UserMutexEntry
* entry
= sUserMutexTable
.Lookup(physicalAddress
);
231 // no waiters - mark as uncontended and release
232 int32 oldValue
= atomic_get(sem
);
234 int32 inc
= oldValue
< 0 ? 2 : 1;
235 int32 value
= atomic_test_and_set(sem
, oldValue
+ inc
, oldValue
);
236 if (value
== oldValue
)
242 bool otherWaiters
= remove_user_mutex_entry(entry
);
244 entry
->locked
= true;
245 entry
->condition
.NotifyOne();
248 // mark the semaphore uncontended
249 atomic_test_and_set(sem
, 0, -1);
255 user_mutex_lock(int32
* mutex
, const char* name
, uint32 flags
, bigtime_t timeout
)
257 // wire the page and get the physical address
258 VMPageWiringInfo wiringInfo
;
259 status_t error
= vm_wire_page(B_CURRENT_TEAM
, (addr_t
)mutex
, true,
266 MutexLocker
locker(sUserMutexTableLock
);
267 error
= user_mutex_lock_locked(mutex
, wiringInfo
.physicalAddress
, name
,
268 flags
, timeout
, locker
);
272 vm_unwire_page(&wiringInfo
);
279 user_mutex_switch_lock(int32
* fromMutex
, int32
* toMutex
, const char* name
,
280 uint32 flags
, bigtime_t timeout
)
282 // wire the pages and get the physical addresses
283 VMPageWiringInfo fromWiringInfo
;
284 status_t error
= vm_wire_page(B_CURRENT_TEAM
, (addr_t
)fromMutex
, true,
289 VMPageWiringInfo toWiringInfo
;
290 error
= vm_wire_page(B_CURRENT_TEAM
, (addr_t
)toMutex
, true, &toWiringInfo
);
292 vm_unwire_page(&fromWiringInfo
);
296 // unlock the first mutex and lock the second one
298 MutexLocker
locker(sUserMutexTableLock
);
299 user_mutex_unlock_locked(fromMutex
, fromWiringInfo
.physicalAddress
,
302 error
= user_mutex_lock_locked(toMutex
, toWiringInfo
.physicalAddress
,
303 name
, flags
, timeout
, locker
);
307 vm_unwire_page(&toWiringInfo
);
308 vm_unwire_page(&fromWiringInfo
);
314 // #pragma mark - kernel private
320 if (sUserMutexTable
.Init() != B_OK
)
321 panic("user_mutex_init(): Failed to init table!");
325 // #pragma mark - syscalls
329 _user_mutex_lock(int32
* mutex
, const char* name
, uint32 flags
,
332 if (mutex
== NULL
|| !IS_USER_ADDRESS(mutex
) || (addr_t
)mutex
% 4 != 0)
333 return B_BAD_ADDRESS
;
335 syscall_restart_handle_timeout_pre(flags
, timeout
);
337 status_t error
= user_mutex_lock(mutex
, name
, flags
| B_CAN_INTERRUPT
,
340 return syscall_restart_handle_timeout_post(error
, timeout
);
345 _user_mutex_unlock(int32
* mutex
, uint32 flags
)
347 if (mutex
== NULL
|| !IS_USER_ADDRESS(mutex
) || (addr_t
)mutex
% 4 != 0)
348 return B_BAD_ADDRESS
;
350 // wire the page and get the physical address
351 VMPageWiringInfo wiringInfo
;
352 status_t error
= vm_wire_page(B_CURRENT_TEAM
, (addr_t
)mutex
, true,
358 MutexLocker
locker(sUserMutexTableLock
);
359 user_mutex_unlock_locked(mutex
, wiringInfo
.physicalAddress
, flags
);
362 vm_unwire_page(&wiringInfo
);
369 _user_mutex_switch_lock(int32
* fromMutex
, int32
* toMutex
, const char* name
,
370 uint32 flags
, bigtime_t timeout
)
372 if (fromMutex
== NULL
|| !IS_USER_ADDRESS(fromMutex
)
373 || (addr_t
)fromMutex
% 4 != 0 || toMutex
== NULL
374 || !IS_USER_ADDRESS(toMutex
) || (addr_t
)toMutex
% 4 != 0) {
375 return B_BAD_ADDRESS
;
378 return user_mutex_switch_lock(fromMutex
, toMutex
, name
,
379 flags
| B_CAN_INTERRUPT
, timeout
);
384 _user_mutex_sem_acquire(int32
* sem
, const char* name
, uint32 flags
,
387 if (sem
== NULL
|| !IS_USER_ADDRESS(sem
) || (addr_t
)sem
% 4 != 0)
388 return B_BAD_ADDRESS
;
390 syscall_restart_handle_timeout_pre(flags
, timeout
);
392 // wire the page and get the physical address
393 VMPageWiringInfo wiringInfo
;
394 status_t error
= vm_wire_page(B_CURRENT_TEAM
, (addr_t
)sem
, true,
400 MutexLocker
locker(sUserMutexTableLock
);
401 error
= user_mutex_sem_acquire_locked(sem
, wiringInfo
.physicalAddress
,
402 name
, flags
| B_CAN_INTERRUPT
, timeout
, locker
);
405 vm_unwire_page(&wiringInfo
);
406 return syscall_restart_handle_timeout_post(error
, timeout
);
411 _user_mutex_sem_release(int32
* sem
)
413 if (sem
== NULL
|| !IS_USER_ADDRESS(sem
) || (addr_t
)sem
% 4 != 0)
414 return B_BAD_ADDRESS
;
416 // wire the page and get the physical address
417 VMPageWiringInfo wiringInfo
;
418 status_t error
= vm_wire_page(B_CURRENT_TEAM
, (addr_t
)sem
, true,
424 MutexLocker
locker(sUserMutexTableLock
);
425 user_mutex_sem_release_locked(sem
, wiringInfo
.physicalAddress
);
428 vm_unwire_page(&wiringInfo
);