blob: 8e8567989089d5edd26b51b31dd3b7dffcb34bf3 [file] [log] [blame]
/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
/* Task scheduling / events module for Chrome EC operating system */
#include <malloc.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "atomic.h"
#include "common.h"
#include "console.h"
#include "host_task.h"
#include "task.h"
#include "task_id.h"
#include "test_util.h"
#include "timer.h"
#define SIGNAL_INTERRUPT SIGUSR1
struct emu_task_t {
pthread_t thread;
pthread_cond_t resume;
uint32_t event;
timestamp_t wake_time;
uint8_t started;
};
struct task_args {
void (*routine)(void *);
void *d;
};
static struct emu_task_t tasks[TASK_ID_COUNT];
static pthread_cond_t scheduler_cond;
static pthread_mutex_t run_lock;
static task_id_t running_task_id;
static int task_started;
static sem_t interrupt_sem;
static pthread_mutex_t interrupt_lock;
static pthread_t interrupt_thread;
static int in_interrupt;
static int interrupt_disabled;
static void (*pending_isr)(void);
static int generator_sleeping;
static timestamp_t generator_sleep_deadline;
static int has_interrupt_generator = 1;
static __thread task_id_t my_task_id; /* thread local task id */
static void task_enable_all_tasks_callback(void);
#define TASK(n, r, d, s) void r(void *);
CONFIG_TASK_LIST
CONFIG_TEST_TASK_LIST
#undef TASK
/* Idle task */
void __idle(void *d)
{
while (1)
task_wait_event(-1);
}
void _run_test(void *d)
{
run_test();
}
#define TASK(n, r, d, s) {r, d},
struct task_args task_info[TASK_ID_COUNT] = {
{__idle, NULL},
CONFIG_TASK_LIST
CONFIG_TEST_TASK_LIST
{_run_test, NULL},
};
#undef TASK
#define TASK(n, r, d, s) #n,
static const char * const task_names[] = {
"<< idle >>",
CONFIG_TASK_LIST
CONFIG_TEST_TASK_LIST
"<< test runner >>",
};
#undef TASK
void task_pre_init(void)
{
/* Nothing */
}
int in_interrupt_context(void)
{
return !!in_interrupt;
}
void interrupt_disable(void)
{
pthread_mutex_lock(&interrupt_lock);
interrupt_disabled = 1;
pthread_mutex_unlock(&interrupt_lock);
}
void interrupt_enable(void)
{
pthread_mutex_lock(&interrupt_lock);
interrupt_disabled = 0;
pthread_mutex_unlock(&interrupt_lock);
}
static void _task_execute_isr(int sig)
{
in_interrupt = 1;
pending_isr();
sem_post(&interrupt_sem);
in_interrupt = 0;
}
void task_register_interrupt(void)
{
sem_init(&interrupt_sem, 0, 0);
signal(SIGNAL_INTERRUPT, _task_execute_isr);
}
void task_trigger_test_interrupt(void (*isr)(void))
{
pid_t main_pid;
pthread_mutex_lock(&interrupt_lock);
if (interrupt_disabled) {
pthread_mutex_unlock(&interrupt_lock);
return;
}
/* Suspend current task and excute ISR */
pending_isr = isr;
if (task_started) {
pthread_kill(tasks[running_task_id].thread, SIGNAL_INTERRUPT);
} else {
main_pid = getpid();
kill(main_pid, SIGNAL_INTERRUPT);
}
/* Wait for ISR to complete */
sem_wait(&interrupt_sem);
while (in_interrupt)
;
pending_isr = NULL;
pthread_mutex_unlock(&interrupt_lock);
}
void interrupt_generator_udelay(unsigned us)
{
generator_sleep_deadline.val = get_time().val + us;
generator_sleeping = 1;
while (get_time().val < generator_sleep_deadline.val)
;
generator_sleeping = 0;
}
const char *task_get_name(task_id_t tskid)
{
return task_names[tskid];
}
pthread_t task_get_thread(task_id_t tskid)
{
return tasks[tskid].thread;
}
uint32_t task_set_event(task_id_t tskid, uint32_t event, int wait)
{
tasks[tskid].event = event;
if (wait)
return task_wait_event(-1);
return 0;
}
uint32_t task_wait_event(int timeout_us)
{
int tid = task_get_current();
int ret;
pthread_mutex_lock(&interrupt_lock);
if (timeout_us > 0)
tasks[tid].wake_time.val = get_time().val + timeout_us;
/* Transfer control to scheduler */
pthread_cond_signal(&scheduler_cond);
pthread_cond_wait(&tasks[tid].resume, &run_lock);
/* Resume */
ret = tasks[tid].event;
tasks[tid].event = 0;
pthread_mutex_unlock(&interrupt_lock);
return ret;
}
uint32_t task_wait_event_mask(uint32_t event_mask, int timeout_us)
{
uint64_t deadline = get_time().val + timeout_us;
uint32_t events = 0;
int time_remaining_us = timeout_us;
/* Add the timer event to the mask so we can indicate a timeout */
event_mask |= TASK_EVENT_TIMER;
while (!(events & event_mask)) {
/* Collect events to re-post later */
events |= task_wait_event(time_remaining_us);
time_remaining_us = deadline - get_time().val;
if (timeout_us > 0 && time_remaining_us <= 0) {
/* Ensure we return a TIMER event if we timeout */
events |= TASK_EVENT_TIMER;
break;
}
}
/* Re-post any other events collected */
if (events & ~event_mask)
tasks[task_get_current()].event |= events & ~event_mask;
return events & event_mask;
}
void mutex_lock(struct mutex *mtx)
{
int value = 0;
int id = 1 << task_get_current();
mtx->waiters |= id;
do {
if (mtx->lock == 0) {
mtx->lock = 1;
value = 1;
}
if (!value)
task_wait_event_mask(TASK_EVENT_MUTEX, 0);
} while (!value);
mtx->waiters &= ~id;
}
void mutex_unlock(struct mutex *mtx)
{
int v;
mtx->lock = 0;
for (v = 31; v >= 0; --v)
if ((1ul << v) & mtx->waiters) {
mtx->waiters &= ~(1ul << v);
task_set_event(v, TASK_EVENT_MUTEX, 0);
break;
}
}
task_id_t task_get_current(void)
{
return my_task_id;
}
task_id_t task_get_running(void)
{
return running_task_id;
}
void wait_for_task_started(void)
{
int i, ok;
while (1) {
ok = 1;
for (i = 0; i < TASK_ID_COUNT - 1; ++i)
if (!tasks[i].started) {
msleep(10);
ok = 0;
break;
}
if (ok)
return;
}
}
static task_id_t task_get_next_wake(void)
{
int i;
timestamp_t min_time;
int which_task = TASK_ID_INVALID;
min_time.val = ~0ull;
for (i = TASK_ID_COUNT - 1; i >= 0; --i)
if (min_time.val >= tasks[i].wake_time.val) {
min_time.val = tasks[i].wake_time.val;
which_task = i;
}
return which_task;
}
static int fast_forward(void)
{
/*
* No task has event pending, and thus the next time we have an
* event to process must be either of:
* 1. Interrupt generator triggers an interrupt
* 2. The next wake alarm is reached
* So we should check whether an interrupt may happen, and fast
* forward to the nearest among:
* 1. When interrupt generator wakes up
* 2. When the next task wakes up
*/
int task_id = task_get_next_wake();
if (!has_interrupt_generator) {
if (task_id == TASK_ID_INVALID) {
return TASK_ID_IDLE;
} else {
force_time(tasks[task_id].wake_time);
return task_id;
}
}
if (!generator_sleeping)
return TASK_ID_IDLE;
if (task_id != TASK_ID_INVALID &&
tasks[task_id].thread != (pthread_t)NULL &&
tasks[task_id].wake_time.val < generator_sleep_deadline.val) {
force_time(tasks[task_id].wake_time);
return task_id;
} else {
force_time(generator_sleep_deadline);
return TASK_ID_IDLE;
}
}
int task_start_called(void)
{
return task_started;
}
void task_scheduler(void)
{
int i;
timestamp_t now;
task_started = 1;
while (1) {
now = get_time();
i = TASK_ID_COUNT - 1;
while (i >= 0) {
/*
* Only tasks with spawned threads are valid to be
* resumed.
*/
if (tasks[i].thread) {
if (tasks[i].event ||
now.val >= tasks[i].wake_time.val)
break;
}
--i;
}
if (i < 0)
i = fast_forward();
tasks[i].wake_time.val = ~0ull;
running_task_id = i;
tasks[i].started = 1;
pthread_cond_signal(&tasks[i].resume);
pthread_cond_wait(&scheduler_cond, &run_lock);
}
}
void *_task_start_impl(void *a)
{
long tid = (long)a;
struct task_args *arg = task_info + tid;
my_task_id = tid;
pthread_mutex_lock(&run_lock);
/* Wait for scheduler */
task_wait_event(1);
tasks[tid].event = 0;
/* Start the task routine */
(arg->routine)(arg->d);
/* Catch exited routine */
while (1)
task_wait_event(-1);
}
test_mockable void interrupt_generator(void)
{
has_interrupt_generator = 0;
}
void *_task_int_generator_start(void *d)
{
my_task_id = TASK_ID_INT_GEN;
interrupt_generator();
return NULL;
}
int task_start(void)
{
int i = TASK_ID_HOOKS;
pthread_mutex_init(&run_lock, NULL);
pthread_mutex_init(&interrupt_lock, NULL);
pthread_cond_init(&scheduler_cond, NULL);
pthread_mutex_lock(&run_lock);
/*
* Initialize the hooks task first. After its init, it will callback to
* enable the remaining tasks.
*/
tasks[i].event = TASK_EVENT_WAKE;
tasks[i].wake_time.val = ~0ull;
tasks[i].started = 0;
pthread_cond_init(&tasks[i].resume, NULL);
pthread_create(&tasks[i].thread, NULL, _task_start_impl,
(void *)(uintptr_t)i);
pthread_cond_wait(&scheduler_cond, &run_lock);
/*
* Interrupt lock is grabbed by the task which just started.
* Let's unlock it so the next task can be started.
*/
pthread_mutex_unlock(&interrupt_lock);
/*
* The hooks task is waiting in task_wait_event(). Lock interrupt_lock
* here so the first task chosen sees it locked.
*/
pthread_mutex_lock(&interrupt_lock);
pthread_create(&interrupt_thread, NULL,
_task_int_generator_start, NULL);
/*
* Tell the hooks task to continue so that it can call back to enable
* the other tasks.
*/
pthread_cond_signal(&tasks[i].resume);
pthread_cond_wait(&scheduler_cond, &run_lock);
task_enable_all_tasks_callback();
task_scheduler();
return 0;
}
static void task_enable_all_tasks_callback(void)
{
int i;
/* Initialize the remaning tasks. */
for (i = 0; i < TASK_ID_COUNT; ++i) {
if (tasks[i].thread != (pthread_t)NULL)
continue;
tasks[i].event = TASK_EVENT_WAKE;
tasks[i].wake_time.val = ~0ull;
tasks[i].started = 0;
pthread_cond_init(&tasks[i].resume, NULL);
pthread_create(&tasks[i].thread, NULL, _task_start_impl,
(void *)(uintptr_t)i);
/*
* Interrupt lock is grabbed by the task which just started.
* Let's unlock it so the next task can be started.
*/
pthread_mutex_unlock(&interrupt_lock);
pthread_cond_wait(&scheduler_cond, &run_lock);
}
}
void task_enable_all_tasks(void)
{
/* Signal to the scheduler to enable the remaining tasks. */
pthread_cond_signal(&scheduler_cond);
}