vfs: check userland buffers before reading them.
[haiku.git] / src / system / kernel / locks / user_mutex.cpp
blob89414ff3798740928226e13a11c72b8b866d3d82
1 /*
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.
5 */
8 #include <user_mutex.h>
9 #include <user_mutex_defs.h>
11 #include <condition_variable.h>
12 #include <kernel.h>
13 #include <lock.h>
14 #include <smp.h>
15 #include <syscall_restart.h>
16 #include <util/AutoLock.h>
17 #include <util/OpenHashTable.h>
18 #include <vm/vm.h>
19 #include <vm/VMArea.h>
22 struct UserMutexEntry;
23 typedef DoublyLinkedList<UserMutexEntry> UserMutexEntryList;
25 struct UserMutexEntry : public DoublyLinkedListLinkImpl<UserMutexEntry> {
26 addr_t address;
27 ConditionVariable condition;
28 bool locked;
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
39 return key >> 2;
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");
65 static void
66 add_user_mutex_entry(UserMutexEntry* entry)
68 UserMutexEntry* firstEntry = sUserMutexTable.Lookup(entry->address);
69 if (firstEntry != NULL)
70 firstEntry->otherEntries.Add(entry);
71 else
72 sUserMutexTable.Insert(entry);
76 static bool
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);
84 return true;
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);
95 return true;
98 return false;
102 static status_t
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);
112 // wait
113 ConditionVariableEntry waitEntry;
114 entry.condition.Init((void*)physicalAddress, "user mutex");
115 entry.condition.Add(&waitEntry);
117 locker.Unlock();
118 status_t error = waitEntry.Wait(flags, timeout);
119 locker.Lock();
121 if (error != B_OK && entry.locked)
122 error = B_OK;
124 if (!entry.locked) {
125 // if nobody woke us up, we have to dequeue ourselves
126 lastWaiter = !remove_user_mutex_entry(&entry);
127 } else {
128 // otherwise the waker has done the work of marking the
129 // mutex or semaphore uncontended
130 lastWaiter = false;
133 return error;
137 static status_t
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);
149 return B_OK;
152 bool lastWaiter;
153 status_t error = user_mutex_wait_locked(mutex, physicalAddress, name,
154 flags, timeout, locker, lastWaiter);
156 if (lastWaiter)
157 atomic_and(mutex, ~(int32)B_USER_MUTEX_WAITING);
159 return error;
163 static void
164 user_mutex_unlock_locked(int32* mutex, addr_t physicalAddress, uint32 flags)
166 UserMutexEntry* entry = sUserMutexTable.Lookup(physicalAddress);
167 if (entry == NULL) {
168 // no one is waiting -- clear locked flag
169 atomic_and(mutex, ~(int32)B_USER_MUTEX_LOCKED);
170 return;
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);
193 } else {
194 bool otherWaiters = remove_user_mutex_entry(entry);
195 if (!otherWaiters)
196 atomic_and(mutex, ~(int32)B_USER_MUTEX_WAITING);
201 static status_t
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)
211 return B_OK;
212 oldValue = value;
215 bool lastWaiter;
216 status_t error = user_mutex_wait_locked(sem, physicalAddress, name, flags,
217 timeout, locker, lastWaiter);
219 if (lastWaiter)
220 atomic_test_and_set(sem, 0, -1);
222 return error;
226 static void
227 user_mutex_sem_release_locked(int32* sem, addr_t physicalAddress)
229 UserMutexEntry* entry = sUserMutexTable.Lookup(physicalAddress);
230 if (!entry) {
231 // no waiters - mark as uncontended and release
232 int32 oldValue = atomic_get(sem);
233 while (true) {
234 int32 inc = oldValue < 0 ? 2 : 1;
235 int32 value = atomic_test_and_set(sem, oldValue + inc, oldValue);
236 if (value == oldValue)
237 return;
238 oldValue = value;
242 bool otherWaiters = remove_user_mutex_entry(entry);
244 entry->locked = true;
245 entry->condition.NotifyOne();
247 if (!otherWaiters) {
248 // mark the semaphore uncontended
249 atomic_test_and_set(sem, 0, -1);
254 static status_t
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,
260 &wiringInfo);
261 if (error != B_OK)
262 return error;
264 // get the lock
266 MutexLocker locker(sUserMutexTableLock);
267 error = user_mutex_lock_locked(mutex, wiringInfo.physicalAddress, name,
268 flags, timeout, locker);
271 // unwire the page
272 vm_unwire_page(&wiringInfo);
274 return error;
278 static status_t
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,
285 &fromWiringInfo);
286 if (error != B_OK)
287 return error;
289 VMPageWiringInfo toWiringInfo;
290 error = vm_wire_page(B_CURRENT_TEAM, (addr_t)toMutex, true, &toWiringInfo);
291 if (error != B_OK) {
292 vm_unwire_page(&fromWiringInfo);
293 return error;
296 // unlock the first mutex and lock the second one
298 MutexLocker locker(sUserMutexTableLock);
299 user_mutex_unlock_locked(fromMutex, fromWiringInfo.physicalAddress,
300 flags);
302 error = user_mutex_lock_locked(toMutex, toWiringInfo.physicalAddress,
303 name, flags, timeout, locker);
306 // unwire the pages
307 vm_unwire_page(&toWiringInfo);
308 vm_unwire_page(&fromWiringInfo);
310 return error;
314 // #pragma mark - kernel private
317 void
318 user_mutex_init()
320 if (sUserMutexTable.Init() != B_OK)
321 panic("user_mutex_init(): Failed to init table!");
325 // #pragma mark - syscalls
328 status_t
329 _user_mutex_lock(int32* mutex, const char* name, uint32 flags,
330 bigtime_t timeout)
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,
338 timeout);
340 return syscall_restart_handle_timeout_post(error, timeout);
344 status_t
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,
353 &wiringInfo);
354 if (error != B_OK)
355 return error;
358 MutexLocker locker(sUserMutexTableLock);
359 user_mutex_unlock_locked(mutex, wiringInfo.physicalAddress, flags);
362 vm_unwire_page(&wiringInfo);
364 return B_OK;
368 status_t
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);
383 status_t
384 _user_mutex_sem_acquire(int32* sem, const char* name, uint32 flags,
385 bigtime_t timeout)
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,
395 &wiringInfo);
396 if (error != B_OK)
397 return error;
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);
410 status_t
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,
419 &wiringInfo);
420 if (error != B_OK)
421 return error;
424 MutexLocker locker(sUserMutexTableLock);
425 user_mutex_sem_release_locked(sem, wiringInfo.physicalAddress);
428 vm_unwire_page(&wiringInfo);
429 return B_OK;