From d055225b7dda9f4a80484f5c440e253fff16b0e7 Mon Sep 17 00:00:00 2001 From: Medha Kini Date: Sat, 20 Aug 2022 01:38:38 +0000 Subject: [PATCH] pw_thread_freertos: Thread iteration API implementation Change-Id: I6444c3e19b5fb25831b2263d5cb560e4ac217108 Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/105161 Commit-Queue: Medha Kini Reviewed-by: Armando Montanez --- pw_system/freertos_backends.gni | 2 + pw_thread_freertos/BUILD.bazel | 39 ++++++++ pw_thread_freertos/BUILD.gn | 56 +++++++++++ pw_thread_freertos/docs.rst | 12 ++- pw_thread_freertos/thread_iteration.cc | 71 +++++++++++++ pw_thread_freertos/thread_iteration_test.cc | 104 ++++++++++++++++++++ 6 files changed, 282 insertions(+), 2 deletions(-) create mode 100644 pw_thread_freertos/thread_iteration.cc create mode 100644 pw_thread_freertos/thread_iteration_test.cc diff --git a/pw_system/freertos_backends.gni b/pw_system/freertos_backends.gni index 0426c82b2c..9b46d6194e 100644 --- a/pw_system/freertos_backends.gni +++ b/pw_system/freertos_backends.gni @@ -29,6 +29,8 @@ PW_SYSTEM_FREERTOS_BACKENDS = { pw_thread_ID_BACKEND = "$dir_pw_thread_freertos:id" pw_thread_SLEEP_BACKEND = "$dir_pw_thread_freertos:sleep" pw_thread_THREAD_BACKEND = "$dir_pw_thread_freertos:thread" + pw_thread_THREAD_ITERATION_BACKEND = + "$dir_pw_thread_freertos:thread_iteration" pw_thread_YIELD_BACKEND = "$dir_pw_thread_freertos:yield" pw_system_TARGET_HOOKS_BACKEND = "$dir_pw_system:freertos_target_hooks" } diff --git a/pw_thread_freertos/BUILD.bazel b/pw_thread_freertos/BUILD.bazel index 8bfa58f37d..2920f45f11 100644 --- a/pw_thread_freertos/BUILD.bazel +++ b/pw_thread_freertos/BUILD.bazel @@ -183,6 +183,45 @@ pw_cc_library( ], ) +pw_cc_library( + name = "thread_iteration", + srcs = [ + "thread_iteration.cc", + ], + hdrs = [ + "public/pw_thread_freertos/thread_iteration.h", + "public_overrides/pw_thread_backend/thread_iteration.h", + ], + deps = [ + ":freertos_tasktcb", + "//pw_function", + "//pw_span", + "//pw_status", + "//pw_thread:thread_info", + "//pw_thread_freertos:util", + ], + # TODO(b/234876414): This should depend on FreeRTOS but our third parties + # currently do not have Bazel support. +) + +pw_cc_test( + name = "thread_iteration_test", + srcs = [ + "thread_iteration_test.cc", + ], + deps = [ + "dir_pw_span", + ":thread_iteration", + "//pw_string:util", + "//pw_sync:thread_notification", + "//pw_thread:thread", + "//pw_thread:thread_info", + "//pw_thread:thread_iteration", + ], + # TODO(b/234876414): This should depend on FreeRTOS but our third parties + # currently do not have Bazel support. +) + pw_cc_library( name = "util", srcs = [ diff --git a/pw_thread_freertos/BUILD.gn b/pw_thread_freertos/BUILD.gn index 1badfcb386..e6ae7750ff 100644 --- a/pw_thread_freertos/BUILD.gn +++ b/pw_thread_freertos/BUILD.gn @@ -131,6 +131,24 @@ pw_source_set("thread") { sources = [ "thread.cc" ] } +# This target provides the backend for pw::thread::thread_iteration. +if (pw_thread_freertos_FREERTOS_TSKTCB_BACKEND != "") { + pw_source_set("thread_iteration") { + public_configs = [ ":backend_config" ] + deps = [ + ":freertos_tsktcb_src", + "$dir_pw_third_party/freertos", + "$dir_pw_thread:thread_info", + "$dir_pw_thread:thread_iteration.facade", + "$dir_pw_thread_freertos:util", + dir_pw_function, + dir_pw_span, + dir_pw_status, + ] + sources = [ "thread_iteration.cc" ] + } +} + # This target provides the backend for pw::this_thread::yield. pw_source_set("yield") { public_configs = [ @@ -169,6 +187,22 @@ pw_facade("freertos_tsktcb") { public_deps = [ "$dir_pw_third_party/freertos" ] } +# TODO(amontanez): Make a script to automatically generate +# pw_thread_freertos_FREERTOS_TSKTCB_BACKEND from source. +if (pw_thread_freertos_FREERTOS_TSKTCB_BACKEND != "") { + pw_source_set("freertos_tsktcb_src") { + public_configs = [ + ":public_include_path", + ":backend_config", + ] + public = [ "public_overrides/pw_thread_backend/freertos_tsktcb.h" ] + public_deps = [ + ":freertos_tsktcb.facade", + "$dir_pw_third_party/freertos", + ] + } +} + pw_source_set("snapshot") { public_configs = [ ":public_include_path" ] public_deps = [ @@ -195,6 +229,28 @@ pw_test_group("tests") { ":dynamic_thread_backend_test", ":static_thread_backend_test", ] + if (pw_thread_freertos_FREERTOS_TSKTCB_BACKEND != "") { + tests += [ ":thread_iteration_test" ] + } +} + +if (pw_thread_freertos_FREERTOS_TSKTCB_BACKEND != "") { + pw_test("thread_iteration_test") { + enable_if = pw_thread_THREAD_BACKEND == "$dir_pw_thread_freertos:thread" + sources = [ "thread_iteration_test.cc" ] + deps = [ + ":static_test_threads", + ":thread_iteration", + "$dir_pw_span", + "$dir_pw_string:util", + "$dir_pw_sync:thread_notification", + "$dir_pw_system", + "$dir_pw_thread:test_threads", + "$dir_pw_thread:thread", + "$dir_pw_thread:thread_info", + "$dir_pw_thread:thread_iteration", + ] + } } pw_source_set("dynamic_test_threads") { diff --git a/pw_thread_freertos/docs.rst b/pw_thread_freertos/docs.rst index 94fb146aa1..2197fdce00 100644 --- a/pw_thread_freertos/docs.rst +++ b/pw_thread_freertos/docs.rst @@ -161,11 +161,19 @@ FreeRTOS Thread Options ----------------------------- Thread Identification Backend ----------------------------- -A backend for ``pw::thread::Id`` and ``pw::thread::get_id()`` is offerred using +A backend for ``pw::thread::Id`` and ``pw::thread::get_id()`` is offered using ``xTaskGetCurrentTaskHandle()``. It uses ``DASSERT`` to ensure that it is not invoked from interrupt context and if possible that the scheduler has started via ``xTaskGetSchedulerState()``. +------------------------ +Thread Iteration Backend +------------------------ +A backend for ``pw::thread::thread_iteration``. This requires +``pw_thread_freertos_TSKTCB_BACKEND`` and +``pw_third_party_freertos_DISABLE_TASKS_STATICS`` to be enabled and configured +properly. + -------------------- Thread Sleep Backend -------------------- @@ -263,7 +271,7 @@ message overlays a snapshot, so it is safe to static cast a Thread Stack Capture -------------------- Snapshot attempts to capture as much of the thread stack state as possible, -however it can be limited by on the FreeRTOS configuration. +however it can be limited by the FreeRTOS configuration. The ``stack_start_ptr`` can only be provided if the ``portSTACK_GROWTH`` is < 0, i.e. the stack grows down, when ``configRECORD_STACK_HIGH_ADDRESS`` is enabled. diff --git a/pw_thread_freertos/thread_iteration.cc b/pw_thread_freertos/thread_iteration.cc new file mode 100644 index 0000000000..83f3070429 --- /dev/null +++ b/pw_thread_freertos/thread_iteration.cc @@ -0,0 +1,71 @@ +// Copyright 2022 The Pigweed Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// #include "pw_thread_freertos/thread_iteration.h" + +#include "pw_thread/thread_iteration.h" + +#include +#include + +#include "FreeRTOS.h" +#include "pw_span/span.h" +#include "pw_status/status.h" +#include "pw_thread/thread_info.h" +#include "pw_thread_backend/freertos_tsktcb.h" +#include "pw_thread_freertos/util.h" + +namespace pw::thread { +namespace { + +bool StackInfoCollector(TaskHandle_t current_thread, + const pw::thread::ThreadCallback& cb) { + const tskTCB& tcb = *reinterpret_cast(current_thread); + ThreadInfo thread_info; + +#if configRECORD_STACK_HIGH_ADDRESS + span current_name = + as_bytes(span(std::string_view(tcb.pcTaskName))); + thread_info.set_thread_name(current_name); + + // Current thread stack bounds. + thread_info.set_stack_low_addr(reinterpret_cast(tcb.pxStack)); + thread_info.set_stack_high_addr( + reinterpret_cast(tcb.pxEndOfStack)); +#if INCLUDE_uxTaskGetStackHighWaterMark + // Walk through the stack from start to end to measure the current peak + // using high-water marked stack data. + thread_info.set_stack_peak_addr(uxTaskGetStackHighWaterMark(thread)); +#endif // INCLUDE_uxTaskGetStackHighWaterMark +#endif // configRECORD_STACK_HIGH_ADDRESS + + return cb(thread_info); +} + +} // namespace + +// This will disable the scheduler. +Status ForEachThread(const pw::thread::ThreadCallback& cb) { + pw::thread::freertos::ThreadCallback adapter_cb = + [&cb](TaskHandle_t current_thread, eTaskState) { + return StackInfoCollector(current_thread, cb); + }; + // Suspend scheduler. + vTaskSuspendAll(); + Status status = pw::thread::freertos::ForEachThread(adapter_cb); + // Resume scheduler. + xTaskResumeAll(); + return status; +} + +} // namespace pw::thread diff --git a/pw_thread_freertos/thread_iteration_test.cc b/pw_thread_freertos/thread_iteration_test.cc new file mode 100644 index 0000000000..e6aafe89a4 --- /dev/null +++ b/pw_thread_freertos/thread_iteration_test.cc @@ -0,0 +1,104 @@ +// Copyright 2022 The Pigweed Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +#include "pw_thread/thread_iteration.h" + +#include +#include + +#include "gtest/gtest.h" +#include "pw_span/span.h" +#include "pw_string/util.h" +#include "pw_sync/thread_notification.h" +#include "pw_thread/test_threads.h" +#include "pw_thread/thread.h" +#include "pw_thread/thread_info.h" + +namespace pw::thread::freertos { +namespace { + +sync::ThreadNotification lock_start; +sync::ThreadNotification lock_end; + +void ForkedThreadEntry(void*) { + // Release start lock to allow test thread to continue execution. + lock_start.release(); + while (true) { + // Return only when end lock released by test thread. + if (lock_end.try_acquire()) { + return; + } + } +} + +// Tests thread iteration API by: +// - Forking a test thread. +// - Using iteration API to iterate over all running threads. +// - Compares name of forked thread and current thread. +// - Confirms thread exists and is iterated over. +TEST(ThreadIteration, ForkOneThread) { + const auto& options = *static_cast( + &thread::test::TestOptionsThread0()); + thread::Thread t(options, ForkedThreadEntry); + + // Blocked until thread t releases start lock. + lock_start.acquire(); + + struct { + bool thread_exists; + span name; + } temp_struct; + + temp_struct.thread_exists = false; + // Max permissible length of task name including null byte. + static constexpr size_t buffer_size = configMAX_TASK_NAME_LEN; + + std::string_view string(string::ClampedCString(options.name(), buffer_size)); + temp_struct.name = as_bytes(span(string)); + + // Callback that confirms forked thread is checked by the iterator. + auto cb = [&temp_struct](const ThreadInfo& thread_info) { + // Compare sizes accounting for null byte. + if (thread_info.thread_name().has_value()) { + for (size_t i = 0; i < thread_info.thread_name().value().size(); i++) { + // Compare character by character of span. + if ((unsigned char)thread_info.thread_name().value().data()[i] != + (unsigned char)temp_struct.name.data()[i]) { + return true; + } + } + temp_struct.thread_exists = true; + } + // Signal to stop iteration. + return false; + }; + + thread::ForEachThread(cb); + + // Signal to forked thread that execution is complete. + lock_end.release(); + + // Clean up the test thread context. +#if PW_THREAD_JOINING_ENABLED + t.join(); +#else + t.detach(); + thread::test::WaitUntilDetachedThreadsCleanedUp(); +#endif // PW_THREAD_JOINING_ENABLED + + EXPECT_TRUE(temp_struct.thread_exists); +} + +} // namespace +} // namespace pw::thread::freertos