few fixes/additions:
[newos.git] / kernel / sem.c
blobc3ee0cdd73f5ef945869ffd6b2142656be7c8e7c
1 /*
2 ** Copyright 2001, Travis Geiselbrecht. All rights reserved.
3 ** Distributed under the terms of the NewOS License.
4 */
5 #include <kernel/kernel.h>
6 #include <kernel/sem.h>
7 #include <kernel/smp.h>
8 #include <kernel/int.h>
9 #include <kernel/timer.h>
10 #include <kernel/debug.h>
11 #include <kernel/heap.h>
12 #include <kernel/thread.h>
13 #include <sys/errors.h>
15 #include <boot/stage2.h>
17 #include <libc/string.h>
19 struct sem_entry {
20 sem_id id;
21 int count;
22 struct thread_queue q;
23 char *name;
24 int lock;
25 proc_id owner;
28 #define MAX_SEMS 4096
30 static struct sem_entry *sems = NULL;
31 static region_id sem_region = 0;
32 static bool sems_active = false;
34 static sem_id next_sem = 0;
36 static int sem_spinlock = 0;
37 #define GRAB_SEM_LIST_LOCK() acquire_spinlock(&sem_spinlock)
38 #define RELEASE_SEM_LIST_LOCK() release_spinlock(&sem_spinlock)
39 #define GRAB_SEM_LOCK(s) acquire_spinlock(&(s).lock)
40 #define RELEASE_SEM_LOCK(s) release_spinlock(&(s).lock)
42 // used in functions that may put a bunch of threads in the run q at once
43 #define READY_THREAD_CACHE_SIZE 16
45 static int remove_thread_from_sem(struct thread *t, struct sem_entry *sem, struct thread_queue *queue, int sem_errcode);
47 struct sem_timeout_args {
48 thread_id blocked_thread;
49 sem_id blocked_sem_id;
50 int sem_count;
53 void dump_sem_list(int argc, char **argv)
55 int i;
57 for(i=0; i<MAX_SEMS; i++) {
58 if(sems[i].id >= 0) {
59 dprintf("0x%x\tid: 0x%x\t\tname: '%s'\n", &sems[i], sems[i].id, sems[i].name);
64 static void _dump_sem_info(struct sem_entry *sem)
66 dprintf("SEM: 0x%x\n", sem);
67 dprintf("name: '%s'\n", sem->name);
68 dprintf("owner: 0x%x\n", sem->owner);
69 dprintf("count: 0x%x\n", sem->count);
70 dprintf("queue: head 0x%x tail 0x%x\n", sem->q.head, sem->q.tail);
73 static void dump_sem_info(int argc, char **argv)
75 int i;
77 if(argc < 2) {
78 dprintf("sem: not enough arguments\n");
79 return;
82 // if the argument looks like a hex number, treat it as such
83 if(strlen(argv[1]) > 2 && argv[1][0] == '0' && argv[1][1] == 'x') {
84 unsigned long num = atoul(argv[1]);
86 if(num > KERNEL_BASE && num <= (KERNEL_BASE + (KERNEL_SIZE - 1))) {
87 // XXX semi-hack
88 _dump_sem_info((struct sem_entry *)num);
89 return;
90 } else {
91 int slot = num % MAX_SEMS;
92 if(sems[slot].id != (int)num) {
93 dprintf("sem 0x%x doesn't exist!\n", num);
94 return;
96 _dump_sem_info(&sems[slot]);
97 return;
101 // walk through the sem list, trying to match name
102 for(i=0; i<MAX_SEMS; i++) {
103 if(strcmp(argv[1], sems[i].name) == 0) {
104 _dump_sem_info(&sems[i]);
105 return;
110 int sem_init(kernel_args *ka)
112 int i;
114 dprintf("sem_init: entry\n");
116 // create and initialize semaphore table
117 sem_region = vm_create_anonymous_region(vm_get_kernel_aspace_id(), "sem_table", (void **)&sems,
118 REGION_ADDR_ANY_ADDRESS, sizeof(struct sem_entry) * MAX_SEMS, REGION_WIRING_WIRED, LOCK_RW|LOCK_KERNEL);
119 if(sem_region < 0) {
120 panic("unable to allocate semaphore table!\n");
123 memset(sems, 0, sizeof(struct sem_entry) * MAX_SEMS);
124 for(i=0; i<MAX_SEMS; i++)
125 sems[i].id = -1;
127 // add debugger commands
128 dbg_add_command(&dump_sem_list, "sems", "Dump a list of all active semaphores");
129 dbg_add_command(&dump_sem_info, "sem", "Dump info about a particular semaphore");
131 dprintf("sem_init: exit\n");
133 sems_active = true;
135 return 0;
138 static sem_id sem_create_etc(int count, const char *name, proc_id owner)
140 int i;
141 int state;
142 sem_id retval = ERR_SEM_OUT_OF_SLOTS;
143 char *temp_name;
145 if(sems_active == false)
146 return ERR_SEM_NOT_ACTIVE;
148 if(name) {
149 int name_len = strlen(name);
151 temp_name = (char *)kmalloc(min(name_len + 1, SYS_MAX_OS_NAME_LEN));
152 if(temp_name == NULL)
153 return ERR_NO_MEMORY;
154 strncpy(temp_name, name, SYS_MAX_OS_NAME_LEN-1);
155 temp_name[SYS_MAX_OS_NAME_LEN-1] = 0;
156 } else {
157 temp_name = (char *)kmalloc(sizeof("default_sem_name")+1);
158 if(temp_name == NULL)
159 return ERR_NO_MEMORY;
160 strcpy(temp_name, "default_sem_name");
163 state = int_disable_interrupts();
164 GRAB_SEM_LIST_LOCK();
166 // find the first empty spot
167 for(i=0; i<MAX_SEMS; i++) {
168 if(sems[i].id == -1) {
169 // make the sem id be a multiple of the slot it's in
170 if(i >= next_sem % MAX_SEMS) {
171 next_sem += i - next_sem % MAX_SEMS;
172 } else {
173 next_sem += MAX_SEMS - (next_sem % MAX_SEMS - i);
175 sems[i].id = next_sem++;
177 sems[i].lock = 0;
178 GRAB_SEM_LOCK(sems[i]);
179 RELEASE_SEM_LIST_LOCK();
181 sems[i].q.tail = NULL;
182 sems[i].q.head = NULL;
183 sems[i].count = count;
184 sems[i].name = temp_name;
185 sems[i].owner = owner;
186 retval = sems[i].id;
188 RELEASE_SEM_LOCK(sems[i]);
189 goto out;
193 err:
194 RELEASE_SEM_LIST_LOCK();
195 kfree(temp_name);
197 out:
198 int_restore_interrupts(state);
200 return retval;
203 sem_id sem_create(int count, const char *name)
205 return sem_create_etc(count, name, proc_get_kernel_proc_id());
208 int sem_delete(sem_id id)
210 return sem_delete_etc(id, 0);
213 int sem_delete_etc(sem_id id, int return_code)
215 int slot;
216 int state;
217 int err = NO_ERROR;
218 struct thread *t;
219 int released_threads;
220 char *old_name;
221 struct thread_queue release_queue;
223 if(sems_active == false)
224 return ERR_SEM_NOT_ACTIVE;
225 if(id < 0)
226 return ERR_INVALID_HANDLE;
228 slot = id % MAX_SEMS;
230 state = int_disable_interrupts();
231 GRAB_SEM_LOCK(sems[slot]);
233 if(sems[slot].id != id) {
234 RELEASE_SEM_LOCK(sems[slot]);
235 int_restore_interrupts(state);
236 dprintf("sem_delete: invalid sem_id %d\n", id);
237 return ERR_INVALID_HANDLE;
240 released_threads = 0;
241 release_queue.head = release_queue.tail = NULL;
243 // free any threads waiting for this semaphore
244 while((t = thread_dequeue(&sems[slot].q)) != NULL) {
245 t->state = THREAD_STATE_READY;
246 t->sem_errcode = ERR_SEM_DELETED;
247 t->sem_deleted_retcode = return_code;
248 t->sem_count = 0;
249 thread_enqueue(t, &release_queue);
250 released_threads++;
253 sems[slot].id = -1;
254 old_name = sems[slot].name;
255 sems[slot].name = NULL;
257 RELEASE_SEM_LOCK(sems[slot]);
259 if(released_threads > 0) {
260 GRAB_THREAD_LOCK();
261 while((t = thread_dequeue(&release_queue)) != NULL) {
262 thread_enqueue_run_q(t);
264 thread_resched();
265 RELEASE_THREAD_LOCK();
268 int_restore_interrupts(state);
270 kfree(old_name);
272 return err;
275 // Called from a timer handler. Wakes up a semaphore
276 static int sem_timeout(void *data)
278 struct sem_timeout_args *args = (struct sem_timeout_args *)data;
279 struct thread *t;
280 int slot;
281 int state;
282 struct thread_queue wakeup_queue;
284 t = thread_get_thread_struct(args->blocked_thread);
285 if(t == NULL)
286 return INT_NO_RESCHEDULE;
287 slot = args->blocked_sem_id % MAX_SEMS;
289 state = int_disable_interrupts();
290 GRAB_SEM_LOCK(sems[slot]);
292 // dprintf("sem_timeout: called on 0x%x sem %d, tid %d\n", to, to->sem_id, to->thread_id);
294 if(sems[slot].id != args->blocked_sem_id) {
295 // this thread was not waiting on this semaphore
296 panic("sem_timeout: thid %d was trying to wait on sem %d which doesn't exist!\n",
297 args->blocked_thread, args->blocked_sem_id);
300 wakeup_queue.head = wakeup_queue.tail = NULL;
301 remove_thread_from_sem(t, &sems[slot], &wakeup_queue, ERR_SEM_TIMED_OUT);
303 RELEASE_SEM_LOCK(sems[slot]);
305 GRAB_THREAD_LOCK();
306 // put the threads in the run q here to make sure we dont deadlock in sem_interrupt_thread
307 while((t = thread_dequeue(&wakeup_queue)) != NULL) {
308 thread_enqueue_run_q(t);
310 RELEASE_THREAD_LOCK();
312 int_restore_interrupts(state);
314 return INT_RESCHEDULE;
317 int sem_acquire(sem_id id, int count)
319 return sem_acquire_etc(id, count, 0, 0, NULL);
322 int sem_acquire_etc(sem_id id, int count, int flags, time_t timeout, int *deleted_retcode)
324 int slot = id % MAX_SEMS;
325 int state;
326 int err = 0;
328 if(sems_active == false)
329 return ERR_SEM_NOT_ACTIVE;
331 if(id < 0)
332 return ERR_INVALID_HANDLE;
334 if(count <= 0)
335 return ERR_INVALID_ARGS;
337 state = int_disable_interrupts();
338 GRAB_SEM_LOCK(sems[slot]);
340 if(sems[slot].id != id) {
341 dprintf("sem_acquire_etc: invalid sem_id %d\n", id);
342 err = ERR_INVALID_HANDLE;
343 goto err;
346 if(sems[slot].count - count < 0 && (flags & SEM_FLAG_TIMEOUT) != 0 && timeout == 0) {
347 // immediate timeout
348 err = ERR_SEM_TIMED_OUT;
349 goto err;
352 if((sems[slot].count -= count) < 0) {
353 // we need to block
354 struct thread *t = thread_get_current_thread();
355 struct timer_event timer; // stick it on the heap, since we may be blocking here
356 struct sem_timeout_args args;
358 // do a quick check to see if the thread has any pending kill signals
359 // this should catch most of the cases where the thread had a signal
360 if((flags & SEM_FLAG_INTERRUPTABLE) && (t->pending_signals & SIG_KILL)) {
361 sems[slot].count += count;
362 err = ERR_SEM_INTERRUPTED;
363 goto err;
366 t->next_state = THREAD_STATE_WAITING;
367 t->sem_flags = flags;
368 t->sem_blocking = id;
369 t->sem_acquire_count = count;
370 t->sem_count = min(-sems[slot].count, count); // store the count we need to restore upon release
371 t->sem_deleted_retcode = 0;
372 t->sem_errcode = NO_ERROR;
373 thread_enqueue(t, &sems[slot].q);
375 if((flags & SEM_FLAG_TIMEOUT) != 0) {
376 // dprintf("sem_acquire_etc: setting timeout sem for %d %d usecs, semid %d, tid %d\n",
377 // timeout, sem_id, t->id);
378 // set up an event to go off with the thread struct as the data
379 args.blocked_sem_id = id;
380 args.blocked_thread = t->id;
381 args.sem_count = count;
382 timer_setup_timer(&sem_timeout, &args, &timer);
383 timer_set_event(timeout, TIMER_MODE_ONESHOT, &timer);
386 RELEASE_SEM_LOCK(sems[slot]);
387 GRAB_THREAD_LOCK();
388 // check again to see if a kill signal is pending.
389 // it may have been delivered while setting up the sem, though it's pretty unlikely
390 if((flags & SEM_FLAG_INTERRUPTABLE) && (t->pending_signals & SIG_KILL)) {
391 struct thread_queue wakeup_queue;
392 // ok, so a tiny race happened where a signal was delivered to this thread while
393 // it was setting up the sem. We can only be sure a signal wasn't delivered
394 // here, since the threadlock is held. The previous check would have found most
395 // instances, but there was a race, so we have to handle it. It'll be more messy...
396 wakeup_queue.head = wakeup_queue.tail = NULL;
397 GRAB_SEM_LOCK(sems[slot]);
398 if(sems[slot].id == id) {
399 remove_thread_from_sem(t, &sems[slot], &wakeup_queue, ERR_SEM_INTERRUPTED);
401 RELEASE_SEM_LOCK(sems[slot]);
402 while((t = thread_dequeue(&wakeup_queue)) != NULL) {
403 thread_enqueue_run_q(t);
405 // fall through and reschedule since another thread with a higher priority may have been woken up
407 thread_resched();
408 RELEASE_THREAD_LOCK();
410 if((flags & SEM_FLAG_TIMEOUT) != 0) {
411 if(t->sem_errcode < 0 && t->sem_errcode != ERR_SEM_TIMED_OUT) {
412 // cancel the timer event, the sem may have been deleted or interrupted
413 // with the timer still active
414 timer_cancel_event(&timer);
418 int_restore_interrupts(state);
419 if(deleted_retcode != NULL)
420 *deleted_retcode = t->sem_deleted_retcode;
421 return t->sem_errcode;
424 err:
425 RELEASE_SEM_LOCK(sems[slot]);
426 int_restore_interrupts(state);
428 return err;
431 int sem_release(sem_id id, int count)
433 return sem_release_etc(id, count, 0);
436 int sem_release_etc(sem_id id, int count, int flags)
438 int slot = id % MAX_SEMS;
439 int state;
440 int released_threads = 0;
441 int err = 0;
442 struct thread_queue release_queue;
444 if(sems_active == false)
445 return ERR_SEM_NOT_ACTIVE;
447 if(id < 0)
448 return ERR_INVALID_HANDLE;
450 if(count <= 0)
451 return ERR_INVALID_ARGS;
453 state = int_disable_interrupts();
454 GRAB_SEM_LOCK(sems[slot]);
456 if(sems[slot].id != id) {
457 dprintf("sem_release_etc: invalid sem_id %d\n", id);
458 err = ERR_INVALID_HANDLE;
459 goto err;
462 // clear out a queue we will use to hold all of the threads that we will have to
463 // put back into the run list. This is done so the thread lock wont be held
464 // while this sems lock is held since the two locks are grabbed in the other
465 // order in sem_interrupt_thread.
466 release_queue.head = release_queue.tail = NULL;
468 while(count > 0) {
469 int delta = count;
470 if(sems[slot].count < 0) {
471 struct thread *t = thread_lookat_queue(&sems[slot].q);
473 delta = min(count, t->sem_count);
474 t->sem_count -= delta;
475 if(t->sem_count <= 0) {
476 // release this thread
477 t = thread_dequeue(&sems[slot].q);
478 thread_enqueue(t, &release_queue);
479 t->state = THREAD_STATE_READY;
480 released_threads++;
481 t->sem_count = 0;
482 t->sem_deleted_retcode = 0;
486 sems[slot].count += delta;
487 count -= delta;
489 RELEASE_SEM_LOCK(sems[slot]);
491 // pull off any items in the release queue and put them in the run queue
492 if(released_threads > 0) {
493 struct thread *t;
494 GRAB_THREAD_LOCK();
495 while((t = thread_dequeue(&release_queue)) != NULL) {
496 thread_enqueue_run_q(t);
498 if((flags & SEM_FLAG_NO_RESCHED) == 0) {
499 thread_resched();
501 RELEASE_THREAD_LOCK();
502 goto outnolock;
505 err:
506 RELEASE_SEM_LOCK(sems[slot]);
507 outnolock:
508 int_restore_interrupts(state);
510 return err;
513 // Wake up a thread that's blocked on a semaphore
514 // this function must be entered with interrupts disabled and THREADLOCK held
515 int sem_interrupt_thread(struct thread *t)
517 struct thread *t1;
518 int slot;
519 int state;
520 struct thread_queue wakeup_queue;
522 dprintf("sem_interrupt_thread: called on thread 0x%x (%d), blocked on sem 0x%x\n", t, t->id, t->sem_blocking);
524 if(t->state != THREAD_STATE_WAITING)
525 return ERR_INVALID_ARGS;
526 if(t->sem_blocking < 0)
527 return ERR_INVALID_ARGS;
528 if((t->sem_flags & SEM_FLAG_INTERRUPTABLE) == 0)
529 return ERR_SEM_NOT_INTERRUPTABLE;
531 slot = t->sem_blocking % MAX_SEMS;
533 GRAB_SEM_LOCK(sems[slot]);
535 if(sems[slot].id != t->sem_blocking) {
536 panic("sem_interrupt_thread: thread 0x%x sez it's blocking on sem 0x%x, but that sem doesn't exist!\n", t->id, t->sem_blocking);
539 wakeup_queue.head = wakeup_queue.tail = NULL;
540 if(remove_thread_from_sem(t, &sems[slot], &wakeup_queue, ERR_SEM_INTERRUPTED) == ERR_NOT_FOUND)
541 panic("sem_interrupt_thread: thread 0x%x not found in sem 0x%x's wait queue\n", t->id, t->sem_blocking);
543 RELEASE_SEM_LOCK(sems[slot]);
545 while((t = thread_dequeue(&wakeup_queue)) != NULL) {
546 thread_enqueue_run_q(t);
549 return NO_ERROR;
552 // forcibly removes a thread from a semaphores wait q. May have to wake up other threads in the
553 // process. All threads that need to be woken up are added to the passed in thread_queue.
554 // must be called with sem lock held
555 static int remove_thread_from_sem(struct thread *t, struct sem_entry *sem, struct thread_queue *queue, int sem_errcode)
557 struct thread *t1;
559 // remove the thread from the queue and place it in the supplied queue
560 t1 = thread_dequeue_id(&sem->q, t->id);
561 if(t != t1)
562 return ERR_NOT_FOUND;
563 sem->count += t->sem_acquire_count;
564 t->state = THREAD_STATE_READY;
565 t->sem_errcode = sem_errcode;
566 thread_enqueue(t, queue);
568 // now see if more threads need to be woken up
569 while(sem->count > 0 && (t1 = thread_lookat_queue(&sem->q))) {
570 int delta = min(t->sem_count, sem->count);
572 t->sem_count -= delta;
573 if(t->sem_count <= 0) {
574 t = thread_dequeue(&sem->q);
575 t->state = THREAD_STATE_READY;
576 thread_enqueue(t, queue);
578 sem->count -= delta;
580 return NO_ERROR;
583 /* this function cycles through the sem table, deleting all the sems that are owned by
584 the passed proc_id */
585 int sem_delete_owned_sems(proc_id owner)
587 int state;
588 int i;
589 int count = 0;
591 state = int_disable_interrupts();
592 GRAB_SEM_LIST_LOCK();
594 for(i=0; i<MAX_SEMS; i++) {
595 if(sems[i].id != -1 && sems[i].owner == owner) {
596 sem_id id = sems[i].id;
598 RELEASE_SEM_LIST_LOCK();
599 int_restore_interrupts(state);
601 sem_delete_etc(id, 0);
602 count++;
604 state = int_disable_interrupts();
605 GRAB_SEM_LIST_LOCK();
609 RELEASE_SEM_LIST_LOCK();
610 int_restore_interrupts(state);
612 return count;
615 sem_id user_sem_create(int count, const char *uname)
617 if(uname != NULL) {
618 char name[SYS_MAX_OS_NAME_LEN];
619 int rc;
621 if((addr)uname >= KERNEL_BASE && (addr)uname <= KERNEL_TOP)
622 return ERR_VM_BAD_USER_MEMORY;
624 rc = user_strncpy(name, uname, SYS_MAX_OS_NAME_LEN-1);
625 if(rc < 0)
626 return rc;
627 name[SYS_MAX_OS_NAME_LEN-1] = 0;
629 return sem_create_etc(count, name, proc_get_current_proc_id());
630 } else {
631 return sem_create_etc(count, NULL, proc_get_current_proc_id());
635 int user_sem_delete(sem_id id)
637 return sem_delete(id);
640 int user_sem_delete_etc(sem_id id, int return_code)
642 return sem_delete_etc(id, return_code);
645 int user_sem_acquire(sem_id id, int count)
647 return sem_acquire(id, count);
650 int user_sem_acquire_etc(sem_id id, int count, int flags, time_t timeout, int *deleted_retcode)
652 if(deleted_retcode != NULL && ((addr)deleted_retcode >= KERNEL_BASE && (addr)deleted_retcode <= KERNEL_TOP))
653 return ERR_VM_BAD_USER_MEMORY;
655 if(deleted_retcode == NULL) {
656 return sem_acquire_etc(id, count, flags, timeout, deleted_retcode);
657 } else {
658 int uretcode;
659 int rc, rc2;
661 rc = user_memcpy(&uretcode, deleted_retcode, sizeof(uretcode));
662 if(rc < 0)
663 return rc;
665 rc = sem_acquire_etc(id, count, flags, timeout, &uretcode);
666 if(rc < 0)
667 return rc;
669 rc2 = user_memcpy(deleted_retcode, &uretcode, sizeof(uretcode));
670 if(rc2 < 0)
671 return rc2;
673 return rc;
677 int user_sem_release(sem_id id, int count)
679 return sem_release(id, count);
682 int user_sem_release_etc(sem_id id, int count, int flags)
684 return sem_release_etc(id, count, flags);