summaryrefslogtreecommitdiff
path: root/zephyr
diff options
context:
space:
mode:
authorJett Rink <jettrink@chromium.org>2020-10-29 16:39:21 -0600
committerCommit Bot <commit-bot@chromium.org>2020-11-05 21:52:05 +0000
commit5ed63c8607fe6b58bfeb5ab1580d2c1585250846 (patch)
treea00f04c1d35381a4f3dfabf1865d1df3c8f31b40 /zephyr
parent00844d592d2db088abb32bbfdafa7e1cf3832561 (diff)
downloadchrome-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.h5
-rw-r--r--zephyr/shim/include/shimmed_task_id.h29
-rw-r--r--zephyr/shim/src/CMakeLists.txt1
-rw-r--r--zephyr/shim/src/tasks.c205
-rw-r--r--zephyr/test/tasks/CMakeLists.txt28
-rw-r--r--zephyr/test/tasks/main.c199
-rw-r--r--zephyr/test/tasks/prj.conf6
-rw-r--r--zephyr/test/tasks/shimmed_tasks.h20
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 */