385 lines
11 KiB
C
385 lines
11 KiB
C
/*
|
|
* Copyright (c) 1997-2015 Wind River Systems, Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
/**
|
|
* @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->original_owner_priority;
|
|
|
|
if (FirstWaiter != NULL) {
|
|
newPriority = (FirstWaiter->priority < newPriority)
|
|
? FirstWaiter->priority
|
|
: newPriority;
|
|
newPriority = (newPriority > CONFIG_PRIORITY_CEILING)
|
|
? newPriority
|
|
: CONFIG_PRIORITY_CEILING;
|
|
}
|
|
|
|
if (Mutex->current_owner_priority != newPriority) {
|
|
GETARGS(PrioChanger);
|
|
PrioChanger->alloc = true;
|
|
PrioChanger->Comm = _K_SVC_TASK_PRIORITY_SET;
|
|
PrioChanger->priority = newPriority;
|
|
PrioChanger->args.g1.task = Mutex->owner;
|
|
PrioChanger->args.g1.prio = newPriority;
|
|
SENDARGS(PrioChanger);
|
|
Mutex->current_owner_priority = 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.task, 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 current owner's priority from the priority found
|
|
* in the current task's task object: the priority stored there
|
|
* may be more recent than the one stored in struct k_args.
|
|
*/
|
|
Mutex->current_owner_priority = _k_current_task->priority;
|
|
|
|
/*
|
|
* 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->original_owner_priority = Mutex->current_owner_priority;
|
|
}
|
|
|
|
Mutex->level++;
|
|
|
|
A->Time.rcode = RC_OK;
|
|
|
|
} else {
|
|
/* The mutex is owned by another task. */
|
|
#ifdef CONFIG_OBJECT_MONITOR
|
|
Mutex->num_conflicts++;
|
|
#endif
|
|
|
|
if (likely(A->Time.ticks != TICKS_NONE)) {
|
|
/*
|
|
* A non-zero timeout was specified. Ensure the
|
|
* priority saved in the request is up to date
|
|
*/
|
|
A->Ctxt.task = _k_current_task;
|
|
A->priority = _k_current_task->priority;
|
|
_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->priority < Mutex->current_owner_priority) {
|
|
/*
|
|
* 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->priority > CONFIG_PRIORITY_CEILING)
|
|
? A->priority
|
|
: CONFIG_PRIORITY_CEILING;
|
|
if (BoostedPrio < Mutex->current_owner_priority) {
|
|
/* Boost the priority level */
|
|
GETARGS(PrioBooster);
|
|
|
|
PrioBooster->alloc = true;
|
|
PrioBooster->Comm = _K_SVC_TASK_PRIORITY_SET;
|
|
PrioBooster->priority = BoostedPrio;
|
|
PrioBooster->args.g1.task = Mutex->owner;
|
|
PrioBooster->args.g1.prio = BoostedPrio;
|
|
SENDARGS(PrioBooster);
|
|
Mutex->current_owner_priority = BoostedPrio;
|
|
}
|
|
}
|
|
} else {
|
|
/*
|
|
* ERROR. The mutex is locked by another task and
|
|
* this is an immediate lock request (timeout = 0).
|
|
*/
|
|
A->Time.rcode = RC_FAIL;
|
|
}
|
|
}
|
|
}
|
|
|
|
int task_mutex_lock(kmutex_t mutex, int32_t timeout)
|
|
{
|
|
struct k_args A; /* argument packet */
|
|
|
|
A.Comm = _K_SVC_MUTEX_LOCK_REQUEST;
|
|
A.Time.ticks = timeout;
|
|
A.args.l1.mutex = mutex;
|
|
A.args.l1.task = _k_current_task->id;
|
|
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 pointer to mutex unlock request arguments
|
|
*
|
|
* @return N/A
|
|
*/
|
|
void _k_mutex_unlock(struct k_args *A)
|
|
{
|
|
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->current_owner_priority != Mutex->original_owner_priority) {
|
|
/*
|
|
* 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->priority = Mutex->original_owner_priority;
|
|
PrioDowner->args.g1.task = Mutex->owner;
|
|
PrioDowner->args.g1.prio = Mutex->original_owner_priority;
|
|
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->next;
|
|
Mutex->owner = X->args.l1.task;
|
|
Mutex->level = 1;
|
|
Mutex->current_owner_priority = X->priority;
|
|
Mutex->original_owner_priority = X->priority;
|
|
|
|
#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.task, 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 to unlock
|
|
*
|
|
* @return N/A
|
|
*/
|
|
void _task_mutex_unlock(kmutex_t mutex)
|
|
{
|
|
struct k_args A; /* argument packet */
|
|
|
|
A.Comm = _K_SVC_MUTEX_UNLOCK;
|
|
A.args.l1.mutex = mutex;
|
|
A.args.l1.task = _k_current_task->id;
|
|
KERNEL_ENTRY(&A);
|
|
}
|