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 "gtest/gtest.h"
22 #include "pw_bytes/span.h"
23 #include "pw_span/span.h"
24 #include "pw_string/string_builder.h"
25 #include "pw_string/util.h"
26 #include "pw_sync/thread_notification.h"
27 #include "pw_thread/test_threads.h"
28 #include "pw_thread/thread.h"
29 #include "pw_thread/thread_info.h"
30 #include "pw_thread_freertos/freertos_tsktcb.h"
31 #include "pw_thread_freertos_private/thread_iteration.h"
32
33 namespace pw::thread::freertos {
34 namespace {
35
36 sync::ThreadNotification lock_start;
37 sync::ThreadNotification lock_end;
38
ForkedThreadEntry(void *)39 void ForkedThreadEntry(void*) {
40 // Release start lock to allow test thread to continue execution.
41 lock_start.release();
42 while (true) {
43 // Return only when end lock released by test thread.
44 if (lock_end.try_acquire()) {
45 return;
46 }
47 }
48 }
49
50 // Tests thread iteration API by:
51 // - Forking a test thread.
52 // - Using iteration API to iterate over all running threads.
53 // - Compares name of forked thread and current thread.
54 // - Confirms thread exists and is iterated over.
TEST(ThreadIteration,ForkOneThread)55 TEST(ThreadIteration, ForkOneThread) {
56 const auto& options = *static_cast<const pw::thread::freertos::Options*>(
57 &thread::test::TestOptionsThread0());
58 thread::Thread t(options, ForkedThreadEntry);
59
60 // Blocked until thread t releases start lock.
61 lock_start.acquire();
62
63 struct {
64 bool thread_exists;
65 span<const std::byte> name;
66 } temp_struct;
67
68 temp_struct.thread_exists = false;
69 // Max permissible length of task name including null byte.
70 static constexpr size_t buffer_size = configMAX_TASK_NAME_LEN;
71
72 std::string_view string(string::ClampedCString(options.name(), buffer_size));
73 temp_struct.name = as_bytes(span(string));
74
75 // Callback that confirms forked thread is checked by the iterator.
76 auto cb = [&temp_struct](const ThreadInfo& thread_info) {
77 // Compare sizes accounting for null byte.
78 if (thread_info.thread_name().has_value()) {
79 for (size_t i = 0; i < thread_info.thread_name().value().size(); i++) {
80 // Compare character by character of span.
81 if ((unsigned char)thread_info.thread_name().value().data()[i] !=
82 (unsigned char)temp_struct.name.data()[i]) {
83 return true;
84 }
85 }
86 temp_struct.thread_exists = true;
87 }
88 // Signal to stop iteration.
89 return false;
90 };
91
92 thread::ForEachThread(cb);
93
94 // Signal to forked thread that execution is complete.
95 lock_end.release();
96
97 // Clean up the test thread context.
98 #if PW_THREAD_JOINING_ENABLED
99 t.join();
100 #else
101 t.detach();
102 thread::test::WaitUntilDetachedThreadsCleanedUp();
103 #endif // PW_THREAD_JOINING_ENABLED
104
105 EXPECT_TRUE(temp_struct.thread_exists);
106 }
107
108 #if INCLUDE_uxTaskGetStackHighWaterMark
109 #if configRECORD_STACK_HIGH_ADDRESS
110
TEST(ThreadIteration,StackInfoCollector_PeakStackUsage)111 TEST(ThreadIteration, StackInfoCollector_PeakStackUsage) {
112 // This is the value FreeRTOS expects, but it's worth noting that there's no
113 // easy way to get this value directly from FreeRTOS.
114 constexpr uint8_t tskSTACK_FILL_BYTE = 0xa5U;
115 std::array<StackType_t, 128> stack;
116 ByteSpan stack_bytes(as_writable_bytes(span(stack)));
117 std::memset(stack_bytes.data(), tskSTACK_FILL_BYTE, stack_bytes.size_bytes());
118
119 tskTCB fake_tcb;
120 StringBuilder sb(fake_tcb.pcTaskName);
121 sb.append("FakeTCB");
122 fake_tcb.pxStack = stack.data();
123 fake_tcb.pxEndOfStack = stack.data() + stack.size();
124
125 // Clobber bytes as if they were used.
126 constexpr size_t kBytesRemaining = 96;
127 #if portSTACK_GROWTH > 0
128 std::memset(stack_bytes.data(),
129 tskSTACK_FILL_BYTE ^ 0x2b,
130 stack_bytes.size() - kBytesRemaining);
131 #else
132 std::memset(&stack_bytes[kBytesRemaining],
133 tskSTACK_FILL_BYTE ^ 0x2b,
134 stack_bytes.size() - kBytesRemaining);
135 #endif // portSTACK_GROWTH > 0
136
137 ThreadCallback cb = [kBytesRemaining](const ThreadInfo& info) -> bool {
138 EXPECT_TRUE(info.stack_high_addr().has_value());
139 EXPECT_TRUE(info.stack_low_addr().has_value());
140 EXPECT_TRUE(info.stack_peak_addr().has_value());
141
142 #if portSTACK_GROWTH > 0
143 EXPECT_EQ(info.stack_high_addr().value() - info.stack_peak_addr().value(),
144 kBytesRemaining);
145 #else
146 EXPECT_EQ(info.stack_peak_addr().value() - info.stack_low_addr().value(),
147 kBytesRemaining);
148 #endif // portSTACK_GROWTH > 0
149 return true;
150 };
151
152 EXPECT_TRUE(
153 StackInfoCollector(reinterpret_cast<TaskHandle_t>(&fake_tcb), cb));
154 }
155
156 #endif // INCLUDE_uxTaskGetStackHighWaterMark
157 #endif // configRECORD_STACK_HIGH_ADDRESS
158
159 } // namespace
160 } // namespace pw::thread::freertos
161