blob: 75247ca7c9e9741b7b151dde993f6ccc307a0ec0 [file] [log] [blame]
Brian Silverman41cdd3e2019-01-19 19:48:58 -08001/*----------------------------------------------------------------------------*/
2/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
3/* Open Source Software - may be modified and shared by FRC teams. The code */
4/* must be accompanied by the FIRST BSD license file in the root directory of */
5/* the project. */
6/*----------------------------------------------------------------------------*/
7
8#include "hal/Interrupts.h"
9
10#include <memory>
11
12#include <wpi/condition_variable.h>
13
14#include "AnalogInternal.h"
15#include "DigitalInternal.h"
16#include "ErrorsInternal.h"
17#include "HALInitializer.h"
18#include "MockHooksInternal.h"
19#include "PortsInternal.h"
20#include "hal/AnalogTrigger.h"
21#include "hal/Errors.h"
22#include "hal/handles/HandlesInternal.h"
23#include "hal/handles/LimitedHandleResource.h"
24#include "hal/handles/UnlimitedHandleResource.h"
25#include "mockdata/AnalogInDataInternal.h"
26#include "mockdata/DIODataInternal.h"
27#include "mockdata/HAL_Value.h"
28
29using namespace hal;
30
31enum WaitResult {
32 Timeout = 0x0,
33 RisingEdge = 0x1,
34 FallingEdge = 0x100,
35 Both = 0x101,
36};
37
38namespace {
39struct Interrupt {
40 bool isAnalog;
41 HAL_Handle portHandle;
42 uint8_t index;
43 HAL_AnalogTriggerType trigType;
44 bool watcher;
45 int64_t risingTimestamp;
46 int64_t fallingTimestamp;
47 bool previousState;
48 bool fireOnUp;
49 bool fireOnDown;
50 int32_t callbackId;
51
52 void* callbackParam;
53 HAL_InterruptHandlerFunction callbackFunction;
54};
55
56struct SynchronousWaitData {
57 HAL_InterruptHandle interruptHandle;
58 wpi::condition_variable waitCond;
59 HAL_Bool waitPredicate;
60};
61} // namespace
62
63static LimitedHandleResource<HAL_InterruptHandle, Interrupt, kNumInterrupts,
64 HAL_HandleEnum::Interrupt>* interruptHandles;
65
66typedef HAL_Handle SynchronousWaitDataHandle;
67static UnlimitedHandleResource<SynchronousWaitDataHandle, SynchronousWaitData,
68 HAL_HandleEnum::Vendor>*
69 synchronousInterruptHandles;
70
71namespace hal {
72namespace init {
73void InitializeInterrupts() {
74 static LimitedHandleResource<HAL_InterruptHandle, Interrupt, kNumInterrupts,
75 HAL_HandleEnum::Interrupt>
76 iH;
77 interruptHandles = &iH;
78 static UnlimitedHandleResource<SynchronousWaitDataHandle, SynchronousWaitData,
79 HAL_HandleEnum::Vendor>
80 siH;
81 synchronousInterruptHandles = &siH;
82}
83} // namespace init
84} // namespace hal
85
86extern "C" {
87HAL_InterruptHandle HAL_InitializeInterrupts(HAL_Bool watcher,
88 int32_t* status) {
89 hal::init::CheckInit();
90 HAL_InterruptHandle handle = interruptHandles->Allocate();
91 if (handle == HAL_kInvalidHandle) {
92 *status = NO_AVAILABLE_RESOURCES;
93 return HAL_kInvalidHandle;
94 }
95 auto anInterrupt = interruptHandles->Get(handle);
96 if (anInterrupt == nullptr) { // would only occur on thread issue.
97 *status = HAL_HANDLE_ERROR;
98 return HAL_kInvalidHandle;
99 }
100
101 anInterrupt->index = getHandleIndex(handle);
102 anInterrupt->callbackId = -1;
103
104 anInterrupt->watcher = watcher;
105
106 return handle;
107}
108void* HAL_CleanInterrupts(HAL_InterruptHandle interruptHandle,
109 int32_t* status) {
110 HAL_DisableInterrupts(interruptHandle, status);
111 auto anInterrupt = interruptHandles->Get(interruptHandle);
112 interruptHandles->Free(interruptHandle);
113 if (anInterrupt == nullptr) {
114 return nullptr;
115 }
116 return anInterrupt->callbackParam;
117}
118
119static void ProcessInterruptDigitalSynchronous(const char* name, void* param,
120 const struct HAL_Value* value) {
121 // void* is a SynchronousWaitDataHandle.
122 // convert to uintptr_t first, then to handle
123 uintptr_t handleTmp = reinterpret_cast<uintptr_t>(param);
124 SynchronousWaitDataHandle handle =
125 static_cast<SynchronousWaitDataHandle>(handleTmp);
126 auto interruptData = synchronousInterruptHandles->Get(handle);
127 if (interruptData == nullptr) return;
128 auto interrupt = interruptHandles->Get(interruptData->interruptHandle);
129 if (interrupt == nullptr) return;
130 // Have a valid interrupt
131 if (value->type != HAL_Type::HAL_BOOLEAN) return;
132 bool retVal = value->data.v_boolean;
133 // If no change in interrupt, return;
134 if (retVal == interrupt->previousState) return;
135 // If its a falling change, and we dont fire on falling return
136 if (interrupt->previousState && !interrupt->fireOnDown) return;
137 // If its a rising change, and we dont fire on rising return.
138 if (!interrupt->previousState && !interrupt->fireOnUp) return;
139
140 interruptData->waitPredicate = true;
141
142 // Pulse interrupt
143 interruptData->waitCond.notify_all();
144}
145
146static double GetAnalogTriggerValue(HAL_Handle triggerHandle,
147 HAL_AnalogTriggerType type,
148 int32_t* status) {
149 return HAL_GetAnalogTriggerOutput(triggerHandle, type, status);
150}
151
152static void ProcessInterruptAnalogSynchronous(const char* name, void* param,
153 const struct HAL_Value* value) {
154 // void* is a SynchronousWaitDataHandle.
155 // convert to uintptr_t first, then to handle
156 uintptr_t handleTmp = reinterpret_cast<uintptr_t>(param);
157 SynchronousWaitDataHandle handle =
158 static_cast<SynchronousWaitDataHandle>(handleTmp);
159 auto interruptData = synchronousInterruptHandles->Get(handle);
160 if (interruptData == nullptr) return;
161 auto interrupt = interruptHandles->Get(interruptData->interruptHandle);
162 if (interrupt == nullptr) return;
163 // Have a valid interrupt
164 if (value->type != HAL_Type::HAL_DOUBLE) return;
165 int32_t status = 0;
166 bool retVal = GetAnalogTriggerValue(interrupt->portHandle,
167 interrupt->trigType, &status);
168 if (status != 0) {
169 // Interrupt and Cancel
170 interruptData->waitPredicate = true;
171 // Pulse interrupt
172 interruptData->waitCond.notify_all();
173 }
174 // If no change in interrupt, return;
175 if (retVal == interrupt->previousState) return;
176 // If its a falling change, and we dont fire on falling return
177 if (interrupt->previousState && !interrupt->fireOnDown) return;
178 // If its a rising change, and we dont fire on rising return.
179 if (!interrupt->previousState && !interrupt->fireOnUp) return;
180
181 interruptData->waitPredicate = true;
182
183 // Pulse interrupt
184 interruptData->waitCond.notify_all();
185}
186
187static int64_t WaitForInterruptDigital(HAL_InterruptHandle handle,
188 Interrupt* interrupt, double timeout,
189 bool ignorePrevious) {
190 auto data = std::make_shared<SynchronousWaitData>();
191
192 auto dataHandle = synchronousInterruptHandles->Allocate(data);
193 if (dataHandle == HAL_kInvalidHandle) {
194 // Error allocating data
195 return WaitResult::Timeout;
196 }
197
198 // auto data = synchronousInterruptHandles->Get(dataHandle);
199 data->waitPredicate = false;
200 data->interruptHandle = handle;
201
202 int32_t status = 0;
203
204 int32_t digitalIndex = GetDigitalInputChannel(interrupt->portHandle, &status);
205
206 if (status != 0) return WaitResult::Timeout;
207
208 interrupt->previousState = SimDIOData[digitalIndex].value;
209
210 int32_t uid = SimDIOData[digitalIndex].value.RegisterCallback(
211 &ProcessInterruptDigitalSynchronous,
212 reinterpret_cast<void*>(static_cast<uintptr_t>(dataHandle)), false);
213
214 bool timedOut = false;
215
216 wpi::mutex waitMutex;
217
218 auto timeoutTime =
219 std::chrono::steady_clock::now() + std::chrono::duration<double>(timeout);
220
221 {
222 std::unique_lock<wpi::mutex> lock(waitMutex);
223 while (!data->waitPredicate) {
224 if (data->waitCond.wait_until(lock, timeoutTime) ==
225 std::cv_status::timeout) {
226 timedOut = true;
227 break;
228 }
229 }
230 }
231
232 // Cancel our callback
233 SimDIOData[digitalIndex].value.CancelCallback(uid);
234 synchronousInterruptHandles->Free(dataHandle);
235
236 // Check for what to return
237 if (timedOut) return WaitResult::Timeout;
238 // True => false, Falling
239 if (interrupt->previousState) {
240 // Set our return value and our timestamps
241 interrupt->fallingTimestamp = hal::GetFPGATime();
242 return 1 << (8 + interrupt->index);
243 } else {
244 interrupt->risingTimestamp = hal::GetFPGATime();
245 return 1 << (interrupt->index);
246 }
247}
248
249static int64_t WaitForInterruptAnalog(HAL_InterruptHandle handle,
250 Interrupt* interrupt, double timeout,
251 bool ignorePrevious) {
252 auto data = std::make_shared<SynchronousWaitData>();
253
254 auto dataHandle = synchronousInterruptHandles->Allocate(data);
255 if (dataHandle == HAL_kInvalidHandle) {
256 // Error allocating data
257 return WaitResult::Timeout;
258 }
259
260 data->waitPredicate = false;
261 data->interruptHandle = handle;
262
263 int32_t status = 0;
264 interrupt->previousState = GetAnalogTriggerValue(
265 interrupt->portHandle, interrupt->trigType, &status);
266
267 if (status != 0) return WaitResult::Timeout;
268
269 int32_t analogIndex =
270 GetAnalogTriggerInputIndex(interrupt->portHandle, &status);
271
272 if (status != 0) return WaitResult::Timeout;
273
274 int32_t uid = SimAnalogInData[analogIndex].voltage.RegisterCallback(
275 &ProcessInterruptAnalogSynchronous,
276 reinterpret_cast<void*>(static_cast<uintptr_t>(dataHandle)), false);
277
278 bool timedOut = false;
279
280 wpi::mutex waitMutex;
281
282 auto timeoutTime =
283 std::chrono::steady_clock::now() + std::chrono::duration<double>(timeout);
284
285 {
286 std::unique_lock<wpi::mutex> lock(waitMutex);
287 while (!data->waitPredicate) {
288 if (data->waitCond.wait_until(lock, timeoutTime) ==
289 std::cv_status::timeout) {
290 timedOut = true;
291 break;
292 }
293 }
294 }
295
296 // Cancel our callback
297 SimAnalogInData[analogIndex].voltage.CancelCallback(uid);
298 synchronousInterruptHandles->Free(dataHandle);
299
300 // Check for what to return
301 if (timedOut) return WaitResult::Timeout;
302 // True => false, Falling
303 if (interrupt->previousState) {
304 // Set our return value and our timestamps
305 interrupt->fallingTimestamp = hal::GetFPGATime();
306 return 1 << (8 + interrupt->index);
307 } else {
308 interrupt->risingTimestamp = hal::GetFPGATime();
309 return 1 << (interrupt->index);
310 }
311}
312
313int64_t HAL_WaitForInterrupt(HAL_InterruptHandle interruptHandle,
314 double timeout, HAL_Bool ignorePrevious,
315 int32_t* status) {
316 auto interrupt = interruptHandles->Get(interruptHandle);
317 if (interrupt == nullptr) {
318 *status = HAL_HANDLE_ERROR;
319 return WaitResult::Timeout;
320 }
321
322 // Check to make sure we are actually an interrupt in synchronous mode
323 if (!interrupt->watcher) {
324 *status = NiFpga_Status_InvalidParameter;
325 return WaitResult::Timeout;
326 }
327
328 if (interrupt->isAnalog) {
329 return WaitForInterruptAnalog(interruptHandle, interrupt.get(), timeout,
330 ignorePrevious);
331 } else {
332 return WaitForInterruptDigital(interruptHandle, interrupt.get(), timeout,
333 ignorePrevious);
334 }
335}
336
337static void ProcessInterruptDigitalAsynchronous(const char* name, void* param,
338 const struct HAL_Value* value) {
339 // void* is a HAL handle
340 // convert to uintptr_t first, then to handle
341 uintptr_t handleTmp = reinterpret_cast<uintptr_t>(param);
342 HAL_InterruptHandle handle = static_cast<HAL_InterruptHandle>(handleTmp);
343 auto interrupt = interruptHandles->Get(handle);
344 if (interrupt == nullptr) return;
345 // Have a valid interrupt
346 if (value->type != HAL_Type::HAL_BOOLEAN) return;
347 bool retVal = value->data.v_boolean;
348 // If no change in interrupt, return;
349 if (retVal == interrupt->previousState) return;
350 int32_t mask = 0;
351 if (interrupt->previousState) {
352 interrupt->previousState = retVal;
353 interrupt->fallingTimestamp = hal::GetFPGATime();
354 mask = 1 << (8 + interrupt->index);
355 if (!interrupt->fireOnDown) return;
356 } else {
357 interrupt->previousState = retVal;
358 interrupt->risingTimestamp = hal::GetFPGATime();
359 mask = 1 << (interrupt->index);
360 if (!interrupt->fireOnUp) return;
361 }
362
363 // run callback
364 auto callback = interrupt->callbackFunction;
365 if (callback == nullptr) return;
366 callback(mask, interrupt->callbackParam);
367}
368
369static void ProcessInterruptAnalogAsynchronous(const char* name, void* param,
370 const struct HAL_Value* value) {
371 // void* is a HAL handle
372 // convert to intptr_t first, then to handle
373 uintptr_t handleTmp = reinterpret_cast<uintptr_t>(param);
374 HAL_InterruptHandle handle = static_cast<HAL_InterruptHandle>(handleTmp);
375 auto interrupt = interruptHandles->Get(handle);
376 if (interrupt == nullptr) return;
377 // Have a valid interrupt
378 if (value->type != HAL_Type::HAL_DOUBLE) return;
379 int32_t status = 0;
380 bool retVal = GetAnalogTriggerValue(interrupt->portHandle,
381 interrupt->trigType, &status);
382 if (status != 0) return;
383 // If no change in interrupt, return;
384 if (retVal == interrupt->previousState) return;
385 int mask = 0;
386 if (interrupt->previousState) {
387 interrupt->previousState = retVal;
388 interrupt->fallingTimestamp = hal::GetFPGATime();
389 if (!interrupt->fireOnDown) return;
390 mask = 1 << (8 + interrupt->index);
391 } else {
392 interrupt->previousState = retVal;
393 interrupt->risingTimestamp = hal::GetFPGATime();
394 if (!interrupt->fireOnUp) return;
395 mask = 1 << (interrupt->index);
396 }
397
398 // run callback
399 auto callback = interrupt->callbackFunction;
400 if (callback == nullptr) return;
401 callback(mask, interrupt->callbackParam);
402}
403
404static void EnableInterruptsDigital(HAL_InterruptHandle handle,
405 Interrupt* interrupt) {
406 int32_t status = 0;
407 int32_t digitalIndex = GetDigitalInputChannel(interrupt->portHandle, &status);
408 if (status != 0) return;
409
410 interrupt->previousState = SimDIOData[digitalIndex].value;
411
412 int32_t uid = SimDIOData[digitalIndex].value.RegisterCallback(
413 &ProcessInterruptDigitalAsynchronous,
414 reinterpret_cast<void*>(static_cast<uintptr_t>(handle)), false);
415 interrupt->callbackId = uid;
416}
417
418static void EnableInterruptsAnalog(HAL_InterruptHandle handle,
419 Interrupt* interrupt) {
420 int32_t status = 0;
421 int32_t analogIndex =
422 GetAnalogTriggerInputIndex(interrupt->portHandle, &status);
423 if (status != 0) return;
424
425 status = 0;
426 interrupt->previousState = GetAnalogTriggerValue(
427 interrupt->portHandle, interrupt->trigType, &status);
428 if (status != 0) return;
429
430 int32_t uid = SimAnalogInData[analogIndex].voltage.RegisterCallback(
431 &ProcessInterruptAnalogAsynchronous,
432 reinterpret_cast<void*>(static_cast<uintptr_t>(handle)), false);
433 interrupt->callbackId = uid;
434}
435
436void HAL_EnableInterrupts(HAL_InterruptHandle interruptHandle,
437 int32_t* status) {
438 auto interrupt = interruptHandles->Get(interruptHandle);
439 if (interrupt == nullptr) {
440 *status = HAL_HANDLE_ERROR;
441 return;
442 }
443
444 // If we have not had a callback set, error out
445 if (interrupt->callbackFunction == nullptr) {
446 *status = INCOMPATIBLE_STATE;
447 return;
448 }
449
450 // EnableInterrupts has already been called
451 if (interrupt->callbackId >= 0) {
452 // We can double enable safely.
453 return;
454 }
455
456 if (interrupt->isAnalog) {
457 EnableInterruptsAnalog(interruptHandle, interrupt.get());
458 } else {
459 EnableInterruptsDigital(interruptHandle, interrupt.get());
460 }
461}
462void HAL_DisableInterrupts(HAL_InterruptHandle interruptHandle,
463 int32_t* status) {
464 auto interrupt = interruptHandles->Get(interruptHandle);
465 if (interrupt == nullptr) {
466 *status = HAL_HANDLE_ERROR;
467 return;
468 }
469
470 // No need to disable if we are already disabled
471 if (interrupt->callbackId < 0) return;
472
473 if (interrupt->isAnalog) {
474 // Do analog
475 int32_t status = 0;
476 int32_t analogIndex =
477 GetAnalogTriggerInputIndex(interrupt->portHandle, &status);
478 if (status != 0) return;
479 SimAnalogInData[analogIndex].voltage.CancelCallback(interrupt->callbackId);
480 } else {
481 int32_t status = 0;
482 int32_t digitalIndex =
483 GetDigitalInputChannel(interrupt->portHandle, &status);
484 if (status != 0) return;
485 SimDIOData[digitalIndex].value.CancelCallback(interrupt->callbackId);
486 }
487 interrupt->callbackId = -1;
488}
489int64_t HAL_ReadInterruptRisingTimestamp(HAL_InterruptHandle interruptHandle,
490 int32_t* status) {
491 auto interrupt = interruptHandles->Get(interruptHandle);
492 if (interrupt == nullptr) {
493 *status = HAL_HANDLE_ERROR;
494 return 0;
495 }
496
497 return interrupt->risingTimestamp;
498}
499int64_t HAL_ReadInterruptFallingTimestamp(HAL_InterruptHandle interruptHandle,
500 int32_t* status) {
501 auto interrupt = interruptHandles->Get(interruptHandle);
502 if (interrupt == nullptr) {
503 *status = HAL_HANDLE_ERROR;
504 return 0;
505 }
506
507 return interrupt->fallingTimestamp;
508}
509void HAL_RequestInterrupts(HAL_InterruptHandle interruptHandle,
510 HAL_Handle digitalSourceHandle,
511 HAL_AnalogTriggerType analogTriggerType,
512 int32_t* status) {
513 auto interrupt = interruptHandles->Get(interruptHandle);
514 if (interrupt == nullptr) {
515 *status = HAL_HANDLE_ERROR;
516 return;
517 }
518
519 bool routingAnalogTrigger = false;
520 uint8_t routingChannel = 0;
521 uint8_t routingModule = 0;
522 bool success =
523 remapDigitalSource(digitalSourceHandle, analogTriggerType, routingChannel,
524 routingModule, routingAnalogTrigger);
525 if (!success) {
526 *status = HAL_HANDLE_ERROR;
527 return;
528 }
529
530 interrupt->isAnalog = routingAnalogTrigger;
531 interrupt->trigType = analogTriggerType;
532 interrupt->portHandle = digitalSourceHandle;
533}
534void HAL_AttachInterruptHandler(HAL_InterruptHandle interruptHandle,
535 HAL_InterruptHandlerFunction handler,
536 void* param, int32_t* status) {
537 auto interrupt = interruptHandles->Get(interruptHandle);
538 if (interrupt == nullptr) {
539 *status = HAL_HANDLE_ERROR;
540 return;
541 }
542
543 interrupt->callbackFunction = handler;
544 interrupt->callbackParam = param;
545}
546
547void HAL_AttachInterruptHandlerThreaded(HAL_InterruptHandle interruptHandle,
548 HAL_InterruptHandlerFunction handler,
549 void* param, int32_t* status) {
550 HAL_AttachInterruptHandler(interruptHandle, handler, param, status);
551}
552
553void HAL_SetInterruptUpSourceEdge(HAL_InterruptHandle interruptHandle,
554 HAL_Bool risingEdge, HAL_Bool fallingEdge,
555 int32_t* status) {
556 auto interrupt = interruptHandles->Get(interruptHandle);
557 if (interrupt == nullptr) {
558 *status = HAL_HANDLE_ERROR;
559 return;
560 }
561
562 interrupt->fireOnDown = fallingEdge;
563 interrupt->fireOnUp = risingEdge;
564}
565} // extern "C"