• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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