• 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/thread_notification.h"
16 
17 #include <mutex>
18 
19 #include "FreeRTOS.h"
20 #include "pw_assert/check.h"
21 #include "pw_interrupt/context.h"
22 #include "pw_sync_freertos/config.h"
23 #include "task.h"
24 
25 namespace pw::sync {
26 namespace {
27 
WaitForNotification(TickType_t xTicksToWait)28 BaseType_t WaitForNotification(TickType_t xTicksToWait) {
29 #ifdef configTASK_NOTIFICATION_ARRAY_ENTRIES
30   return xTaskNotifyWaitIndexed(
31       pw::sync::freertos::config::kThreadNotificationIndex,
32       0,        // Clear no bits on entry.
33       0,        // Clear no bits on exit.
34       nullptr,  // Don't care about the notification value.
35       xTicksToWait);
36 #else   // !configTASK_NOTIFICATION_ARRAY_ENTRIES
37   return xTaskNotifyWait(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 #endif  // configTASK_NOTIFICATION_ARRAY_ENTRIES
42 }
43 
44 }  // namespace
45 
acquire()46 void ThreadNotification::acquire() {
47   // Enforce the pw::sync::ThreadNotification IRQ contract.
48   PW_DCHECK(!interrupt::InInterruptContext());
49 
50   // Enforce that only a single thread can block at a time.
51   PW_DCHECK(native_type_.blocked_thread == nullptr);
52 
53   // Ensure that no one forgot to clean up nor corrupted the task notification
54   // state in the TCB.
55   PW_DCHECK(xTaskNotifyStateClear(nullptr) == pdFALSE);
56 
57   {
58     std::lock_guard lock(native_type_.shared_spin_lock);
59     if (native_type_.notified) {
60       native_type_.notified = false;
61       return;
62     }
63     // Not notified yet, set the task handle for a one-time notification.
64     native_type_.blocked_thread = xTaskGetCurrentTaskHandle();
65   }
66 
67   // Even if INCLUDE_vTaskSuspend == 1 and ergo portMAX_DELAY means indefinite,
68   // vTaskSuspend() can abort xTaskNotifyWait() causing it to spuriously wake up
69   // after vTaskResume() returning pdFALSE as we were not actually notified.
70   while (WaitForNotification(portMAX_DELAY) == pdFALSE) {
71   }
72 
73   std::lock_guard lock(native_type_.shared_spin_lock);
74   // The task handle was cleared by the notifier.
75   // Note that this may hide another notification, however this is considered
76   // a form of notification saturation just like as if this happened before
77   // acquire() was invoked.
78   native_type_.notified = false;
79 }
80 
release()81 void ThreadNotification::release() {
82   if (!interrupt::InInterruptContext()) {  // Task context
83     std::lock_guard lock(native_type_.shared_spin_lock);
84     if (native_type_.blocked_thread != nullptr) {
85 #ifdef configTASK_NOTIFICATION_ARRAY_ENTRIES
86       xTaskNotifyIndexed(native_type_.blocked_thread,
87                          pw::sync::freertos::config::kThreadNotificationIndex,
88                          0u,
89                          eNoAction);
90 #else   // !configTASK_NOTIFICATION_ARRAY_ENTRIES
91       xTaskNotify(native_type_.blocked_thread, 0u, eNoAction);
92 #endif  // configTASK_NOTIFICATION_ARRAY_ENTRIES
93       native_type_.blocked_thread = nullptr;
94     }
95     native_type_.notified = true;
96     return;
97   }
98 
99   // Interrupt context
100   std::lock_guard lock(native_type_.shared_spin_lock);
101   if (native_type_.blocked_thread != nullptr) {
102     BaseType_t woke_higher_task = pdFALSE;
103 
104 #ifdef configTASK_NOTIFICATION_ARRAY_ENTRIES
105     xTaskNotifyIndexedFromISR(
106         native_type_.blocked_thread,
107         pw::sync::freertos::config::kThreadNotificationIndex,
108         0u,
109         eNoAction,
110         &woke_higher_task);
111 #else   // !configTASK_NOTIFICATION_ARRAY_ENTRIES
112     xTaskNotifyFromISR(
113         native_type_.blocked_thread, 0u, eNoAction, &woke_higher_task);
114 #endif  // configTASK_NOTIFICATION_ARRAY_ENTRIES
115 
116     native_type_.blocked_thread = nullptr;
117     portYIELD_FROM_ISR(woke_higher_task);
118   }
119   native_type_.notified = true;
120 }
121 
122 }  // namespace pw::sync
123