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