/**************************************************************************** * sched/pthread/pthread_condtimedwait.c * * 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 #include #include #include #include #include #include #include #include #include "sched/sched.h" #include "pthread/pthread.h" #include "clock/clock.h" #include "signal/signal.h" /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: pthread_condtimedout * * Description: * This function is called if the timeout elapses before * the condition is signaled. * * Input Parameters: * argc - the number of arguments (should be 2) * pid - the task ID of the task to wakeup * signo - The signal to use to wake up the task * * Returned Value: * None * * Assumptions: * ****************************************************************************/ static void pthread_condtimedout(int argc, wdparm_t arg1, ...) { pid_t pid = (pid_t)arg1; int signo; va_list ap; /* Retrieve the variadic argument */ va_start(ap, arg1); signo = (int)va_arg(ap, wdparm_t); va_end(ap); #ifdef HAVE_GROUP_MEMBERS { FAR struct tcb_s *tcb; siginfo_t info; /* The logic below if equivalent to nxsig_queue(), but uses * nxsig_tcbdispatch() instead of nxsig_dispatch(). This avoids the * group signal deliver logic and assures, instead, that the signal is * delivered specifically to this thread that is known to be waiting on * the signal. */ /* Get the waiting TCB. sched_gettcb() might return NULL if the task * has exited for some reason. */ tcb = sched_gettcb(pid); if (tcb) { /* Create the siginfo structure */ info.si_signo = signo; info.si_code = SI_QUEUE; info.si_errno = ETIMEDOUT; info.si_value.sival_ptr = NULL; #ifdef CONFIG_SCHED_HAVE_PARENT info.si_pid = pid; info.si_status = OK; #endif /* Process the receipt of the signal. The scheduler is not locked * as is normally the case when this function is called because we * are in a watchdog timer interrupt handler. */ nxsig_tcbdispatch(tcb, &info); } } #else /* HAVE_GROUP_MEMBERS */ { /* Things are a little easier if there are not group members. We can * just use nxsig_queue(). */ union sigval value; /* Send the specified signal to the specified task. */ value.sival_ptr = NULL; nxsig_queue((int)pid, signo, value); } #endif /* HAVE_GROUP_MEMBERS */ } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: pthread_cond_timedwait * * Description: * A thread can perform a timed wait on a condition variable. * * Input Parameters: * cond - the condition variable to wait on * mutex - the mutex that protects the condition variable * abstime - wait until this absolute time * * Returned Value: * OK (0) on success; A non-zero errno value is returned on failure. * * Assumptions: * Timing is of resolution 1 msec, with +/-1 millisecond accuracy. * ****************************************************************************/ int pthread_cond_timedwait(FAR pthread_cond_t *cond, FAR pthread_mutex_t *mutex, FAR const struct timespec *abstime) { FAR struct tcb_s *rtcb = this_task(); irqstate_t flags; sclock_t ticks; int mypid = (int)getpid(); int ret = OK; int status; sinfo("cond=0x%p mutex=0x%p abstime=0x%p\n", cond, mutex, abstime); DEBUGASSERT(rtcb->waitdog == NULL); /* pthread_cond_timedwait() is a cancellation point */ enter_cancellation_point(); /* Make sure that non-NULL references were provided. */ if (!cond || !mutex) { ret = EINVAL; } /* Make sure that the caller holds the mutex */ else if (mutex->pid != mypid) { ret = EPERM; } /* If no wait time is provided, this function degenerates to * the same behavior as pthread_cond_wait(). */ else if (!abstime) { ret = pthread_cond_wait(cond, mutex); } else { /* Create a watchdog */ rtcb->waitdog = wd_create(); if (!rtcb->waitdog) { ret = EINVAL; } else { sinfo("Give up mutex...\n"); /* We must disable pre-emption and interrupts here so that * the time stays valid until the wait begins. This adds * complexity because we assure that interrupts and * pre-emption are re-enabled correctly. */ sched_lock(); flags = enter_critical_section(); /* Convert the timespec to clock ticks. We must disable pre- * emption here so that this time stays valid until the wait * begins. */ ret = clock_abstime2ticks(CLOCK_REALTIME, abstime, &ticks); if (ret) { /* Restore interrupts (pre-emption will be enabled when * we fall through the if/then/else) */ leave_critical_section(flags); } else { /* Check the absolute time to wait. If it is now or in the * past, then just return with the timedout condition. */ if (ticks <= 0) { /* Restore interrupts and indicate that we have already * timed out. (pre-emption will be enabled when we fall * through the if/then/else */ leave_critical_section(flags); ret = ETIMEDOUT; } else { #ifndef CONFIG_PTHREAD_MUTEX_UNSAFE uint8_t mflags; #endif #ifdef CONFIG_PTHREAD_MUTEX_TYPES uint8_t type; int16_t nlocks; #endif /* Give up the mutex */ mutex->pid = -1; #ifndef CONFIG_PTHREAD_MUTEX_UNSAFE mflags = mutex->flags; #endif #ifdef CONFIG_PTHREAD_MUTEX_TYPES type = mutex->type; nlocks = mutex->nlocks; #endif ret = pthread_mutex_give(mutex); if (ret != 0) { /* Restore interrupts (pre-emption will be enabled * when we fall through the if/then/else) */ leave_critical_section(flags); } else { /* Start the watchdog */ wd_start(rtcb->waitdog, ticks, pthread_condtimedout, 2, (wdparm_t)mypid, (wdparm_t)SIGCONDTIMEDOUT); /* Take the condition semaphore. Do not restore * interrupts until we return from the wait. This is * necessary to make sure that the watchdog timer and * the condition wait are started atomically. */ status = nxsem_wait((FAR sem_t *)&cond->sem); /* Did we get the condition semaphore. */ if (status < 0) { /* NO.. Handle the special case where the semaphore * wait was awakened by the receipt of a signal -- * presumably the signal posted by * pthread_condtimedout(). */ if (status == -EINTR) { swarn("WARNING: Timedout!\n"); ret = ETIMEDOUT; } else { ret = status; } } /* The interrupts stay disabled until after we sample * the errno. This is because when debug is enabled * and the console is used for debug output, then the * errno can be altered by interrupt handling! (bad) */ leave_critical_section(flags); } /* Reacquire the mutex (retaining the ret). */ sinfo("Re-locking...\n"); status = pthread_mutex_take(mutex, NULL, false); if (status == OK) { mutex->pid = mypid; #ifndef CONFIG_PTHREAD_MUTEX_UNSAFE mutex->flags = mflags; #endif #ifdef CONFIG_PTHREAD_MUTEX_TYPES mutex->type = type; mutex->nlocks = nlocks; #endif } else if (ret == 0) { ret = status; } } /* Re-enable pre-emption (It is expected that interrupts * have already been re-enabled in the above logic) */ sched_unlock(); } /* We no longer need the watchdog */ wd_delete(rtcb->waitdog); rtcb->waitdog = NULL; } } leave_cancellation_point(); sinfo("Returning %d\n", ret); return ret; }