Files
allwpilib/hal/lib/athena/Notifier.cpp
Thad House c57a7f0a41 Switches all notifiers created with the external API to be threaded (#546)
Testing showed this wasn't an issue with timing, and allows for more
safety in user code making mistakes. Places where the extra thread
wouldn't help have been kept non threaded, using a new internal API.
2017-06-30 18:58:58 -07:00

301 lines
9.5 KiB
C++

/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2016-2017. 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 <cstdlib>
#include <atomic>
#include <memory>
#include <mutex>
#include "HAL/ChipObject.h"
#include "HAL/Errors.h"
#include "HAL/HAL.h"
#include "HAL/cpp/NotifierInternal.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 hal::priority_mutex notifierInterruptMutex;
static priority_recursive_mutex notifierMutex;
static std::unique_ptr<tAlarm> notifierAlarm;
static std::unique_ptr<tInterruptManager> notifierManager;
static uint64_t closestTrigger = UINT64_MAX;
namespace {
struct Notifier {
std::shared_ptr<Notifier> 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<std::mutex> 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<NotifierThread> {
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<Notifier> notifiers;
static std::atomic_flag notifierAtexitRegistered = ATOMIC_FLAG_INIT;
static std::atomic_int notifierRefCount{0};
using namespace hal;
static UnlimitedHandleResource<HAL_NotifierHandle, Notifier,
HAL_HandleEnum::Notifier>
notifierHandles;
// internal version of updateAlarm used during the alarmCallback when we know
// that the pointer is a valid pointer.
void updateNotifierAlarmInternal(std::shared_ptr<Notifier> notifierPointer,
uint64_t triggerTime, int32_t* status) {
std::lock_guard<priority_recursive_mutex> 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<uint32_t>(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<priority_recursive_mutex> 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> 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<NotifierThreadOwner*>(notifierPointer);
owner->Notify(currentTimeInt, handle);
}
extern "C" {
HAL_NotifierHandle HAL_InitializeNotifierNonThreadedUnsafe(
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<hal::priority_mutex> sync(notifierInterruptMutex);
// create manager and alarm if not already created
if (!notifierManager) {
notifierManager = std::make_unique<tInterruptManager>(
1 << kTimerInterruptNumber, false, status);
notifierManager->registerHandler(alarmCallback, nullptr, status);
notifierManager->enable(status);
}
if (!notifierAlarm) notifierAlarm.reset(tAlarm::create(status));
}
std::lock_guard<priority_recursive_mutex> sync(notifierMutex);
std::shared_ptr<Notifier> notifier = std::make_shared<Notifier>();
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_InitializeNotifier(HAL_NotifierProcessFunction process,
void* param, int32_t* status) {
NotifierThreadOwner* notify = new NotifierThreadOwner;
notify->Start();
notify->SetFunc(process, param);
auto notifierHandle = HAL_InitializeNotifierNonThreadedUnsafe(
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<priority_recursive_mutex> 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<NotifierThreadOwner*>(notifier->param);
delete owner;
}
}
if (notifierRefCount.fetch_sub(1) == 1) {
std::lock_guard<hal::priority_mutex> 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<NotifierThreadOwner*>(notifier->param);
return owner->m_param;
}
return notifier->param;
}
void HAL_UpdateNotifierAlarm(HAL_NotifierHandle notifierHandle,
uint64_t triggerTime, int32_t* status) {
std::lock_guard<priority_recursive_mutex> 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<priority_recursive_mutex> sync(notifierMutex);
auto notifier = notifierHandles.Get(notifierHandle);
if (!notifier) return;
notifier->triggerTime = UINT64_MAX;
}
} // extern "C"