From b0de0b7386ab58e9ebacedd6b215cc26c455f116 Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Tue, 29 Dec 2015 10:41:31 -0800 Subject: [PATCH] Rewrite C++ Notifier to use HAL multi-notifier support. This removes redundant queue code from the C++ library. The old queue code is still needed by simulation, and as the delta between the simulation and athena headers has grown significantly, this splits the header into two separate files. Change-Id: Ia76b38337a25eb9d4890b3eb9bd76b1cbda7f285 --- wpilibc/Athena/include/Notifier.h | 48 ++++ wpilibc/Athena/src/Notifier.cpp | 213 +++--------------- .../{shared => simulation}/include/Notifier.h | 46 ++-- 3 files changed, 109 insertions(+), 198 deletions(-) create mode 100644 wpilibc/Athena/include/Notifier.h rename wpilibc/{shared => simulation}/include/Notifier.h (50%) diff --git a/wpilibc/Athena/include/Notifier.h b/wpilibc/Athena/include/Notifier.h new file mode 100644 index 0000000000..35dea6873a --- /dev/null +++ b/wpilibc/Athena/include/Notifier.h @@ -0,0 +1,48 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2008. 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 $(WIND_BASE)/WPILib. */ +/*----------------------------------------------------------------------------*/ +#pragma once + +#include "ErrorBase.h" +#include "HAL/cpp/priority_mutex.h" + +typedef void (*TimerEventHandler)(void *param); + +class Notifier : public ErrorBase { + public: + Notifier(TimerEventHandler handler, void *param = nullptr); + virtual ~Notifier(); + + Notifier(const Notifier&) = delete; + Notifier& operator=(const Notifier&) = delete; + + void StartSingle(double delay); + void StartPeriodic(double period); + void Stop(); + + private: + // update the HAL alarm + void UpdateAlarm(); + // HAL callback + static void Notify(uint32_t currentTimeInt, void *param); + + // held while updating process information + priority_mutex m_processMutex; + // HAL handle + void *m_notifier; + // address of the handler + TimerEventHandler m_handler; + // a parameter to pass to the handler + void *m_param; + // the absolute expiration time + double m_expirationTime = 0; + // the relative time (either periodic or single) + double m_period = 0; + // true if this is a periodic event + bool m_periodic = false; + + // held by interrupt manager task while handler call is in progress + priority_mutex m_handlerMutex; +}; diff --git a/wpilibc/Athena/src/Notifier.cpp b/wpilibc/Athena/src/Notifier.cpp index fc3059c41b..893a07e1e7 100644 --- a/wpilibc/Athena/src/Notifier.cpp +++ b/wpilibc/Athena/src/Notifier.cpp @@ -11,12 +11,6 @@ #include "WPIErrors.h" #include "HAL/HAL.hpp" -Notifier *Notifier::timerQueueHead = nullptr; -priority_recursive_mutex Notifier::queueMutex; -priority_mutex Notifier::halMutex; -void *Notifier::m_notifier = nullptr; -std::atomic Notifier::refcount{0}; - /** * Create a Notifier for timer event notification. * @param handler The handler is called at the notification time which is set @@ -27,41 +21,18 @@ Notifier::Notifier(TimerEventHandler handler, void *param) { wpi_setWPIErrorWithContext(NullParameter, "handler must not be nullptr"); m_handler = handler; m_param = param; - // do the first time intialization of static variables - if (refcount.fetch_add(1) == 0) { - int32_t status = 0; - { - std::lock_guard sync(halMutex); - if (!m_notifier) - m_notifier = initializeNotifier(ProcessQueue, nullptr, &status); - } - wpi_setErrorWithContext(status, getHALErrorMessage(status)); - } + int32_t status = 0; + m_notifier = initializeNotifier(&Notifier::Notify, this, &status); + wpi_setErrorWithContext(status, getHALErrorMessage(status)); } /** * Free the resources for a timer event. - * All resources will be freed and the timer event will be removed from the - * queue if necessary. */ Notifier::~Notifier() { - { - std::lock_guard sync(queueMutex); - DeleteFromQueue(); - } - - // Delete the static variables when the last one is going away - if (refcount.fetch_sub(1) == 1) { - int32_t status = 0; - { - std::lock_guard sync(halMutex); - if (m_notifier) { - cleanNotifier(m_notifier, &status); - m_notifier = nullptr; - } - } - wpi_setErrorWithContext(status, getHALErrorMessage(status)); - } + int32_t status = 0; + cleanNotifier(m_notifier, &status); + wpi_setErrorWithContext(status, getHALErrorMessage(status)); // Acquire the mutex; this makes certain that the handler is // not being executed by the interrupt manager. @@ -69,149 +40,35 @@ Notifier::~Notifier() { } /** - * Update the alarm hardware to reflect the current first element in the queue. - * Compute the time the next alarm should occur based on the current time and - * the - * period for the first element in the timer queue. - * WARNING: this method does not do synchronization! It must be called from - * somewhere - * that is taking care of synchronizing access to the queue. + * Update the HAL alarm time. */ void Notifier::UpdateAlarm() { - if (timerQueueHead != nullptr) { - int32_t status = 0; - // This locking is necessary in order to avoid two things: - // 1) Race condition issues with calling cleanNotifer() and - // updateNotifierAlarm() at the same time. - // 2) Avoid deadlock by making it so that this won't block waiting - // for the mutex to unlock. - // Checking refcount as well is unnecessary, but will not hurt. - if (halMutex.try_lock() && refcount != 0) { - if (m_notifier) - updateNotifierAlarm(m_notifier, - (uint32_t)(timerQueueHead->m_expirationTime * 1e6), - &status); - halMutex.unlock(); - } - wpi_setStaticErrorWithContext(timerQueueHead, status, - getHALErrorMessage(status)); - } + int32_t status = 0; + updateNotifierAlarm(m_notifier, (uint32_t)(m_expirationTime * 1e6), &status); + wpi_setErrorWithContext(status, getHALErrorMessage(status)); } /** - * ProcessQueue is called whenever there is a timer interrupt. - * We need to wake up and process the current top item in the timer queue as - * long - * as its scheduled time is after the current time. Then the item is removed or - * rescheduled (repetitive events) in the queue. + * Notify is called by the HAL layer. We simply need to pass it through to + * the user handler. */ -void Notifier::ProcessQueue(uint32_t currentTimeInt, void *params) { - Notifier *current; - while (true) // keep processing past events until no more - { - { - std::lock_guard sync(queueMutex); - double currentTime = currentTimeInt * 1.0e-6; - current = timerQueueHead; - if (current == nullptr || current->m_expirationTime > currentTime) { - break; // no more timer events to process - } - // need to process this entry - timerQueueHead = current->m_nextEvent; - if (current->m_periodic) { - // if periodic, requeue the event - // compute when to put into queue - current->InsertInQueue(true); - } else { - // not periodic; removed from queue - current->m_queued = false; - } - // Take handler mutex while holding queue mutex to make sure - // the handler will execute to completion in case we are being deleted. - current->m_handlerMutex.lock(); - } +void Notifier::Notify(uint32_t currentTimeInt, void *param) { + Notifier* notifier = static_cast(param); - current->m_handler(current->m_param); // call the event handler - current->m_handlerMutex.unlock(); + notifier->m_processMutex.lock(); + if (notifier->m_periodic) { + notifier->m_expirationTime += notifier->m_period; + notifier->UpdateAlarm(); } - // reschedule the first item in the queue - std::lock_guard sync(queueMutex); - UpdateAlarm(); -} -/** - * Insert this Notifier into the timer queue in right place. - * WARNING: this method does not do synchronization! It must be called from - * somewhere - * that is taking care of synchronizing access to the queue. - * @param reschedule If false, the scheduled alarm is based on the current time - * and UpdateAlarm - * method is called which will enable the alarm if necessary. - * If true, update the time by adding the period (no drift) when rescheduled - * periodic from ProcessQueue. - * This ensures that the public methods only update the queue after finishing - * inserting. - */ -void Notifier::InsertInQueue(bool reschedule) { - if (reschedule) { - m_expirationTime += m_period; - } else { - m_expirationTime = GetClock() + m_period; - } - if (m_expirationTime > Timer::kRolloverTime) { - m_expirationTime -= Timer::kRolloverTime; - } - if (timerQueueHead == nullptr || - timerQueueHead->m_expirationTime >= this->m_expirationTime) { - // the queue is empty or greater than the new entry - // the new entry becomes the first element - this->m_nextEvent = timerQueueHead; - timerQueueHead = this; - if (!reschedule) { - // since the first element changed, update alarm, unless we already plan - // to - UpdateAlarm(); - } - } else { - for (Notifier **npp = &(timerQueueHead->m_nextEvent);; - npp = &(*npp)->m_nextEvent) { - Notifier *n = *npp; - if (n == nullptr || n->m_expirationTime > this->m_expirationTime) { - *npp = this; - this->m_nextEvent = n; - break; - } - } - } - m_queued = true; -} + auto handler = notifier->m_handler; + auto hparam = notifier->m_param; -/** - * Delete this Notifier from the timer queue. - * WARNING: this method does not do synchronization! It must be called from - * somewhere - * that is taking care of synchronizing access to the queue. - * Remove this Notifier from the timer queue and adjust the next interrupt time - * to reflect - * the current top of the queue. - */ -void Notifier::DeleteFromQueue() { - if (m_queued) { - m_queued = false; - wpi_assert(timerQueueHead != nullptr); - if (timerQueueHead == this) { - // remove the first item in the list - update the alarm - timerQueueHead = this->m_nextEvent; - UpdateAlarm(); - } else { - for (Notifier *n = timerQueueHead; n != nullptr; n = n->m_nextEvent) { - if (n->m_nextEvent == this) { - // this element is the next element from *n from the queue - n->m_nextEvent = this->m_nextEvent; // point around this one - } - } - } - } + notifier->m_handlerMutex.lock(); + notifier->m_processMutex.unlock(); + + if (handler) handler(hparam); + notifier->m_handlerMutex.unlock(); } /** @@ -220,11 +77,11 @@ void Notifier::DeleteFromQueue() { * @param delay Seconds to wait before the handler is called. */ void Notifier::StartSingle(double delay) { - std::lock_guard sync(queueMutex); + std::lock_guard sync(m_processMutex); m_periodic = false; m_period = delay; - DeleteFromQueue(); - InsertInQueue(false); + m_expirationTime = GetClock() + m_period; + UpdateAlarm(); } /** @@ -236,11 +93,11 @@ void Notifier::StartSingle(double delay) { * the call to this method. */ void Notifier::StartPeriodic(double period) { - std::lock_guard sync(queueMutex); + std::lock_guard sync(m_processMutex); m_periodic = true; m_period = period; - DeleteFromQueue(); - InsertInQueue(false); + m_expirationTime = GetClock() + m_period; + UpdateAlarm(); } /** @@ -253,10 +110,10 @@ void Notifier::StartPeriodic(double period) { * block until the handler call is complete. */ void Notifier::Stop() { - { - std::lock_guard sync(queueMutex); - DeleteFromQueue(); - } + int32_t status = 0; + stopNotifierAlarm(m_notifier, &status); + wpi_setErrorWithContext(status, getHALErrorMessage(status)); + // Wait for a currently executing handler to complete before returning from // Stop() std::lock_guard sync(m_handlerMutex); diff --git a/wpilibc/shared/include/Notifier.h b/wpilibc/simulation/include/Notifier.h similarity index 50% rename from wpilibc/shared/include/Notifier.h rename to wpilibc/simulation/include/Notifier.h index 056ed0a3fd..452f20859d 100644 --- a/wpilibc/shared/include/Notifier.h +++ b/wpilibc/simulation/include/Notifier.h @@ -1,6 +1,5 @@ /*----------------------------------------------------------------------------*/ -/* Copyright (c) FIRST 2008. All Rights Reserved. - */ +/* Copyright (c) FIRST 2008. 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 $(WIND_BASE)/WPILib. */ /*----------------------------------------------------------------------------*/ @@ -32,26 +31,33 @@ class Notifier : public ErrorBase { static void *m_notifier; static std::atomic refcount; - static void ProcessQueue( - uint32_t mask, void *params); // process the timer queue on a timer event - static void - UpdateAlarm(); // update the FPGA alarm since the queue has changed - void InsertInQueue( - bool reschedule); // insert this Notifier in the timer queue - void DeleteFromQueue(); // delete this Notifier from the timer queue - TimerEventHandler m_handler; // address of the handler - void *m_param; // a parameter to pass to the handler - double m_period = 0; // the relative time (either periodic or single) - double m_expirationTime = 0; // absolute expiration time for the current event - Notifier *m_nextEvent = nullptr; // next Nofifier event - bool m_periodic = false; // true if this is a periodic event - bool m_queued = false; // indicates if this entry is queued - priority_mutex m_handlerMutex; // held by interrupt manager task while - // handler call is in progress + // process the timer queue on a timer event + static void ProcessQueue(uint32_t mask, void *params); + // update the FPGA alarm since the queue has changed + static void UpdateAlarm(); + // insert this Notifier in the timer queue + void InsertInQueue(bool reschedule); + // delete this Notifier from the timer queue + void DeleteFromQueue(); + + // address of the handler + TimerEventHandler m_handler; + // a parameter to pass to the handler + void *m_param; + // the relative time (either periodic or single) + double m_period = 0; + // absolute expiration time for the current event + double m_expirationTime = 0; + // next Nofifier event + Notifier *m_nextEvent = nullptr; + // true if this is a periodic event + bool m_periodic = false; + // indicates if this entry is queued + bool m_queued = false; + // held by interrupt manager task while handler call is in progress + priority_mutex m_handlerMutex; -#ifdef FRC_SIMULATOR static std::thread m_task; static std::atomic m_stopped; -#endif static void Run(); };