/*
|
* Copyright (C) 2011 Philippe Gerum <rpm@xenomai.org>.
|
*
|
* This library is free software; you can redistribute it and/or
|
* modify it under the terms of the GNU Lesser General Public
|
* License as published by the Free Software Foundation; either
|
* version 2 of the License, or (at your option) any later version.
|
*
|
* This library is distributed in the hope that it will be useful,
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* Lesser General Public License for more details.
|
|
* You should have received a copy of the GNU Lesser General Public
|
* License along with this library; if not, write to the Free Software
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
*/
|
|
#include <sched.h>
|
#include <pthread.h>
|
#include <errno.h>
|
#include <stdio.h>
|
#include <string.h>
|
#include "copperplate/heapobj.h"
|
#include "copperplate/internal.h"
|
#include "internal.h"
|
#include "task.h"
|
#include "buffer.h"
|
#include "queue.h"
|
#include "timer.h"
|
#include "heap.h"
|
|
/**
|
* @ingroup alchemy
|
* @defgroup alchemy_task Task management services
|
*
|
* Services dealing with preemptive multi-tasking
|
*
|
* Each Alchemy task is an independent portion of the overall
|
* application code embodied in a C procedure, which executes on its
|
* own stack context.
|
*
|
* @{
|
*/
|
|
union alchemy_wait_union {
|
struct alchemy_task_wait task_wait;
|
struct alchemy_buffer_wait buffer_wait;
|
struct alchemy_queue_wait queue_wait;
|
struct alchemy_heap_wait heap_wait;
|
};
|
|
struct syncluster alchemy_task_table;
|
|
static DEFINE_NAME_GENERATOR(task_namegen, "task",
|
struct alchemy_task, name);
|
|
#ifdef CONFIG_XENO_REGISTRY
|
|
static int task_registry_open(struct fsobj *fsobj, void *priv)
|
{
|
struct fsobstack *o = priv;
|
struct threadobj_stat buf;
|
struct alchemy_task *tcb;
|
int ret;
|
|
tcb = container_of(fsobj, struct alchemy_task, fsobj);
|
ret = threadobj_lock(&tcb->thobj);
|
if (ret)
|
return -EIO;
|
|
ret = threadobj_stat(&tcb->thobj, &buf);
|
threadobj_unlock(&tcb->thobj);
|
if (ret)
|
return ret;
|
|
fsobstack_init(o);
|
|
fsobstack_finish(o);
|
|
return 0;
|
}
|
|
static struct registry_operations registry_ops = {
|
.open = task_registry_open,
|
.release = fsobj_obstack_release,
|
.read = fsobj_obstack_read
|
};
|
|
#else /* !CONFIG_XENO_REGISTRY */
|
|
static struct registry_operations registry_ops;
|
|
#endif /* CONFIG_XENO_REGISTRY */
|
|
static struct alchemy_task *find_alchemy_task(RT_TASK *task, int *err_r)
|
{
|
struct alchemy_task *tcb;
|
|
if (bad_pointer(task))
|
goto bad_handle;
|
|
tcb = mainheap_deref(task->handle, struct alchemy_task);
|
if (bad_pointer(tcb))
|
goto bad_handle;
|
|
if (threadobj_get_magic(&tcb->thobj) == task_magic)
|
return tcb;
|
bad_handle:
|
*err_r = -EINVAL;
|
|
return NULL;
|
}
|
|
static struct alchemy_task *find_alchemy_task_or_self(RT_TASK *task, int *err_r)
|
{
|
struct alchemy_task *current;
|
|
if (task)
|
return find_alchemy_task(task, err_r);
|
|
current = alchemy_task_current();
|
if (current == NULL) {
|
*err_r = -EPERM;
|
return NULL;
|
}
|
|
return current;
|
}
|
|
struct alchemy_task *get_alchemy_task(RT_TASK *task, int *err_r)
|
{
|
struct alchemy_task *tcb = find_alchemy_task(task, err_r);
|
|
/*
|
* Grab the task lock, assuming that the task might have been
|
* deleted, and/or maybe we have been lucky, and some random
|
* opaque pointer might lead us to something which is laid in
|
* valid memory but certainly not to a task object. Last
|
* chance is pthread_mutex_lock() detecting a wrong mutex kind
|
* and bailing out.
|
*/
|
if (tcb == NULL || threadobj_lock(&tcb->thobj) == -EINVAL) {
|
*err_r = -EINVAL;
|
return NULL;
|
}
|
|
/* Check the magic word again, while we hold the lock. */
|
if (threadobj_get_magic(&tcb->thobj) != task_magic) {
|
threadobj_unlock(&tcb->thobj);
|
*err_r = -EINVAL;
|
return NULL;
|
}
|
|
return tcb;
|
}
|
|
struct alchemy_task *get_alchemy_task_or_self(RT_TASK *task, int *err_r)
|
{
|
struct alchemy_task *current;
|
|
if (task)
|
return get_alchemy_task(task, err_r);
|
|
current = alchemy_task_current();
|
if (current == NULL) {
|
*err_r = -EPERM;
|
return NULL;
|
}
|
|
/* This one might block but can't fail, it is ours. */
|
threadobj_lock(¤t->thobj);
|
|
return current;
|
}
|
|
void put_alchemy_task(struct alchemy_task *tcb)
|
{
|
threadobj_unlock(&tcb->thobj);
|
}
|
|
static void task_finalizer(struct threadobj *thobj)
|
{
|
struct alchemy_task *tcb;
|
struct syncstate syns;
|
int ret;
|
|
tcb = container_of(thobj, struct alchemy_task, thobj);
|
registry_destroy_file(&tcb->fsobj);
|
syncluster_delobj(&alchemy_task_table, &tcb->cobj);
|
/*
|
* The msg sync may be pended by other threads, so we do have
|
* to use syncobj_destroy() on it (i.e. NOT syncobj_uninit()).
|
*/
|
ret = __bt(syncobj_lock(&tcb->sobj_msg, &syns));
|
if (ret == 0)
|
syncobj_destroy(&tcb->sobj_msg, &syns);
|
}
|
|
static int task_prologue_1(void *arg)
|
{
|
struct alchemy_task *tcb = arg;
|
|
return __bt(threadobj_prologue(&tcb->thobj, tcb->name));
|
}
|
|
static int task_prologue_2(struct alchemy_task *tcb)
|
{
|
int ret;
|
|
threadobj_wait_start();
|
threadobj_lock(&tcb->thobj);
|
ret = threadobj_set_mode(0, tcb->mode, NULL);
|
threadobj_unlock(&tcb->thobj);
|
|
return ret;
|
}
|
|
static void *task_entry(void *arg)
|
{
|
struct alchemy_task *tcb = arg;
|
struct service svc;
|
int ret;
|
|
CANCEL_DEFER(svc);
|
|
ret = __bt(task_prologue_2(tcb));
|
if (ret) {
|
CANCEL_RESTORE(svc);
|
return (void *)(long)ret;
|
}
|
|
threadobj_notify_entry();
|
|
CANCEL_RESTORE(svc);
|
|
tcb->entry(tcb->arg);
|
|
return NULL;
|
}
|
|
static void delete_tcb(struct alchemy_task *tcb)
|
{
|
syncobj_uninit(&tcb->sobj_msg);
|
threadobj_uninit(&tcb->thobj);
|
threadobj_free(&tcb->thobj);
|
}
|
|
static int create_tcb(struct alchemy_task **tcbp, RT_TASK *task,
|
const char *name, int prio, int mode)
|
{
|
struct threadobj_init_data idata;
|
struct alchemy_task *tcb;
|
int ret;
|
|
if (threadobj_irq_p())
|
return -EPERM;
|
|
ret = check_task_priority(prio);
|
if (ret)
|
return ret;
|
|
tcb = threadobj_alloc(struct alchemy_task, thobj,
|
union alchemy_wait_union);
|
if (tcb == NULL)
|
return -ENOMEM;
|
|
generate_name(tcb->name, name, &task_namegen);
|
|
tcb->mode = mode;
|
tcb->entry = NULL; /* Not yet known. */
|
tcb->arg = NULL;
|
|
CPU_ZERO(&tcb->affinity);
|
|
ret = syncobj_init(&tcb->sobj_msg, CLOCK_COPPERPLATE,
|
SYNCOBJ_PRIO, fnref_null);
|
if (ret)
|
goto fail_syncinit;
|
|
tcb->suspends = 0;
|
tcb->flowgen = 0;
|
|
idata.magic = task_magic;
|
idata.finalizer = task_finalizer;
|
idata.policy = prio ? SCHED_FIFO : SCHED_OTHER;
|
idata.param_ex.sched_priority = prio;
|
ret = threadobj_init(&tcb->thobj, &idata);
|
if (ret)
|
goto fail_threadinit;
|
|
*tcbp = tcb;
|
|
/*
|
* CAUTION: The task control block must be fully built before
|
* we publish it through syncluster_addobj(), at which point
|
* it could be referred to immediately from another task as we
|
* got preempted. In addition, the task descriptor must be
|
* updated prior to starting the task.
|
*/
|
tcb->self.handle = mainheap_ref(tcb, uintptr_t);
|
|
registry_init_file_obstack(&tcb->fsobj, ®istry_ops);
|
ret = __bt(registry_add_file(&tcb->fsobj, O_RDONLY,
|
"/alchemy/tasks/%s", tcb->name));
|
if (ret)
|
warning("failed to export task %s to registry, %s",
|
tcb->name, symerror(ret));
|
|
ret = syncluster_addobj(&alchemy_task_table, tcb->name, &tcb->cobj);
|
if (ret)
|
goto fail_register;
|
|
if (task)
|
task->handle = tcb->self.handle;
|
|
return 0;
|
|
fail_register:
|
registry_destroy_file(&tcb->fsobj);
|
threadobj_uninit(&tcb->thobj);
|
fail_threadinit:
|
syncobj_uninit(&tcb->sobj_msg);
|
fail_syncinit:
|
threadobj_free(&tcb->thobj);
|
|
return ret;
|
}
|
|
/**
|
* @fn int rt_task_create(RT_TASK *task, const char *name, int stksize, int prio, int mode)
|
* @brief Create a task with Alchemy personality.
|
*
|
* This service creates a task with access to the full set of Alchemy
|
* services. If @a prio is non-zero, the new task belongs to Xenomai's
|
* real-time FIFO scheduling class, aka SCHED_FIFO. If @a prio is
|
* zero, the task belongs to the regular SCHED_OTHER class.
|
*
|
* Creating tasks with zero priority is useful for running non
|
* real-time processes which may invoke blocking real-time services,
|
* such as pending on a semaphore, reading from a message queue or a
|
* buffer, and so on.
|
*
|
* Once created, the task is left dormant until it is actually started
|
* by rt_task_start().
|
*
|
* @param task The address of a task descriptor which can be later
|
* used to identify uniquely the created object, upon success of this
|
* call.
|
*
|
* @param name An ASCII string standing for the symbolic name of the
|
* task. When non-NULL and non-empty, a copy of this string is
|
* used for indexing the created task into the object registry.
|
*
|
* @param stksize The size of the stack (in bytes) for the new
|
* task. If zero is passed, a system-dependent default size will be
|
* substituted.
|
*
|
* @param prio The base priority of the new task. This value must be
|
* in the [0 .. 99] range, where 0 is the lowest effective priority.
|
*
|
* @param mode The task creation mode. The following flags can be
|
* OR'ed into this bitmask:
|
*
|
* - T_JOINABLE allows another task to wait on the termination of the
|
* new task. rt_task_join() shall be called for this task to clean up
|
* any resources after its termination.
|
*
|
* - T_LOCK causes the new task to lock the scheduler prior to
|
* entering the user routine specified by rt_task_start(). A call to
|
* rt_task_set_mode() from the new task is required to drop this lock.
|
*
|
* - When running over the Cobalt core, T_WARNSW causes the SIGDEBUG
|
* signal to be sent to the current task whenever it switches to the
|
* secondary mode. This feature is useful to detect unwanted
|
* migrations to the Linux domain. This flag has no effect over the
|
* Mercury core.
|
*
|
* @return Zero is returned upon success. Otherwise:
|
*
|
* - -EINVAL is returned if either @a prio, @a mode or @a stksize are
|
* invalid.
|
*
|
* - -ENOMEM is returned if the system fails to get memory from the
|
* main heap in order to create the task.
|
*
|
* - -EEXIST is returned if the @a name is conflicting with an already
|
* registered task.
|
*
|
* - -EPERM is returned if this service was called from an invalid
|
* context, e.g. interrupt or non-Xenomai thread.
|
*
|
* @apitags{xthread-only, mode-unrestricted, switch-secondary}
|
*
|
* @sideeffect
|
* - When running over the Cobalt core:
|
*
|
* - calling rt_task_create() causes SCHED_FIFO tasks to switch to
|
* secondary mode.
|
*
|
* - members of Xenomai's SCHED_FIFO class running in the primary
|
* domain have utmost priority over all Linux activities in the
|
* system, including Linux interrupt handlers.
|
*
|
* - When running over the Mercury core, the new task belongs to the
|
* regular POSIX SCHED_FIFO class.
|
*
|
* @note Tasks can be referred to from multiple processes which all
|
* belong to the same Xenomai session.
|
*/
|
#ifndef DOXYGEN_CPP
|
CURRENT_IMPL(int, rt_task_create, (RT_TASK *task, const char *name,
|
int stksize, int prio, int mode))
|
#else
|
int rt_task_create(RT_TASK *task, const char *name,
|
int stksize, int prio, int mode)
|
#endif
|
{
|
struct corethread_attributes cta;
|
struct alchemy_task *tcb;
|
struct service svc;
|
int ret;
|
|
if (mode & ~(T_LOCK | T_WARNSW | T_JOINABLE))
|
return -EINVAL;
|
|
CANCEL_DEFER(svc);
|
|
ret = create_tcb(&tcb, task, name, prio, mode);
|
if (ret)
|
goto out;
|
|
/* We want this to be set prior to spawning the thread. */
|
tcb->self = *task;
|
|
cta.detachstate = mode & T_JOINABLE ?
|
PTHREAD_CREATE_JOINABLE : PTHREAD_CREATE_DETACHED;
|
cta.policy = threadobj_get_policy(&tcb->thobj);
|
threadobj_copy_schedparam(&cta.param_ex, &tcb->thobj);
|
cta.prologue = task_prologue_1;
|
cta.run = task_entry;
|
cta.arg = tcb;
|
cta.stacksize = stksize;
|
|
ret = __bt(copperplate_create_thread(&cta, &tcb->thobj.ptid));
|
if (ret) {
|
delete_tcb(tcb);
|
} else {
|
tcb->self.thread = tcb->thobj.ptid;
|
task->thread = tcb->thobj.ptid;
|
}
|
out:
|
CANCEL_RESTORE(svc);
|
|
return ret;
|
}
|
|
/**
|
* @fn int rt_task_delete(RT_TASK *task)
|
* @brief Delete a real-time task.
|
*
|
* This call terminates a task previously created by
|
* rt_task_create().
|
*
|
* Tasks created with the T_JOINABLE flag shall be joined by a
|
* subsequent call to rt_task_join() once successfully deleted, to
|
* reclaim all resources.
|
*
|
* @param task The task descriptor.
|
*
|
* @return Zero is returned upon success. Otherwise:
|
*
|
* - -EINVAL is returned if @a task is not a valid task descriptor.
|
*
|
* - -EPERM is returned if @a task is NULL and this service was called
|
* from an invalid context. In addition, this error is always raised
|
* when this service is called from asynchronous context, such as a
|
* timer/alarm handler.
|
*
|
* @apitags{mode-unrestricted, switch-secondary}
|
*
|
* @note The caller must be an Alchemy task if @a task is NULL.
|
*/
|
int rt_task_delete(RT_TASK *task)
|
{
|
struct alchemy_task *tcb;
|
struct service svc;
|
int ret;
|
|
if (threadobj_irq_p())
|
return -EPERM;
|
|
tcb = find_alchemy_task_or_self(task, &ret);
|
if (tcb == NULL)
|
return ret;
|
|
CANCEL_DEFER(svc);
|
threadobj_lock(&tcb->thobj);
|
/* Self-deletion is handled by threadobj_cancel(). */
|
threadobj_cancel(&tcb->thobj);
|
CANCEL_RESTORE(svc);
|
|
return 0;
|
}
|
|
/**
|
* @fn int rt_task_join(RT_TASK *task)
|
* @brief Wait on the termination of a real-time task.
|
*
|
* This service blocks the caller in non-real-time context until @a
|
* task has terminated. All resources are released after successful
|
* completion of this service.
|
*
|
* The specified task must have been created by the same process that
|
* wants to join it, and the T_JOINABLE mode flag must have been set
|
* on creation to rt_task_create().
|
*
|
* Tasks created with the T_JOINABLE flag shall be joined by a
|
* subsequent call to rt_task_join() once successfully deleted, to
|
* reclaim all resources.
|
*
|
* @param task The task descriptor.
|
*
|
* @return Zero is returned upon success. Otherwise:
|
*
|
* - -EINVAL is returned if @a task is not a valid task descriptor.
|
*
|
* - -EINVAL is returned if the task was not created with T_JOINABLE
|
* set or some other task is already waiting on the termination.
|
*
|
* - -EDEADLK is returned if @a task refers to the caller.
|
*
|
* - -ESRCH is returned if @a task no longer exists or refers to task
|
* created by a different process.
|
*
|
* @apitags{mode-unrestricted, switch-primary}
|
*
|
* @note After successful completion of this service, it is neither
|
* required nor valid to additionally invoke rt_task_delete() on the
|
* same task.
|
*/
|
int rt_task_join(RT_TASK *task)
|
{
|
if (bad_pointer(task))
|
return -EINVAL;
|
|
return -__RT(pthread_join(task->thread, NULL));
|
}
|
|
/**
|
* @fn int rt_task_set_affinity(RT_TASK *task, const cpu_set_t *cpus)
|
* @brief Set CPU affinity of real-time task.
|
*
|
* This calls makes @a task affine to the set of CPUs defined by @a
|
* cpus.
|
*
|
* @param task The task descriptor. If @a task is NULL, the CPU
|
* affinity of the current task is changed.
|
*
|
* @param cpus The set of CPUs @a task should be affine to.
|
*
|
* @return Zero is returned upon success. Otherwise:
|
*
|
* - -EINVAL is returned if @a task is NULL but the caller is not a
|
* Xenomai task, or if @a task is non-NULL but not a valid task
|
* descriptor.
|
*
|
* - -EINVAL is returned if @a cpus contains no processors that are
|
* currently physically on the system and permitted to the process
|
* according to any restrictions that may be imposed by the "cpuset"
|
* mechanism described in cpuset(7).
|
*
|
* @apitags{mode-unrestricted, switch-secondary}
|
*
|
* @note The caller must be an Alchemy task if @a task is NULL.
|
*/
|
int rt_task_set_affinity(RT_TASK *task, const cpu_set_t *cpus)
|
{
|
struct alchemy_task *tcb;
|
struct service svc;
|
int ret;
|
|
CANCEL_DEFER(svc);
|
|
tcb = get_alchemy_task_or_self(task, &ret);
|
if (tcb == NULL)
|
goto out;
|
|
tcb->affinity = *cpus;
|
|
ret = sched_setaffinity(threadobj_get_pid(&tcb->thobj),
|
sizeof(tcb->affinity), &tcb->affinity);
|
if (ret)
|
ret = -errno;
|
|
put_alchemy_task(tcb);
|
out:
|
CANCEL_RESTORE(svc);
|
|
return ret;
|
}
|
|
/**
|
* @fn int rt_task_start(RT_TASK *task, void (*entry)(void *arg), void *arg)
|
* @brief Start a real-time task.
|
*
|
* This call starts execution of a task previously created by
|
* rt_task_create(). This service causes the started task to leave the
|
* initial dormant state.
|
*
|
* @param task The task descriptor.
|
*
|
* @param entry The address of the task entry point.
|
*
|
* @param arg A user-defined opaque argument @a entry will receive.
|
*
|
* @return Zero is returned upon success. Otherwise:
|
*
|
* - -EINVAL is returned if @a task is not a valid task descriptor.
|
*
|
* @apitags{mode-unrestricted, switch-primary}
|
*
|
* @note Starting an already started task leads to a nop, returning a
|
* success status.
|
*/
|
int rt_task_start(RT_TASK *task,
|
void (*entry)(void *arg),
|
void *arg)
|
{
|
struct alchemy_task *tcb;
|
struct service svc;
|
int ret;
|
|
CANCEL_DEFER(svc);
|
|
tcb = get_alchemy_task(task, &ret);
|
if (tcb == NULL)
|
goto out;
|
|
tcb->entry = entry;
|
tcb->arg = arg;
|
ret = threadobj_start(&tcb->thobj);
|
if (ret == -EIDRM)
|
/*
|
* The started thread has run then exited, tcb->thobj
|
* is stale: don't touch it anymore.
|
*/
|
ret = 0;
|
else
|
put_alchemy_task(tcb);
|
out:
|
CANCEL_DEFER(svc);
|
|
return ret;
|
}
|
|
/**
|
* @fn int rt_task_shadow(RT_TASK *task, const char *name, int prio, int mode)
|
* @brief Turn caller into a real-time task.
|
*
|
* Set the calling thread personality to the Alchemy API, enabling the
|
* full set of Alchemy services. Upon success, the caller is no more a
|
* regular POSIX thread, but a Xenomai-extended thread.
|
*
|
* If @a prio is non-zero, the new task moves to Xenomai's real-time
|
* FIFO scheduling class, aka SCHED_FIFO. If @a prio is zero, the task
|
* moves to the regular SCHED_OTHER class.
|
*
|
* Running Xenomai tasks with zero priority is useful for running non
|
* real-time processes which may invoke blocking real-time services,
|
* such as pending on a semaphore, reading from a message queue or a
|
* buffer, and so on.
|
*
|
* @param task If non-NULL, the address of a task descriptor which can
|
* be later used to identify uniquely the task, upon success of this
|
* call. If NULL, no descriptor is returned.
|
*
|
* @param name An ASCII string standing for the symbolic name of the
|
* task. When non-NULL and non-empty, a copy of this string is
|
* used for indexing the task into the object registry.
|
*
|
* @param prio The base priority of the task. This value must be in
|
* the [0 .. 99] range, where 0 is the lowest effective priority.
|
*
|
* @param mode The task shadowing mode. The following flags can be
|
* OR'ed into this bitmask:
|
*
|
* - T_LOCK causes the current task to lock the scheduler before
|
* returning to the caller, preventing all further involuntary task
|
* switches on the current CPU. A call to rt_task_set_mode() from the
|
* current task is required to drop this lock.
|
*
|
* - When running over the Cobalt core, T_WARNSW causes the SIGDEBUG
|
* signal to be sent to the current task whenever it switches to the
|
* secondary mode. This feature is useful to detect unwanted
|
* migrations to the Linux domain. This flag has no effect over the
|
* Mercury core.
|
*
|
* @return Zero is returned upon success. Otherwise:
|
*
|
* - -EINVAL is returned if @a prio is invalid.
|
*
|
* - -ENOMEM is returned if the system fails to get memory from the
|
* main heap in order to create the task extension.
|
*
|
* - -EEXIST is returned if the @a name is conflicting with an already
|
* registered task.
|
*
|
* - -EBUSY is returned if the caller is not a regular POSIX thread.
|
*
|
* - -EPERM is returned if this service was called from an invalid
|
* context, e.g. interrupt handler.
|
*
|
* @apitags{pthread-only, switch-secondary, switch-primary}
|
*
|
* @sideeffect Over Cobalt, if the caller is a plain POSIX thread, it
|
* is turned into a Xenomai _shadow_ thread, with full access to all
|
* Cobalt services. The caller always returns from this service in
|
* primary mode.
|
*
|
* @note Tasks can be referred to from multiple processes which all
|
* belong to the same Xenomai session.
|
*/
|
int rt_task_shadow(RT_TASK *task, const char *name, int prio, int mode)
|
{
|
struct threadobj *current = threadobj_current();
|
struct sched_param_ex param_ex;
|
struct alchemy_task *tcb;
|
struct service svc;
|
int policy, ret;
|
pthread_t self;
|
|
if (mode & ~(T_LOCK | T_WARNSW))
|
return -EINVAL;
|
|
CANCEL_DEFER(svc);
|
|
/*
|
* This is ok to overlay the default TCB for the main thread
|
* assigned by Copperplate at init, but it is not to
|
* over-shadow a Xenomai thread. A valid TCB pointer with a
|
* zero magic identifies the default main TCB.
|
*/
|
if (current && threadobj_get_magic(current))
|
return -EBUSY;
|
|
/*
|
* Over Cobalt, the following call turns the current context
|
* into a dual-kernel thread. Do this early, since this will
|
* be required next for creating the TCB and running the
|
* prologue code (i.e. real-time mutexes and monitors are
|
* locked there).
|
*/
|
self = pthread_self();
|
policy = prio ? SCHED_FIFO : SCHED_OTHER;
|
param_ex.sched_priority = prio;
|
ret = __bt(copperplate_renice_local_thread(self, policy, ¶m_ex));
|
if (ret)
|
goto out;
|
|
ret = create_tcb(&tcb, task, name, prio, mode);
|
if (ret)
|
goto out;
|
|
CANCEL_RESTORE(svc);
|
|
if (task)
|
task->thread = self;
|
|
ret = threadobj_shadow(&tcb->thobj, tcb->name);
|
if (ret)
|
goto undo;
|
|
CANCEL_DEFER(svc);
|
|
ret = task_prologue_2(tcb);
|
if (ret)
|
goto undo;
|
out:
|
CANCEL_RESTORE(svc);
|
|
return ret;
|
undo:
|
delete_tcb(tcb);
|
goto out;
|
}
|
|
/**
|
* @fn int rt_task_set_periodic(RT_TASK *task, RTIME idate, RTIME period)
|
* @brief Make a real-time task periodic.
|
*
|
* Make a task periodic by programing its first release point and its
|
* period in the processor time line. @a task should then call
|
* rt_task_wait_period() to sleep until the next periodic release
|
* point in the processor timeline is reached.
|
*
|
* @param task The task descriptor. If @a task is NULL, the current
|
* task is made periodic. @a task must belong the current process.
|
*
|
* @param idate The initial (absolute) date of the first release
|
* point, expressed in clock ticks (see note). If @a idate is equal
|
* to TM_NOW, the current system date is used.
|
*
|
* @param period The period of the task, expressed in clock ticks (see
|
* note). Passing TM_INFINITE stops the task's periodic timer if
|
* enabled, then returns successfully.
|
*
|
* @return Zero is returned upon success. Otherwise:
|
*
|
* - -EINVAL is returned if @a task is NULL but the caller is not a
|
* Xenomai task, or if @a task is non-NULL but not a valid task
|
* descriptor.
|
*
|
* - -ETIMEDOUT is returned if @a idate is different from TM_INFINITE
|
* and represents a date in the past.
|
*
|
* @apitags{mode-unrestricted, switch-primary}
|
*
|
* @note The caller must be an Alchemy task if @a task is NULL.
|
*
|
* @note Over Cobalt, -EINVAL is returned if @a period is
|
* different from TM_INFINITE but shorter than the user scheduling
|
* latency value for the target system, as displayed by
|
* /proc/xenomai/latency.
|
*
|
* @note The @a idate and @a period values are interpreted as a
|
* multiple of the Alchemy clock resolution (see
|
* --alchemy-clock-resolution option, defaults to 1 nanosecond).
|
*
|
* @attention Unlike its Xenomai 2.x counterpart,
|
* rt_task_set_periodic() will @b NOT block @a task until @a idate is
|
* reached. The first beat in the periodic timeline should be awaited
|
* for by a call to rt_task_wait_period().
|
*/
|
#ifndef DOXYGEN_CPP
|
CURRENT_IMPL(int, rt_task_set_periodic,
|
(RT_TASK *task, RTIME idate, RTIME period))
|
#else
|
int rt_task_set_periodic(RT_TASK *task, RTIME idate, RTIME period)
|
#endif
|
{
|
struct timespec its, pts, now;
|
struct alchemy_task *tcb;
|
struct service svc;
|
int ret;
|
|
CANCEL_DEFER(svc);
|
|
if (period == TM_INFINITE) {
|
pts.tv_sec = 0;
|
pts.tv_nsec = 0;
|
its = pts;
|
} else {
|
clockobj_ticks_to_timespec(&alchemy_clock, period, &pts);
|
if (idate == TM_NOW) {
|
__RT(clock_gettime(CLOCK_COPPERPLATE, &now));
|
timespec_add(&its, &now, &pts);
|
} else
|
/*
|
* idate is an absolute time specification
|
* already, so we want a direct conversion to
|
* timespec.
|
*/
|
clockobj_ticks_to_timespec(&alchemy_clock, idate, &its);
|
}
|
|
tcb = get_alchemy_task_or_self(task, &ret);
|
if (tcb == NULL)
|
goto out;
|
|
if (!threadobj_local_p(&tcb->thobj)) {
|
ret = -EINVAL;
|
goto out;
|
}
|
|
ret = threadobj_set_periodic(&tcb->thobj, &its, &pts);
|
put_alchemy_task(tcb);
|
out:
|
CANCEL_RESTORE(svc);
|
|
return ret;
|
}
|
|
/**
|
* @fn int rt_task_wait_period(unsigned long *overruns_r)
|
* @brief Wait for the next periodic release point.
|
*
|
* Delay the current task until the next periodic release point is
|
* reached. The periodic timer should have been previously started for
|
* @a task by a call to rt_task_set_periodic().
|
*
|
* @param overruns_r If non-NULL, @a overruns_r shall be a pointer to
|
* a memory location which will be written with the count of pending
|
* overruns. This value is written to only when rt_task_wait_period()
|
* returns -ETIMEDOUT or success. The memory location remains
|
* unmodified otherwise. If NULL, this count will not be returned.
|
*
|
* @return Zero is returned upon success. If @a overruns_r is
|
* non-NULL, zero is written to the pointed memory
|
* location. Otherwise:
|
*
|
* - -EWOULDBLOCK is returned if rt_task_set_periodic() was not called
|
* for the current task.
|
*
|
* - -EINTR is returned if rt_task_unblock() was called for the
|
* waiting task before the next periodic release point was reached. In
|
* this case, the overrun counter is also cleared.
|
*
|
* - -ETIMEDOUT is returned if a timer overrun occurred, which
|
* indicates that a previous release point was missed by the calling
|
* task. If @a overruns_r is non-NULL, the count of pending overruns
|
* is written to the pointed memory location.
|
*
|
* - -EPERM is returned if this service was called from an invalid
|
* context.
|
*
|
* @apitags{xthread-only, switch-primary}
|
*
|
* @note If the current release point has already been reached at the
|
* time of the call, the current task immediately returns from this
|
* service with no delay.
|
*/
|
int rt_task_wait_period(unsigned long *overruns_r)
|
{
|
if (!threadobj_current_p())
|
return -EPERM;
|
|
return threadobj_wait_period(overruns_r);
|
}
|
|
/**
|
* @fn int rt_task_sleep_until(RTIME date)
|
* @brief Delay the current real-time task (with absolute wakeup date).
|
*
|
* Delay the execution of the calling task until a given date is
|
* reached. The caller is put to sleep, and does not consume any CPU
|
* time in such a state.
|
*
|
* @param date An absolute date expressed in clock ticks, specifying a
|
* wakeup date (see note). As a special case, TM_INFINITE is an
|
* acceptable value that causes the caller to block indefinitely,
|
* until rt_task_unblock() is called against it. Otherwise, any wake
|
* up date in the past causes the task to return immediately.
|
*
|
* @return Zero is returned upon success. Otherwise:
|
*
|
* - -EINTR is returned if rt_task_unblock() was called for the
|
* current task.
|
*
|
* - -ETIMEDOUT is returned if @a date has already elapsed.
|
*
|
* - -EPERM is returned if this service was called from an invalid
|
* context.
|
*
|
* @apitags{xthread-only, switch-primary}
|
*
|
* @note The caller must be an Alchemy task if @a task is NULL.
|
*
|
* @note The @a date value is interpreted as a multiple of the Alchemy
|
* clock resolution (see --alchemy-clock-resolution option, defaults
|
* to 1 nanosecond).
|
*/
|
int rt_task_sleep_until(RTIME date)
|
{
|
struct timespec ts;
|
struct service svc;
|
ticks_t now;
|
|
if (!threadobj_current_p())
|
return -EPERM;
|
|
if (date == TM_INFINITE)
|
ts = zero_time;
|
else {
|
now = clockobj_get_time(&alchemy_clock);
|
if (date <= now)
|
return -ETIMEDOUT;
|
CANCEL_DEFER(svc);
|
clockobj_ticks_to_timespec(&alchemy_clock, date, &ts);
|
CANCEL_RESTORE(svc);
|
}
|
|
return threadobj_sleep(&ts);
|
}
|
|
/**
|
* @fn int rt_task_sleep(RTIME delay)
|
* @brief Delay the current real-time task (with relative delay).
|
*
|
* This routine is a variant of rt_task_sleep_until() accepting a
|
* relative timeout specification.
|
*
|
* @param delay A relative delay expressed in clock ticks (see
|
* note). A zero delay causes this service to return immediately to
|
* the caller with a success status.
|
*
|
* @return See rt_task_sleep_until().
|
*
|
* @apitags{xthread-only, switch-primary}
|
*
|
* @note The @a delay value is interpreted as a multiple of the
|
* Alchemy clock resolution (see --alchemy-clock-resolution option,
|
* defaults to 1 nanosecond).
|
*/
|
int rt_task_sleep(RTIME delay)
|
{
|
struct timespec ts;
|
struct service svc;
|
|
if (!threadobj_current_p())
|
return -EPERM;
|
|
if (delay == 0)
|
return 0;
|
|
CANCEL_DEFER(svc);
|
clockobj_ticks_to_timeout(&alchemy_clock, delay, &ts);
|
CANCEL_RESTORE(svc);
|
|
return threadobj_sleep(&ts);
|
}
|
|
/**
|
* @fn int rt_task_spawn(RT_TASK *task, const char *name, int stksize, int prio, int mode, void (*entry)(void *arg), void *arg)
|
* @brief Create and start a real-time task.
|
*
|
* This service spawns a task by combining calls to rt_task_create()
|
* and rt_task_start() for the new task.
|
*
|
* @param task The address of a task descriptor which can be later
|
* used to identify uniquely the created object, upon success of this
|
* call.
|
*
|
* @param name An ASCII string standing for the symbolic name of the
|
* task. When non-NULL and non-empty, a copy of this string is
|
* used for indexing the created task into the object registry.
|
*
|
* @param stksize The size of the stack (in bytes) for the new
|
* task. If zero is passed, a system-dependent default size will be
|
* substituted.
|
*
|
* @param prio The base priority of the new task. This value must be
|
* in the [0 .. 99] range, where 0 is the lowest effective priority.
|
*
|
* @param mode The task creation mode. See rt_task_create().
|
*
|
* @param entry The address of the task entry point.
|
*
|
* @param arg A user-defined opaque argument @a entry will receive.
|
*
|
* @return See rt_task_create().
|
*
|
* @apitags{mode-unrestricted, switch-secondary}
|
*
|
* @sideeffect see rt_task_create().
|
*/
|
#ifndef DOXYGEN_CPP
|
CURRENT_IMPL(int, rt_task_spawn, (RT_TASK *task, const char *name,
|
int stksize, int prio, int mode,
|
void (*entry)(void *arg),
|
void *arg))
|
#else
|
int rt_task_spawn(RT_TASK *task, const char *name,
|
int stksize, int prio, int mode,
|
void (*entry)(void *arg),
|
void *arg)
|
#endif
|
{
|
int ret;
|
|
ret = rt_task_create(task, name, stksize, prio, mode);
|
if (ret)
|
return ret;
|
|
return rt_task_start(task, entry, arg);
|
}
|
|
/**
|
* @fn int rt_task_same(RT_TASK *task1, RT_TASK *task2)
|
* @brief Compare real-time task descriptors.
|
*
|
* This predicate returns true if @a task1 and @a task2 refer to the
|
* same task.
|
*
|
* @param task1 First task descriptor to compare.
|
*
|
* @param task2 Second task descriptor to compare.
|
*
|
* @return A non-zero value is returned if both descriptors refer to
|
* the same task, zero otherwise.
|
*
|
* @apitags{unrestricted}
|
*/
|
int rt_task_same(RT_TASK *task1, RT_TASK *task2)
|
{
|
return task1->handle == task2->handle;
|
}
|
|
/**
|
* @fn int rt_task_suspend(RT_TASK *task)
|
* @brief Suspend a real-time task.
|
*
|
* Forcibly suspend the execution of a task. This task will not be
|
* eligible for scheduling until it is explicitly resumed by a call to
|
* rt_task_resume(). In other words, the suspended state caused by a
|
* call to rt_task_suspend() is cumulative with respect to the delayed
|
* and blocked states caused by other services, and is managed
|
* separately from them.
|
*
|
* A nesting count is maintained so that rt_task_suspend() and
|
* rt_task_resume() must be used in pairs.
|
*
|
* Receiving a Linux signal causes the suspended task to resume
|
* immediately.
|
*
|
* @param task The task descriptor. If @a task is NULL, the current
|
* task is suspended.
|
*
|
* @return Zero is returned upon success. Otherwise:
|
*
|
* - -EINVAL is returned if @a task is NULL but the caller is not a
|
* Xenomai task, or if @a task is non-NULL but not a valid task
|
* descriptor.
|
*
|
* - -EINTR is returned if a Linux signal has been received by the
|
* caller if suspended.
|
*
|
* - -EPERM is returned if @a task is NULL and this service was called
|
* from an invalid context.
|
*
|
* @apitags{mode-unrestricted, switch-primary}
|
*
|
* @note The caller must be an Alchemy task if @a task is NULL.
|
*
|
* @note Blocked and suspended task states are cumulative. Therefore,
|
* suspending a task currently waiting on a synchronization object
|
* (e.g. semaphore, queue) holds its execution until it is resumed,
|
* despite the awaited resource may have been acquired, or a timeout
|
* has elapsed in the meantime.
|
*/
|
int rt_task_suspend(RT_TASK *task)
|
{
|
struct alchemy_task *tcb;
|
struct service svc;
|
int ret;
|
|
CANCEL_DEFER(svc);
|
|
tcb = get_alchemy_task_or_self(task, &ret);
|
if (tcb == NULL)
|
goto out;
|
|
if (tcb->suspends++ == 0)
|
ret = threadobj_suspend(&tcb->thobj);
|
|
put_alchemy_task(tcb);
|
out:
|
CANCEL_RESTORE(svc);
|
|
return ret;
|
}
|
|
/**
|
* @fn int rt_task_resume(RT_TASK *task)
|
* @brief Resume a real-time task.
|
*
|
* Forcibly resume the execution of a task which was previously
|
* suspended by a call to rt_task_suspend(), if the suspend nesting
|
* count decrements to zero.
|
*
|
* @param task The task descriptor.
|
*
|
* @return Zero is returned upon success. Otherwise:
|
*
|
* - -EINVAL is returned if @a task is not a valid task descriptor.
|
*
|
* @apitags{unrestricted, switch-primary}
|
*
|
* @note Blocked and suspended task states are cumulative. Therefore,
|
* resuming a task currently waiting on a synchronization object
|
* (e.g. semaphore, queue) does not make it eligible for scheduling
|
* until the awaited resource is eventually acquired, or a timeout
|
* elapses.
|
*/
|
int rt_task_resume(RT_TASK *task)
|
{
|
struct alchemy_task *tcb;
|
struct service svc;
|
int ret = 0;
|
|
CANCEL_DEFER(svc);
|
|
tcb = get_alchemy_task(task, &ret);
|
if (tcb == NULL)
|
goto out;
|
|
if (tcb->suspends > 0 && --tcb->suspends == 0)
|
ret = threadobj_resume(&tcb->thobj);
|
|
put_alchemy_task(tcb);
|
out:
|
CANCEL_RESTORE(svc);
|
|
return ret;
|
}
|
|
/**
|
* @fn RT_TASK *rt_task_self(void)
|
* @brief Retrieve the current task descriptor.
|
*
|
* Return the address of the current Alchemy task descriptor.
|
*
|
* @return The address of the task descriptor referring to the current
|
* Alchemy task is returned upon success, or NULL if not called from a
|
* valid Alchemy task context.
|
*
|
* @apitags{xthread-only}
|
*/
|
RT_TASK *rt_task_self(void)
|
{
|
struct alchemy_task *tcb;
|
|
tcb = alchemy_task_current();
|
if (tcb == NULL)
|
return NULL;
|
|
return &tcb->self;
|
}
|
|
/**
|
* @fn int rt_task_set_priority(RT_TASK *task, int prio)
|
* @brief Change the base priority of a real-time task.
|
*
|
* The base priority of a task defines the relative importance of the
|
* work being done by each task, which gains conrol of the CPU
|
* accordingly.
|
*
|
* Changing the base priority of a task does not affect the priority
|
* boost the target task might have obtained as a consequence of a
|
* priority inheritance undergoing.
|
*
|
* @param task The task descriptor. If @a task is NULL, the priority
|
* of the current task is changed.
|
*
|
* @param prio The new priority. This value must range from [T_LOPRIO
|
* .. T_HIPRIO] (inclusive) where T_LOPRIO is the lowest effective
|
* priority.
|
*
|
* @return Zero is returned upon success. Otherwise:
|
*
|
* - -EINVAL is returned if @a task is not a valid task descriptor, or
|
* if @a prio is invalid.
|
*
|
* - -EPERM is returned if @a task is NULL and this service was called
|
* from an invalid context.
|
*
|
* @apitags{mode-unrestricted, switch-primary, switch-secondary}
|
*
|
* @note The caller must be an Alchemy task if @a task is NULL.
|
*
|
* @note Assigning the same priority to a running or ready task moves
|
* it to the end of its priority group, thus causing a manual
|
* round-robin.
|
*/
|
int rt_task_set_priority(RT_TASK *task, int prio)
|
{
|
struct sched_param_ex param_ex;
|
struct alchemy_task *tcb;
|
struct service svc;
|
int policy, ret;
|
|
ret = check_task_priority(prio);
|
if (ret)
|
return ret;
|
|
CANCEL_DEFER(svc);
|
|
tcb = get_alchemy_task_or_self(task, &ret);
|
if (tcb == NULL)
|
goto out;
|
|
policy = prio ? SCHED_FIFO : SCHED_OTHER;
|
param_ex.sched_priority = prio;
|
ret = threadobj_set_schedparam(&tcb->thobj, policy, ¶m_ex);
|
switch (ret) {
|
case -EIDRM:
|
ret = 0;
|
break;
|
default:
|
put_alchemy_task(tcb);
|
}
|
out:
|
CANCEL_RESTORE(svc);
|
|
return ret;
|
}
|
|
/**
|
* @fn int rt_task_yield(void)
|
* @brief Manual round-robin.
|
*
|
* Move the current task to the end of its priority group, so that the
|
* next equal-priority task in ready state is switched in.
|
*
|
* @return Zero is returned upon success. Otherwise:
|
*
|
* - -EPERM is returned if this service was called from an invalid
|
* context.
|
*
|
* @apitags{xthread-only, switch-primary}
|
*/
|
int rt_task_yield(void)
|
{
|
if (!threadobj_current_p())
|
return -EPERM;
|
|
threadobj_yield();
|
|
return 0;
|
}
|
|
/**
|
* @fn int rt_task_unblock(RT_TASK *task)
|
* @brief Unblock a real-time task.
|
*
|
* Break the task out of any wait it is currently in. This call
|
* clears all delay and/or resource wait condition for the target
|
* task.
|
*
|
* However, rt_task_unblock() does not resume a task which has been
|
* forcibly suspended by a previous call to rt_task_suspend(). If all
|
* suspensive conditions are gone, the task becomes eligible anew for
|
* scheduling.
|
*
|
* @param task The task descriptor.
|
*
|
* @return Zero is returned upon success. Otherwise:
|
*
|
* - -EINVAL is returned if @a task is not a valid task descriptor.
|
*
|
* @apitags{unrestricted, switch-primary}
|
*/
|
int rt_task_unblock(RT_TASK *task)
|
{
|
struct alchemy_task *tcb;
|
struct service svc;
|
int ret;
|
|
CANCEL_DEFER(svc);
|
|
tcb = get_alchemy_task(task, &ret);
|
if (tcb == NULL)
|
goto out;
|
|
ret = threadobj_unblock(&tcb->thobj);
|
put_alchemy_task(tcb);
|
out:
|
CANCEL_RESTORE(svc);
|
|
return ret;
|
}
|
|
/**
|
* @fn int rt_task_slice(RT_TASK *task, RTIME quantum)
|
* @brief Set a task's round-robin quantum.
|
*
|
* Set the time credit allotted to a task undergoing the round-robin
|
* scheduling. If @a quantum is non-zero, rt_task_slice() also refills
|
* the current quantum for the target task, otherwise, time-slicing is
|
* stopped for that task.
|
*
|
* In other words, rt_task_slice() should be used to toggle
|
* round-robin scheduling for an Alchemy task.
|
*
|
* @param task The task descriptor. If @a task is NULL, the time
|
* credit of the current task is changed. @a task must belong to the
|
* current process.
|
*
|
* @param quantum The round-robin quantum for the task expressed in
|
* clock ticks (see note).
|
*
|
* @return Zero is returned upon success. Otherwise:
|
*
|
* - -EINVAL is returned if @a task is not a valid task descriptor, or
|
* if @a prio is invalid.
|
*
|
* - -EPERM is returned if @a task is NULL and this service was called
|
* from an invalid context.
|
*
|
* @apitags{mode-unrestricted, switch-primary}
|
*
|
* @note The caller must be an Alchemy task if @a task is NULL.
|
*
|
* @note The @a quantum value is interpreted as a multiple of the
|
* Alchemy clock resolution (see --alchemy-clock-resolution option,
|
* defaults to 1 nanosecond).
|
*/
|
int rt_task_slice(RT_TASK *task, RTIME quantum)
|
{
|
struct sched_param_ex param_ex;
|
struct alchemy_task *tcb;
|
struct service svc;
|
int ret, policy;
|
|
CANCEL_DEFER(svc);
|
|
tcb = get_alchemy_task_or_self(task, &ret);
|
if (tcb == NULL)
|
goto out;
|
|
param_ex.sched_priority = threadobj_get_priority(&tcb->thobj);
|
if (quantum) {
|
struct timespec ts;
|
policy = SCHED_RR;
|
clockobj_ticks_to_timespec(&alchemy_clock, quantum, &ts);
|
param_ex.sched_rr_quantum.tv_sec = ts.tv_sec;
|
param_ex.sched_rr_quantum.tv_nsec = ts.tv_nsec;
|
} else {
|
policy = param_ex.sched_priority ? SCHED_FIFO : SCHED_OTHER;
|
}
|
|
ret = threadobj_set_schedparam(&tcb->thobj, policy, ¶m_ex);
|
switch (ret) {
|
case -EIDRM:
|
ret = 0;
|
break;
|
default:
|
put_alchemy_task(tcb);
|
}
|
out:
|
CANCEL_RESTORE(svc);
|
|
return ret;
|
}
|
|
/**
|
* @fn int rt_task_set_mode(int clrmask, int setmask, int *mode_r)
|
* @brief Change the current task mode.
|
*
|
* Each Alchemy task has a set of internal flags determining several
|
* operating conditions. rt_task_set_mode() takes a bitmask of mode
|
* bits to clear for disabling the corresponding modes for the current
|
* task, and another one to set for enabling them. The mode bits which
|
* were previously in effect before the change can be returned upon
|
* request.
|
*
|
* The following bits can be part of the bitmask:
|
*
|
* - T_LOCK causes the current task to lock the scheduler on the
|
* current CPU, preventing all further involuntary task switches on
|
* this CPU. Clearing this bit unlocks the scheduler.
|
*
|
* - Only when running over the Cobalt core:
|
*
|
* - T_WARNSW causes the SIGDEBUG signal to be sent to the current
|
* task whenever it switches to the secondary mode. This feature is
|
* useful to detect unwanted migrations to the Linux domain.
|
*
|
* - T_CONFORMING can be passed in @a setmask to switch the current
|
* Alchemy task to its preferred runtime mode. The only meaningful use
|
* of this switch is to force a real-time task back to primary
|
* mode (see note). Any other use leads to a nop.
|
*
|
* These two last flags have no effect over the Mercury core, and are
|
* simply ignored.
|
*
|
* @param clrmask A bitmask of mode bits to clear for the current
|
* task, before @a setmask is applied. Zero is an acceptable value
|
* which leads to a no-op.
|
*
|
* @param setmask A bitmask of mode bits to set for the current
|
* task. Zero is an acceptable value which leads to a no-op.
|
*
|
* @param mode_r If non-NULL, @a mode_r must be a pointer to a memory
|
* location which will be written upon success with the previous set
|
* of active mode bits. If NULL, the previous set of active mode bits
|
* will not be returned.
|
*
|
* @return Zero is returned upon success. Otherwise:
|
*
|
* - -EINVAL is returned if @a task is not a valid task descriptor, or
|
* if any bit from @a clrmask or @a setmask is invalid.
|
|
* - -EPERM is returned if this service was called from an invalid
|
* context.
|
*
|
* @apitags{xthread-only, switch-primary}
|
*
|
* @note The caller must be an Alchemy task.
|
*
|
* @note Forcing the task mode using the T_CONFORMING bit from user
|
* code is almost always wrong, since the Xenomai/cobalt core handles
|
* mode switches internally when/if required. Most often, manual mode
|
* switching from applications introduces useless overhead. This mode
|
* bit is part of the API only to cover rare use cases in middleware
|
* code based on the Alchemy interface.
|
*/
|
int rt_task_set_mode(int clrmask, int setmask, int *mode_r)
|
{
|
struct alchemy_task *tcb;
|
struct service svc;
|
int ret = 0;
|
|
if (threadobj_irq_p()) {
|
clrmask &= ~T_LOCK;
|
setmask &= ~T_LOCK;
|
return (clrmask | setmask) ? -EPERM : 0;
|
}
|
|
if (((clrmask | setmask) & ~(T_LOCK | T_WARNSW | T_CONFORMING)) != 0)
|
return -EINVAL;
|
|
CANCEL_DEFER(svc);
|
|
tcb = get_alchemy_task_or_self(NULL, &ret);
|
if (tcb == NULL)
|
goto out;
|
|
ret = threadobj_set_mode(clrmask, setmask, mode_r);
|
put_alchemy_task(tcb);
|
out:
|
CANCEL_RESTORE(svc);
|
|
return ret;
|
}
|
|
/**
|
* @fn int rt_task_inquire(RT_TASK *task, RT_TASK_INFO *info)
|
* @brief Retrieve information about a real-time task.
|
*
|
* Return various information about an Alchemy task. This service may
|
* also be used to probe for task existence.
|
*
|
* @param task The task descriptor. If @a task is NULL, the
|
* information about the current task is returned.
|
*
|
* @param info The address of a structure the task information will be
|
* written to. Passing NULL is valid, in which case the system is only
|
* probed for existence of the specified task.
|
*
|
* @return Zero is returned if the task exists. In addition, if @a
|
* info is non-NULL, it is filled in with task information.
|
*
|
* - -EINVAL is returned if @a task is not a valid task descriptor, or
|
* if @a prio is invalid.
|
*
|
* - -EPERM is returned if @a task is NULL and this service was called
|
* from an invalid context.
|
*
|
* @apitags{mode-unrestricted, switch-primary}
|
*
|
* @note The caller must be an Alchemy task if @a task is NULL.
|
*/
|
int rt_task_inquire(RT_TASK *task, RT_TASK_INFO *info)
|
{
|
struct alchemy_task *tcb;
|
struct service svc;
|
int ret = 0;
|
|
CANCEL_DEFER(svc);
|
|
tcb = get_alchemy_task_or_self(task, &ret);
|
if (tcb == NULL)
|
goto out;
|
|
ret = __bt(threadobj_stat(&tcb->thobj, &info->stat));
|
if (ret)
|
goto out;
|
|
strcpy(info->name, tcb->name);
|
info->prio = threadobj_get_priority(&tcb->thobj);
|
info->pid = threadobj_get_pid(&tcb->thobj);
|
|
put_alchemy_task(tcb);
|
out:
|
CANCEL_RESTORE(svc);
|
|
return ret;
|
}
|
|
|
/**
|
* @fn ssize_t rt_task_send(RT_TASK *task, RT_TASK_MCB *mcb_s, RT_TASK_MCB *mcb_r, RTIME timeout)
|
* @brief Send a message to a real-time task (with relative scalar timeout).
|
*
|
* This routine is a variant of rt_task_send_timed() accepting a
|
* relative timeout specification expressed as a scalar value.
|
*
|
* @param task The task descriptor.
|
*
|
* @param mcb_s The address of the message control block referring to
|
* the message to be sent.
|
*
|
* @param mcb_r The address of an optional message control block
|
* referring to the reply message area.
|
*
|
* @param timeout A delay expressed in clock ticks. Passing
|
* TM_INFINITE causes the caller to block indefinitely until a reply
|
* is received. Passing TM_NONBLOCK causes the service to return
|
* without blocking in case the recipient task is not waiting for
|
* messages at the time of the call.
|
*
|
* @apitags{xthread-only, switch-primary}
|
*/
|
|
/**
|
* @fn ssize_t rt_task_send_until(RT_TASK *task, RT_TASK_MCB *mcb_s, RT_TASK_MCB *mcb_r, RTIME abs_timeout)
|
* @brief Send a message to a real-time task (with absolute scalar timeout).
|
*
|
* This routine is a variant of rt_task_send_timed() accepting an
|
* absolute timeout specification expressed as a scalar value.
|
*
|
* @param task The task descriptor.
|
*
|
* @param mcb_s The address of the message control block referring to
|
* the message to be sent.
|
*
|
* @param mcb_r The address of an optional message control block
|
* referring to the reply message area.
|
*
|
* @param abs_timeout An absolute date expressed in clock ticks.
|
* Passing TM_INFINITE causes the caller to block indefinitely until
|
* a reply is received. Passing TM_NONBLOCK causes the service to
|
* return without blocking in case the recipient task is not waiting
|
* for messages at the time of the call.
|
*
|
* @apitags{xthread-only, switch-primary}
|
*/
|
|
/**
|
* @fn ssize_t rt_task_send_timed(RT_TASK *task, RT_TASK_MCB *mcb_s, RT_TASK_MCB *mcb_r, const struct timespec *abs_timeout)
|
* @brief Send a message to a real-time task.
|
*
|
* This service is part of the synchronous message passing support
|
* available to Alchemy tasks. The caller sends a variable-sized
|
* message to another task, waiting for the remote to receive the
|
* initial message by a call to rt_task_receive(), then reply to it
|
* using rt_task_reply().
|
*
|
* A basic message control block is used to store the location and
|
* size of the data area to send or retrieve upon reply, in addition
|
* to a user-defined operation code.
|
*
|
* @param task The task descriptor.
|
*
|
* @param mcb_s The address of the message control block referring to
|
* the message to be sent. The fields from this control block should
|
* be set as follows:
|
*
|
* - mcb_s->data should contain the address of the payload data to
|
* send to the remote task.
|
*
|
* - mcb_s->size should contain the size in bytes of the payload data
|
* pointed at by mcb_s->data. Zero is a legitimate value, and
|
* indicates that no payload data will be transferred. In the latter
|
* case, mcb_s->data will be ignored.
|
*
|
* - mcb_s->opcode is an opaque operation code carried during the
|
* message transfer, the caller can fill with any appropriate
|
* value. It will be made available "as is" to the remote task into
|
* the operation code field by the rt_task_receive() service.
|
*
|
* @param mcb_r The address of an optional message control block
|
* referring to the reply message area. If @a mcb_r is NULL and a
|
* reply is sent back by the remote task, the reply message will be
|
* discarded, and -ENOBUFS will be returned to the caller. When @a
|
* mcb_r is valid, the fields from this control block should be set as
|
* follows:
|
*
|
* - mcb_r->data should contain the address of a buffer large enough
|
* to collect the reply data from the remote task.
|
*
|
* - mcb_r->size should contain the size in bytes of the buffer space
|
* pointed at by mcb_r->data. If mcb_r->size is lower than the actual
|
* size of the reply message, no data copy takes place and -ENOBUFS is
|
* returned to the caller.
|
*
|
* Upon return, mcb_r->opcode will contain the status code sent back
|
* from the remote task using rt_task_reply(), or zero if unspecified.
|
*
|
* @param abs_timeout An absolute date expressed in seconds / nanoseconds,
|
* based on the Alchemy clock, specifying a time limit to wait for the
|
* recipient task to reply to the initial message. Passing NULL causes
|
* the caller to block indefinitely until a reply is received. Passing
|
* { .tv_sec = 0, .tv_nsec = 0 } causes the service to return without
|
* blocking in case the recipient task is not waiting for messages at
|
* the time of the call.
|
*
|
* @return A positive value is returned upon success, representing the
|
* length (in bytes) of the reply message returned by the remote
|
* task. Zero is a success status, meaning either that @a mcb_r was
|
* NULL on entry, or that no actual message was passed to the remote
|
* call to rt_task_reply(). Otherwise:
|
*
|
* - -EINVAL is returned if @a task is not a valid task descriptor.
|
*
|
* - -EPERM is returned if this service was called from an invalid
|
* context.
|
*
|
* - -ENOBUFS is returned if @a mcb_r does not point at a message area
|
* large enough to collect the remote task's reply. This includes the
|
* case where @a mcb_r is NULL on entry, despite the remote task
|
* attempts to send a reply message.
|
*
|
* - -EWOULDBLOCK is returned if @a abs_timeout is { .tv_sec = 0,
|
* .tv_nsec = 0 } and the recipient @a task is not currently waiting
|
* for a message on the rt_task_receive() service.
|
*
|
* - -EIDRM is returned if @a task has been deleted while waiting for
|
* a reply.
|
*
|
* - -EINTR is returned if rt_task_unblock() was called for the
|
* current task before any reply was received from the recipient @a
|
* task.
|
*
|
* @apitags{xthread-only, switch-primary}
|
*/
|
ssize_t rt_task_send_timed(RT_TASK *task,
|
RT_TASK_MCB *mcb_s, RT_TASK_MCB *mcb_r,
|
const struct timespec *abs_timeout)
|
{
|
void *rbufin = NULL, *rbufout = NULL;
|
struct alchemy_task_wait *wait;
|
struct threadobj *current;
|
struct alchemy_task *tcb;
|
struct syncstate syns;
|
struct service svc;
|
ssize_t ret;
|
int err;
|
|
current = threadobj_current();
|
if (current == NULL)
|
return -EPERM;
|
|
CANCEL_DEFER(svc);
|
|
tcb = find_alchemy_task(task, &err);
|
if (tcb == NULL) {
|
ret = err;
|
goto out;
|
}
|
|
ret = syncobj_lock(&tcb->sobj_msg, &syns);
|
if (ret)
|
goto out;
|
|
if (alchemy_poll_mode(abs_timeout)) {
|
if (!syncobj_count_drain(&tcb->sobj_msg)) {
|
ret = -EWOULDBLOCK;
|
goto done;
|
}
|
abs_timeout = NULL;
|
}
|
|
/* Get space for the reply. */
|
wait = threadobj_prepare_wait(struct alchemy_task_wait);
|
|
/*
|
* Compute the next flow identifier, making sure that we won't
|
* draw a null or negative value.
|
*/
|
if (++tcb->flowgen < 0)
|
tcb->flowgen = 1;
|
|
wait->request = *mcb_s;
|
/*
|
* Payloads exchanged with remote tasks have to go through the
|
* main heap.
|
*/
|
if (mcb_s->size > 0 && !threadobj_local_p(&tcb->thobj)) {
|
rbufin = xnmalloc(mcb_s->size);
|
if (rbufin == NULL) {
|
ret = -ENOMEM;
|
goto cleanup;
|
}
|
memcpy(rbufin, mcb_s->data, mcb_s->size);
|
wait->request.__dref = __moff(rbufin);
|
}
|
wait->request.flowid = tcb->flowgen;
|
if (mcb_r) {
|
wait->reply.size = mcb_r->size;
|
wait->reply.data = mcb_r->data;
|
if (mcb_r->size > 0 && !threadobj_local_p(&tcb->thobj)) {
|
rbufout = xnmalloc(mcb_r->size);
|
if (rbufout == NULL) {
|
ret = -ENOMEM;
|
goto cleanup;
|
}
|
wait->reply.__dref = __moff(rbufout);
|
}
|
} else {
|
wait->reply.data = NULL;
|
wait->reply.size = 0;
|
}
|
|
if (syncobj_count_drain(&tcb->sobj_msg))
|
syncobj_drain(&tcb->sobj_msg);
|
|
ret = syncobj_wait_grant(&tcb->sobj_msg, abs_timeout, &syns);
|
if (ret) {
|
threadobj_finish_wait();
|
if (ret == -EIDRM)
|
goto out;
|
goto done;
|
}
|
|
ret = wait->reply.size;
|
if (!threadobj_local_p(&tcb->thobj) && ret > 0 && mcb_r)
|
memcpy(mcb_r->data, rbufout, ret);
|
cleanup:
|
threadobj_finish_wait();
|
done:
|
syncobj_unlock(&tcb->sobj_msg, &syns);
|
out:
|
if (rbufin)
|
xnfree(rbufin);
|
if (rbufout)
|
xnfree(rbufout);
|
|
CANCEL_RESTORE(svc);
|
|
return ret;
|
}
|
|
/**
|
* @fn ssize_t rt_task_receive(RT_TASK_MCB *mcb_r, RTIME timeout)
|
* @brief Receive a message from a real-time task (with relative scalar timeout).
|
*
|
* This routine is a variant of rt_task_receive_timed() accepting a
|
* relative timeout specification expressed as a scalar value.
|
*
|
* @param mcb_r The address of a message control block referring to
|
* the receive message area.
|
*
|
* @param timeout A delay expressed in clock ticks. Passing
|
* TM_INFINITE causes the caller to block indefinitely until a remote
|
* task eventually sends a message.Passing TM_NONBLOCK causes the
|
* service to return immediately without waiting if no remote task is
|
* currently waiting for sending a message.
|
*
|
* @apitags{xthread-only, switch-primary}
|
*/
|
|
/**
|
* @fn ssize_t rt_task_receive_until(RT_TASK_MCB *mcb_r, RTIME abs_timeout)
|
* @brief Receive a message from a real-time task (with absolute scalar timeout).
|
*
|
* This routine is a variant of rt_task_receive_timed() accepting an
|
* absolute timeout specification expressed as a scalar value.
|
*
|
* @param mcb_r The address of a message control block referring to
|
* the receive message area.
|
*
|
* @param abs_timeout An absolute date expressed in clock ticks.
|
* Passing TM_INFINITE causes the caller to block indefinitely until
|
* a remote task eventually sends a message.Passing TM_NONBLOCK
|
* causes the service to return immediately without waiting if no
|
* remote task is currently waiting for sending a message.
|
*
|
* @apitags{xthread-only, switch-primary}
|
*/
|
|
/**
|
* @fn ssize_t rt_task_receive_timed(RT_TASK_MCB *mcb_r, const struct timespec *abs_timeout)
|
* @brief Receive a message from a real-time task.
|
*
|
* This service is part of the synchronous message passing support
|
* available to Alchemy tasks. The caller receives a variable-sized
|
* message from another task. The sender is blocked until the caller
|
* invokes rt_task_reply() to finish the transaction.
|
*
|
* A basic message control block is used to store the location and
|
* size of the data area to receive from the client, in addition to a
|
* user-defined operation code.
|
*
|
* @param mcb_r The address of a message control block referring to
|
* the receive message area. The fields from this control block should
|
* be set as follows:
|
*
|
* - mcb_r->data should contain the address of a buffer large enough
|
* to collect the data sent by the remote task;
|
*
|
* - mcb_r->size should contain the size in bytes of the buffer space
|
* pointed at by mcb_r->data. If mcb_r->size is lower than the actual
|
* size of the received message, no data copy takes place and -ENOBUFS
|
* is returned to the caller. See note.
|
*
|
* Upon return, mcb_r->opcode will contain the operation code sent
|
* from the remote task using rt_task_send().
|
*
|
* @param abs_timeout An absolute date expressed in seconds / nanoseconds,
|
* based on the Alchemy clock, specifying the time limit to wait for
|
* receiving a message. Passing NULL causes the caller to block
|
* indefinitely until a remote task eventually sends a message.
|
* Passing { .tv_sec = 0, .tv_nsec = 0 } causes the service to return
|
* immediately without waiting if no remote task is currently waiting
|
* for sending a message.
|
*
|
* @return A strictly positive value is returned upon success,
|
* representing a flow identifier for the opening transaction; this
|
* token should be passed to rt_task_reply(), in order to send back a
|
* reply to and unblock the remote task appropriately. Otherwise:
|
*
|
* - -EPERM is returned if this service was called from an invalid
|
* context.
|
*
|
* - -EINTR is returned if rt_task_unblock() was called for the
|
* current task before a message was received.
|
*
|
* - -ENOBUFS is returned if @a mcb_r does not point at a message area
|
* large enough to collect the remote task's message.
|
*
|
* - -EWOULDBLOCK is returned if @a abs_timeout is { .tv_sec = 0,
|
* .tv_nsec = 0 } and no remote task is currently waiting for sending
|
* a message to the caller.
|
*
|
* - -ETIMEDOUT is returned if no message was received within the @a
|
* timeout.
|
*
|
* @apitags{xthread-only, switch-primary}
|
*/
|
int rt_task_receive_timed(RT_TASK_MCB *mcb_r,
|
const struct timespec *abs_timeout)
|
{
|
struct alchemy_task_wait *wait;
|
struct alchemy_task *current;
|
struct threadobj *thobj;
|
struct syncstate syns;
|
struct service svc;
|
RT_TASK_MCB *mcb_s;
|
int ret;
|
|
current = alchemy_task_current();
|
if (current == NULL)
|
return -EPERM;
|
|
CANCEL_DEFER(svc);
|
|
ret = syncobj_lock(¤t->sobj_msg, &syns);
|
if (ret)
|
goto out;
|
|
while (!syncobj_grant_wait_p(¤t->sobj_msg)) {
|
if (alchemy_poll_mode(abs_timeout)) {
|
ret = -EWOULDBLOCK;
|
goto done;
|
}
|
ret = syncobj_wait_drain(¤t->sobj_msg, abs_timeout, &syns);
|
if (ret)
|
goto done;
|
}
|
|
thobj = syncobj_peek_grant(¤t->sobj_msg);
|
wait = threadobj_get_wait(thobj);
|
mcb_s = &wait->request;
|
|
if (mcb_s->size > mcb_r->size) {
|
ret = -ENOBUFS;
|
goto fixup;
|
}
|
|
if (mcb_s->size > 0) {
|
if (!threadobj_local_p(thobj))
|
memcpy(mcb_r->data, __mptr(mcb_s->__dref), mcb_s->size);
|
else
|
memcpy(mcb_r->data, mcb_s->data, mcb_s->size);
|
}
|
|
/* The flow identifier is always strictly positive. */
|
ret = mcb_s->flowid;
|
mcb_r->opcode = mcb_s->opcode;
|
fixup:
|
mcb_r->size = mcb_s->size;
|
done:
|
syncobj_unlock(¤t->sobj_msg, &syns);
|
out:
|
CANCEL_RESTORE(svc);
|
|
return ret;
|
}
|
|
/**
|
* @fn int rt_task_reply(int flowid, RT_TASK_MCB *mcb_s)
|
* @brief Reply to a remote task message.
|
*
|
* This service is part of the synchronous message passing support
|
* available to Alchemy tasks. The caller sends a variable-sized
|
* message back to a remote task, in response to this task's initial
|
* message received by a call to rt_task_receive(). As a consequence
|
* of calling rt_task_reply(), the remote task will be unblocked from
|
* the rt_task_send() service.
|
*
|
* A basic message control block is used to store the location and
|
* size of the data area to send back, in addition to a user-defined
|
* status code.
|
*
|
* @param flowid The flow identifier returned by a previous call to
|
* rt_task_receive() which uniquely identifies the current
|
* transaction.
|
*
|
* @param mcb_s The address of an optional message control block
|
* referring to the message to be sent back. If @a mcb_s is NULL, the
|
* remote will be unblocked without getting any reply data. When @a
|
* mcb_s is valid, the fields from this control block should be set as
|
* follows:
|
*
|
* - mcb_s->data should contain the address of the payload data to
|
* send to the remote task.
|
*
|
* - mcb_s->size should contain the size in bytes of the payload data
|
* pointed at by mcb_s->data. Zero is a legitimate value, and
|
* indicates that no payload data will be transferred. In the latter
|
* case, mcb_s->data will be ignored.
|
*
|
* - mcb_s->opcode is an opaque status code carried during the message
|
* transfer the caller can fill with any appropriate value. It will be
|
* made available "as is" to the remote task into the status code
|
* field by the rt_task_send() service. If @a mcb_s is NULL, Zero will
|
* be returned to the remote task into the status code field.
|
*
|
* @return Zero is returned upon success. Otherwise:
|
*
|
* - -EINVAL is returned if @a flowid is invalid.
|
*
|
* - -ENXIO is returned if @a flowid does not match the expected
|
* identifier returned from the latest call of the current task to
|
* rt_task_receive(), or if the remote task stopped waiting for the
|
* reply in the meantime (e.g. the remote could have been deleted or
|
* forcibly unblocked).
|
*
|
* - -ENOBUFS is returned if the reply data referred to by @a mcb_s is
|
* larger than the reply area mentioned by the remote task when
|
* calling rt_task_send(). In such a case, the remote task also
|
* receives -ENOBUFS on return from rt_task_send().
|
*
|
* - -EPERM is returned if this service was called from an invalid
|
* context.
|
*
|
* @apitags{xthread-only, switch-primary}
|
*/
|
int rt_task_reply(int flowid, RT_TASK_MCB *mcb_s)
|
{
|
struct alchemy_task_wait *wait = NULL;
|
struct alchemy_task *current;
|
struct threadobj *thobj;
|
struct syncstate syns;
|
struct service svc;
|
RT_TASK_MCB *mcb_r;
|
size_t size;
|
int ret;
|
|
current = alchemy_task_current();
|
if (current == NULL)
|
return -EPERM;
|
|
if (flowid <= 0)
|
return -EINVAL;
|
|
CANCEL_DEFER(svc);
|
|
ret = __bt(syncobj_lock(¤t->sobj_msg, &syns));
|
if (ret)
|
goto out;
|
|
ret = -ENXIO;
|
if (!syncobj_grant_wait_p(¤t->sobj_msg))
|
goto done;
|
|
syncobj_for_each_grant_waiter(¤t->sobj_msg, thobj) {
|
wait = threadobj_get_wait(thobj);
|
if (wait->request.flowid == flowid)
|
goto reply;
|
}
|
goto done;
|
reply:
|
size = mcb_s ? mcb_s->size : 0;
|
syncobj_grant_to(¤t->sobj_msg, thobj);
|
mcb_r = &wait->reply;
|
|
/*
|
* NOTE: sending back a NULL or zero-length reply is perfectly
|
* valid; it just means to unblock the client without passing
|
* it back any reply data. Sending a response larger than what
|
* the client expects is invalid.
|
*/
|
if (mcb_r->size < size) {
|
ret = -ENOBUFS; /* Client will get this too. */
|
mcb_r->size = -ENOBUFS;
|
} else {
|
ret = 0;
|
mcb_r->size = size;
|
if (size > 0) {
|
if (!threadobj_local_p(thobj))
|
memcpy(__mptr(mcb_r->__dref), mcb_s->data, size);
|
else
|
memcpy(mcb_r->data, mcb_s->data, size);
|
}
|
}
|
|
mcb_r->flowid = flowid;
|
mcb_r->opcode = mcb_s ? mcb_s->opcode : 0;
|
done:
|
syncobj_unlock(¤t->sobj_msg, &syns);
|
out:
|
CANCEL_RESTORE(svc);
|
|
return ret;
|
}
|
|
/**
|
* @fn int rt_task_bind(RT_TASK *task, const char *name, RTIME timeout)
|
* @brief Bind to a task.
|
*
|
* This routine creates a new descriptor to refer to an existing
|
* Alchemy task identified by its symbolic name. If the object does
|
* not exist on entry, the caller may block until a task of the given
|
* name is created.
|
*
|
* @param task The address of a task descriptor filled in by the
|
* operation. Contents of this memory is undefined upon failure.
|
*
|
* @param name A valid NULL-terminated name which identifies the task
|
* to bind to. This string should match the object name argument
|
* passed to rt_task_create(), or rt_task_shadow().
|
*
|
* @param timeout The number of clock ticks to wait for the
|
* registration to occur (see note). Passing TM_INFINITE causes the
|
* caller to block indefinitely until the object is
|
* registered. Passing TM_NONBLOCK causes the service to return
|
* immediately without waiting if the object is not registered on
|
* entry.
|
*
|
* @return Zero is returned upon success. Otherwise:
|
*
|
* - -EINTR is returned if rt_task_unblock() was called for the
|
* current task before the retrieval has completed.
|
*
|
* - -EWOULDBLOCK is returned if @a timeout is equal to TM_NONBLOCK
|
* and the searched object is not registered on entry.
|
*
|
* - -ETIMEDOUT is returned if the object cannot be retrieved within
|
* the specified amount of time.
|
*
|
* - -EPERM is returned if this service should block, but was not
|
* called from a Xenomai thread.
|
*
|
* @apitags{xthread-nowait, switch-primary}
|
*
|
* @note The @a timeout value is interpreted as a multiple of the
|
* Alchemy clock resolution (see --alchemy-clock-resolution option,
|
* defaults to 1 nanosecond).
|
*/
|
int rt_task_bind(RT_TASK *task,
|
const char *name, RTIME timeout)
|
{
|
return alchemy_bind_object(name,
|
&alchemy_task_table,
|
timeout,
|
offsetof(struct alchemy_task, cobj),
|
&task->handle);
|
}
|
|
/**
|
* @fn int rt_task_unbind(RT_TASK *task)
|
* @brief Unbind from a task.
|
*
|
* @param task The task descriptor.
|
*
|
* This routine releases a previous binding to an Alchemy task. After
|
* this call has returned, the descriptor is no more valid for
|
* referencing this object.
|
*
|
* @apitags{thread-unrestricted}
|
*/
|
int rt_task_unbind(RT_TASK *task)
|
{
|
*task = NO_ALCHEMY_TASK;
|
return 0;
|
}
|
|
/** @} */
|