summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVic Yang <victoryang@chromium.org>2013-05-20 00:00:27 +0800
committerchrome-internal-fetch <chrome-internal-fetch@google.com>2014-01-06 12:40:45 +0000
commitcdcaf6ed8a1d18bdedb72fb665263c0dbff0ac8e (patch)
treeab01862ee3a4114e43ec974fbbbdd615e1e61d63
parent7c673390aecdd35da01ddf446a23667d95dccef1 (diff)
downloadchrome-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.c2
-rw-r--r--core/host/task.c179
-rw-r--r--core/host/timer.c14
-rw-r--r--include/task_id.h5
-rw-r--r--include/test_util.h22
-rw-r--r--test/build.mk3
-rw-r--r--test/host_command.c1
-rw-r--r--test/interrupt.c82
-rw-r--r--test/interrupt.tasklist17
-rw-r--r--test/mutex.c1
-rw-r--r--test/pingpong.c1
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);
}