walnux/sched/semaphore/sem_wait.c
Jukka Laitinen b6f2729730 Integrate nxmutex support fully into nxsem
This puts the mutex support fully inside nxsem, allowing
locking the mutex and setting the holder with single atomic
operation.

This enables fast mutex locking from userspace, avoiding taking
critical_sections, which may be heavy in SMP and cleanup
of nxmutex library in the future.

Signed-off-by: Jukka Laitinen <jukka.laitinen@tii.ae>
2025-05-08 16:00:05 +08:00

317 lines
9 KiB
C

/****************************************************************************
* sched/semaphore/sem_wait.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 <nuttx/config.h>
#include <stdbool.h>
#include <errno.h>
#include <assert.h>
#include <nuttx/init.h>
#include <nuttx/irq.h>
#include <nuttx/arch.h>
#include <nuttx/mm/kmap.h>
#include "sched/sched.h"
#include "semaphore/semaphore.h"
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: nxsem_wait_slow
*
* Description:
* This function attempts to lock the semaphore referenced by 'sem' in
* slow mode.
*
* This is an internal OS interface. It is functionally equivalent to
* sem_wait except that:
*
* - It is not a cancellation point, and
* - It does not modify the errno value.
*
* Input Parameters:
* sem - Semaphore descriptor.
*
* Returned Value:
* This is an internal OS interface and should not be used by applications.
* It follows the NuttX internal error return policy: Zero (OK) is
* returned on success. A negated errno value is returned on failure.
* Possible returned errors:
*
* - EINVAL: Invalid attempt to get the semaphore
* - EINTR: The wait was interrupted by the receipt of a signal.
*
****************************************************************************/
int nxsem_wait_slow(FAR sem_t *sem)
{
FAR struct tcb_s *rtcb = this_task();
irqstate_t flags;
int ret;
bool unlocked;
FAR struct tcb_s *htcb = NULL;
bool mutex = NXSEM_IS_MUTEX(sem);
/* The following operations must be performed with interrupts
* disabled because nxsem_post() may be called from an interrupt
* handler.
*/
flags = enter_critical_section();
/* Make sure we were supplied with a valid semaphore. */
/* Check if the lock is available */
if (mutex)
{
uint32_t mholder;
/* We lock the mutex for us by setting the blocks bit,
* this is all that is needed if we block
*/
mholder = atomic_fetch_or(NXSEM_MHOLDER(sem), NXSEM_MBLOCKING_BIT);
if (NXSEM_MACQUIRED(mholder))
{
/* htcb gets NULL if
* - the only holder did exit (without posting first)
* - the mutex was reset before
* In both cases we simply acquire the mutex, thus recovering
* from these situations.
*/
htcb = nxsched_get_tcb(mholder & (~NXSEM_MBLOCKING_BIT));
}
unlocked = htcb == NULL;
}
else
{
unlocked = atomic_fetch_sub(NXSEM_COUNT(sem), 1) > 0;
}
if (unlocked)
{
/* It is, let the task take the semaphore. */
ret = nxsem_protect_wait(sem);
if (ret < 0)
{
if (mutex)
{
atomic_set(NXSEM_MHOLDER(sem), NXSEM_NO_MHOLDER);
}
else
{
atomic_fetch_add(NXSEM_COUNT(sem), 1);
}
leave_critical_section(flags);
return ret;
}
/* For mutexes, we only add the holder to the tasks list at the
* time when a task blocks on the mutex, for priority restoration
*/
if (!mutex)
{
nxsem_add_holder(sem);
}
}
/* The semaphore is NOT available, We will have to block the
* current thread of execution.
*/
else
{
#ifdef CONFIG_PRIORITY_INHERITANCE
uint8_t prioinherit = sem->flags & SEM_PRIO_MASK;
#endif
/* First, verify that the task is not already waiting on a
* semaphore
*/
DEBUGASSERT(rtcb->waitobj == NULL);
#ifdef CONFIG_MM_KMAP
sem = kmm_map_user(rtcb, sem, sizeof(*sem));
#endif
/* Save the waited on semaphore in the TCB */
rtcb->waitobj = sem;
/* In case of a mutex, store the previous holder in the task's list */
if (mutex)
{
nxsem_add_holder_tcb(htcb, sem);
}
/* If priority inheritance is enabled, then check the priority of
* the holder of the semaphore.
*/
#ifdef CONFIG_PRIORITY_INHERITANCE
if (prioinherit == SEM_PRIO_INHERIT)
{
/* Disable context switching. The following operations must be
* atomic with regard to the scheduler.
*/
sched_lock();
/* Boost the priority of any threads holding a count on the
* semaphore.
*/
nxsem_boost_priority(sem);
}
#endif
/* Set the errno value to zero (preserving the original errno)
* value). We reuse the per-thread errno to pass information
* between sem_waitirq() and this functions.
*/
rtcb->errcode = OK;
/* Add the TCB to the prioritized semaphore wait queue, after
* checking this is not the idle task - descheduling that
* isn't going to end well.
*/
DEBUGASSERT(!is_idle_task(rtcb));
/* Remove the tcb task from the running list. */
nxsched_remove_self(rtcb);
/* Add the task to the specified blocked task list */
rtcb->task_state = TSTATE_WAIT_SEM;
nxsched_add_prioritized(rtcb, SEM_WAITLIST(sem));
/* Now, perform the context switch */
up_switch_context(this_task(), rtcb);
/* When we resume at this point, either (1) the semaphore has been
* assigned to this thread of execution, or (2) the semaphore wait
* has been interrupted by a signal or a timeout. We can detect
* these latter cases be examining the per-thread errno value.
*
* In the event that the semaphore wait was interrupted by a
* signal or a timeout, certain semaphore clean-up operations have
* already been performed (see sem_waitirq.c). Specifically:
*
* - nxsem_canceled() was called to restore the priority of all
* threads that hold a reference to the semaphore,
* - The semaphore count was decremented, and
* - tcb->waitobj was nullifed.
*
* It is necessary to do these things in sem_waitirq.c because a
* long time may elapse between the time that the signal was issued
* and this thread is awakened and this leaves a door open to
* several race conditions.
*/
/* Check if an error occurred while we were sleeping. Expected
* errors include EINTR meaning that we were awakened by a signal
* or ETIMEDOUT meaning that the timer expired for the case of
* sem_timedwait().
*
* If we were not awakened by a signal or a timeout, then
* nxsem_add_holder() was called by logic in sem_wait() fore this
* thread was restarted.
*/
ret = rtcb->errcode != OK ? -rtcb->errcode : OK;
#ifdef CONFIG_MM_KMAP
kmm_unmap(sem);
#endif
#ifdef CONFIG_PRIORITY_INHERITANCE
if (prioinherit != 0)
{
sched_unlock();
}
#endif
}
/* If this now holds the mutex, set the holder TID and the lock bit */
if (mutex && ret == OK)
{
uint32_t blocking_bit =
dq_empty(SEM_WAITLIST(sem)) ? 0 : NXSEM_MBLOCKING_BIT;
atomic_set(NXSEM_MHOLDER(sem), ((uint32_t)rtcb->pid) | blocking_bit);
}
leave_critical_section(flags);
return ret;
}
/****************************************************************************
* Name: nxsem_wait_uninterruptible
*
* Description:
* This function is wrapped version of nxsem_wait(), which is
* uninterruptible and convenient for use.
*
* Parameters:
* sem - Semaphore descriptor.
*
* Return Value:
* Zero(OK) - On success
* EINVAL - Invalid attempt to get the semaphore
* ECANCELED - May be returned if the thread is canceled while waiting.
*
****************************************************************************/
int nxsem_wait_uninterruptible(FAR sem_t *sem)
{
int ret;
do
{
/* Take the semaphore (perhaps waiting) */
ret = nxsem_wait(sem);
}
while (ret == -EINTR);
return ret;
}