Linux Audio

Check our new training course

Loading...
/*
 * 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);
}