532 lines
11 KiB
C
532 lines
11 KiB
C
/* Copyright 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;
|
|
|
|
/* thread local task id */
|
|
static __thread task_id_t my_task_id = TASK_ID_INVALID;
|
|
|
|
static void task_enable_all_tasks_callback(void);
|
|
|
|
#define TASK(n, r, d, s) void r(void *);
|
|
CONFIG_TASK_LIST
|
|
CONFIG_TEST_TASK_LIST
|
|
CONFIG_CTS_TASK_LIST
|
|
#undef TASK
|
|
|
|
/* usleep that uses OS functions, instead of emulated timer. */
|
|
void _usleep(int usec)
|
|
{
|
|
struct timespec req;
|
|
|
|
req.tv_sec = usec / 1000000;
|
|
req.tv_nsec = (usec % 1000000) * 1000;
|
|
|
|
nanosleep(&req, NULL);
|
|
}
|
|
|
|
/* msleep that uses OS functions, instead of emulated timer. */
|
|
void _msleep(int msec)
|
|
{
|
|
_usleep(1000 * msec);
|
|
}
|
|
|
|
/* 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},
|
|
const struct task_args task_info[TASK_ID_COUNT] = {
|
|
{__idle, NULL},
|
|
CONFIG_TASK_LIST
|
|
CONFIG_TEST_TASK_LIST
|
|
CONFIG_CTS_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
|
|
CONFIG_CTS_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)
|
|
_usleep(10);
|
|
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)
|
|
{
|
|
atomic_or(&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 = atomic_read_clear(&tasks[tid].event);
|
|
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)
|
|
atomic_or(&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;
|
|
}
|
|
|
|
static void _wait_for_task_started(int can_sleep)
|
|
{
|
|
int i, ok;
|
|
|
|
while (1) {
|
|
ok = 1;
|
|
for (i = 0; i < TASK_ID_COUNT - 1; ++i) {
|
|
if (!tasks[i].started) {
|
|
if (can_sleep)
|
|
msleep(10);
|
|
else
|
|
_msleep(10);
|
|
ok = 0;
|
|
break;
|
|
}
|
|
}
|
|
if (ok)
|
|
return;
|
|
}
|
|
}
|
|
|
|
void wait_for_task_started(void)
|
|
{
|
|
_wait_for_task_started(1);
|
|
}
|
|
|
|
void wait_for_task_started_nosleep(void)
|
|
{
|
|
_wait_for_task_started(0);
|
|
}
|
|
|
|
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;
|
|
const 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);
|
|
}
|