blob: fc3059c41b8b97f6aa8516be97d996ba76f7faad [file] [log] [blame]
Brian Silverman26e4e522015-12-17 01:56:40 -05001/*----------------------------------------------------------------------------*/
2/* Copyright (c) FIRST 2008. All Rights Reserved.
3 */
4/* Open Source Software - may be modified and shared by FRC teams. The code */
5/* must be accompanied by the FIRST BSD license file in $(WIND_BASE)/WPILib. */
6/*----------------------------------------------------------------------------*/
7
8#include "Notifier.h"
9#include "Timer.h"
10#include "Utility.h"
11#include "WPIErrors.h"
12#include "HAL/HAL.hpp"
13
14Notifier *Notifier::timerQueueHead = nullptr;
15priority_recursive_mutex Notifier::queueMutex;
16priority_mutex Notifier::halMutex;
17void *Notifier::m_notifier = nullptr;
18std::atomic<int> Notifier::refcount{0};
19
20/**
21 * Create a Notifier for timer event notification.
22 * @param handler The handler is called at the notification time which is set
23 * using StartSingle or StartPeriodic.
24 */
25Notifier::Notifier(TimerEventHandler handler, void *param) {
26 if (handler == nullptr)
27 wpi_setWPIErrorWithContext(NullParameter, "handler must not be nullptr");
28 m_handler = handler;
29 m_param = param;
30 // do the first time intialization of static variables
31 if (refcount.fetch_add(1) == 0) {
32 int32_t status = 0;
33 {
34 std::lock_guard<priority_mutex> sync(halMutex);
35 if (!m_notifier)
36 m_notifier = initializeNotifier(ProcessQueue, nullptr, &status);
37 }
38 wpi_setErrorWithContext(status, getHALErrorMessage(status));
39 }
40}
41
42/**
43 * Free the resources for a timer event.
44 * All resources will be freed and the timer event will be removed from the
45 * queue if necessary.
46 */
47Notifier::~Notifier() {
48 {
49 std::lock_guard<priority_recursive_mutex> sync(queueMutex);
50 DeleteFromQueue();
51 }
52
53 // Delete the static variables when the last one is going away
54 if (refcount.fetch_sub(1) == 1) {
55 int32_t status = 0;
56 {
57 std::lock_guard<priority_mutex> sync(halMutex);
58 if (m_notifier) {
59 cleanNotifier(m_notifier, &status);
60 m_notifier = nullptr;
61 }
62 }
63 wpi_setErrorWithContext(status, getHALErrorMessage(status));
64 }
65
66 // Acquire the mutex; this makes certain that the handler is
67 // not being executed by the interrupt manager.
68 std::lock_guard<priority_mutex> lock(m_handlerMutex);
69}
70
71/**
72 * Update the alarm hardware to reflect the current first element in the queue.
73 * Compute the time the next alarm should occur based on the current time and
74 * the
75 * period for the first element in the timer queue.
76 * WARNING: this method does not do synchronization! It must be called from
77 * somewhere
78 * that is taking care of synchronizing access to the queue.
79 */
80void Notifier::UpdateAlarm() {
81 if (timerQueueHead != nullptr) {
82 int32_t status = 0;
83 // This locking is necessary in order to avoid two things:
84 // 1) Race condition issues with calling cleanNotifer() and
85 // updateNotifierAlarm() at the same time.
86 // 2) Avoid deadlock by making it so that this won't block waiting
87 // for the mutex to unlock.
88 // Checking refcount as well is unnecessary, but will not hurt.
89 if (halMutex.try_lock() && refcount != 0) {
90 if (m_notifier)
91 updateNotifierAlarm(m_notifier,
92 (uint32_t)(timerQueueHead->m_expirationTime * 1e6),
93 &status);
94 halMutex.unlock();
95 }
96 wpi_setStaticErrorWithContext(timerQueueHead, status,
97 getHALErrorMessage(status));
98 }
99}
100
101/**
102 * ProcessQueue is called whenever there is a timer interrupt.
103 * We need to wake up and process the current top item in the timer queue as
104 * long
105 * as its scheduled time is after the current time. Then the item is removed or
106 * rescheduled (repetitive events) in the queue.
107 */
108void Notifier::ProcessQueue(uint32_t currentTimeInt, void *params) {
109 Notifier *current;
110 while (true) // keep processing past events until no more
111 {
112 {
113 std::lock_guard<priority_recursive_mutex> sync(queueMutex);
114 double currentTime = currentTimeInt * 1.0e-6;
115 current = timerQueueHead;
116 if (current == nullptr || current->m_expirationTime > currentTime) {
117 break; // no more timer events to process
118 }
119 // need to process this entry
120 timerQueueHead = current->m_nextEvent;
121 if (current->m_periodic) {
122 // if periodic, requeue the event
123 // compute when to put into queue
124 current->InsertInQueue(true);
125 } else {
126 // not periodic; removed from queue
127 current->m_queued = false;
128 }
129 // Take handler mutex while holding queue mutex to make sure
130 // the handler will execute to completion in case we are being deleted.
131 current->m_handlerMutex.lock();
132 }
133
134 current->m_handler(current->m_param); // call the event handler
135 current->m_handlerMutex.unlock();
136 }
137 // reschedule the first item in the queue
138 std::lock_guard<priority_recursive_mutex> sync(queueMutex);
139 UpdateAlarm();
140}
141
142/**
143 * Insert this Notifier into the timer queue in right place.
144 * WARNING: this method does not do synchronization! It must be called from
145 * somewhere
146 * that is taking care of synchronizing access to the queue.
147 * @param reschedule If false, the scheduled alarm is based on the current time
148 * and UpdateAlarm
149 * method is called which will enable the alarm if necessary.
150 * If true, update the time by adding the period (no drift) when rescheduled
151 * periodic from ProcessQueue.
152 * This ensures that the public methods only update the queue after finishing
153 * inserting.
154 */
155void Notifier::InsertInQueue(bool reschedule) {
156 if (reschedule) {
157 m_expirationTime += m_period;
158 } else {
159 m_expirationTime = GetClock() + m_period;
160 }
161 if (m_expirationTime > Timer::kRolloverTime) {
162 m_expirationTime -= Timer::kRolloverTime;
163 }
164 if (timerQueueHead == nullptr ||
165 timerQueueHead->m_expirationTime >= this->m_expirationTime) {
166 // the queue is empty or greater than the new entry
167 // the new entry becomes the first element
168 this->m_nextEvent = timerQueueHead;
169 timerQueueHead = this;
170 if (!reschedule) {
171 // since the first element changed, update alarm, unless we already plan
172 // to
173 UpdateAlarm();
174 }
175 } else {
176 for (Notifier **npp = &(timerQueueHead->m_nextEvent);;
177 npp = &(*npp)->m_nextEvent) {
178 Notifier *n = *npp;
179 if (n == nullptr || n->m_expirationTime > this->m_expirationTime) {
180 *npp = this;
181 this->m_nextEvent = n;
182 break;
183 }
184 }
185 }
186 m_queued = true;
187}
188
189/**
190 * Delete this Notifier from the timer queue.
191 * WARNING: this method does not do synchronization! It must be called from
192 * somewhere
193 * that is taking care of synchronizing access to the queue.
194 * Remove this Notifier from the timer queue and adjust the next interrupt time
195 * to reflect
196 * the current top of the queue.
197 */
198void Notifier::DeleteFromQueue() {
199 if (m_queued) {
200 m_queued = false;
201 wpi_assert(timerQueueHead != nullptr);
202 if (timerQueueHead == this) {
203 // remove the first item in the list - update the alarm
204 timerQueueHead = this->m_nextEvent;
205 UpdateAlarm();
206 } else {
207 for (Notifier *n = timerQueueHead; n != nullptr; n = n->m_nextEvent) {
208 if (n->m_nextEvent == this) {
209 // this element is the next element from *n from the queue
210 n->m_nextEvent = this->m_nextEvent; // point around this one
211 }
212 }
213 }
214 }
215}
216
217/**
218 * Register for single event notification.
219 * A timer event is queued for a single event after the specified delay.
220 * @param delay Seconds to wait before the handler is called.
221 */
222void Notifier::StartSingle(double delay) {
223 std::lock_guard<priority_recursive_mutex> sync(queueMutex);
224 m_periodic = false;
225 m_period = delay;
226 DeleteFromQueue();
227 InsertInQueue(false);
228}
229
230/**
231 * Register for periodic event notification.
232 * A timer event is queued for periodic event notification. Each time the
233 * interrupt
234 * occurs, the event will be immediately requeued for the same time interval.
235 * @param period Period in seconds to call the handler starting one period after
236 * the call to this method.
237 */
238void Notifier::StartPeriodic(double period) {
239 std::lock_guard<priority_recursive_mutex> sync(queueMutex);
240 m_periodic = true;
241 m_period = period;
242 DeleteFromQueue();
243 InsertInQueue(false);
244}
245
246/**
247 * Stop timer events from occuring.
248 * Stop any repeating timer events from occuring. This will also remove any
249 * single
250 * notification events from the queue.
251 * If a timer-based call to the registered handler is in progress, this function
252 * will
253 * block until the handler call is complete.
254 */
255void Notifier::Stop() {
256 {
257 std::lock_guard<priority_recursive_mutex> sync(queueMutex);
258 DeleteFromQueue();
259 }
260 // Wait for a currently executing handler to complete before returning from
261 // Stop()
262 std::lock_guard<priority_mutex> sync(m_handlerMutex);
263}