1 // Copyright 2022 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include "pw_thread/thread_iteration.h"
16
17 #include <cstddef>
18 #include <string_view>
19
20 #include "FreeRTOS.h"
21 #include "pw_bytes/span.h"
22 #include "pw_span/span.h"
23 #include "pw_string/string_builder.h"
24 #include "pw_string/util.h"
25 #include "pw_sync/thread_notification.h"
26 #include "pw_thread/non_portable_test_thread_options.h"
27 #include "pw_thread/thread.h"
28 #include "pw_thread/thread_info.h"
29 #include "pw_thread_freertos/freertos_tsktcb.h"
30 #include "pw_thread_freertos_private/thread_iteration.h"
31 #include "pw_unit_test/framework.h"
32
33 namespace pw::thread::freertos {
34 namespace {
35
36 // Tests thread iteration API by:
37 // - Forking a test thread.
38 // - Using iteration API to iterate over all running threads.
39 // - Compares name of forked thread and current thread.
40 // - Confirms thread exists and is iterated over.
TEST(ThreadIteration,ForkOneThread)41 TEST(ThreadIteration, ForkOneThread) {
42 struct {
43 sync::ThreadNotification start;
44 sync::ThreadNotification end;
45 } notify;
46
47 const auto& options = *static_cast<const pw::thread::freertos::Options*>(
48 &thread::test::TestOptionsThread0());
49
50 Thread t(options, [¬ify]() {
51 // Release start lock to allow test thread to continue execution.
52 notify.start.release();
53 while (true) {
54 // Return only when end lock released by test thread.
55 if (notify.end.try_acquire()) {
56 return;
57 }
58 }
59 });
60
61 // Blocked until thread t releases start lock.
62 notify.start.acquire();
63
64 struct {
65 bool thread_exists;
66 span<const std::byte> name;
67 } temp_struct;
68
69 temp_struct.thread_exists = false;
70 // Max permissible length of task name including null byte.
71 static constexpr size_t buffer_size = configMAX_TASK_NAME_LEN;
72
73 std::string_view string(string::ClampedCString(options.name(), buffer_size));
74 temp_struct.name = as_bytes(span(string));
75
76 // Callback that confirms forked thread is checked by the iterator.
77 auto cb = [&temp_struct](const ThreadInfo& thread_info) {
78 // Compare sizes accounting for null byte.
79 if (thread_info.thread_name().has_value()) {
80 for (size_t i = 0; i < thread_info.thread_name().value().size(); i++) {
81 // Compare character by character of span.
82 if ((unsigned char)thread_info.thread_name().value().data()[i] !=
83 (unsigned char)temp_struct.name.data()[i]) {
84 return true;
85 }
86 }
87 temp_struct.thread_exists = true;
88 }
89 // Signal to stop iteration.
90 return false;
91 };
92
93 ASSERT_EQ(OkStatus(), thread::ForEachThread(cb));
94
95 // Signal to forked thread that execution is complete.
96 notify.end.release();
97
98 // Clean up the test thread context.
99 #if PW_THREAD_JOINING_ENABLED
100 t.join();
101 #else
102 t.detach();
103 thread::test::WaitUntilDetachedThreadsCleanedUp();
104 #endif // PW_THREAD_JOINING_ENABLED
105
106 EXPECT_TRUE(temp_struct.thread_exists);
107 }
108
109 #if INCLUDE_uxTaskGetStackHighWaterMark
110 #if configRECORD_STACK_HIGH_ADDRESS
111
TEST(ThreadIteration,StackInfoCollector_PeakStackUsage)112 TEST(ThreadIteration, StackInfoCollector_PeakStackUsage) {
113 // This is the value FreeRTOS expects, but it's worth noting that there's no
114 // easy way to get this value directly from FreeRTOS.
115 constexpr uint8_t tskSTACK_FILL_BYTE = 0xa5U;
116 std::array<StackType_t, 128> stack;
117 ByteSpan stack_bytes(as_writable_bytes(span(stack)));
118 std::memset(stack_bytes.data(), tskSTACK_FILL_BYTE, stack_bytes.size_bytes());
119
120 tskTCB fake_tcb;
121 StringBuilder sb(fake_tcb.pcTaskName);
122 sb.append("FakeTCB");
123 fake_tcb.pxStack = stack.data();
124 fake_tcb.pxEndOfStack = stack.data() + stack.size();
125
126 // Clobber bytes as if they were used.
127 constexpr size_t kBytesRemaining = 96;
128 #if portSTACK_GROWTH > 0
129 std::memset(stack_bytes.data(),
130 tskSTACK_FILL_BYTE ^ 0x2b,
131 stack_bytes.size() - kBytesRemaining);
132 #else
133 std::memset(&stack_bytes[kBytesRemaining],
134 tskSTACK_FILL_BYTE ^ 0x2b,
135 stack_bytes.size() - kBytesRemaining);
136 #endif // portSTACK_GROWTH > 0
137
138 ThreadCallback cb = [kBytesRemaining](const ThreadInfo& info) -> bool {
139 EXPECT_TRUE(info.stack_high_addr().has_value());
140 EXPECT_TRUE(info.stack_low_addr().has_value());
141 EXPECT_TRUE(info.stack_peak_addr().has_value());
142
143 #if portSTACK_GROWTH > 0
144 EXPECT_EQ(info.stack_high_addr().value() - info.stack_peak_addr().value(),
145 kBytesRemaining);
146 #else
147 EXPECT_EQ(info.stack_peak_addr().value() - info.stack_low_addr().value(),
148 kBytesRemaining);
149 #endif // portSTACK_GROWTH > 0
150 return true;
151 };
152
153 EXPECT_TRUE(
154 StackInfoCollector(reinterpret_cast<TaskHandle_t>(&fake_tcb), cb));
155 }
156
157 #endif // INCLUDE_uxTaskGetStackHighWaterMark
158 #endif // configRECORD_STACK_HIGH_ADDRESS
159
160 } // namespace
161 } // namespace pw::thread::freertos
162