blob: e6d82bb2236a7691e81076c22dbbdd3007247d1c [file] [log] [blame]
Ewout van Bekkum258171a2021-11-01 12:46:03 -07001// 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 "FreeRTOS.h"
18#include "pw_assert/check.h"
19#include "pw_chrono/system_clock.h"
20#include "pw_chrono_freertos/system_clock_constants.h"
21#include "pw_interrupt/context.h"
22#include "pw_sync_freertos/config.h"
23#include "task.h"
24
25using pw::chrono::SystemClock;
26
27namespace pw::sync {
28namespace {
29
30BaseType_t WaitForNotification(TickType_t xTicksToWait) {
31#ifdef configTASK_NOTIFICATION_ARRAY_ENTRIES
32 return xTaskNotifyWaitIndexed(
33 pw::sync::freertos::config::kThreadNotificationIndex,
34 0, // Clear no bits on entry.
35 0, // Clear no bits on exit.
36 nullptr, // Don't care about the notification value.
37 xTicksToWait);
38#else // !configTASK_NOTIFICATION_ARRAY_ENTRIES
39 return xTaskNotifyWait(0, // Clear no bits on entry.
40 0, // Clear no bits on exit.
41 nullptr, // Don't care about the notification value.
42 xTicksToWait);
43#endif // configTASK_NOTIFICATION_ARRAY_ENTRIES
44}
45
46} // namespace
47
48bool TimedThreadNotification::try_acquire_for(SystemClock::duration timeout) {
Ewout van Bekkuma6642d72021-11-05 09:43:14 -070049 // Enforce the pw::sync::TImedThreadNotification IRQ contract.
Ewout van Bekkum258171a2021-11-01 12:46:03 -070050 PW_DCHECK(!interrupt::InInterruptContext());
Ewout van Bekkuma6642d72021-11-05 09:43:14 -070051
52 // Enforce that only a single thread can block at a time.
Ewout van Bekkum258171a2021-11-01 12:46:03 -070053 PW_DCHECK(native_handle().blocked_thread == nullptr);
54
Ewout van Bekkumdabf9152022-03-18 10:07:01 -070055 // Ensure that no one forgot to clean up nor corrupted the task notification
56 // state in the TCB.
57 PW_DCHECK(xTaskNotifyStateClear(nullptr) == pdFALSE);
58
Ewout van Bekkum258171a2021-11-01 12:46:03 -070059 taskENTER_CRITICAL();
Ewout van Bekkum0e1b11f2021-11-04 12:09:49 -070060 {
61 const bool notified = native_handle().notified;
62 // Don't block for negative or zero length durations.
63 if (notified || (timeout <= SystemClock::duration::zero())) {
64 native_handle().notified = false;
65 taskEXIT_CRITICAL();
66 return notified;
67 }
68 // Not notified yet, set the task handle for a one-time notification.
69 native_handle().blocked_thread = xTaskGetCurrentTaskHandle();
Ewout van Bekkum258171a2021-11-01 12:46:03 -070070 }
Ewout van Bekkum258171a2021-11-01 12:46:03 -070071 taskEXIT_CRITICAL();
72
73 const bool notified = [&]() {
Ewout van Bekkuma6642d72021-11-05 09:43:14 -070074 // In case the timeout is too long for us to express through the native
75 // FreeRTOS API, we repeatedly wait with shorter durations. Note that on a
76 // tick based kernel we cannot tell how far along we are on the current
77 // tick, ergo we add one whole tick to the final duration. However, this
78 // also means that the loop must ensure that timeout + 1 is less than the
79 // max timeout.
Ewout van Bekkum258171a2021-11-01 12:46:03 -070080 constexpr SystemClock::duration kMaxTimeoutMinusOne =
81 pw::chrono::freertos::kMaxTimeout - SystemClock::duration(1);
82 // In case the timeout is too long for us to express through the native
83 // FreeRTOS API, we repeatedly wait with shorter durations.
84 while (timeout > kMaxTimeoutMinusOne) {
85 if (WaitForNotification(
86 static_cast<TickType_t>(kMaxTimeoutMinusOne.count())) == pdTRUE) {
87 return true;
88 }
89 timeout -= kMaxTimeoutMinusOne;
90 }
91
Ewout van Bekkuma6642d72021-11-05 09:43:14 -070092 // On a tick based kernel we cannot tell how far along we are on the current
93 // tick, ergo we add one whole tick to the final duration.
Ewout van Bekkum0e1b11f2021-11-04 12:09:49 -070094 return WaitForNotification(static_cast<TickType_t>(timeout.count() + 1)) ==
Ewout van Bekkum258171a2021-11-01 12:46:03 -070095 pdTRUE;
96 }();
97
98 taskENTER_CRITICAL();
99 if (notified) {
100 // Note that this may hide another notification, however this is considered
101 // a form of notification saturation just like as if this happened before
102 // acquire() was invoked.
103 native_handle().notified = false;
104 // The task handle and notification state were cleared by the notifier.
105 } else {
106 // Note that we do NOT want to clear the notified value so the next call
107 // can detect the notification which came after we timed out but before this
108 // critical section.
109 //
110 // However, we do need to clear the task handle if we weren't notified and
111 // the notification state in case we were notified to ensure we can block
112 // in the future.
113 native_handle().blocked_thread = nullptr;
114#ifdef configTASK_NOTIFICATION_ARRAY_ENTRIES
115 xTaskNotifyStateClearIndexed(
Anthony DiGirolamo7588aed2021-12-10 15:06:16 -0800116 nullptr, pw::sync::freertos::config::kThreadNotificationIndex);
Ewout van Bekkum258171a2021-11-01 12:46:03 -0700117#else // !configTASK_NOTIFICATION_ARRAY_ENTRIES
118 xTaskNotifyStateClear(nullptr);
119#endif // configTASK_NOTIFICATION_ARRAY_ENTRIES
120 }
121 taskEXIT_CRITICAL();
122 return notified;
123}
124
125} // namespace pw::sync