mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2025-12-27 18:25:05 +00:00
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
This commit is contained in:
committed by
chrome-internal-fetch
parent
7c673390ae
commit
cdcaf6ed8a
@@ -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;
|
||||
|
||||
|
||||
179
core/host/task.c
179
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;
|
||||
|
||||
@@ -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)
|
||||
;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
82
test/interrupt.c
Normal file
82
test/interrupt.c
Normal file
@@ -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();
|
||||
}
|
||||
17
test/interrupt.tasklist
Normal file
17
test/interrupt.tasklist
Normal file
@@ -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 */
|
||||
@@ -111,5 +111,6 @@ int mutex_main_task(void *unused)
|
||||
|
||||
void run_test(void)
|
||||
{
|
||||
wait_for_task_started();
|
||||
task_wake(TASK_ID_MTX1);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user