diff options
author | Vic Yang <victoryang@chromium.org> | 2013-05-20 00:00:27 +0800 |
---|---|---|
committer | chrome-internal-fetch <chrome-internal-fetch@google.com> | 2014-01-06 12:40:45 +0000 |
commit | cdcaf6ed8a1d18bdedb72fb665263c0dbff0ac8e (patch) | |
tree | ab01862ee3a4114e43ec974fbbbdd615e1e61d63 | |
parent | 7c673390aecdd35da01ddf446a23667d95dccef1 (diff) | |
download | chrome-ec-cdcaf6ed8a1d18bdedb72fb665263c0dbff0ac8e.tar.gz |
Add interrupt support for emulator
This provides us a way to inject interrupts during a test. If a test has
interrupt_generator() defined, it will run in a separate thread. The
generator can then trigger interrupts when it decides to. The current
running task is suspended while emulator is executing ISR.
Also fixes a bug that tasks run without scheduler notifying them during
emulator start-up.
BUG=chrome-os-partner:19235
TEST=Repeatedly run all tests.
BRANCH=None
Change-Id: I0f921c47c0f848a9626da6272d9040e2b7c5ac86
Signed-off-by: Vic Yang <victoryang@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/55671
-rw-r--r-- | chip/host/uart.c | 2 | ||||
-rw-r--r-- | core/host/task.c | 179 | ||||
-rw-r--r-- | core/host/timer.c | 14 | ||||
-rw-r--r-- | include/task_id.h | 5 | ||||
-rw-r--r-- | include/test_util.h | 22 | ||||
-rw-r--r-- | test/build.mk | 3 | ||||
-rw-r--r-- | test/host_command.c | 1 | ||||
-rw-r--r-- | test/interrupt.c | 82 | ||||
-rw-r--r-- | test/interrupt.tasklist | 17 | ||||
-rw-r--r-- | test/mutex.c | 1 | ||||
-rw-r--r-- | test/pingpong.c | 1 |
11 files changed, 302 insertions, 25 deletions
diff --git a/chip/host/uart.c b/chip/host/uart.c index 2a05fb686e..ce8310f171 100644 --- a/chip/host/uart.c +++ b/chip/host/uart.c @@ -16,7 +16,7 @@ #include "uart.h" #include "util.h" -static int stopped; +static int stopped = 1; static int int_disabled; static int init_done; 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; diff --git a/core/host/timer.c b/core/host/timer.c index d945a2c4f2..dd8b88dbb4 100644 --- a/core/host/timer.c +++ b/core/host/timer.c @@ -10,7 +10,9 @@ #include <time.h> #include "task.h" +#include "test_util.h" #include "timer.h" +#include "util.h" /* * For test that need to test for longer than 10 seconds, adjust @@ -26,6 +28,8 @@ static int time_set; void usleep(unsigned us) { + ASSERT(!in_interrupt_context() && + task_get_current() != TASK_ID_INT_GEN); task_wait_event(us); } @@ -55,8 +59,14 @@ void force_time(timestamp_t ts) void udelay(unsigned us) { - timestamp_t deadline = get_time(); - deadline.val += us; + timestamp_t deadline; + + if (!in_interrupt_context() && task_get_current() == TASK_ID_INT_GEN) { + interrupt_generator_udelay(us); + return; + } + + deadline.val = get_time().val + us; while (get_time().val < deadline.val) ; } diff --git a/include/task_id.h b/include/task_id.h index 985c39848c..2f82e83a8b 100644 --- a/include/task_id.h +++ b/include/task_id.h @@ -53,7 +53,10 @@ enum { /* Number of tasks */ TASK_ID_COUNT, /* Special task identifiers */ - TASK_ID_INVALID = 0xff /* unable to find the task */ +#ifdef EMU_BUILD + TASK_ID_INT_GEN = 0xfe, /* interrupt generator */ +#endif + TASK_ID_INVALID = 0xff, /* unable to find the task */ }; #undef TASK diff --git a/include/test_util.h b/include/test_util.h index a6b110f6d1..182ad9d233 100644 --- a/include/test_util.h +++ b/include/test_util.h @@ -109,6 +109,28 @@ int test_get_error_count(void); int test_send_host_command(int command, int version, const void *params, int params_size, void *resp, int resp_size); +/* Optionally defined interrupt generator entry point */ +void interrupt_generator(void); + +/* + * Trigger an interrupt. This function must only be called by interrupt + * generator. + */ +void task_trigger_test_interrupt(void (*isr)(void)); + +/* + * Special implementation of udelay() for interrupt generator. Calls + * to udelay() from interrupt generator are delegated to this function + * automatically. + */ +void interrupt_generator_udelay(unsigned us); + +#ifdef EMU_BUILD +void wait_for_task_started(void); +#else +static inline void wait_for_task_started(void) { } +#endif + uint32_t prng(uint32_t seed); uint32_t prng_no_seed(void); diff --git a/test/build.mk b/test/build.mk index 76a6a68d6f..170b0e37b0 100644 --- a/test/build.mk +++ b/test/build.mk @@ -22,7 +22,7 @@ test-list-$(BOARD_SAMUS)= test-list-host=mutex pingpong utils kb_scan kb_mkbp lid_sw power_button hooks test-list-host+=thermal flash queue kb_8042 extpwr_gpio console_edit system test-list-host+=sbs_charging adapter host_command thermal_falco led_spring -test-list-host+=bklight_lid bklight_passthru +test-list-host+=bklight_lid bklight_passthru interrupt adapter-y=adapter.o bklight_lid-y=bklight_lid.o @@ -33,6 +33,7 @@ flash-y=flash.o hooks-y=hooks.o host_command-y=host_command.o kb_8042-y=kb_8042.o +interrupt-y=interrupt.o kb_mkbp-y=kb_mkbp.o kb_scan-y=kb_scan.o led_spring-y=led_spring.o led_spring_impl.o diff --git a/test/host_command.c b/test/host_command.c index cd8d57413c..a98ee87ef6 100644 --- a/test/host_command.c +++ b/test/host_command.c @@ -169,6 +169,7 @@ static int test_hostcmd_invalid_checksum(void) void run_test(void) { + wait_for_task_started(); test_reset(); RUN_TEST(test_hostcmd_ok); diff --git a/test/interrupt.c b/test/interrupt.c new file mode 100644 index 0000000000..b7a42d1588 --- /dev/null +++ b/test/interrupt.c @@ -0,0 +1,82 @@ +/* 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. + * + * Test interrupt support of EC emulator. + */ +#include <stdio.h> + +#include "common.h" +#include "console.h" +#include "test_util.h" +#include "task.h" +#include "timer.h" +#include "util.h" + +static int main_count; +static int has_error; +static int interrupt_count; + +/* period between 50us and 3.2ms */ +#define PERIOD_US(num) (((num % 64) + 1) * 50) + +void my_isr(void) +{ + int i = main_count; + udelay(3 * PERIOD_US(prng_no_seed())); + if (i != main_count || !in_interrupt_context()) + has_error = 1; + interrupt_count++; +} + +void interrupt_generator(void) +{ + while (1) { + udelay(3 * PERIOD_US(prng_no_seed())); + task_trigger_test_interrupt(my_isr); + } +} + +static int interrupt_test(void) +{ + timestamp_t deadline = get_time(); + deadline.val += SECOND / 2; + while (!timestamp_expired(deadline, NULL)) + ++main_count; + + ccprintf("Interrupt count: %d\n", interrupt_count); + ccprintf("Main thread tick: %d\n", main_count); + + TEST_ASSERT(!has_error); + TEST_ASSERT(!in_interrupt_context()); + + return EC_SUCCESS; +} + +static int interrupt_disable_test(void) +{ + timestamp_t deadline = get_time(); + int start_int_cnt, end_int_cnt; + deadline.val += SECOND / 2; + + interrupt_disable(); + start_int_cnt = interrupt_count; + while (!timestamp_expired(deadline, NULL)) + ; + end_int_cnt = interrupt_count; + interrupt_enable(); + + TEST_ASSERT(start_int_cnt == end_int_cnt); + + return EC_SUCCESS; +} + +void run_test(void) +{ + test_reset(); + + RUN_TEST(interrupt_test); + RUN_TEST(interrupt_disable_test); + + test_print_result(); +} diff --git a/test/interrupt.tasklist b/test/interrupt.tasklist new file mode 100644 index 0000000000..26cfc53453 --- /dev/null +++ b/test/interrupt.tasklist @@ -0,0 +1,17 @@ +/* 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. + */ + +/** + * List of enabled tasks in the priority order + * + * The first one has the lowest priority. + * + * For each task, use the macro TASK_TEST(n, r, d, s) where : + * 'n' in the name of the task + * 'r' in the main routine of the task + * 'd' in an opaque parameter passed to the routine at startup + * 's' is the stack size in bytes; must be a multiple of 8 + */ +#define CONFIG_TEST_TASK_LIST /* No test task */ diff --git a/test/mutex.c b/test/mutex.c index 10cf300e22..96bc69feee 100644 --- a/test/mutex.c +++ b/test/mutex.c @@ -111,5 +111,6 @@ int mutex_main_task(void *unused) void run_test(void) { + wait_for_task_started(); task_wake(TASK_ID_MTX1); } diff --git a/test/pingpong.c b/test/pingpong.c index c11267db74..f08a9554a7 100644 --- a/test/pingpong.c +++ b/test/pingpong.c @@ -61,6 +61,7 @@ int task_tick(void *data) void run_test(void) { + wait_for_task_started(); task_wake(TASK_ID_TICK); task_wake(TASK_ID_TESTA); } |