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