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 std::lock_guard lock(native_handle().shared_spin_lock);
65 const bool notified = native_handle().notified;
66 // Don't block if we've already reached the specified deadline time.
67 if (notified || (SystemClock::now() >= deadline)) {
68 native_handle().notified = false;
69 return notified;
70 }
71 // Not notified yet, set the task handle for a one-time notification.
72 native_handle().blocked_thread = xTaskGetCurrentTaskHandle();
73 }
74
75 // xTaskNotifyWait may spuriously return pdFALSE due to vTaskSuspend &
76 // vTaskResume. Ergo, loop until we have been notified or the specified
77 // deadline time has been reached (whichever comes first).
78 for (SystemClock::time_point now = SystemClock::now(); now < deadline;
79 now = SystemClock::now()) {
80 // Note that this must be greater than zero, due to the condition above.
81 const SystemClock::duration timeout =
82 std::min(deadline - now, pw::chrono::freertos::kMaxTimeout);
83 if (WaitForNotification(static_cast<TickType_t>(timeout.count())) ==
84 pdTRUE) {
85 break; // We were notified!
86 }
87 }
88
89 std::lock_guard lock(native_handle().shared_spin_lock);
90 // We need to clear the thread notification state in case we were
91 // notified after timing out but before entering this critical section.
92 #ifdef configTASK_NOTIFICATION_ARRAY_ENTRIES
93 xTaskNotifyStateClearIndexed(
94 nullptr, pw::sync::freertos::config::kThreadNotificationIndex);
95 #else // !configTASK_NOTIFICATION_ARRAY_ENTRIES
96 xTaskNotifyStateClear(nullptr);
97 #endif // configTASK_NOTIFICATION_ARRAY_ENTRIES
98 // Instead of determining whether we were notified above while blocking in
99 // the loop above, we instead read it in this subsequent critical section in
100 // order to also include notifications which arrived after we timed out but
101 // before we entered this critical section.
102 const bool notified = native_handle().notified;
103 native_handle().notified = false;
104 native_handle().blocked_thread = nullptr;
105 return notified;
106 }
107
108 } // namespace pw::sync
109