diff options
author | Jett Rink <jettrink@chromium.org> | 2020-10-29 16:39:21 -0600 |
---|---|---|
committer | Commit Bot <commit-bot@chromium.org> | 2020-11-05 21:52:05 +0000 |
commit | 5ed63c8607fe6b58bfeb5ab1580d2c1585250846 (patch) | |
tree | a00f04c1d35381a4f3dfabf1865d1df3c8f31b40 /zephyr | |
parent | 00844d592d2db088abb32bbfdafa7e1cf3832561 (diff) | |
download | chrome-ec-5ed63c8607fe6b58bfeb5ab1580d2c1585250846.tar.gz |
zephyr: add task shim
Provide shim/translation layer for converting platform/ec tasks into
zephyr threads. Provide implementation API for platform/ec task_ API
BRANCH=none
BUG=b:171741620
TEST=unit test provided
TEST=clean_build.sh ~/chromiumos/src/platform/ec/zephyr/tests/tasks &&
../build/zephyr/zephyr.elf
Change-Id: Ia2a1f808ec56a89c2a08df9de318edb1b6e9f869
Signed-off-by: Jett Rink <jettrink@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2518665
Reviewed-by: Simon Glass <sjg@chromium.org>
Diffstat (limited to 'zephyr')
-rw-r--r-- | zephyr/shim/include/board.h | 5 | ||||
-rw-r--r-- | zephyr/shim/include/shimmed_task_id.h | 29 | ||||
-rw-r--r-- | zephyr/shim/src/CMakeLists.txt | 1 | ||||
-rw-r--r-- | zephyr/shim/src/tasks.c | 205 | ||||
-rw-r--r-- | zephyr/test/tasks/CMakeLists.txt | 28 | ||||
-rw-r--r-- | zephyr/test/tasks/main.c | 199 | ||||
-rw-r--r-- | zephyr/test/tasks/prj.conf | 6 | ||||
-rw-r--r-- | zephyr/test/tasks/shimmed_tasks.h | 20 |
8 files changed, 493 insertions, 0 deletions
diff --git a/zephyr/shim/include/board.h b/zephyr/shim/include/board.h index 2a54b097b0..186edb5864 100644 --- a/zephyr/shim/include/board.h +++ b/zephyr/shim/include/board.h @@ -15,4 +15,9 @@ #include <gpio_map.h> #endif +/* Once SHIMMED_TASKS is enabled, must provide a shimmed_tasks header */ +#ifdef CONFIG_SHIMMED_TASKS +#include "shimmed_tasks.h" +#endif + #endif /* __BOARD_H */ diff --git a/zephyr/shim/include/shimmed_task_id.h b/zephyr/shim/include/shimmed_task_id.h new file mode 100644 index 0000000000..bc6a5d828b --- /dev/null +++ b/zephyr/shim/include/shimmed_task_id.h @@ -0,0 +1,29 @@ +/* Copyright 2020 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. + */ + +#ifndef __CROS_EC_SHIMMED_TASK_ID_H +#define __CROS_EC_SHIMMED_TASK_ID_H + +/* Task identifier (8 bits) */ +typedef uint8_t task_id_t; + +/* Include the shimmed tasks for the project/board */ +#ifdef CONFIG_SHIMMED_TASKS +#include "shimmed_tasks.h" +#else +#define CROS_EC_TASK_LIST +#endif + +/* Define the task_ids globally for all shimmed platform/ec code to use */ +#define CROS_EC_TASK(name, ...) TASK_ID_##name, +enum { + TASK_ID_IDLE = -1, /* We don't shim the idle task */ + CROS_EC_TASK_LIST + TASK_ID_COUNT, + TASK_ID_INVALID = 0xff, /* Unable to find the task */ +}; +#undef CROS_EC_TASK + +#endif /* __CROS_EC_SHIMMED_TASK_ID_H */ diff --git a/zephyr/shim/src/CMakeLists.txt b/zephyr/shim/src/CMakeLists.txt index 12f8aee33e..57ade258a3 100644 --- a/zephyr/shim/src/CMakeLists.txt +++ b/zephyr/shim/src/CMakeLists.txt @@ -6,4 +6,5 @@ zephyr_sources(console.c) zephyr_sources(util.c) zephyr_sources(gpio.c) zephyr_sources_ifdef(CONFIG_CROS_EC system.c) +zephyr_sources_ifdef(CONFIG_SHIMMED_TASKS tasks.c) zephyr_sources_ifdef(CONFIG_PLATFORM_EC_TIMER hwtimer.c) diff --git a/zephyr/shim/src/tasks.c b/zephyr/shim/src/tasks.c new file mode 100644 index 0000000000..b3450c9387 --- /dev/null +++ b/zephyr/shim/src/tasks.c @@ -0,0 +1,205 @@ +/* Copyright 2020 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. + */ + +#include <kernel.h> +#include <init.h> +#include <sys/atomic.h> +#include <task.h> + +#include "shimmed_tasks.h" + +BUILD_ASSERT(CONFIG_NUM_PREEMPT_PRIORITIES >= TASK_ID_COUNT, + "Must increase number of available preempt priorities"); + +/* Ensure all of the manually defined HAS_TASK_ defines are present */ +#define CROS_EC_TASK(name, ...) \ + BUILD_ASSERT(HAS_TASK_##name, "Must define HAS_TASK_*"); +CROS_EC_TASK_LIST +#undef CROS_EC_TASK + +/* Declare all task stacks here */ +#define CROS_EC_TASK(name, e, p, size) \ + K_THREAD_STACK_DEFINE(name##_STACK, size); +CROS_EC_TASK_LIST +#undef CROS_EC_TASK + +/* Forward declare all task entry point functions */ +#define CROS_EC_TASK(name, entry, ...) void entry(void *p); +CROS_EC_TASK_LIST +#undef CROS_EC_TASK + +/** Context for each CROS EC task that is run in its own zephyr thread */ +struct task_ctx { + /** Name of thread (for debugging) */ + const char *name; + /** Zephyr thread structure that hosts EC tasks */ + struct k_thread zephyr_thread; + /** Zephyr thread id for above thread */ + k_tid_t zephyr_tid; + /** Address of Zephyr thread's stack */ + k_thread_stack_t *stack; + /** Usabled size in bytes of above thread stack */ + size_t stack_size; + /** Task (platform/ec) entry point */ + void (*entry)(void *p); + /** The parameter that is passed into the task entry point */ + intptr_t parameter; + /** A wait-able event that is raised when a new task event is posted */ + struct k_poll_signal new_event; + /** The current platform/ec events set for this task/thread */ + uint32_t event_mask; +}; + +#define CROS_EC_TASK(_name, _entry, _parameter, _size) \ + { \ + .entry = _entry, \ + .parameter = _parameter, \ + .stack = _name##_STACK, \ + .stack_size = _size, \ + .name = #_name, \ + }, +static struct task_ctx shimmed_tasks[] = { CROS_EC_TASK_LIST }; +#undef CROS_EC_TASK + +task_id_t task_get_current(void) +{ + for (size_t i = 0; i < ARRAY_SIZE(shimmed_tasks); ++i) { + if (shimmed_tasks[i].zephyr_tid == k_current_get()) { + return i; + } + } + __ASSERT(false, "Task index out of bound"); + return 0; +} + +uint32_t task_set_event(task_id_t cros_task_id, uint32_t event, int wait) +{ + struct task_ctx *const ctx = &shimmed_tasks[cros_task_id]; + + atomic_or(&ctx->event_mask, event); + k_poll_signal_raise(&ctx->new_event, 0); + + /* TODO(b/172360521): Remove wait parameter from EC. No one uses it */ + return 0; +} + +uint32_t task_wait_event(int timeout_us) +{ + struct task_ctx *const ctx = &shimmed_tasks[task_get_current()]; + const k_timeout_t timeout = (timeout_us == -1) ? K_FOREVER : + K_USEC(timeout_us); + const int64_t tick_deadline = + k_uptime_ticks() + k_us_to_ticks_near64(timeout_us); + + struct k_poll_event poll_events[1] = { + K_POLL_EVENT_INITIALIZER(K_POLL_TYPE_SIGNAL, + K_POLL_MODE_NOTIFY_ONLY, + &ctx->new_event), + }; + + /* Wait for signal, then clear it before reading events */ + const int rv = k_poll(poll_events, ARRAY_SIZE(poll_events), timeout); + + k_poll_signal_reset(&ctx->new_event); + uint32_t events = atomic_set(&ctx->event_mask, 0); + + if (rv == -EAGAIN) { + events |= TASK_EVENT_TIMER; + } + + /* If we didn't get an event, we need to wait again. There is a very + * small change of us reading the event_mask one signaled event too + * early. In that case, just wait again for the remaining timeout + */ + if (events == 0) { + const int64_t ticks_left = tick_deadline - k_uptime_ticks(); + + if (ticks_left > 0) { + return task_wait_event( + k_ticks_to_us_near64(ticks_left)); + } + + events |= TASK_EVENT_TIMER; + } + + return events; +} + +uint32_t task_wait_event_mask(uint32_t event_mask, int timeout_us) +{ + struct task_ctx *const ctx = &shimmed_tasks[task_get_current()]; + uint32_t events = 0; + const int64_t tick_deadline = + k_uptime_ticks() + k_us_to_ticks_near64(timeout_us); + + /* Need to return timeout flags if it occurs as well */ + event_mask |= TASK_EVENT_TIMER; + + while (!(event_mask & events)) { + const int64_t ticks_left = tick_deadline - k_uptime_ticks(); + + if (timeout_us != -1 && ticks_left <= 0) { + events |= TASK_EVENT_TIMER; + break; + } + + struct k_poll_event poll_events[1] = { + K_POLL_EVENT_INITIALIZER(K_POLL_TYPE_SIGNAL, + K_POLL_MODE_NOTIFY_ONLY, + &ctx->new_event), + }; + + /* Ensure to honor the -1 timeout as FOREVER */ + k_poll(poll_events, ARRAY_SIZE(poll_events), + timeout_us == -1 ? K_FOREVER : K_TICKS(ticks_left)); + k_poll_signal_reset(&ctx->new_event); + events |= atomic_set(&ctx->event_mask, 0); + } + + /* Replace any events that weren't in the mask */ + if (events & ~event_mask) { + atomic_or(&ctx->event_mask, events & ~event_mask); + k_poll_signal_raise(&ctx->new_event, 0); + } + + return events & event_mask; +} + +static void task_entry(void *task_contex, void *unused1, void *unused2) +{ + ARG_UNUSED(unused1); + ARG_UNUSED(unused2); + + struct task_ctx *const ctx = (struct task_ctx *)task_contex; + /* Name thread for debugging */ + k_thread_name_set(ctx->zephyr_tid, ctx->name); + + /* Initialize the new_event structure */ + k_poll_signal_init(&ctx->new_event); + + /* Call into task entry point */ + ctx->entry((void *)ctx->parameter); +} + +static int start_ec_tasks(const struct device *unused) +{ + ARG_UNUSED(unused); + + for (size_t i = 0; i < ARRAY_SIZE(shimmed_tasks); ++i) { + struct task_ctx *const ctx = &shimmed_tasks[i]; + + /* + * TODO(b/172361873): Add K_FP_REGS for FPU tasks. See + * comment in config.h for CONFIG_TASK_LIST for existing flags + * implementation. + */ + ctx->zephyr_tid = k_thread_create( + &ctx->zephyr_thread, ctx->stack, ctx->stack_size, + task_entry, ctx, NULL, NULL, + K_PRIO_PREEMPT(TASK_ID_COUNT - i), 0, K_NO_WAIT); + } + return 0; +} +SYS_INIT(start_ec_tasks, APPLICATION, 10); diff --git a/zephyr/test/tasks/CMakeLists.txt b/zephyr/test/tasks/CMakeLists.txt new file mode 100644 index 0000000000..714fde1d9a --- /dev/null +++ b/zephyr/test/tasks/CMakeLists.txt @@ -0,0 +1,28 @@ +# Copyright 2020 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. +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) +set(BOARD native_posix) +project(tasks) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +# Need to ensure that we are including only zephyr definitions in include files +# We cannot set these via kconfig, since this unit test does not bring in the +# zephyr-chrome repository +zephyr_compile_definitions("CONFIG_ZEPHYR") +zephyr_compile_definitions("CONFIG_SHIMMED_TASKS") + +# We need to include the EC include directory and this local test directory +# for the task defines +zephyr_include_directories( + "${CMAKE_CURRENT_SOURCE_DIR}" + "${PLATFORM_EC}/zephyr/shim/include" + "${PLATFORM_EC}/fuzz" + "${PLATFORM_EC}/test" + "${PLATFORM_EC}/include") + +# Include the test source and the file under test +target_sources(app PRIVATE main.c) +target_sources(app PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../../shim/src/tasks.c") diff --git a/zephyr/test/tasks/main.c b/zephyr/test/tasks/main.c new file mode 100644 index 0000000000..0c659fd624 --- /dev/null +++ b/zephyr/test/tasks/main.c @@ -0,0 +1,199 @@ +/* Copyright 2020 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. + */ + +#include <kernel.h> +#include <task.h> +#include <ztest.h> + +/* Second for platform/ec task API (in microseconds). */ +#define TASK_SEC(s) (s * 1000 * 1000) + +K_SEM_DEFINE(task_done1, 0, 1); +K_SEM_DEFINE(task_done2, 0, 1); +K_SEM_DEFINE(test_ready1, 0, 1); +K_SEM_DEFINE(test_ready2, 0, 1); + +static void (*task1)(void); +static void (*task2)(void); + +static void run_test(void (*task1_run)(void), void (*task2_run)(void)) +{ + task1 = task1_run; + task2 = task2_run; + k_sem_give(&test_ready1); + k_sem_give(&test_ready2); + k_sem_take(&task_done1, K_FOREVER); + k_sem_take(&task_done2, K_FOREVER); +} + +void task1_entry(void *p) +{ + while (1) { + k_sem_take(&test_ready1, K_FOREVER); + task1(); + k_sem_give(&task_done1); + } +} + +void task2_entry(void *p) +{ + while (1) { + k_sem_take(&test_ready2, K_FOREVER); + task2(); + k_sem_give(&task_done2); + } +} + +static void task_get_current1(void) +{ + zassert_equal(task_get_current(), TASK_ID_TASK_1, "ID matches"); +} + +static void task_get_current2(void) +{ + zassert_equal(task_get_current(), TASK_ID_TASK_2, "ID matches"); +} + +static void test_task_get_current(void) +{ + run_test(&task_get_current1, &task_get_current2); +} + + +static void timeout1(void) +{ + const uint32_t start_ms = k_uptime_get(); + const uint32_t events = task_wait_event(TASK_SEC(2)); + const uint32_t end_ms = k_uptime_get(); + + zassert_equal(events, TASK_EVENT_TIMER, "Should have timeout event"); + zassert_within(end_ms - start_ms, 2000, 100, "Timeout for 2 seconds"); +} + +static void timeout2(void) +{ + /* Do nothing */ +} + +static void test_timeout(void) +{ + run_test(&timeout1, &timeout2); +} + + +static void event_delivered1(void) +{ + const uint32_t start_ms = k_uptime_get(); + const uint32_t events = task_wait_event(-1); + const uint32_t end_ms = k_uptime_get(); + + zassert_equal(events, 0x1234, "Verify event bits"); + zassert_within(end_ms - start_ms, 5000, 100, "Waited for 5 seconds"); +} + +static void event_delivered2(void) +{ + k_sleep(K_SECONDS(5)); + + task_set_event(TASK_ID_TASK_1, 0x1234, 0); +} + +static void test_event_delivered(void) +{ + run_test(&event_delivered1, &event_delivered2); +} + + +static void event_mask_not_delivered1(void) +{ + task_set_event(TASK_ID_TASK_2, 0x007F, 0); +} + +static void event_mask_not_delivered2(void) +{ + const uint32_t start_ms = k_uptime_get(); + const uint32_t events = task_wait_event_mask(0x0080, TASK_SEC(7)); + const uint32_t end_ms = k_uptime_get(); + + zassert_equal(events, TASK_EVENT_TIMER, "Should have timeout event"); + zassert_within(end_ms - start_ms, 7000, 100, "Timeout for 7 seconds"); + + const uint32_t leftover_events = task_wait_event(0); + + zassert_equal(leftover_events, 0x007F, "All events should be waiting"); +} + +static void test_event_mask_not_delivered(void) +{ + run_test(&event_mask_not_delivered1, &event_mask_not_delivered2); +} + + +static void event_mask_extra1(void) +{ + k_sleep(K_SECONDS(1)); + + task_set_event(TASK_ID_TASK_2, 0x00FF, 0); +} + +static void event_mask_extra2(void) +{ + const uint32_t start_ms = k_uptime_get(); + const uint32_t events = task_wait_event_mask(0x0001, TASK_SEC(10)); + const uint32_t end_ms = k_uptime_get(); + + zassert_equal(events, 0x0001, "Verify only waited for event"); + zassert_within(end_ms - start_ms, 1000, 100, "Timeout for 1 second"); + + const uint32_t leftover_events = task_wait_event(0); + + zassert_equal(leftover_events, 0x00FE, "All events should be waiting"); +} + +static void test_event_mask_extra(void) +{ + run_test(&event_mask_extra1, &event_mask_extra2); +} + + +static void empty_set_mask1(void) +{ + k_sleep(K_SECONDS(1)); + /* + * It is generally invalid to set a 0 event, but this simulates a race + * condition and exercises fallback code in task_wait_event + */ + task_set_event(TASK_ID_TASK_2, 0, 0); + k_sleep(K_SECONDS(1)); + task_set_event(TASK_ID_TASK_2, 0x1234, 0); +} + +static void empty_set_mask2(void) +{ + const uint32_t start_ms = k_uptime_get(); + const uint32_t events = task_wait_event_mask(0x1234, TASK_SEC(10)); + const uint32_t end_ms = k_uptime_get(); + + zassert_equal(events, 0x1234, "Verify only waited for event"); + zassert_within(end_ms - start_ms, 2000, 100, "Timeout for 2 seconds"); +} + +static void test_empty_set_mask(void) +{ + run_test(&empty_set_mask1, &empty_set_mask2); +} + + +void test_main(void) +{ + ztest_test_suite(test_task_shim, + ztest_unit_test(test_task_get_current), + ztest_unit_test(test_timeout), + ztest_unit_test(test_event_delivered), + ztest_unit_test(test_event_mask_not_delivered), + ztest_unit_test(test_event_mask_extra), + ztest_unit_test(test_empty_set_mask)); + ztest_run_test_suite(test_task_shim); +} diff --git a/zephyr/test/tasks/prj.conf b/zephyr/test/tasks/prj.conf new file mode 100644 index 0000000000..572ee9bb25 --- /dev/null +++ b/zephyr/test/tasks/prj.conf @@ -0,0 +1,6 @@ +# Copyright 2020 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. + +CONFIG_ZTEST=y +CONFIG_POLL=y diff --git a/zephyr/test/tasks/shimmed_tasks.h b/zephyr/test/tasks/shimmed_tasks.h new file mode 100644 index 0000000000..df3f7ceef0 --- /dev/null +++ b/zephyr/test/tasks/shimmed_tasks.h @@ -0,0 +1,20 @@ +/* Copyright 2020 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. + */ + +#ifndef __CROS_EC_SHIMMED_TASKS_H +#define __CROS_EC_SHIMMED_TASKS_H + +/* + * Manually define these HAS_TASK_* defines. There is a build time assert + * to at least verify we have the minimum set defined correctly. */ +#define HAS_TASK_TASK_1 1 +#define HAS_TASK_TASK_2 1 + +/* Highest priority on bottom same as in platform/ec */ +#define CROS_EC_TASK_LIST \ + CROS_EC_TASK(TASK_1, task1_entry, 0, 512) \ + CROS_EC_TASK(TASK_2, task2_entry, 0, 512) + +#endif /* __CROS_EC_SHIMMED_TASKS_H */ |