417 lines
12 KiB
C
417 lines
12 KiB
C
/*
|
|
* Copyright (c) 1997-2015 Wind River Systems, Inc.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* 1) Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
*
|
|
* 2) Redistributions in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
* and/or other materials provided with the distribution.
|
|
*
|
|
* 3) Neither the name of Wind River Systems nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software without
|
|
* specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
/**
|
|
* @file
|
|
* @brief mutex kernel services
|
|
*
|
|
* This module contains routines for handling mutex locking and unlocking. It
|
|
* also includes routines that force the release of mutex objects when a task
|
|
* is aborted or unloaded.
|
|
*
|
|
* Mutexes implement a priority inheritance algorithm that boosts the priority
|
|
* level of the owning task to match the priority level of the highest priority
|
|
* task waiting on the mutex.
|
|
*
|
|
* Each mutex that contributes to priority inheritance must be released in the
|
|
* reverse order in which is was acquired. Furthermore each subsequent mutex
|
|
* that contributes to raising the owning task's priority level must be acquired
|
|
* at a point after the most recent "bumping" of the priority level.
|
|
*
|
|
* For example, if task A has two mutexes contributing to the raising of its
|
|
* priority level, the second mutex M2 must be acquired by task A after task
|
|
* A's priority level was bumped due to owning the first mutex M1. When
|
|
* releasing the mutex, task A must release M2 before it releases M1. Failure
|
|
* to follow this nested model may result in tasks running at unexpected priority
|
|
* levels (too high, or too low).
|
|
*/
|
|
|
|
#include <microkernel.h>
|
|
#include <micro_private.h>
|
|
#include <nano_private.h>
|
|
|
|
/**
|
|
* @brief Reply to a mutex lock request.
|
|
*
|
|
* This routine replies to a mutex lock request. This will occur if either
|
|
* the waiting task times out or acquires the mutex lock.
|
|
*
|
|
* @param A k_args
|
|
*
|
|
* @return N/A
|
|
*/
|
|
void _k_mutex_lock_reply(
|
|
struct k_args *A /* pointer to mutex lock reply request arguments */
|
|
)
|
|
{
|
|
#ifdef CONFIG_SYS_CLOCK_EXISTS
|
|
struct _k_mutex_struct *Mutex; /* pointer to internal mutex structure */
|
|
struct k_args *PrioChanger; /* used to change a task's priority level */
|
|
struct k_args *FirstWaiter; /* pointer to first task in wait queue */
|
|
kpriority_t newPriority; /* priority level to which to drop */
|
|
int MutexId; /* mutex ID obtained from request args */
|
|
|
|
if (A->Time.timer) {
|
|
FREETIMER(A->Time.timer);
|
|
}
|
|
|
|
if (A->Comm == _K_SVC_MUTEX_LOCK_REPLY_TIMEOUT) {/* Timeout case */
|
|
|
|
REMOVE_ELM(A);
|
|
A->Time.rcode = RC_TIME;
|
|
|
|
MutexId = A->Args.l1.mutex;
|
|
Mutex = (struct _k_mutex_struct *)MutexId;
|
|
|
|
FirstWaiter = Mutex->Waiters;
|
|
|
|
/*
|
|
* When timing out, there are two cases to consider.
|
|
* 1. There are no waiting tasks.
|
|
* - As there are no waiting tasks, this mutex is no longer
|
|
* involved in priority inheritance. It's current priority
|
|
* level should be dropped (if needed) to the original
|
|
* priority level.
|
|
* 2. There is at least one waiting task in a priority ordered
|
|
* list.
|
|
* - Depending upon the the priority level of the first
|
|
* waiting task, the owner task's original priority and
|
|
* the ceiling priority, the owner's priority level may
|
|
* be dropped but not necessarily to the original priority
|
|
* level.
|
|
*/
|
|
|
|
newPriority = Mutex->OwnerOriginalPrio;
|
|
|
|
if (FirstWaiter != NULL) {
|
|
newPriority = (FirstWaiter->Prio < newPriority)
|
|
? FirstWaiter->Prio
|
|
: newPriority;
|
|
newPriority = (newPriority > CONFIG_PRIORITY_CEILING)
|
|
? newPriority
|
|
: CONFIG_PRIORITY_CEILING;
|
|
}
|
|
|
|
if (Mutex->OwnerCurrentPrio != newPriority) {
|
|
GETARGS(PrioChanger);
|
|
PrioChanger->alloc = true;
|
|
PrioChanger->Comm = _K_SVC_TASK_PRIORITY_SET;
|
|
PrioChanger->Prio = newPriority;
|
|
PrioChanger->Args.g1.task = Mutex->Owner;
|
|
PrioChanger->Args.g1.prio = newPriority;
|
|
SENDARGS(PrioChanger);
|
|
Mutex->OwnerCurrentPrio = newPriority;
|
|
}
|
|
} else {/* LOCK_RPL: Reply case */
|
|
A->Time.rcode = RC_OK;
|
|
}
|
|
#else
|
|
/* LOCK_RPL: Reply case */
|
|
A->Time.rcode = RC_OK;
|
|
#endif
|
|
|
|
_k_state_bit_reset(A->Ctxt.proc, TF_LOCK);
|
|
}
|
|
|
|
/**
|
|
* @brief Reply to a mutex lock request with timeout.
|
|
*
|
|
* This routine replies to a mutex lock request. This will occur if either
|
|
* the waiting task times out or acquires the mutex lock.
|
|
*
|
|
* @param A Pointer to a k_args structure.
|
|
*
|
|
* @return N/A
|
|
*/
|
|
void _k_mutex_lock_reply_timeout(struct k_args *A)
|
|
{
|
|
_k_mutex_lock_reply(A);
|
|
}
|
|
|
|
/**
|
|
* @brief Process a mutex lock request
|
|
*
|
|
* This routine processes a mutex lock request (LOCK_REQ). If the mutex
|
|
* is already locked, and the timeout is non-zero then the priority inheritance
|
|
* algorithm may be applied to prevent priority inversion scenarios.
|
|
*
|
|
* @param A k_args
|
|
*
|
|
* @return N/A
|
|
*/
|
|
void _k_mutex_lock_request(struct k_args *A /* pointer to mutex lock
|
|
request arguments */
|
|
)
|
|
{
|
|
struct _k_mutex_struct *Mutex; /* pointer to internal mutex structure */
|
|
int MutexId; /* mutex ID obtained from lock request */
|
|
struct k_args *PrioBooster; /* used to change a task's priority level */
|
|
kpriority_t BoostedPrio; /* new "boosted" priority level */
|
|
|
|
MutexId = A->Args.l1.mutex;
|
|
|
|
|
|
Mutex = (struct _k_mutex_struct *)MutexId;
|
|
if (Mutex->Level == 0 || Mutex->Owner == A->Args.l1.task) {
|
|
/* The mutex is either unowned or this is a nested lock. */
|
|
#ifdef CONFIG_OBJECT_MONITOR
|
|
Mutex->Count++;
|
|
#endif
|
|
|
|
Mutex->Owner = A->Args.l1.task;
|
|
|
|
/*
|
|
* Assign the task's priority directly if the requesting
|
|
* task is on this node. This may be more recent than
|
|
* that stored in struct k_args.
|
|
*/
|
|
Mutex->OwnerCurrentPrio = _k_current_task->Prio;
|
|
|
|
/*
|
|
* Save the original priority when first acquiring the lock (but
|
|
* not on nested locks). The original priority level only
|
|
* reflects the priority level of the requesting task at the
|
|
* time the lock is acquired. Consequently, if the requesting
|
|
* task is already involved in priority inheritance, this
|
|
* original priority reflects its "boosted" priority.
|
|
*/
|
|
if (Mutex->Level == 0) {
|
|
Mutex->OwnerOriginalPrio = Mutex->OwnerCurrentPrio;
|
|
}
|
|
|
|
Mutex->Level++;
|
|
|
|
A->Time.rcode = RC_OK;
|
|
|
|
} else {
|
|
/* The mutex is owned by another task. */
|
|
#ifdef CONFIG_OBJECT_MONITOR
|
|
Mutex->Confl++;
|
|
#endif
|
|
|
|
if (likely(A->Time.ticks != TICKS_NONE)) {
|
|
/* A non-zero timeout was specified. */
|
|
/*
|
|
* The requesting task is on this node. Ensure
|
|
* the priority saved in the request is up to
|
|
* date.
|
|
*/
|
|
A->Ctxt.proc = _k_current_task;
|
|
A->Prio = _k_current_task->Prio;
|
|
_k_state_bit_set(_k_current_task, TF_LOCK);
|
|
/* Note: Mutex->Waiters is a priority sorted list */
|
|
INSERT_ELM(Mutex->Waiters, A);
|
|
#ifdef CONFIG_SYS_CLOCK_EXISTS
|
|
if (A->Time.ticks == TICKS_UNLIMITED) {
|
|
/* Request will not time out */
|
|
A->Time.timer = NULL;
|
|
} else {
|
|
/*
|
|
* Prepare to call _k_mutex_lock_reply() should
|
|
* the request time out.
|
|
*/
|
|
A->Comm = _K_SVC_MUTEX_LOCK_REPLY_TIMEOUT;
|
|
_k_timeout_alloc(A);
|
|
}
|
|
#endif
|
|
if (A->Prio < Mutex->OwnerCurrentPrio) {
|
|
/*
|
|
* The priority level of the owning task is less
|
|
* than that of the requesting task. Boost the
|
|
* priority level of the owning task to match
|
|
* the priority level of the requesting task.
|
|
* Note that the boosted priority level is
|
|
* limited to <K_PrioCeiling>.
|
|
*/
|
|
BoostedPrio = (A->Prio > CONFIG_PRIORITY_CEILING)
|
|
? A->Prio
|
|
: CONFIG_PRIORITY_CEILING;
|
|
if (BoostedPrio < Mutex->OwnerCurrentPrio) {
|
|
/* Boost the priority level */
|
|
GETARGS(PrioBooster);
|
|
|
|
PrioBooster->alloc = true;
|
|
PrioBooster->Comm = _K_SVC_TASK_PRIORITY_SET;
|
|
PrioBooster->Prio = BoostedPrio;
|
|
PrioBooster->Args.g1.task = Mutex->Owner;
|
|
PrioBooster->Args.g1.prio = BoostedPrio;
|
|
SENDARGS(PrioBooster);
|
|
Mutex->OwnerCurrentPrio = BoostedPrio;
|
|
}
|
|
}
|
|
} else {
|
|
/*
|
|
* ERROR. The mutex is locked by another task and
|
|
* this is an immediate lock request (timeout = 0).
|
|
*/
|
|
A->Time.rcode = RC_FAIL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Mutex lock kernel service
|
|
*
|
|
* This routine is the entry to the mutex lock kernel service.
|
|
*
|
|
* @param mutex Mutex object
|
|
* @param time Timeout value (in ticks)
|
|
*
|
|
* @return RC_OK on success, RC_FAIL on error, RC_TIME on timeout
|
|
*/
|
|
int _task_mutex_lock(
|
|
kmutex_t mutex, /* mutex to lock */
|
|
int32_t time /* max # of ticks to wait for mutex */
|
|
)
|
|
{
|
|
struct k_args A; /* argument packet */
|
|
|
|
A.Comm = _K_SVC_MUTEX_LOCK_REQUEST;
|
|
A.Time.ticks = time;
|
|
A.Args.l1.mutex = mutex;
|
|
A.Args.l1.task = _k_current_task->Ident;
|
|
KERNEL_ENTRY(&A);
|
|
return A.Time.rcode;
|
|
}
|
|
|
|
/**
|
|
* @brief Process a mutex unlock request
|
|
*
|
|
* This routine processes a mutex unlock request (UNLOCK). If the mutex
|
|
* was involved in priority inheritance, then it will change the priority level
|
|
* of the current owner to the priority level it had when it acquired the
|
|
* mutex.
|
|
*
|
|
* @param A k_args
|
|
*
|
|
* @return N/A
|
|
*/
|
|
void _k_mutex_unlock(struct k_args *A /* pointer to mutex unlock
|
|
request arguments */
|
|
)
|
|
{
|
|
struct _k_mutex_struct *Mutex; /* pointer internal mutex structure */
|
|
int MutexId; /* mutex ID obtained from unlock request */
|
|
struct k_args *PrioDowner; /* used to change a task's priority level */
|
|
|
|
MutexId = A->Args.l1.mutex;
|
|
Mutex = (struct _k_mutex_struct *)MutexId;
|
|
if (Mutex->Owner == A->Args.l1.task && --(Mutex->Level) == 0) {
|
|
/*
|
|
* The requesting task owns the mutex and all locks
|
|
* have been released.
|
|
*/
|
|
|
|
struct k_args *X;
|
|
|
|
#ifdef CONFIG_OBJECT_MONITOR
|
|
Mutex->Count++;
|
|
#endif
|
|
|
|
if (Mutex->OwnerCurrentPrio != Mutex->OwnerOriginalPrio) {
|
|
/*
|
|
* This mutex is involved in priority inheritance.
|
|
* Send a request to revert the priority level of
|
|
* the owning task back to its priority level when
|
|
* it first acquired the mutex.
|
|
*/
|
|
GETARGS(PrioDowner);
|
|
|
|
PrioDowner->alloc = true;
|
|
PrioDowner->Comm = _K_SVC_TASK_PRIORITY_SET;
|
|
PrioDowner->Prio = Mutex->OwnerOriginalPrio;
|
|
PrioDowner->Args.g1.task = Mutex->Owner;
|
|
PrioDowner->Args.g1.prio = Mutex->OwnerOriginalPrio;
|
|
SENDARGS(PrioDowner);
|
|
}
|
|
|
|
X = Mutex->Waiters;
|
|
if (X != NULL) {
|
|
/*
|
|
* At least one task was waiting for the mutex.
|
|
* Assign the new owner of the task to be the
|
|
* first in the queue.
|
|
*/
|
|
|
|
Mutex->Waiters = X->Forw;
|
|
Mutex->Owner = X->Args.l1.task;
|
|
Mutex->Level = 1;
|
|
Mutex->OwnerCurrentPrio = X->Prio;
|
|
Mutex->OwnerOriginalPrio = X->Prio;
|
|
|
|
#ifdef CONFIG_SYS_CLOCK_EXISTS
|
|
if (X->Time.timer) {
|
|
/*
|
|
* Trigger a call to _k_mutex_lock_reply()--it will
|
|
* send a reply with a return code of RC_OK.
|
|
*/
|
|
_k_timeout_cancel(X);
|
|
X->Comm = _K_SVC_MUTEX_LOCK_REPLY;
|
|
} else {
|
|
#endif
|
|
/*
|
|
* There is no timer to update.
|
|
* Set the return code.
|
|
*/
|
|
X->Time.rcode = RC_OK;
|
|
_k_state_bit_reset(X->Ctxt.proc, TF_LOCK);
|
|
#ifdef CONFIG_SYS_CLOCK_EXISTS
|
|
}
|
|
#endif
|
|
} else {
|
|
/* No task is waiting in the queue. */
|
|
Mutex->Owner = ANYTASK;
|
|
Mutex->Level = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Mutex unlock kernel service
|
|
*
|
|
* This routine is the entry to the mutex unlock kernel service.
|
|
*
|
|
* @param mutex Mutex
|
|
*
|
|
* @return N/A
|
|
*/
|
|
void _task_mutex_unlock(kmutex_t mutex /* mutex to unlock */
|
|
)
|
|
{
|
|
struct k_args A; /* argument packet */
|
|
|
|
A.Comm = _K_SVC_MUTEX_UNLOCK;
|
|
A.Args.l1.mutex = mutex;
|
|
A.Args.l1.task = _k_current_task->Ident;
|
|
KERNEL_ENTRY(&A);
|
|
}
|
|
|
|
|