2 ** Copyright 2001, Travis Geiselbrecht. All rights reserved.
3 ** Distributed under the terms of the NewOS License.
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>
22 struct thread_queue q
;
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
;
53 void dump_sem_list(int argc
, char **argv
)
57 for(i
=0; i
<MAX_SEMS
; i
++) {
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
)
78 dprintf("sem: not enough arguments\n");
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))) {
88 _dump_sem_info((struct sem_entry
*)num
);
91 int slot
= num
% MAX_SEMS
;
92 if(sems
[slot
].id
!= (int)num
) {
93 dprintf("sem 0x%x doesn't exist!\n", num
);
96 _dump_sem_info(&sems
[slot
]);
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
]);
110 int sem_init(kernel_args
*ka
)
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
);
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
++)
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");
138 static sem_id
sem_create_etc(int count
, const char *name
, proc_id owner
)
142 sem_id retval
= ERR_SEM_OUT_OF_SLOTS
;
145 if(sems_active
== false)
146 return ERR_SEM_NOT_ACTIVE
;
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;
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
;
173 next_sem
+= MAX_SEMS
- (next_sem
% MAX_SEMS
- i
);
175 sems
[i
].id
= next_sem
++;
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
;
188 RELEASE_SEM_LOCK(sems
[i
]);
194 RELEASE_SEM_LIST_LOCK();
198 int_restore_interrupts(state
);
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
)
219 int released_threads
;
221 struct thread_queue release_queue
;
223 if(sems_active
== false)
224 return ERR_SEM_NOT_ACTIVE
;
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
;
249 thread_enqueue(t
, &release_queue
);
254 old_name
= sems
[slot
].name
;
255 sems
[slot
].name
= NULL
;
257 RELEASE_SEM_LOCK(sems
[slot
]);
259 if(released_threads
> 0) {
261 while((t
= thread_dequeue(&release_queue
)) != NULL
) {
262 thread_enqueue_run_q(t
);
265 RELEASE_THREAD_LOCK();
268 int_restore_interrupts(state
);
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
;
282 struct thread_queue wakeup_queue
;
284 t
= thread_get_thread_struct(args
->blocked_thread
);
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
]);
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
;
328 if(sems_active
== false)
329 return ERR_SEM_NOT_ACTIVE
;
332 return ERR_INVALID_HANDLE
;
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
;
346 if(sems
[slot
].count
- count
< 0 && (flags
& SEM_FLAG_TIMEOUT
) != 0 && timeout
== 0) {
348 err
= ERR_SEM_TIMED_OUT
;
352 if((sems
[slot
].count
-= count
) < 0) {
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
;
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
]);
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
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
;
425 RELEASE_SEM_LOCK(sems
[slot
]);
426 int_restore_interrupts(state
);
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
;
440 int released_threads
= 0;
442 struct thread_queue release_queue
;
444 if(sems_active
== false)
445 return ERR_SEM_NOT_ACTIVE
;
448 return ERR_INVALID_HANDLE
;
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
;
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
;
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
;
482 t
->sem_deleted_retcode
= 0;
486 sems
[slot
].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) {
495 while((t
= thread_dequeue(&release_queue
)) != NULL
) {
496 thread_enqueue_run_q(t
);
498 if((flags
& SEM_FLAG_NO_RESCHED
) == 0) {
501 RELEASE_THREAD_LOCK();
506 RELEASE_SEM_LOCK(sems
[slot
]);
508 int_restore_interrupts(state
);
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
)
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
);
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
)
559 // remove the thread from the queue and place it in the supplied queue
560 t1
= thread_dequeue_id(&sem
->q
, t
->id
);
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
);
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
)
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);
604 state
= int_disable_interrupts();
605 GRAB_SEM_LIST_LOCK();
609 RELEASE_SEM_LIST_LOCK();
610 int_restore_interrupts(state
);
615 sem_id
user_sem_create(int count
, const char *uname
)
618 char name
[SYS_MAX_OS_NAME_LEN
];
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);
627 name
[SYS_MAX_OS_NAME_LEN
-1] = 0;
629 return sem_create_etc(count
, name
, proc_get_current_proc_id());
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
);
661 rc
= user_memcpy(&uretcode
, deleted_retcode
, sizeof(uretcode
));
665 rc
= sem_acquire_etc(id
, count
, flags
, timeout
, &uretcode
);
669 rc2
= user_memcpy(deleted_retcode
, &uretcode
, sizeof(uretcode
));
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
);