blob: 944f0f625a710a050bee1fdadb56bb94c4df94a1 [file] [log] [blame]
/**
* Copyright (c) 2015 Runtime 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.
*/
#include "os/os.h"
#include "os/queue.h"
#include <assert.h>
TAILQ_HEAD(, os_task) g_os_run_list = TAILQ_HEAD_INITIALIZER(g_os_run_list);
TAILQ_HEAD(, os_task) g_os_sleep_list = TAILQ_HEAD_INITIALIZER(g_os_sleep_list);
struct os_task *g_current_task;
extern os_time_t g_os_time;
os_time_t g_os_last_ctx_sw_time;
/**
* os sched insert
*
* Insert a task into the scheduler list. This causes the task to be evaluated
* for running when os_sched is called.
*
* @param t Pointer to task to insert in run list
*
* @return int OS_OK: task was inserted into run list
* OS_EINVAL: Task was not in ready state.
*/
os_error_t
os_sched_insert(struct os_task *t)
{
struct os_task *entry;
os_sr_t sr;
os_error_t rc;
if (t->t_state != OS_TASK_READY) {
rc = OS_EINVAL;
goto err;
}
entry = NULL;
OS_ENTER_CRITICAL(sr);
TAILQ_FOREACH(entry, &g_os_run_list, t_os_list) {
if (t->t_prio < entry->t_prio) {
break;
}
}
if (entry) {
TAILQ_INSERT_BEFORE(entry, (struct os_task *) t, t_os_list);
} else {
TAILQ_INSERT_TAIL(&g_os_run_list, (struct os_task *) t, t_os_list);
}
OS_EXIT_CRITICAL(sr);
return (0);
err:
return (rc);
}
/**
* Walk all the active tasks currently being scheduled.
*
* @param walk_func The walk function to call for each task
* @param arg The argument to pass the walk function
*
* @return 0 on success, < 0 on abort.
*/
int
os_sched_walk(os_sched_walk_func_t walk_func, void *arg)
{
struct os_task *t;
os_sr_t sr;
int rc;
/* Go through tasks and fill out the info blocks
*/
OS_ENTER_CRITICAL(sr);
TAILQ_FOREACH(t, &g_os_run_list, t_os_list) {
rc = walk_func(t, arg);
if (rc != 0) {
goto done;
}
}
TAILQ_FOREACH(t, &g_os_sleep_list, t_os_list) {
rc = walk_func(t, arg);
if (rc != 0) {
goto done;
}
}
done:
OS_EXIT_CRITICAL(sr);
return (rc);
}
void
os_sched_ctx_sw_hook(struct os_task *next_t)
{
if (g_current_task == next_t) {
return;
}
next_t->t_ctx_sw_cnt++;
g_current_task->t_run_time += g_os_time - g_os_last_ctx_sw_time;
g_os_last_ctx_sw_time = g_os_time;
}
/**
* os sched get current task
*
* Returns the currently running task. Note that this task may or may not be
* the highest priority task ready to run.
*
*
* @return struct os_task*
*/
struct os_task *
os_sched_get_current_task(void)
{
return (g_current_task);
}
/**
* os sched set current task
*
* Sets the currently running task to 't'. Note that this function simply sets
* the global variable holding the currently running task. It does not perform
* a context switch or change the os run or sleep list.
*
* @param t Pointer to currently running task.
*/
void
os_sched_set_current_task(struct os_task *t)
{
g_current_task = t;
}
/**
* os sched
*
* Performs a context switch. When called, it will either find the highest
* priority task ready to run if next_t is NULL (i.e. the head of the os run
* list) or will schedule next_t as the task to run.
*
* @param next_t Task to run
* @param isr Flag denoting whether we are inside an isr (0:no, 1:yes).
*/
void
os_sched(struct os_task *next_t, int isr)
{
os_sr_t sr;
OS_ENTER_CRITICAL(sr);
if (!next_t) {
next_t = os_sched_next_task();
}
if (next_t != g_current_task) {
OS_EXIT_CRITICAL(sr);
if (isr) {
os_arch_ctx_sw_isr(next_t);
} else {
os_arch_ctx_sw(next_t);
}
} else {
OS_EXIT_CRITICAL(sr);
}
}
/**
* os sched sleep
*
* Removes the task from the run list and puts it on the sleep list.
*
* @param t Task to put to sleep
* @param nticks Number of ticks to put task to sleep
*
* @return int
*
* NOTE: must be called with interrupts disabled! This function does not call
* the scheduler
*/
int
os_sched_sleep(struct os_task *t, os_time_t nticks)
{
struct os_task *entry;
entry = NULL;
TAILQ_REMOVE(&g_os_run_list, t, t_os_list);
t->t_state = OS_TASK_SLEEP;
t->t_next_wakeup = os_time_get() + nticks;
if (nticks == OS_TIMEOUT_NEVER) {
t->t_flags |= OS_TASK_FLAG_NO_TIMEOUT;
TAILQ_INSERT_TAIL(&g_os_sleep_list, t, t_os_list);
} else {
TAILQ_FOREACH(entry, &g_os_sleep_list, t_os_list) {
if ((entry->t_flags & OS_TASK_FLAG_NO_TIMEOUT) ||
OS_TIME_TICK_GT(entry->t_next_wakeup, t->t_next_wakeup)) {
break;
}
}
if (entry) {
TAILQ_INSERT_BEFORE(entry, t, t_os_list);
} else {
TAILQ_INSERT_TAIL(&g_os_sleep_list, t, t_os_list);
}
}
return (0);
}
/**
* os sched wakeup
*
* Called to wake up a task. Waking up a task consists of setting the task state
* to READY and moving it from the sleep list to the run list.
*
* @param t Pointer to task to wake up.
*
* @return int
*
* NOTE: This function must be called with interrupts disabled.
*/
int
os_sched_wakeup(struct os_task *t)
{
/* Remove self from mutex list if waiting on one */
if (t->t_mutex) {
assert(!SLIST_EMPTY(&t->t_mutex->mu_head));
SLIST_REMOVE(&t->t_mutex->mu_head, t, os_task, t_obj_list);
SLIST_NEXT(t, t_obj_list) = NULL;
t->t_mutex = NULL;
}
/* Remove task from sleep list */
t->t_state = OS_TASK_READY;
t->t_next_wakeup = 0;
t->t_flags &= ~OS_TASK_FLAG_NO_TIMEOUT;
TAILQ_REMOVE(&g_os_sleep_list, t, t_os_list);
os_sched_insert(t);
return (0);
}
/**
* os sched os timer exp
*
* Called when the OS tick timer expires. Search the sleep list for any tasks
* that need waking up. This occurs when the current OS time exceeds the next
* wakeup time stored in the task. Any tasks that need waking up will be
* removed from the sleep list and added to the run list.
*
*/
void
os_sched_os_timer_exp(void)
{
struct os_task *t;
struct os_task *next;
os_time_t now;
os_sr_t sr;
now = os_time_get();
OS_ENTER_CRITICAL(sr);
/*
* Wakeup any tasks that have their sleep timer expired
*/
t = TAILQ_FIRST(&g_os_sleep_list);
while (t) {
/* If task waiting forever, do not check next wakeup time */
if (t->t_flags & OS_TASK_FLAG_NO_TIMEOUT) {
break;
}
next = TAILQ_NEXT(t, t_os_list);
if (OS_TIME_TICK_GEQ(now, t->t_next_wakeup)) {
os_sched_wakeup(t);
} else {
break;
}
t = next;
}
OS_EXIT_CRITICAL(sr);
}
/**
* os sched next task
*
* Returns the task that we should be running. This is the task at the head
* of the run list.
*
* NOTE: if you want to guarantee that the os run list does not change after
* calling this function you have to call it with interrupts disabled.
*
* @return struct os_task*
*/
struct os_task *
os_sched_next_task(void)
{
return (TAILQ_FIRST(&g_os_run_list));
}
/**
* os sched resort
*
* Resort a task that is in the ready list as its priority has
* changed. If the task is not in the ready state, there is
* nothing to do.
*
* @param t Pointer to task to insert back into ready to run
* list.
*
* NOTE: this function expects interrupts to be disabled so they
* are not disabled here.
*/
void
os_sched_resort(struct os_task *t)
{
if (t->t_state == OS_TASK_READY) {
TAILQ_REMOVE(&g_os_run_list, t, t_os_list);
os_sched_insert(t);
}
}