/*----------------------------------------------------------------------------*/ /* Copyright (c) FIRST 2016. 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/Notifier.h" // For std::atexit() #include #include #include #include #include "HAL/ChipObject.h" #include "HAL/Errors.h" #include "HAL/HAL.h" #include "HAL/cpp/make_unique.h" #include "HAL/cpp/priority_mutex.h" #include "HAL/handles/UnlimitedHandleResource.h" #include "support/SafeThread.h" using namespace hal; static const int32_t kTimerInterruptNumber = 28; static priority_mutex notifierInterruptMutex; static priority_recursive_mutex notifierMutex; static std::unique_ptr notifierAlarm; static std::unique_ptr notifierManager; static uint64_t closestTrigger = UINT64_MAX; namespace { struct Notifier { std::shared_ptr prev, next; void* param; HAL_NotifierProcessFunction process; uint64_t triggerTime = UINT64_MAX; HAL_NotifierHandle handle; bool threaded; }; // Safe thread to allow callbacks to run on their own thread class NotifierThread : public wpi::SafeThread { public: void Main() { std::unique_lock lock(m_mutex); while (m_active) { m_cond.wait(lock, [&] { return !m_active || m_notify; }); if (!m_active) break; m_notify = false; uint64_t currentTime = m_currentTime; HAL_NotifierHandle handle = m_handle; HAL_NotifierProcessFunction process = m_process; lock.unlock(); // don't hold mutex during callback execution process(currentTime, handle); lock.lock(); } } bool m_notify = false; HAL_NotifierHandle m_handle = HAL_kInvalidHandle; HAL_NotifierProcessFunction m_process; uint64_t m_currentTime; }; class NotifierThreadOwner : public wpi::SafeThreadOwner { public: void SetFunc(HAL_NotifierProcessFunction process, void* param) { auto thr = GetThread(); if (!thr) return; thr->m_process = process; m_param = param; } void Notify(uint64_t currentTime, HAL_NotifierHandle handle) { auto thr = GetThread(); if (!thr) return; thr->m_currentTime = currentTime; thr->m_handle = handle; thr->m_notify = true; thr->m_cond.notify_one(); } void* m_param; }; } // namespace static std::shared_ptr notifiers; static std::atomic_flag notifierAtexitRegistered = ATOMIC_FLAG_INIT; static std::atomic_int notifierRefCount{0}; using namespace hal; static UnlimitedHandleResource notifierHandles; // internal version of updateAlarm used during the alarmCallback when we know // that the pointer is a valid pointer. void updateNotifierAlarmInternal(std::shared_ptr notifierPointer, uint64_t triggerTime, int32_t* status) { std::lock_guard sync(notifierMutex); auto notifier = notifierPointer; // no need for a null check, as this must always be a valid pointer. notifier->triggerTime = triggerTime; bool wasActive = (closestTrigger != UINT64_MAX); if (!notifierInterruptMutex.try_lock() || notifierRefCount == 0 || !notifierAlarm) return; // Update alarm time if closer than current. if (triggerTime < closestTrigger) { closestTrigger = triggerTime; // Simply truncate the hardware trigger time to 32-bit. notifierAlarm->writeTriggerTime(static_cast(triggerTime), status); } // Enable the alarm. The hardware disables itself after each alarm. if (!wasActive) notifierAlarm->writeEnable(true, status); notifierInterruptMutex.unlock(); } static void alarmCallback(uint32_t, void*) { std::unique_lock sync(notifierMutex); int32_t status = 0; uint64_t currentTime = 0; // the hardware disables itself after each alarm closestTrigger = UINT64_MAX; // process all notifiers std::shared_ptr notifier = notifiers; while (notifier) { if (notifier->triggerTime != UINT64_MAX) { if (currentTime == 0) currentTime = HAL_GetFPGATime(&status); if (notifier->triggerTime < currentTime) { notifier->triggerTime = UINT64_MAX; auto process = notifier->process; auto handle = notifier->handle; sync.unlock(); process(currentTime, handle); sync.lock(); } else if (notifier->triggerTime < closestTrigger) { updateNotifierAlarmInternal(notifier, notifier->triggerTime, &status); } } notifier = notifier->next; } } static void cleanupNotifierAtExit() { notifierAlarm = nullptr; notifierManager = nullptr; } static void threadedNotifierHandler(uint64_t currentTimeInt, HAL_NotifierHandle handle) { // Grab notifier and get handler param auto notifier = notifierHandles.Get(handle); if (!notifier) return; auto notifierPointer = notifier->param; if (notifierPointer == nullptr) return; NotifierThreadOwner* owner = static_cast(notifierPointer); owner->Notify(currentTimeInt, handle); } extern "C" { HAL_NotifierHandle HAL_InitializeNotifier(HAL_NotifierProcessFunction process, void* param, int32_t* status) { if (!process) { *status = NULL_PARAMETER; return 0; } if (!notifierAtexitRegistered.test_and_set()) std::atexit(cleanupNotifierAtExit); if (notifierRefCount.fetch_add(1) == 0) { std::lock_guard sync(notifierInterruptMutex); // create manager and alarm if not already created if (!notifierManager) { notifierManager = std::make_unique( 1 << kTimerInterruptNumber, false, status); notifierManager->registerHandler(alarmCallback, nullptr, status); notifierManager->enable(status); } if (!notifierAlarm) notifierAlarm.reset(tAlarm::create(status)); } std::lock_guard sync(notifierMutex); std::shared_ptr notifier = std::make_shared(); HAL_NotifierHandle handle = notifierHandles.Allocate(notifier); if (handle == HAL_kInvalidHandle) { *status = HAL_HANDLE_ERROR; return HAL_kInvalidHandle; } // create notifier structure and add to list notifier->next = notifiers; if (notifier->next) notifier->next->prev = notifier; notifier->param = param; notifier->process = process; notifier->handle = handle; notifier->threaded = false; notifiers = notifier; return handle; } HAL_NotifierHandle HAL_InitializeNotifierThreaded( HAL_NotifierProcessFunction process, void* param, int32_t* status) { NotifierThreadOwner* notify = new NotifierThreadOwner; notify->Start(); notify->SetFunc(process, param); auto notifierHandle = HAL_InitializeNotifier(threadedNotifierHandler, notify, status); if (notifierHandle == HAL_kInvalidHandle || *status != 0) { delete notify; return HAL_kInvalidHandle; } auto notifier = notifierHandles.Get(notifierHandle); if (!notifier) { return HAL_kInvalidHandle; } notifier->threaded = true; return notifierHandle; } void HAL_CleanNotifier(HAL_NotifierHandle notifierHandle, int32_t* status) { { std::lock_guard sync(notifierMutex); auto notifier = notifierHandles.Get(notifierHandle); if (!notifier) return; // remove from list if (notifier->prev) notifier->prev->next = notifier->next; if (notifier->next) notifier->next->prev = notifier->prev; if (notifiers == notifier) notifiers = notifier->next; notifierHandles.Free(notifierHandle); if (notifier->threaded) { NotifierThreadOwner* owner = static_cast(notifier->param); delete owner; } } if (notifierRefCount.fetch_sub(1) == 1) { std::lock_guard sync(notifierInterruptMutex); // if this was the last notifier, clean up alarm and manager if (notifierAlarm) { notifierAlarm->writeEnable(false, status); notifierAlarm = nullptr; } if (notifierManager) { notifierManager->disable(status); notifierManager = nullptr; } closestTrigger = UINT64_MAX; } } void* HAL_GetNotifierParam(HAL_NotifierHandle notifierHandle, int32_t* status) { auto notifier = notifierHandles.Get(notifierHandle); if (!notifier) return nullptr; if (notifier->threaded) { // If threaded, return thread param rather then notifier param NotifierThreadOwner* owner = static_cast(notifier->param); return owner->m_param; } return notifier->param; } void HAL_UpdateNotifierAlarm(HAL_NotifierHandle notifierHandle, uint64_t triggerTime, int32_t* status) { std::lock_guard sync(notifierMutex); auto notifier = notifierHandles.Get(notifierHandle); if (!notifier) return; updateNotifierAlarmInternal(notifier, triggerTime, status); } void HAL_StopNotifierAlarm(HAL_NotifierHandle notifierHandle, int32_t* status) { std::lock_guard sync(notifierMutex); auto notifier = notifierHandles.Get(notifierHandle); if (!notifier) return; notifier->triggerTime = UINT64_MAX; } } // extern "C"