Skip to content

Commit

Permalink
pw_thread_freertos: Thread iteration API implementation
Browse files Browse the repository at this point in the history
Change-Id: I6444c3e19b5fb25831b2263d5cb560e4ac217108
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/105161
Commit-Queue: Medha Kini <[email protected]>
Reviewed-by: Armando Montanez <[email protected]>
  • Loading branch information
Medha Kini authored and CQ Bot Account committed Aug 20, 2022
1 parent fe1681d commit d055225
Show file tree
Hide file tree
Showing 6 changed files with 282 additions and 2 deletions.
2 changes: 2 additions & 0 deletions pw_system/freertos_backends.gni
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
39 changes: 39 additions & 0 deletions pw_thread_freertos/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
56 changes: 56 additions & 0 deletions pw_thread_freertos/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -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 = [
Expand All @@ -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") {
Expand Down
12 changes: 10 additions & 2 deletions pw_thread_freertos/docs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
--------------------
Expand Down Expand Up @@ -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.
Expand Down
71 changes: 71 additions & 0 deletions pw_thread_freertos/thread_iteration.cc
Original file line number Diff line number Diff line change
@@ -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 <cstddef>
#include <string_view>

#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<tskTCB*>(current_thread);
ThreadInfo thread_info;

#if configRECORD_STACK_HIGH_ADDRESS
span<const std::byte> 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<uintptr_t>(tcb.pxStack));
thread_info.set_stack_high_addr(
reinterpret_cast<uintptr_t>(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
104 changes: 104 additions & 0 deletions pw_thread_freertos/thread_iteration_test.cc
Original file line number Diff line number Diff line change
@@ -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 <cstddef>
#include <string_view>

#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<const pw::thread::freertos::Options*>(
&thread::test::TestOptionsThread0());
thread::Thread t(options, ForkedThreadEntry);

// Blocked until thread t releases start lock.
lock_start.acquire();

struct {
bool thread_exists;
span<const std::byte> 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

0 comments on commit d055225

Please sign in to comment.