/* * Copyright (C) 2015 Kaspar Schleiser * 2013 Freie UniversitÀt Berlin * * This file is subject to the terms and conditions of the GNU Lesser * General Public License v2.1. See the file LICENSE in the top level * directory for more details. */ /** * @ingroup core_sync * @{ * * @file * @brief Kernel mutex implementation * * @author Kaspar Schleiser * @author Joakim NohlgÄrd * * @} */ #include #include #include #include "mutex.h" #include "thread.h" #include "sched.h" #include "irq.h" #include "list.h" #define ENABLE_DEBUG 0 #include "debug.h" #if MAXTHREADS > 1 /** * @brief Block waiting for a locked mutex * @pre IRQs are disabled * @post IRQs are restored to @p irq_state * @post The calling thread is no longer waiting for the mutex, either * because it got the mutex, or because the operation was cancelled * (only possible for @ref mutex_lock_cancelable) * * Most applications don't use @ref mutex_lock_cancelable. Inlining this * function into both @ref mutex_lock and @ref mutex_lock_cancelable is, * therefore, beneficial for the majority of applications. */ static inline __attribute__((always_inline)) void _block(mutex_t *mutex, unsigned irq_state) { thread_t *me = thread_get_active(); /* Fail visibly even if a blocking action is called from somewhere where * it's subtly not allowed, eg. board_init */ assert(me != NULL); DEBUG("PID[%" PRIkernel_pid "] mutex_lock() Adding node to mutex queue: " "prio: %" PRIu32 "\n", thread_getpid(), (uint32_t)me->priority); sched_set_status(me, STATUS_MUTEX_BLOCKED); if (mutex->queue.next == MUTEX_LOCKED) { mutex->queue.next = (list_node_t *)&me->rq_entry; mutex->queue.next->next = NULL; } else { thread_add_to_list(&mutex->queue, me); } #ifdef MODULE_CORE_MUTEX_PRIORITY_INHERITANCE thread_t *owner = thread_get(mutex->owner); if ((owner) && (owner->priority > me->priority)) { DEBUG("PID[%" PRIkernel_pid "] prio of %" PRIkernel_pid ": %u --> %u\n", thread_getpid(), mutex->owner, (unsigned)owner->priority, (unsigned)me->priority); sched_change_priority(owner, me->priority); } #endif irq_restore(irq_state); thread_yield_higher(); /* We were woken up by scheduler. Waker removed us from queue. */ } void mutex_lock(mutex_t *mutex) { unsigned irq_state = irq_disable(); DEBUG("PID[%" PRIkernel_pid "] mutex_lock().\n", thread_getpid()); if (mutex->queue.next == NULL) { /* mutex is unlocked. */ mutex->queue.next = MUTEX_LOCKED; #ifdef MODULE_CORE_MUTEX_PRIORITY_INHERITANCE thread_t *me = thread_get_active(); mutex->owner = me->pid; mutex->owner_original_priority = me->priority; #endif DEBUG("PID[%" PRIkernel_pid "] mutex_lock(): early out.\n", thread_getpid()); irq_restore(irq_state); } else { _block(mutex, irq_state); } } int mutex_lock_cancelable(mutex_cancel_t *mc) { unsigned irq_state = irq_disable(); DEBUG("PID[%" PRIkernel_pid "] mutex_lock_cancelable()\n", thread_getpid()); if (mc->cancelled) { DEBUG("PID[%" PRIkernel_pid "] mutex_lock_cancelable cancelled " "early.\n", thread_getpid()); irq_restore(irq_state); return -ECANCELED; } mutex_t *mutex = mc->mutex; if (mutex->queue.next == NULL) { /* mutex is unlocked. */ mutex->queue.next = MUTEX_LOCKED; #ifdef MODULE_CORE_MUTEX_PRIORITY_INHERITANCE thread_t *me = thread_get_active(); mutex->owner = me->pid; mutex->owner_original_priority = me->priority; #endif DEBUG("PID[%" PRIkernel_pid "] mutex_lock_cancelable() early out.\n", thread_getpid()); irq_restore(irq_state); return 0; } else { _block(mutex, irq_state); if (mc->cancelled) { DEBUG("PID[%" PRIkernel_pid "] mutex_lock_cancelable() " "cancelled.\n", thread_getpid()); } return (mc->cancelled) ? -ECANCELED : 0; } } void mutex_unlock(mutex_t *mutex) { unsigned irqstate = irq_disable(); DEBUG("PID[%" PRIkernel_pid "] mutex_unlock(): queue.next: %p\n", thread_getpid(), (void *)mutex->queue.next); if (mutex->queue.next == NULL) { /* the mutex was not locked */ irq_restore(irqstate); return; } #ifdef MODULE_CORE_MUTEX_PRIORITY_INHERITANCE thread_t *owner = thread_get(mutex->owner); if ((owner) && (owner->priority != mutex->owner_original_priority)) { DEBUG("PID[%" PRIkernel_pid "] prio %u --> %u\n", owner->pid, (unsigned)owner->priority, (unsigned)owner->priority); sched_change_priority(owner, mutex->owner_original_priority); } #endif if (mutex->queue.next == MUTEX_LOCKED) { mutex->queue.next = NULL; /* the mutex was locked and no thread was waiting for it */ irq_restore(irqstate); return; } list_node_t *next = list_remove_head(&mutex->queue); thread_t *process = container_of((clist_node_t *)next, thread_t, rq_entry); DEBUG("PID[%" PRIkernel_pid "] mutex_unlock(): waking up waiting thread %" PRIkernel_pid "\n", thread_getpid(), process->pid); sched_set_status(process, STATUS_PENDING); if (!mutex->queue.next) { mutex->queue.next = MUTEX_LOCKED; } uint16_t process_priority = process->priority; irq_restore(irqstate); sched_switch(process_priority); } void mutex_unlock_and_sleep(mutex_t *mutex) { DEBUG("PID[%" PRIkernel_pid "] mutex_unlock_and_sleep(): queue.next: %p\n", thread_getpid(), (void *)mutex->queue.next); unsigned irqstate = irq_disable(); if (mutex->queue.next) { if (mutex->queue.next == MUTEX_LOCKED) { mutex->queue.next = NULL; } else { list_node_t *next = list_remove_head(&mutex->queue); thread_t *process = container_of((clist_node_t *)next, thread_t, rq_entry); DEBUG("PID[%" PRIkernel_pid "] mutex_unlock_and_sleep(): waking up " "waiter.\n", process->pid); sched_set_status(process, STATUS_PENDING); if (!mutex->queue.next) { mutex->queue.next = MUTEX_LOCKED; } } } DEBUG("PID[%" PRIkernel_pid "] mutex_unlock_and_sleep(): going to sleep.\n", thread_getpid()); sched_set_status(thread_get_active(), STATUS_SLEEPING); irq_restore(irqstate); thread_yield_higher(); } void mutex_cancel(mutex_cancel_t *mc) { unsigned irq_state = irq_disable(); mc->cancelled = 1; mutex_t *mutex = mc->mutex; thread_t *thread = mc->thread; if (thread_is_active(thread)) { /* thread is still running or about to run, so it will check * `mc-cancelled` in time */ irq_restore(irq_state); return; } if ((mutex->queue.next != MUTEX_LOCKED) && (mutex->queue.next != NULL) && list_remove(&mutex->queue, (list_node_t *)&thread->rq_entry)) { /* Thread was queued and removed from list, wake it up */ if (mutex->queue.next == NULL) { mutex->queue.next = MUTEX_LOCKED; } sched_set_status(thread, STATUS_PENDING); irq_restore(irq_state); sched_switch(thread->priority); return; } irq_restore(irq_state); } /* Helper for compatibility with C++ or other non-C languages */ int mutex_trylock_ffi(mutex_t *mutex) { return mutex_trylock(mutex); } #else /* MAXTHREADS < 2 */ typedef int dont_be_pedantic; #endif