1 // Copyright 2021 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_sync/timed_thread_notification.h"
16
17 #include <algorithm>
18 #include <optional>
19
20 #include "FreeRTOS.h"
21 #include "pw_assert/check.h"
22 #include "pw_chrono/system_clock.h"
23 #include "pw_chrono_freertos/system_clock_constants.h"
24 #include "pw_interrupt/context.h"
25 #include "pw_sync_freertos/config.h"
26 #include "task.h"
27
28 using pw::chrono::SystemClock;
29
30 namespace pw::sync {
31 namespace {
32
WaitForNotification(TickType_t xTicksToWait)33 BaseType_t WaitForNotification(TickType_t xTicksToWait) {
34 #ifdef configTASK_NOTIFICATION_ARRAY_ENTRIES
35 return xTaskNotifyWaitIndexed(
36 pw::sync::freertos::config::kThreadNotificationIndex,
37 0, // Clear no bits on entry.
38 0, // Clear no bits on exit.
39 nullptr, // Don't care about the notification value.
40 xTicksToWait);
41 #else // !configTASK_NOTIFICATION_ARRAY_ENTRIES
42 return xTaskNotifyWait(0, // Clear no bits on entry.
43 0, // Clear no bits on exit.
44 nullptr, // Don't care about the notification value.
45 xTicksToWait);
46 #endif // configTASK_NOTIFICATION_ARRAY_ENTRIES
47 }
48
49 } // namespace
50
try_acquire_until(const SystemClock::time_point deadline)51 bool TimedThreadNotification::try_acquire_until(
52 const SystemClock::time_point deadline) {
53 // Enforce the pw::sync::TImedThreadNotification IRQ contract.
54 PW_DCHECK(!interrupt::InInterruptContext());
55
56 // Enforce that only a single thread can block at a time.
57 PW_DCHECK(native_handle().blocked_thread == nullptr);
58
59 // Ensure that no one forgot to clean up nor corrupted the task notification
60 // state in the TCB.
61 PW_DCHECK(xTaskNotifyStateClear(nullptr) == pdFALSE);
62
63 {
64 native_handle_type handle = native_handle();
65 std::lock_guard lock(handle.shared_spin_lock);
66 const bool notified = handle.notified;
67 // Don't block if we've already reached the specified deadline time.
68 if (notified || (SystemClock::now() >= deadline)) {
69 handle.notified = false;
70 return notified;
71 }
72 // Not notified yet, set the task handle for a one-time notification.
73 handle.blocked_thread = xTaskGetCurrentTaskHandle();
74 }
75
76 // xTaskNotifyWait may spuriously return pdFALSE due to vTaskSuspend &
77 // vTaskResume. Ergo, loop until we have been notified or the specified
78 // deadline time has been reached (whichever comes first).
79 for (SystemClock::time_point now = SystemClock::now(); now < deadline;
80 now = SystemClock::now()) {
81 // Note that this must be greater than zero, due to the condition above.
82 const SystemClock::duration timeout =
83 std::min(deadline - now, pw::chrono::freertos::kMaxTimeout);
84 if (WaitForNotification(static_cast<TickType_t>(timeout.count())) ==
85 pdTRUE) {
86 break; // We were notified!
87 }
88 }
89
90 native_handle_type handle = native_handle();
91 std::lock_guard lock(handle.shared_spin_lock);
92 // We need to clear the thread notification state in case we were
93 // notified after timing out but before entering this critical section.
94 #ifdef configTASK_NOTIFICATION_ARRAY_ENTRIES
95 xTaskNotifyStateClearIndexed(
96 nullptr, pw::sync::freertos::config::kThreadNotificationIndex);
97 #else // !configTASK_NOTIFICATION_ARRAY_ENTRIES
98 xTaskNotifyStateClear(nullptr);
99 #endif // configTASK_NOTIFICATION_ARRAY_ENTRIES
100 // Instead of determining whether we were notified above while blocking in
101 // the loop above, we instead read it in this subsequent critical section in
102 // order to also include notifications which arrived after we timed out but
103 // before we entered this critical section.
104 const bool notified = handle.notified;
105 handle.notified = false;
106 handle.blocked_thread = nullptr;
107 return notified;
108 }
109
110 } // namespace pw::sync
111