int pthread_cond_wait(FAR pthread_cond_t *cond, FAR pthread_mutex_t *mutex) { int ret; sdbg("cond=0x%p mutex=0x%p\n", cond, mutex); /* 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 != (int)getpid()) { ret = EPERM; } else { /* Give up the mutex */ sdbg("Give up mutex / take cond\n"); sched_lock(); mutex->pid = 0; ret = pthread_givesemaphore((sem_t*)&mutex->sem); /* Take the semaphore */ ret |= pthread_takesemaphore((sem_t*)&cond->sem); sched_unlock(); /* Reacquire the mutex */ sdbg("Reacquire mutex...\n"); ret |= pthread_takesemaphore((sem_t*)&mutex->sem); if (!ret) { mutex->pid = getpid();; } } sdbg("Returning %d\n", ret); return ret; }
static void pthread_start(void) { FAR _TCB *ptcb = (FAR _TCB*)g_readytorun.head; FAR join_t *pjoin = (FAR join_t*)ptcb->joininfo; pthread_addr_t exit_status; /* Sucessfully spawned, add the pjoin to our data set. * Don't re-enable pre-emption until this is done. */ (void)pthread_takesemaphore(&g_join_semaphore); pthread_addjoininfo(pjoin); (void)pthread_givesemaphore(&g_join_semaphore); /* Report to the spawner that we successfully started. */ pjoin->started = true; (void)pthread_givesemaphore(&pjoin->data_sem); /* Pass control to the thread entry point. The argument is * argv[1]. argv[0] (the thread name) and argv[2-4] are not made * available to the pthread. */ exit_status = (*ptcb->entry.pthread)((pthread_addr_t)ptcb->argv[1]); /* The thread has returned */ pthread_exit(exit_status); }
int pthread_completejoin(pid_t pid, FAR void *exit_value) { FAR struct task_group_s *group = task_getgroup(pid); FAR struct join_s *pjoin; sinfo("pid=%d exit_value=%p group=%p\n", pid, exit_value, group); DEBUGASSERT(group); /* First, find thread's structure in the private data set. */ (void)pthread_takesemaphore(&group->tg_joinsem); pjoin = pthread_findjoininfo(group, pid); if (!pjoin) { serr("ERROR: Could not find join info, pid=%d\n", pid); (void)pthread_givesemaphore(&group->tg_joinsem); return ERROR; } else { bool waiters; /* Save the return exit value in the thread structure. */ pjoin->terminated = true; pjoin->exit_value = exit_value; /* Notify waiters of the availability of the exit value */ waiters = pthread_notifywaiters(pjoin); /* If there are no waiters and if the thread is marked as detached. * then discard the join information now. Otherwise, the pthread * join logic will call pthread_destroyjoin() when all of the threads * have sampled the exit value. */ if (!waiters && pjoin->detached) { pthread_destroyjoin(group, pjoin); } /* Giving the following semaphore will allow the waiters * to call pthread_destroyjoin. */ (void)pthread_givesemaphore(&group->tg_joinsem); } return OK; }
int pthread_detach(pthread_t thread) { FAR struct tcb_s *rtcb = (FAR struct tcb_s *)g_readytorun.head; FAR struct task_group_s *group = rtcb->group; FAR struct join_s *pjoin; int ret; sdbg("Thread=%d group=%p\n", thread, group); DEBUGASSERT(group); /* Find the entry associated with this pthread. */ (void)pthread_takesemaphore(&group->tg_joinsem); pjoin = pthread_findjoininfo(group, (pid_t)thread); if (!pjoin) { sdbg("Could not find thread entry\n"); ret = EINVAL; } else { /* Has the thread already terminated? */ if (pjoin->terminated) { /* YES.. just remove the thread entry. */ pthread_destroyjoin(group, pjoin); } else { /* NO.. Just mark the thread as detached. It * will be removed and deallocated when the * thread exits */ pjoin->detached = true; } /* Either case is successful */ ret = OK; } (void)pthread_givesemaphore(&group->tg_joinsem); sdbg("Returning %d\n", ret); return ret; }
int pthread_completejoin(pid_t pid, FAR void *exit_value) { FAR join_t *pjoin; sdbg("process_id=%d exit_value=%p\n", pid, exit_value); /* First, find thread's structure in the private data set. */ (void)pthread_takesemaphore(&g_join_semaphore); pjoin = pthread_findjoininfo(pid); if (!pjoin) { (void)pthread_givesemaphore(&g_join_semaphore); return ERROR; } else { bool waiters; /* Save the return exit value in the thread structure. */ pjoin->terminated = true; pjoin->exit_value = exit_value; /* Notify waiters of the availability of the exit value */ waiters = pthread_notifywaiters(pjoin); /* If there are no waiters and if the thread is marked as detached. * then discard the join information now. Otherwise, the pthread * join logic will call pthread_destroyjoin() when all of the threads * have sampled the exit value. */ if (!waiters && pjoin->detached) { pthread_destroyjoin(pjoin); } /* Giving the following semaphore will allow the waiters * to call pthread_destroyjoin. */ (void)pthread_givesemaphore(&g_join_semaphore); } return OK; }
static bool pthread_notifywaiters(FAR struct join_s *pjoin) { int ntasks_waiting; int status; sinfo("pjoin=0x%p\n", pjoin); /* Are any tasks waiting for our exit value? */ status = sem_getvalue(&pjoin->exit_sem, &ntasks_waiting); if (status == OK && ntasks_waiting < 0) { /* Set the data semaphore so that this thread will be * awakened when all waiting tasks receive the data */ (void)sem_init(&pjoin->data_sem, 0, (ntasks_waiting+1)); /* Post the semaphore to restart each thread that is waiting * on the semaphore */ do { status = pthread_givesemaphore(&pjoin->exit_sem); if (status == OK) { status = sem_getvalue(&pjoin->exit_sem, &ntasks_waiting); } } while (ntasks_waiting < 0 && status == OK); /* Now wait for all these restarted tasks to obtain the return * value. */ (void)pthread_takesemaphore(&pjoin->data_sem); return true; } return false; }
static void pthread_start(void) { FAR struct pthread_tcb_s *ptcb = (FAR struct pthread_tcb_s *)g_readytorun.head; FAR struct task_group_s *group = ptcb->cmn.group; FAR struct join_s *pjoin = (FAR struct join_s *)ptcb->joininfo; pthread_addr_t exit_status; DEBUGASSERT(group && pjoin); /* Sucessfully spawned, add the pjoin to our data set. */ (void)pthread_takesemaphore(&group->tg_joinsem); pthread_addjoininfo(group, pjoin); (void)pthread_givesemaphore(&group->tg_joinsem); /* Report to the spawner that we successfully started. */ pjoin->started = true; (void)pthread_givesemaphore(&pjoin->data_sem); /* Pass control to the thread entry point. In the kernel build this has to * be handled differently if we are starting a user-space pthread; we have * to switch to user-mode before calling into the pthread. */ #if defined(CONFIG_BUILD_PROTECTED) || defined(CONFIG_BUILD_KERNEL) up_pthread_start(ptcb->cmn.entry.pthread, ptcb->arg); exit_status = NULL; #else exit_status = (*ptcb->cmn.entry.pthread)(ptcb->arg); #endif /* The thread has returned (should never happen in the kernel mode case) */ pthread_exit(exit_status); }
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(); int ticks; int mypid = (int)getpid(); irqstate_t int_state; int ret = OK; int status; sdbg("cond=0x%p mutex=0x%p abstime=0x%p\n", cond, mutex, abstime); DEBUGASSERT(rtcb->waitdog == NULL); /* 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 { sdbg("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(); int_state = irqsave(); /* 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 */ irqrestore(int_state); } 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 */ irqrestore(int_state); ret = ETIMEDOUT; } else { /* Give up the mutex */ mutex->pid = -1; ret = pthread_givesemaphore((FAR sem_t *)&mutex->sem); if (ret) { /* Restore interrupts (pre-emption will be enabled when * we fall through the if/then/else) */ irqrestore(int_state); } else { /* Start the watchdog */ wd_start(rtcb->waitdog, ticks, (wdentry_t)pthread_condtimedout, 2, (uint32_t)mypid, (uint32_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 = sem_wait((FAR sem_t *)&cond->sem); /* Did we get the condition semaphore. */ if (status != OK) { /* 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 (get_errno() == EINTR) { sdbg("Timedout!\n"); ret = ETIMEDOUT; } else { ret = EINVAL; } } /* 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) */ irqrestore(int_state); } /* Reacquire the mutex (retaining the ret). */ sdbg("Re-locking...\n"); status = pthread_takesemaphore((FAR sem_t *)&mutex->sem); if (!status) { mutex->pid = mypid; } else if (!ret) { 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; } } sdbg("Returning %d\n", ret); return ret; }
int pthread_join(pthread_t thread, FAR pthread_addr_t *pexit_value) { FAR struct tcb_s *rtcb = this_task(); FAR struct task_group_s *group = rtcb->group; FAR struct join_s *pjoin; int ret; sdbg("thread=%d group=%p\n", thread, group); DEBUGASSERT(group); /* First make sure that this is not an attempt to join to * ourself. */ if ((pid_t)thread == getpid()) { return EDEADLK; } /* Make sure no other task is mucking with the data structures * while we are performing the following operations. NOTE: * we can be also sure that pthread_exit() will not execute * because it will also attempt to get this semaphore. */ (void)pthread_takesemaphore(&group->tg_joinsem); /* Find the join information associated with this thread. * This can fail for one of three reasons: (1) There is no * thread associated with 'thread,' (2) the thread is a task * and does not have join information, or (3) the thread * was detached and has exited. */ pjoin = pthread_findjoininfo(group, (pid_t)thread); if (!pjoin) { /* Determine what kind of error to return */ FAR struct tcb_s *tcb = sched_gettcb((pthread_t)thread); sdbg("Could not find thread data\n"); /* Case (1) or (3) -- we can't tell which. Assume (3) */ if (!tcb) { ret = ESRCH; } /* The thread is still active but has no join info. In that * case, it must be a task and not a pthread. */ else { ret = EINVAL; } (void)pthread_givesemaphore(&group->tg_joinsem); } else { /* We found the join info structure. Increment for the reference * to the join structure that we have. This will keep things * stable for we have to do */ sched_lock(); pjoin->crefs++; /* Check if the thread is still running. If not, then things are * simpler. There are still race conditions to be concerned with. * For example, there could be multiple threads executing in the * 'else' block below when we enter! */ if (pjoin->terminated) { sdbg("Thread has terminated\n"); /* Get the thread exit value from the terminated thread. */ if (pexit_value) { sdbg("exit_value=0x%p\n", pjoin->exit_value); *pexit_value = pjoin->exit_value; } } else { sdbg("Thread is still running\n"); /* Relinquish the data set semaphore. Since pre-emption is * disabled, we can be certain that no task has the * opportunity to run between the time we relinquish the * join semaphore and the time that we wait on the thread exit * semaphore. */ (void)pthread_givesemaphore(&group->tg_joinsem); /* Take the thread's thread exit semaphore. We will sleep here * until the thread exits. We need to exercise caution because * there could be multiple threads waiting here for the same * pthread to exit. */ (void)pthread_takesemaphore(&pjoin->exit_sem); /* The thread has exited! Get the thread exit value */ if (pexit_value) { *pexit_value = pjoin->exit_value; sdbg("exit_value=0x%p\n", pjoin->exit_value); } /* Post the thread's data semaphore so that the exiting thread * will know that we have received the data. */ (void)pthread_givesemaphore(&pjoin->data_sem); /* Retake the join semaphore, we need to hold this when * pthread_destroyjoin is called. */ (void)pthread_takesemaphore(&group->tg_joinsem); } /* Pre-emption is okay now. The logic still cannot be re-entered * because we hold the join semaphore */ sched_unlock(); /* Release our reference to the join structure and, if the reference * count decrements to zero, deallocate the join structure. */ if (--pjoin->crefs <= 0) { (void)pthread_destroyjoin(group, pjoin); } (void)pthread_givesemaphore(&group->tg_joinsem); ret = OK; } sdbg("Returning %d\n", ret); return ret; }
int pthread_create(FAR pthread_t *thread, FAR pthread_attr_t *attr, pthread_startroutine_t start_routine, pthread_addr_t arg) { FAR _TCB *ptcb; FAR join_t *pjoin; int status; int priority; #if CONFIG_RR_INTERVAL > 0 int policy; #endif pid_t pid; /* If attributes were not supplied, use the default attributes */ if (!attr) { attr = &g_default_pthread_attr; } /* Allocate a TCB for the new task. */ ptcb = (FAR _TCB*)kzalloc(sizeof(_TCB)); if (!ptcb) { return ENOMEM; } /* Associate file descriptors with the new task */ status = sched_setuppthreadfiles(ptcb); if (status != OK) { sched_releasetcb(ptcb); return status; } /* Share the parent's envionment */ (void)env_share(ptcb); /* Allocate a detachable structure to support pthread_join logic */ pjoin = (FAR join_t*)kzalloc(sizeof(join_t)); if (!pjoin) { sched_releasetcb(ptcb); return ENOMEM; } /* Allocate the stack for the TCB */ status = up_create_stack(ptcb, attr->stacksize); if (status != OK) { sched_releasetcb(ptcb); sched_free(pjoin); return ENOMEM; } /* Should we use the priority and scheduler specified in the * pthread attributes? Or should we use the current thread's * priority and scheduler? */ if (attr->inheritsched == PTHREAD_INHERIT_SCHED) { /* Get the priority for this thread. */ struct sched_param param; status = sched_getparam(0, ¶m); if (status == OK) { priority = param.sched_priority; } else { priority = SCHED_FIFO; } /* Get the scheduler policy for this thread */ #if CONFIG_RR_INTERVAL > 0 policy = sched_getscheduler(0); if (policy == ERROR) { policy = SCHED_FIFO; } #endif } else { /* Use the priority and scheduler from the attributes */ priority = attr->priority; #if CONFIG_RR_INTERVAL > 0 policy = attr->policy; #endif } /* Mark this task as a pthread (this setting will be needed in * task_schedsetup() when up_initial_state() is called. */ ptcb->flags |= TCB_FLAG_TTYPE_PTHREAD; /* Initialize the task control block */ status = task_schedsetup(ptcb, priority, pthread_start, (main_t)start_routine); if (status != OK) { sched_releasetcb(ptcb); sched_free(pjoin); return EBUSY; } /* Configure the TCB for a pthread receiving on parameter * passed by value */ pthread_argsetup(ptcb, arg); /* Attach the join info to the TCB. */ ptcb->joininfo = (void*)pjoin; /* If round robin scheduling is selected, set the appropriate flag * in the TCB. */ #if CONFIG_RR_INTERVAL > 0 if (policy == SCHED_RR) { ptcb->flags |= TCB_FLAG_ROUND_ROBIN; ptcb->timeslice = CONFIG_RR_INTERVAL / MSEC_PER_TICK; } #endif /* Get the assigned pid before we start the task (who knows what * could happen to ptcb after this!). Copy this ID into the join structure * as well. */ pid = (int)ptcb->pid; pjoin->thread = (pthread_t)pid; /* Initialize the semaphores in the join structure to zero. */ status = sem_init(&pjoin->data_sem, 0, 0); if (status == OK) { status = sem_init(&pjoin->exit_sem, 0, 0); } /* Activate the task */ sched_lock(); if (status == OK) { status = task_activate(ptcb); } if (status == OK) { /* Wait for the task to actually get running and to register * its join_t */ (void)pthread_takesemaphore(&pjoin->data_sem); /* Return the thread information to the caller */ if (thread) *thread = (pthread_t)pid; if (!pjoin->started) status = ERROR; sched_unlock(); (void)sem_destroy(&pjoin->data_sem); } else { sched_unlock(); dq_rem((FAR dq_entry_t*)ptcb, (dq_queue_t*)&g_inactivetasks); (void)sem_destroy(&pjoin->data_sem); (void)sem_destroy(&pjoin->exit_sem); sched_releasetcb(ptcb); sched_free(pjoin); return EIO; } return OK; }
int pthread_create(FAR pthread_t *thread, FAR const pthread_attr_t *attr, pthread_startroutine_t start_routine, pthread_addr_t arg) { FAR struct pthread_tcb_s *ptcb; FAR struct join_s *pjoin; struct sched_param param; int policy; int errcode; pid_t pid; int ret; #ifdef HAVE_TASK_GROUP bool group_joined = false; #endif /* If attributes were not supplied, use the default attributes */ if (!attr) { attr = &g_default_pthread_attr; } /* Allocate a TCB for the new task. */ ptcb = (FAR struct pthread_tcb_s *)kmm_zalloc(sizeof(struct pthread_tcb_s)); if (!ptcb) { sdbg("ERROR: Failed to allocate TCB\n"); return ENOMEM; } #ifdef HAVE_TASK_GROUP /* Bind the parent's group to the new TCB (we have not yet joined the * group). */ ret = group_bind(ptcb); if (ret < 0) { errcode = ENOMEM; goto errout_with_tcb; } #endif #ifdef CONFIG_ARCH_ADDRENV /* Share the address environment of the parent task group. */ ret = up_addrenv_attach(ptcb->cmn.group, (FAR struct tcb_s *)g_readytorun.head); if (ret < 0) { errcode = -ret; goto errout_with_tcb; } #endif /* Allocate a detachable structure to support pthread_join logic */ pjoin = (FAR struct join_s *)kmm_zalloc(sizeof(struct join_s)); if (!pjoin) { sdbg("ERROR: Failed to allocate join\n"); errcode = ENOMEM; goto errout_with_tcb; } /* Allocate the stack for the TCB */ ret = up_create_stack((FAR struct tcb_s *)ptcb, attr->stacksize, TCB_FLAG_TTYPE_PTHREAD); if (ret != OK) { errcode = ENOMEM; goto errout_with_join; } /* Should we use the priority and scheduler specified in the pthread * attributes? Or should we use the current thread's priority and * scheduler? */ if (attr->inheritsched == PTHREAD_INHERIT_SCHED) { /* Get the priority (and any other scheduling parameters) for this * thread. */ ret = sched_getparam(0, ¶m); if (ret == ERROR) { errcode = get_errno(); goto errout_with_join; } /* Get the scheduler policy for this thread */ policy = sched_getscheduler(0); if (policy == ERROR) { errcode = get_errno(); goto errout_with_join; } } else { /* Use the scheduler policy and policy the attributes */ policy = attr->policy; param.sched_priority = attr->priority; #ifdef CONFIG_SCHED_SPORADIC param.sched_ss_low_priority = attr->low_priority; param.sched_ss_max_repl = attr->max_repl; param.sched_ss_repl_period.tv_sec = attr->repl_period.tv_sec; param.sched_ss_repl_period.tv_nsec = attr->repl_period.tv_nsec; param.sched_ss_init_budget.tv_sec = attr->budget.tv_sec; param.sched_ss_init_budget.tv_nsec = attr->budget.tv_nsec; #endif } #ifdef CONFIG_SCHED_SPORADIC if (policy == SCHED_SPORADIC) { FAR struct sporadic_s *sporadic; int repl_ticks; int budget_ticks; /* Convert timespec values to system clock ticks */ (void)clock_time2ticks(¶m.sched_ss_repl_period, &repl_ticks); (void)clock_time2ticks(¶m.sched_ss_init_budget, &budget_ticks); /* The replenishment period must be greater than or equal to the * budget period. */ if (repl_ticks < budget_ticks) { errcode = EINVAL; goto errout_with_join; } /* Initialize the sporadic policy */ ret = sched_sporadic_initialize(&ptcb->cmn); if (ret >= 0) { sporadic = ptcb->cmn.sporadic; DEBUGASSERT(sporadic != NULL); /* Save the sporadic scheduling parameters */ sporadic->hi_priority = param.sched_priority; sporadic->low_priority = param.sched_ss_low_priority; sporadic->max_repl = param.sched_ss_max_repl; sporadic->repl_period = repl_ticks; sporadic->budget = budget_ticks; /* And start the first replenishment interval */ ret = sched_sporadic_start(&ptcb->cmn); } /* Handle any failures */ if (ret < 0) { errcode = -ret; goto errout_with_join; } } #endif /* Initialize the task control block */ ret = pthread_schedsetup(ptcb, param.sched_priority, pthread_start, start_routine); if (ret != OK) { errcode = EBUSY; goto errout_with_join; } /* Configure the TCB for a pthread receiving on parameter * passed by value */ pthread_argsetup(ptcb, arg); #ifdef HAVE_TASK_GROUP /* Join the parent's task group */ ret = group_join(ptcb); if (ret < 0) { errcode = ENOMEM; goto errout_with_join; } group_joined = true; #endif /* Attach the join info to the TCB. */ ptcb->joininfo = (FAR void *)pjoin; /* Set the appropriate scheduling policy in the TCB */ ptcb->cmn.flags &= ~TCB_FLAG_POLICY_MASK; switch (policy) { default: DEBUGPANIC(); case SCHED_FIFO: ptcb->cmn.flags |= TCB_FLAG_SCHED_FIFO; break; #if CONFIG_RR_INTERVAL > 0 case SCHED_RR: ptcb->cmn.flags |= TCB_FLAG_SCHED_RR; ptcb->cmn.timeslice = MSEC2TICK(CONFIG_RR_INTERVAL); break; #endif #ifdef CONFIG_SCHED_SPORADIC case SCHED_SPORADIC: ptcb->cmn.flags |= TCB_FLAG_SCHED_SPORADIC; break; #endif #if 0 /* Not supported */ case SCHED_OTHER: ptcb->cmn.flags |= TCB_FLAG_SCHED_OTHER; break; #endif } /* Get the assigned pid before we start the task (who knows what * could happen to ptcb after this!). Copy this ID into the join structure * as well. */ pid = (int)ptcb->cmn.pid; pjoin->thread = (pthread_t)pid; /* Initialize the semaphores in the join structure to zero. */ ret = sem_init(&pjoin->data_sem, 0, 0); if (ret == OK) { ret = sem_init(&pjoin->exit_sem, 0, 0); } /* Activate the task */ sched_lock(); if (ret == OK) { ret = task_activate((FAR struct tcb_s *)ptcb); } if (ret == OK) { /* Wait for the task to actually get running and to register * its join structure. */ (void)pthread_takesemaphore(&pjoin->data_sem); /* Return the thread information to the caller */ if (thread) { *thread = (pthread_t)pid; } if (!pjoin->started) { ret = EINVAL; } sched_unlock(); (void)sem_destroy(&pjoin->data_sem); } else { sched_unlock(); dq_rem((FAR dq_entry_t *)ptcb, (FAR dq_queue_t *)&g_inactivetasks); (void)sem_destroy(&pjoin->data_sem); (void)sem_destroy(&pjoin->exit_sem); errcode = EIO; goto errout_with_join; } return ret; errout_with_join: sched_kfree(pjoin); ptcb->joininfo = NULL; errout_with_tcb: #ifdef HAVE_TASK_GROUP /* Clear group binding */ if (ptcb && !group_joined) { ptcb->cmn.group = NULL; } #endif sched_releasetcb((FAR struct tcb_s *)ptcb, TCB_FLAG_TTYPE_PTHREAD); return errcode; }