diff options
Diffstat (limited to 'core/host/task.c')
-rw-r--r-- | core/host/task.c | 179 |
1 files changed, 159 insertions, 20 deletions
diff --git a/core/host/task.c b/core/host/task.c index e945b603a8..23d16bd802 100644 --- a/core/host/task.c +++ b/core/host/task.c @@ -6,6 +6,8 @@ /* Task scheduling / events module for Chrome EC operating system */ #include <pthread.h> +#include <semaphore.h> +#include <signal.h> #include <stdint.h> #include <stdio.h> @@ -21,6 +23,7 @@ struct emu_task_t { pthread_cond_t resume; uint32_t event; timestamp_t wake_time; + uint8_t started; }; struct task_args { @@ -31,6 +34,17 @@ struct task_args { 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 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 */ @@ -76,17 +90,59 @@ void task_pre_init(void) int in_interrupt_context(void) { - return 0; /* No interrupt support yet */ + return !!in_interrupt; } void interrupt_disable(void) { - /* Not supported yet */ + interrupt_disabled = 1; } void interrupt_enable(void) { - /* Not supported yet */ + interrupt_disabled = 0; +} + +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(SIGUSR1, _task_execute_isr); +} + +void task_trigger_test_interrupt(void (*isr)(void)) +{ + if (interrupt_disabled) + return; + pthread_mutex_lock(&interrupt_lock); + + /* Suspend current task and excute ISR */ + pending_isr = isr; + pthread_kill(tasks[running_task_id].thread, SIGUSR1); + + /* 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; } uint32_t task_set_event(task_id_t tskid, uint32_t event, int wait) @@ -101,12 +157,18 @@ 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; } @@ -148,6 +210,23 @@ task_id_t task_get_current(void) return my_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; @@ -165,6 +244,42 @@ static task_id_t task_get_next_wake(void) 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].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; + } +} + void task_scheduler(void) { int i; @@ -178,25 +293,12 @@ void task_scheduler(void) break; --i; } - if (i < 0) { - /* - * No task has event pending, and thus we are only - * waiting for the next wake-up timer to fire. Let's - * just find out which timer is the next and fast - * forward the system time to its deadline. - * - * Note that once we have interrupt support, we need - * to take into account the fact that an interrupt - * might set an event before the next timer fires. - */ - i = task_get_next_wake(); - if (i == TASK_ID_INVALID) - i = TASK_ID_IDLE; - else - force_time(tasks[i].wake_time); - } + 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); } @@ -208,17 +310,39 @@ void *_task_start_impl(void *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_register_interrupt(); + pthread_mutex_init(&run_lock, NULL); + pthread_mutex_init(&interrupt_lock, NULL); pthread_cond_init(&scheduler_cond, NULL); pthread_mutex_lock(&run_lock); @@ -226,12 +350,27 @@ int task_start(void) for (i = 0; i < TASK_ID_COUNT; ++i) { 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); } + /* + * All tasks are now 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); + task_scheduler(); return 0; |