/**************************************************************************** * sched/semaphore/sem_holder.c * * SPDX-License-Identifier: Apache-2.0 * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include "sched/sched.h" #include "semaphore/semaphore.h" #ifdef CONFIG_PRIORITY_INHERITANCE /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* Configuration ************************************************************/ #ifndef CONFIG_SEM_PREALLOCHOLDERS # define CONFIG_SEM_PREALLOCHOLDERS 0 #endif /**************************************************************************** * Private Type Declarations ****************************************************************************/ typedef int (*holderhandler_t)(FAR struct semholder_s *pholder, FAR sem_t *sem, FAR void *arg); /**************************************************************************** * Private Data ****************************************************************************/ /* Preallocated holder structures */ #if CONFIG_SEM_PREALLOCHOLDERS > 0 static struct semholder_s g_holderalloc[CONFIG_SEM_PREALLOCHOLDERS]; static FAR struct semholder_s *g_freeholders; #endif /**************************************************************************** * Name: nxsem_allocholder ****************************************************************************/ static inline FAR struct semholder_s * nxsem_allocholder(FAR sem_t *sem, FAR struct tcb_s *htcb) { FAR struct semholder_s *pholder; /* Check if the "built-in" holder is being used. We have this built-in * holder to optimize for the simplest case where semaphores are only * used to implement mutexes. */ #if CONFIG_SEM_PREALLOCHOLDERS > 0 pholder = g_freeholders; if (pholder != NULL) { /* Remove the holder from the free list and * put it into the semaphore's holder list */ g_freeholders = pholder->flink; pholder->flink = sem->hhead; sem->hhead = pholder; } #else if (sem->holder.htcb == NULL) { pholder = &sem->holder; } #endif else { serr("ERROR: Insufficient pre-allocated holders\n"); PANIC(); } pholder->sem = sem; pholder->htcb = htcb; pholder->counts = 0; /* Put it into the task's list */ pholder->tlink = htcb->holdsem; htcb->holdsem = pholder; return pholder; } /**************************************************************************** * Name: nxsem_findholder * * NOTE: htcb may be used only as a look-up key. It certain cases, the task * may have exited and htcb may refer to a stale memory. It must not be * dereferenced. * ****************************************************************************/ static FAR struct semholder_s * nxsem_findholder(FAR sem_t *sem, FAR struct tcb_s *htcb) { FAR struct semholder_s *pholder; #if CONFIG_SEM_PREALLOCHOLDERS > 0 /* Try to find the holder in the list of holders associated with this * semaphore */ for (pholder = sem->hhead; pholder != NULL; pholder = pholder->flink) { if (pholder->htcb == htcb) { /* Got it! */ return pholder; } } #else /* We have one hard-allocated holder structures in sem_t */ pholder = &sem->holder; if (pholder->htcb == htcb) { /* Got it! */ return pholder; } #endif /* The holder does not appear in the list */ return NULL; } /**************************************************************************** * Name: nxsem_findorallocateholder ****************************************************************************/ static inline FAR struct semholder_s * nxsem_findorallocateholder(FAR sem_t *sem, FAR struct tcb_s *htcb) { FAR struct semholder_s *pholder = nxsem_findholder(sem, htcb); if (pholder == NULL) { pholder = nxsem_allocholder(sem, htcb); } return pholder; } /**************************************************************************** * Name: nxsem_freeholder ****************************************************************************/ static inline void nxsem_freeholder(FAR sem_t *sem, FAR struct semholder_s *pholder) { FAR struct semholder_s * FAR *curr; /* Remove the holder from the task's list */ for (curr = &pholder->htcb->holdsem; *curr != NULL; curr = &(*curr)->tlink) { if (*curr == pholder) { *curr = pholder->tlink; break; } } /* Release the holder and counts */ pholder->tlink = NULL; pholder->sem = NULL; pholder->htcb = NULL; pholder->counts = 0; #if CONFIG_SEM_PREALLOCHOLDERS > 0 /* Remove the holder from the semaphore's list */ for (curr = &sem->hhead; *curr != NULL; curr = &(*curr)->flink) { if (*curr == pholder) { *curr = pholder->flink; break; } } /* And put it in the free list */ pholder->flink = g_freeholders; g_freeholders = pholder; #endif } /**************************************************************************** * Name: nxsem_freecount0holder ****************************************************************************/ #if CONFIG_SEM_PREALLOCHOLDERS > 0 static int nxsem_freecount0holder(FAR struct semholder_s *pholder, FAR sem_t *sem, FAR void *arg) { /* When no more counts are held, remove the holder from the list. The * count was decremented in nxsem_release_holder. */ if (pholder->counts <= 0) { nxsem_freeholder(sem, pholder); return 1; } return 0; } #endif /**************************************************************************** * Name: nxsem_foreachholder ****************************************************************************/ static int nxsem_foreachholder(FAR sem_t *sem, holderhandler_t handler, FAR void *arg) { FAR struct semholder_s *pholder; int ret = 0; #if CONFIG_SEM_PREALLOCHOLDERS > 0 FAR struct semholder_s *next; for (pholder = sem->hhead; pholder && ret == 0; pholder = next) { /* In case this holder gets deleted */ next = pholder->flink; DEBUGASSERT(pholder->htcb != NULL); /* Call the handler */ ret = handler(pholder, sem, arg); } #else /* We have one hard-allocated holder structures in sem_t */ pholder = &sem->holder; /* The hard-allocated containers may hold a NULL holder */ if (pholder->htcb != NULL) { /* Call the handler */ ret = handler(pholder, sem, arg); } #endif return ret; } /**************************************************************************** * Name: nxsem_recoverholders ****************************************************************************/ static int nxsem_recoverholders(FAR struct semholder_s *pholder, FAR sem_t *sem, FAR void *arg) { nxsem_freeholder(sem, pholder); return 0; } /**************************************************************************** * Name: nxsem_boostholderprio ****************************************************************************/ static int nxsem_boostholderprio(FAR struct semholder_s *pholder, FAR sem_t *sem, FAR void *arg) { FAR struct tcb_s *htcb = pholder->htcb; FAR struct tcb_s *rtcb = (FAR struct tcb_s *)arg; /* If the priority of the thread that is waiting for a count is less than * or equal to the priority of the thread holding a count, then do nothing * because the thread is already running at a sufficient priority. */ if (rtcb->sched_priority > htcb->sched_priority) { /* Raise the priority of the holder of the semaphore. This * cannot cause a context switch because we have preemption * disabled. The task will be marked "pending" and the switch * will occur during up_contex_switch() processing. */ nxsched_set_priority(htcb, rtcb->sched_priority); } return 0; } /**************************************************************************** * Name: nxsem_verifyholder ****************************************************************************/ #ifdef CONFIG_DEBUG_ASSERTIONS static int nxsem_verifyholder(FAR struct semholder_s *pholder, FAR sem_t *sem, FAR void *arg) { /* Need to revisit this, but these assumptions seem to be untrue -- OR * there is a bug??? */ #if 0 FAR struct tcb_s *htcb = pholder->htcb; /* Called after a semaphore has been released (incremented), the semaphore * could be non-negative, and there is no thread waiting for the count. * In this case, the priority of the holder should not be boosted. */ DEBUGASSERT(htcb->sched_priority == htcb->base_priority); #endif return 0; } #endif /**************************************************************************** * Name: nxsem_dumpholder ****************************************************************************/ #if defined(CONFIG_DEBUG_INFO) && defined(CONFIG_SEM_PHDEBUG) static int nxsem_dumpholder(FAR struct semholder_s *pholder, FAR sem_t *sem, FAR void *arg) { #if CONFIG_SEM_PREALLOCHOLDERS > 0 _info(" %08x: %08x %08x %08x %08x %04x\n", pholder, pholder->flink, #else _info(" %08x: %08x %08x %08x %04x\n", pholder, #endif pholder->tlink, pholder->sem, pholder->htcb, pholder->counts); return 0; } #endif /**************************************************************************** * Name: nxsem_restore_priority ****************************************************************************/ static void nxsem_restore_priority(FAR struct tcb_s *htcb) { int hpriority; /* We attempt to restore thread priority to its base priority. If * there is any thread with the higher priority waiting for the * semaphore held by htcb then this value will be overwritten. */ hpriority = htcb->boost_priority > htcb->base_priority ? htcb->boost_priority : htcb->base_priority; /* Was the priority of the holder thread boosted? If so, then drop its * priority back to the correct level. What is the correct level? */ if (htcb->sched_priority != hpriority) { FAR struct semholder_s *pholder; #ifdef CONFIG_ARCH_ADDRENV FAR struct addrenv_s *oldenv; if (htcb->addrenv_own) { addrenv_select(htcb->addrenv_own, &oldenv); } #endif /* Try to find the highest priority across all the threads that are * waiting for any semaphore held by htcb. */ for (pholder = htcb->holdsem; pholder != NULL; pholder = pholder->tlink) { FAR struct tcb_s *stcb; stcb = (FAR struct tcb_s *)dq_peek(SEM_WAITLIST(pholder->sem)); if (stcb != NULL && stcb->sched_priority > hpriority) { hpriority = stcb->sched_priority; } } #ifdef CONFIG_ARCH_ADDRENV if (htcb->addrenv_own) { addrenv_restore(oldenv); } #endif /* Apply the selected priority to the thread (hopefully back to the * threads base_priority). */ nxsched_set_priority(htcb, hpriority); } } /**************************************************************************** * Name: nxsem_restoreholderprio ****************************************************************************/ static int nxsem_restoreholderprio(FAR struct semholder_s *pholder, FAR sem_t *sem, FAR void *arg) { FAR struct tcb_s *htcb = pholder->htcb; /* Release the holder if all counts have been given up * before reprioritizing causes a context switch. */ if (pholder->counts <= 0) { nxsem_freeholder(sem, pholder); } nxsem_restore_priority(htcb); return 0; } #if CONFIG_SEM_PREALLOCHOLDERS > 0 /**************************************************************************** * Name: nxsem_restoreholderprio_others * * Description: * Reprioritize all holders except the currently executing task * ****************************************************************************/ static int nxsem_restoreholderprio_others(FAR struct semholder_s *pholder, FAR sem_t *sem, FAR void *arg) { FAR struct tcb_s *rtcb = this_task(); if (pholder->htcb != rtcb) { return nxsem_restoreholderprio(pholder, sem, arg); } return 0; } /**************************************************************************** * Name: nxsem_restoreholderprio_self * * Description: * Reprioritize only the currently executing task * ****************************************************************************/ static int nxsem_restoreholderprio_self(FAR struct semholder_s *pholder, FAR sem_t *sem, FAR void *arg) { FAR struct tcb_s *rtcb = this_task(); if (pholder->htcb == rtcb) { /* The running task has given up a count on the semaphore */ nxsem_restoreholderprio(pholder, sem, arg); return 1; } return 0; } #endif /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: nxsem_initialize_holders * * Description: * Called from nxsem_initialize() to set up semaphore holder information. * * Input Parameters: * None * * Returned Value: * None * * Assumptions: * ****************************************************************************/ void nxsem_initialize_holders(void) { #if CONFIG_SEM_PREALLOCHOLDERS > 0 int i; /* Put all of the pre-allocated holder structures into the free list */ g_freeholders = g_holderalloc; for (i = 0; i < (CONFIG_SEM_PREALLOCHOLDERS - 1); i++) { g_holderalloc[i].flink = &g_holderalloc[i + 1]; } g_holderalloc[CONFIG_SEM_PREALLOCHOLDERS - 1].flink = NULL; #endif } /**************************************************************************** * Name: nxsem_destroyholder * * Description: * Called from nxsem_destroy() to handle any holders of a semaphore * when it is destroyed. * * Input Parameters: * sem - A reference to the semaphore being destroyed * * Returned Value: * None * * Assumptions: * ****************************************************************************/ void nxsem_destroyholder(FAR sem_t *sem) { /* It might be an error if a semaphore is destroyed while there are any * holders of the semaphore (except perhaps the thread that release the * semaphore itself). We actually have to assume that the caller knows * what it is doing because could have killed another thread that is the * actual holder of the semaphore. * * It is also a standard practice to destroy the semaphore while the * caller holds it. Of course, the caller MUST assure that there are no * other holders of the semaphore in this case. This occurs, for example, * when a driver is unlink'ed and the driver instance must be destroyed. * * Therefore, we cannot make any assumptions about the state of the * semaphore or the state of any of the holder threads. So just recover * any stranded holders and hope the task knows what it is doing. */ #if CONFIG_SEM_PREALLOCHOLDERS > 0 if (sem->hhead != NULL) { /* There may be an issue if there are multiple holders of * the semaphore. */ DEBUGASSERT(sem->hhead->flink == NULL); } #else /* There may be an issue if there are multiple holders of the semaphore. */ DEBUGASSERT(sem->holder.htcb == NULL); #endif nxsem_foreachholder(sem, nxsem_recoverholders, NULL); } /**************************************************************************** * Name: nxsem_add_holder_tcb * * Description: * Called from nxsem_wait() when the calling thread obtains the semaphore; * Called from sem_post() when the waiting thread obtains the semaphore. * * Input Parameters: * htcb - TCB of the thread that just obtained the semaphore * sem - A reference to the incremented semaphore * * Returned Value: * 0 (OK) or -1 (ERROR) if unsuccessful * * Assumptions: * Interrupts are disabled. * ****************************************************************************/ void nxsem_add_holder_tcb(FAR struct tcb_s *htcb, FAR sem_t *sem) { FAR struct semholder_s *pholder; uint8_t prioinherit = sem->flags & SEM_PRIO_MASK; /* If priority inheritance is disabled for this thread or it is IDLE * thread, then do not add the holder. * If there are never holders of the semaphore, the priority * inheritance is effectively disabled. */ if (!is_idle_task(htcb) && prioinherit == SEM_PRIO_INHERIT) { /* Find or allocate a container for this new holder */ pholder = nxsem_findorallocateholder(sem, htcb); if (pholder->counts < SEM_VALUE_MAX) { /* Increment the number of counts held by this holder */ pholder->counts++; } } } /**************************************************************************** * Name: nxsem_add_holder * * Description: * Called from nxsem_wait() when the calling thread obtains the semaphore * * Input Parameters: * sem - A reference to the incremented semaphore * * Returned Value: * 0 (OK) or -1 (ERROR) if unsuccessful * * Assumptions: * Interrupts are disabled. * ****************************************************************************/ void nxsem_add_holder(FAR sem_t *sem) { nxsem_add_holder_tcb(this_task(), sem); } /**************************************************************************** * Name: void nxsem_boost_priority(sem_t *sem) * * Description: * * * Input Parameters: * None * * Returned Value: * 0 (OK) or -1 (ERROR) if unsuccessful * * Assumptions: * ****************************************************************************/ void nxsem_boost_priority(FAR sem_t *sem) { FAR struct tcb_s *rtcb = this_task(); /* Boost the priority of every thread holding counts on this semaphore * that are lower in priority than the new thread that is waiting for a * count. */ #if CONFIG_SEM_PREALLOCHOLDERS > 0 nxsem_foreachholder(sem, nxsem_boostholderprio, rtcb); #else nxsem_boostholderprio(&sem->holder, sem, rtcb); #endif } /**************************************************************************** * Name: nxsem_release_holder * * Description: * Called from sem_post() after a thread releases one count on the * semaphore. * * Input Parameters: * sem - A reference to the semaphore being posted * * Returned Value: * None * * Assumptions: * ****************************************************************************/ void nxsem_release_holder(FAR sem_t *sem) { FAR struct tcb_s *rtcb = this_task(); uint8_t prioinherit = sem->flags & SEM_PRIO_MASK; /* If priority inheritance is disabled for this thread or it is IDLE * thread, then do not add the holder. * If there are never holders of the semaphore, the priority * inheritance is effectively disabled. */ if (!is_idle_task(rtcb) && prioinherit == SEM_PRIO_INHERIT) { FAR struct semholder_s *pholder; DEBUGASSERT(!up_interrupt_context()); /* Find the container for this holder */ #if CONFIG_SEM_PREALLOCHOLDERS > 0 for (pholder = sem->hhead; pholder != NULL; pholder = pholder->flink) { DEBUGASSERT(pholder->counts > 0); if (pholder->htcb == rtcb) { /* Decrement the counts on this holder -- the holder will be * freed later in nxsem_restore_baseprio. */ pholder->counts--; return; } } /* The current task is not a holder */ DEBUGPANIC(); #else pholder = &sem->holder; DEBUGASSERT(pholder->htcb == rtcb); nxsem_freeholder(sem, pholder); #endif } } /**************************************************************************** * Name: nxsem_restore_baseprio * * Description: * This function is called after the current running task releases a * count on the semaphore or an interrupt handler posts a new count. It * will check if we need to drop the priority of any threads holding a * count on the semaphore. Their priority could have been boosted while * they held the count. * * Input Parameters: * stcb - The TCB of the task that was just started (if any). If the * post action caused a count to be given to another thread, then stcb * is the TCB that received the count. Note, just because stcb received * the count, it does not mean that it is higher priority than other * threads. * sem - A reference to the semaphore being posted. * - If the semaphore count is <0 then there are still threads waiting * for a count. stcb should be non-null and will be higher priority * than all of the other threads still waiting. * - If it is ==0 then stcb refers to the thread that got the last count; * no other threads are waiting. * - If it is >0 then there should be no threads waiting for counts and * stcb should be null. * * Returned Value: * None * * Assumptions: * The scheduler is locked. * ****************************************************************************/ void nxsem_restore_baseprio(FAR struct tcb_s *stcb, FAR sem_t *sem) { #if 0 /* DSA: sometimes crashes when Telnet calls external cmd (i.e. 'i2c') */ /* Check our assumptions */ DEBUGASSERT((sem->semcount > 0 && stcb == NULL) || (sem->semcount <= 0 && stcb != NULL)); #endif DEBUGASSERT(!up_interrupt_context()); /* Perform the following actions only if a new thread was given a count. * The thread that received the count should be the highest priority * of all threads waiting for a count from the semaphore. So in that * case, the priority of all holder threads should be dropped to the * next highest pending priority. */ if (stcb != NULL) { #if CONFIG_SEM_PREALLOCHOLDERS > 0 /* The currently executed thread should be the lower priority * thread that just posted the count and caused this action. * However, we cannot drop the priority of the currently running * thread -- because that will cause it to be suspended. * * So, do this in two passes. First, reprioritizing all holders * except for the running thread. */ nxsem_foreachholder(sem, nxsem_restoreholderprio_others, stcb); /* Now, find an reprioritize only the ready to run task */ nxsem_foreachholder(sem, nxsem_restoreholderprio_self, stcb); #else /* New owner is already the highest priority since the wait queue * is priority-based, no need to adjust its priority, only restore * the older owner when posted the count. */ nxsem_restore_priority(this_task()); #endif } else { #if CONFIG_SEM_PREALLOCHOLDERS > 0 /* Remove the holder from the list if it's counts is zero. */ nxsem_foreachholder(sem, nxsem_freecount0holder, NULL); /* If there are no tasks waiting for available counts, then all holders * should be at their base priority. */ #endif #ifdef CONFIG_DEBUG_ASSERTIONS nxsem_foreachholder(sem, nxsem_verifyholder, NULL); #endif } } /**************************************************************************** * Name: nxsem_canceled * * Description: * Called from nxsem_wait_irq() after a thread that was waiting for a * semaphore count was awakened because of a signal and the semaphore wait * has been canceled. This function restores the correct thread priority * of each holder of the semaphore. * * Input Parameters: * sem - A reference to the semaphore no longer being waited for * * Returned Value: * None * * Assumptions: * ****************************************************************************/ void nxsem_canceled(FAR struct tcb_s *stcb, FAR sem_t *sem) { /* Check our assumptions */ DEBUGASSERT(sem->semcount <= 0); /* Adjust the priority of every holder as necessary */ nxsem_foreachholder(sem, nxsem_restoreholderprio, stcb); } /**************************************************************************** * Name: sem_enumholders * * Description: * Show information about threads currently waiting on this semaphore * * Input Parameters: * sem - A reference to the semaphore * * Returned Value: * None * * Assumptions: * ****************************************************************************/ #if defined(CONFIG_DEBUG_FEATURES) && defined(CONFIG_SEM_PHDEBUG) void sem_enumholders(FAR sem_t *sem) { #ifdef CONFIG_DEBUG_INFO nxsem_foreachholder(sem, nxsem_dumpholder, NULL); #endif } #endif /**************************************************************************** * Name: nxsem_nfreeholders * * Description: * Return the number of available holder containers. This is a good way * to find out which threads are not calling sem_destroy. * * Input Parameters: * sem - A reference to the semaphore * * Returned Value: * The number of available holder containers * * Assumptions: * ****************************************************************************/ #if defined(CONFIG_DEBUG_FEATURES) && defined(CONFIG_SEM_PHDEBUG) int nxsem_nfreeholders(void) { #if CONFIG_SEM_PREALLOCHOLDERS > 0 FAR struct semholder_s *pholder; int n; for (pholder = g_freeholders, n = 0; pholder; pholder = pholder->flink) { n++; } return n; #else return 0; #endif } #endif /**************************************************************************** * Name: nxsem_release_all * * Description: * Release all semaphore holders for the task. * * Input Parameters: * htcb - TCB of the task * * Returned Value: * None * * Assumptions: * ****************************************************************************/ void nxsem_release_all(FAR struct tcb_s *htcb) { FAR struct semholder_s *pholder; while ((pholder = htcb->holdsem) != NULL) { FAR sem_t *sem = pholder->sem; nxsem_freeholder(sem, pholder); /* Increment the count on the semaphore, to releases the count * that was taken by sem_wait() or sem_post(). */ sem->semcount++; } } #endif /* CONFIG_PRIORITY_INHERITANCE */