| /*----------------------------------------------------------------------------*/ |
| /* Copyright (c) 2017-2019 FIRST. All Rights Reserved. */ |
| /* Open Source Software - may be modified and shared by FRC teams. The code */ |
| /* must be accompanied by the FIRST BSD license file in the root directory of */ |
| /* the project. */ |
| /*----------------------------------------------------------------------------*/ |
| |
| #include "hal/Interrupts.h" |
| |
| #include <memory> |
| |
| #include <wpi/condition_variable.h> |
| |
| #include "AnalogInternal.h" |
| #include "DigitalInternal.h" |
| #include "ErrorsInternal.h" |
| #include "HALInitializer.h" |
| #include "MockHooksInternal.h" |
| #include "PortsInternal.h" |
| #include "hal/AnalogTrigger.h" |
| #include "hal/Errors.h" |
| #include "hal/Value.h" |
| #include "hal/handles/HandlesInternal.h" |
| #include "hal/handles/LimitedHandleResource.h" |
| #include "hal/handles/UnlimitedHandleResource.h" |
| #include "mockdata/AnalogInDataInternal.h" |
| #include "mockdata/DIODataInternal.h" |
| |
| #ifdef _WIN32 |
| #pragma warning(disable : 4996 4018 6297 26451 4334) |
| #endif |
| |
| using namespace hal; |
| |
| enum WaitResult { |
| Timeout = 0x0, |
| RisingEdge = 0x1, |
| FallingEdge = 0x100, |
| Both = 0x101, |
| }; |
| |
| namespace { |
| struct Interrupt { |
| bool isAnalog; |
| HAL_Handle portHandle; |
| uint8_t index; |
| HAL_AnalogTriggerType trigType; |
| bool watcher; |
| int64_t risingTimestamp; |
| int64_t fallingTimestamp; |
| bool previousState; |
| bool fireOnUp; |
| bool fireOnDown; |
| int32_t callbackId; |
| |
| void* callbackParam; |
| HAL_InterruptHandlerFunction callbackFunction; |
| }; |
| |
| struct SynchronousWaitData { |
| HAL_InterruptHandle interruptHandle{HAL_kInvalidHandle}; |
| wpi::condition_variable waitCond; |
| HAL_Bool waitPredicate{false}; |
| }; |
| } // namespace |
| |
| static LimitedHandleResource<HAL_InterruptHandle, Interrupt, kNumInterrupts, |
| HAL_HandleEnum::Interrupt>* interruptHandles; |
| |
| typedef HAL_Handle SynchronousWaitDataHandle; |
| static UnlimitedHandleResource<SynchronousWaitDataHandle, SynchronousWaitData, |
| HAL_HandleEnum::Vendor>* |
| synchronousInterruptHandles; |
| |
| namespace hal { |
| namespace init { |
| void InitializeInterrupts() { |
| static LimitedHandleResource<HAL_InterruptHandle, Interrupt, kNumInterrupts, |
| HAL_HandleEnum::Interrupt> |
| iH; |
| interruptHandles = &iH; |
| static UnlimitedHandleResource<SynchronousWaitDataHandle, SynchronousWaitData, |
| HAL_HandleEnum::Vendor> |
| siH; |
| synchronousInterruptHandles = &siH; |
| } |
| } // namespace init |
| } // namespace hal |
| |
| extern "C" { |
| HAL_InterruptHandle HAL_InitializeInterrupts(HAL_Bool watcher, |
| int32_t* status) { |
| hal::init::CheckInit(); |
| HAL_InterruptHandle handle = interruptHandles->Allocate(); |
| if (handle == HAL_kInvalidHandle) { |
| *status = NO_AVAILABLE_RESOURCES; |
| return HAL_kInvalidHandle; |
| } |
| auto anInterrupt = interruptHandles->Get(handle); |
| if (anInterrupt == nullptr) { // would only occur on thread issue. |
| *status = HAL_HANDLE_ERROR; |
| return HAL_kInvalidHandle; |
| } |
| |
| anInterrupt->index = getHandleIndex(handle); |
| anInterrupt->callbackId = -1; |
| |
| anInterrupt->watcher = watcher; |
| |
| return handle; |
| } |
| void* HAL_CleanInterrupts(HAL_InterruptHandle interruptHandle, |
| int32_t* status) { |
| HAL_DisableInterrupts(interruptHandle, status); |
| auto anInterrupt = interruptHandles->Get(interruptHandle); |
| interruptHandles->Free(interruptHandle); |
| if (anInterrupt == nullptr) { |
| return nullptr; |
| } |
| return anInterrupt->callbackParam; |
| } |
| |
| static void ProcessInterruptDigitalSynchronous(const char* name, void* param, |
| const struct HAL_Value* value) { |
| // void* is a SynchronousWaitDataHandle. |
| // convert to uintptr_t first, then to handle |
| uintptr_t handleTmp = reinterpret_cast<uintptr_t>(param); |
| SynchronousWaitDataHandle handle = |
| static_cast<SynchronousWaitDataHandle>(handleTmp); |
| auto interruptData = synchronousInterruptHandles->Get(handle); |
| if (interruptData == nullptr) return; |
| auto interrupt = interruptHandles->Get(interruptData->interruptHandle); |
| if (interrupt == nullptr) return; |
| // Have a valid interrupt |
| if (value->type != HAL_Type::HAL_BOOLEAN) return; |
| bool retVal = value->data.v_boolean; |
| // If no change in interrupt, return; |
| if (retVal == interrupt->previousState) return; |
| // If its a falling change, and we dont fire on falling return |
| if (interrupt->previousState && !interrupt->fireOnDown) return; |
| // If its a rising change, and we dont fire on rising return. |
| if (!interrupt->previousState && !interrupt->fireOnUp) return; |
| |
| interruptData->waitPredicate = true; |
| |
| // Pulse interrupt |
| interruptData->waitCond.notify_all(); |
| } |
| |
| static double GetAnalogTriggerValue(HAL_Handle triggerHandle, |
| HAL_AnalogTriggerType type, |
| int32_t* status) { |
| return HAL_GetAnalogTriggerOutput(triggerHandle, type, status); |
| } |
| |
| static void ProcessInterruptAnalogSynchronous(const char* name, void* param, |
| const struct HAL_Value* value) { |
| // void* is a SynchronousWaitDataHandle. |
| // convert to uintptr_t first, then to handle |
| uintptr_t handleTmp = reinterpret_cast<uintptr_t>(param); |
| SynchronousWaitDataHandle handle = |
| static_cast<SynchronousWaitDataHandle>(handleTmp); |
| auto interruptData = synchronousInterruptHandles->Get(handle); |
| if (interruptData == nullptr) return; |
| auto interrupt = interruptHandles->Get(interruptData->interruptHandle); |
| if (interrupt == nullptr) return; |
| // Have a valid interrupt |
| if (value->type != HAL_Type::HAL_DOUBLE) return; |
| int32_t status = 0; |
| bool retVal = GetAnalogTriggerValue(interrupt->portHandle, |
| interrupt->trigType, &status); |
| if (status != 0) { |
| // Interrupt and Cancel |
| interruptData->waitPredicate = true; |
| // Pulse interrupt |
| interruptData->waitCond.notify_all(); |
| } |
| // If no change in interrupt, return; |
| if (retVal == interrupt->previousState) return; |
| // If its a falling change, and we dont fire on falling return |
| if (interrupt->previousState && !interrupt->fireOnDown) return; |
| // If its a rising change, and we dont fire on rising return. |
| if (!interrupt->previousState && !interrupt->fireOnUp) return; |
| |
| interruptData->waitPredicate = true; |
| |
| // Pulse interrupt |
| interruptData->waitCond.notify_all(); |
| } |
| |
| static int64_t WaitForInterruptDigital(HAL_InterruptHandle handle, |
| Interrupt* interrupt, double timeout, |
| bool ignorePrevious) { |
| auto data = std::make_shared<SynchronousWaitData>(); |
| |
| auto dataHandle = synchronousInterruptHandles->Allocate(data); |
| if (dataHandle == HAL_kInvalidHandle) { |
| // Error allocating data |
| return WaitResult::Timeout; |
| } |
| |
| // auto data = synchronousInterruptHandles->Get(dataHandle); |
| data->waitPredicate = false; |
| data->interruptHandle = handle; |
| |
| int32_t status = 0; |
| |
| int32_t digitalIndex = GetDigitalInputChannel(interrupt->portHandle, &status); |
| |
| if (status != 0) return WaitResult::Timeout; |
| |
| interrupt->previousState = SimDIOData[digitalIndex].value; |
| |
| int32_t uid = SimDIOData[digitalIndex].value.RegisterCallback( |
| &ProcessInterruptDigitalSynchronous, |
| reinterpret_cast<void*>(static_cast<uintptr_t>(dataHandle)), false); |
| |
| bool timedOut = false; |
| |
| wpi::mutex waitMutex; |
| |
| auto timeoutTime = |
| std::chrono::steady_clock::now() + std::chrono::duration<double>(timeout); |
| |
| { |
| std::unique_lock lock(waitMutex); |
| while (!data->waitPredicate) { |
| if (data->waitCond.wait_until(lock, timeoutTime) == |
| std::cv_status::timeout) { |
| timedOut = true; |
| break; |
| } |
| } |
| } |
| |
| // Cancel our callback |
| SimDIOData[digitalIndex].value.CancelCallback(uid); |
| (void)synchronousInterruptHandles->Free(dataHandle); |
| |
| // Check for what to return |
| if (timedOut) return WaitResult::Timeout; |
| // True => false, Falling |
| if (interrupt->previousState) { |
| // Set our return value and our timestamps |
| interrupt->fallingTimestamp = hal::GetFPGATime(); |
| return 1 << (8 + interrupt->index); |
| } else { |
| interrupt->risingTimestamp = hal::GetFPGATime(); |
| return 1 << (interrupt->index); |
| } |
| } |
| |
| static int64_t WaitForInterruptAnalog(HAL_InterruptHandle handle, |
| Interrupt* interrupt, double timeout, |
| bool ignorePrevious) { |
| auto data = std::make_shared<SynchronousWaitData>(); |
| |
| auto dataHandle = synchronousInterruptHandles->Allocate(data); |
| if (dataHandle == HAL_kInvalidHandle) { |
| // Error allocating data |
| return WaitResult::Timeout; |
| } |
| |
| data->waitPredicate = false; |
| data->interruptHandle = handle; |
| |
| int32_t status = 0; |
| interrupt->previousState = GetAnalogTriggerValue( |
| interrupt->portHandle, interrupt->trigType, &status); |
| |
| if (status != 0) return WaitResult::Timeout; |
| |
| int32_t analogIndex = |
| GetAnalogTriggerInputIndex(interrupt->portHandle, &status); |
| |
| if (status != 0) return WaitResult::Timeout; |
| |
| int32_t uid = SimAnalogInData[analogIndex].voltage.RegisterCallback( |
| &ProcessInterruptAnalogSynchronous, |
| reinterpret_cast<void*>(static_cast<uintptr_t>(dataHandle)), false); |
| |
| bool timedOut = false; |
| |
| wpi::mutex waitMutex; |
| |
| auto timeoutTime = |
| std::chrono::steady_clock::now() + std::chrono::duration<double>(timeout); |
| |
| { |
| std::unique_lock lock(waitMutex); |
| while (!data->waitPredicate) { |
| if (data->waitCond.wait_until(lock, timeoutTime) == |
| std::cv_status::timeout) { |
| timedOut = true; |
| break; |
| } |
| } |
| } |
| |
| // Cancel our callback |
| SimAnalogInData[analogIndex].voltage.CancelCallback(uid); |
| (void)synchronousInterruptHandles->Free(dataHandle); |
| |
| // Check for what to return |
| if (timedOut) return WaitResult::Timeout; |
| // True => false, Falling |
| if (interrupt->previousState) { |
| // Set our return value and our timestamps |
| interrupt->fallingTimestamp = hal::GetFPGATime(); |
| return 1 << (8 + interrupt->index); |
| } else { |
| interrupt->risingTimestamp = hal::GetFPGATime(); |
| return 1 << (interrupt->index); |
| } |
| } |
| |
| int64_t HAL_WaitForInterrupt(HAL_InterruptHandle interruptHandle, |
| double timeout, HAL_Bool ignorePrevious, |
| int32_t* status) { |
| auto interrupt = interruptHandles->Get(interruptHandle); |
| if (interrupt == nullptr) { |
| *status = HAL_HANDLE_ERROR; |
| return WaitResult::Timeout; |
| } |
| |
| // Check to make sure we are actually an interrupt in synchronous mode |
| if (!interrupt->watcher) { |
| *status = NiFpga_Status_InvalidParameter; |
| return WaitResult::Timeout; |
| } |
| |
| if (interrupt->isAnalog) { |
| return WaitForInterruptAnalog(interruptHandle, interrupt.get(), timeout, |
| ignorePrevious); |
| } else { |
| return WaitForInterruptDigital(interruptHandle, interrupt.get(), timeout, |
| ignorePrevious); |
| } |
| } |
| |
| static void ProcessInterruptDigitalAsynchronous(const char* name, void* param, |
| const struct HAL_Value* value) { |
| // void* is a HAL handle |
| // convert to uintptr_t first, then to handle |
| uintptr_t handleTmp = reinterpret_cast<uintptr_t>(param); |
| HAL_InterruptHandle handle = static_cast<HAL_InterruptHandle>(handleTmp); |
| auto interrupt = interruptHandles->Get(handle); |
| if (interrupt == nullptr) return; |
| // Have a valid interrupt |
| if (value->type != HAL_Type::HAL_BOOLEAN) return; |
| bool retVal = value->data.v_boolean; |
| // If no change in interrupt, return; |
| if (retVal == interrupt->previousState) return; |
| int32_t mask = 0; |
| if (interrupt->previousState) { |
| interrupt->previousState = retVal; |
| interrupt->fallingTimestamp = hal::GetFPGATime(); |
| mask = 1 << (8 + interrupt->index); |
| if (!interrupt->fireOnDown) return; |
| } else { |
| interrupt->previousState = retVal; |
| interrupt->risingTimestamp = hal::GetFPGATime(); |
| mask = 1 << (interrupt->index); |
| if (!interrupt->fireOnUp) return; |
| } |
| |
| // run callback |
| auto callback = interrupt->callbackFunction; |
| if (callback == nullptr) return; |
| callback(mask, interrupt->callbackParam); |
| } |
| |
| static void ProcessInterruptAnalogAsynchronous(const char* name, void* param, |
| const struct HAL_Value* value) { |
| // void* is a HAL handle |
| // convert to intptr_t first, then to handle |
| uintptr_t handleTmp = reinterpret_cast<uintptr_t>(param); |
| HAL_InterruptHandle handle = static_cast<HAL_InterruptHandle>(handleTmp); |
| auto interrupt = interruptHandles->Get(handle); |
| if (interrupt == nullptr) return; |
| // Have a valid interrupt |
| if (value->type != HAL_Type::HAL_DOUBLE) return; |
| int32_t status = 0; |
| bool retVal = GetAnalogTriggerValue(interrupt->portHandle, |
| interrupt->trigType, &status); |
| if (status != 0) return; |
| // If no change in interrupt, return; |
| if (retVal == interrupt->previousState) return; |
| int mask = 0; |
| if (interrupt->previousState) { |
| interrupt->previousState = retVal; |
| interrupt->fallingTimestamp = hal::GetFPGATime(); |
| if (!interrupt->fireOnDown) return; |
| mask = 1 << (8 + interrupt->index); |
| } else { |
| interrupt->previousState = retVal; |
| interrupt->risingTimestamp = hal::GetFPGATime(); |
| if (!interrupt->fireOnUp) return; |
| mask = 1 << (interrupt->index); |
| } |
| |
| // run callback |
| auto callback = interrupt->callbackFunction; |
| if (callback == nullptr) return; |
| callback(mask, interrupt->callbackParam); |
| } |
| |
| static void EnableInterruptsDigital(HAL_InterruptHandle handle, |
| Interrupt* interrupt) { |
| int32_t status = 0; |
| int32_t digitalIndex = GetDigitalInputChannel(interrupt->portHandle, &status); |
| if (status != 0) return; |
| |
| interrupt->previousState = SimDIOData[digitalIndex].value; |
| |
| int32_t uid = SimDIOData[digitalIndex].value.RegisterCallback( |
| &ProcessInterruptDigitalAsynchronous, |
| reinterpret_cast<void*>(static_cast<uintptr_t>(handle)), false); |
| interrupt->callbackId = uid; |
| } |
| |
| static void EnableInterruptsAnalog(HAL_InterruptHandle handle, |
| Interrupt* interrupt) { |
| int32_t status = 0; |
| int32_t analogIndex = |
| GetAnalogTriggerInputIndex(interrupt->portHandle, &status); |
| if (status != 0) return; |
| |
| status = 0; |
| interrupt->previousState = GetAnalogTriggerValue( |
| interrupt->portHandle, interrupt->trigType, &status); |
| if (status != 0) return; |
| |
| int32_t uid = SimAnalogInData[analogIndex].voltage.RegisterCallback( |
| &ProcessInterruptAnalogAsynchronous, |
| reinterpret_cast<void*>(static_cast<uintptr_t>(handle)), false); |
| interrupt->callbackId = uid; |
| } |
| |
| void HAL_EnableInterrupts(HAL_InterruptHandle interruptHandle, |
| int32_t* status) { |
| auto interrupt = interruptHandles->Get(interruptHandle); |
| if (interrupt == nullptr) { |
| *status = HAL_HANDLE_ERROR; |
| return; |
| } |
| |
| // If we have not had a callback set, error out |
| if (interrupt->callbackFunction == nullptr) { |
| *status = INCOMPATIBLE_STATE; |
| return; |
| } |
| |
| // EnableInterrupts has already been called |
| if (interrupt->callbackId >= 0) { |
| // We can double enable safely. |
| return; |
| } |
| |
| if (interrupt->isAnalog) { |
| EnableInterruptsAnalog(interruptHandle, interrupt.get()); |
| } else { |
| EnableInterruptsDigital(interruptHandle, interrupt.get()); |
| } |
| } |
| void HAL_DisableInterrupts(HAL_InterruptHandle interruptHandle, |
| int32_t* status) { |
| auto interrupt = interruptHandles->Get(interruptHandle); |
| if (interrupt == nullptr) { |
| *status = HAL_HANDLE_ERROR; |
| return; |
| } |
| |
| // No need to disable if we are already disabled |
| if (interrupt->callbackId < 0) return; |
| |
| if (interrupt->isAnalog) { |
| // Do analog |
| int32_t status = 0; |
| int32_t analogIndex = |
| GetAnalogTriggerInputIndex(interrupt->portHandle, &status); |
| if (status != 0) return; |
| SimAnalogInData[analogIndex].voltage.CancelCallback(interrupt->callbackId); |
| } else { |
| int32_t status = 0; |
| int32_t digitalIndex = |
| GetDigitalInputChannel(interrupt->portHandle, &status); |
| if (status != 0) return; |
| SimDIOData[digitalIndex].value.CancelCallback(interrupt->callbackId); |
| } |
| interrupt->callbackId = -1; |
| } |
| int64_t HAL_ReadInterruptRisingTimestamp(HAL_InterruptHandle interruptHandle, |
| int32_t* status) { |
| auto interrupt = interruptHandles->Get(interruptHandle); |
| if (interrupt == nullptr) { |
| *status = HAL_HANDLE_ERROR; |
| return 0; |
| } |
| |
| return interrupt->risingTimestamp; |
| } |
| int64_t HAL_ReadInterruptFallingTimestamp(HAL_InterruptHandle interruptHandle, |
| int32_t* status) { |
| auto interrupt = interruptHandles->Get(interruptHandle); |
| if (interrupt == nullptr) { |
| *status = HAL_HANDLE_ERROR; |
| return 0; |
| } |
| |
| return interrupt->fallingTimestamp; |
| } |
| void HAL_RequestInterrupts(HAL_InterruptHandle interruptHandle, |
| HAL_Handle digitalSourceHandle, |
| HAL_AnalogTriggerType analogTriggerType, |
| int32_t* status) { |
| auto interrupt = interruptHandles->Get(interruptHandle); |
| if (interrupt == nullptr) { |
| *status = HAL_HANDLE_ERROR; |
| return; |
| } |
| |
| bool routingAnalogTrigger = false; |
| uint8_t routingChannel = 0; |
| uint8_t routingModule = 0; |
| bool success = |
| remapDigitalSource(digitalSourceHandle, analogTriggerType, routingChannel, |
| routingModule, routingAnalogTrigger); |
| if (!success) { |
| *status = HAL_HANDLE_ERROR; |
| return; |
| } |
| |
| interrupt->isAnalog = routingAnalogTrigger; |
| interrupt->trigType = analogTriggerType; |
| interrupt->portHandle = digitalSourceHandle; |
| } |
| void HAL_AttachInterruptHandler(HAL_InterruptHandle interruptHandle, |
| HAL_InterruptHandlerFunction handler, |
| void* param, int32_t* status) { |
| auto interrupt = interruptHandles->Get(interruptHandle); |
| if (interrupt == nullptr) { |
| *status = HAL_HANDLE_ERROR; |
| return; |
| } |
| |
| interrupt->callbackFunction = handler; |
| interrupt->callbackParam = param; |
| } |
| |
| void HAL_AttachInterruptHandlerThreaded(HAL_InterruptHandle interruptHandle, |
| HAL_InterruptHandlerFunction handler, |
| void* param, int32_t* status) { |
| HAL_AttachInterruptHandler(interruptHandle, handler, param, status); |
| } |
| |
| void HAL_SetInterruptUpSourceEdge(HAL_InterruptHandle interruptHandle, |
| HAL_Bool risingEdge, HAL_Bool fallingEdge, |
| int32_t* status) { |
| auto interrupt = interruptHandles->Get(interruptHandle); |
| if (interrupt == nullptr) { |
| *status = HAL_HANDLE_ERROR; |
| return; |
| } |
| |
| interrupt->fireOnDown = fallingEdge; |
| interrupt->fireOnUp = risingEdge; |
| } |
| } // extern "C" |