xref: /aosp_15_r20/external/pigweed/pw_sync_freertos/timed_thread_notification.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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