Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions cores/arduino/PendSV.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#include "PendSV.h"

#include <Arduino.h>
#include <limits.h>

namespace {
PendSV pendSv;
}

PendSV &PendSV::instance() {
return pendSv;
}

uint32_t PendSV::enterCritical() {
const uint32_t primask = __get_PRIMASK();
__disable_irq();
return primask;
}

void PendSV::exitCritical(uint32_t primask) {
__set_PRIMASK(primask);
}

bool PendSV::registerService(uint8_t serviceId, ServiceFn fn, void *context) {
if (serviceId >= kMaxServices || fn == nullptr)
return false;

const uint32_t primask = enterCritical();
pendingCount_[serviceId] = 0;
pendingMask_ &= ~(1u << serviceId);
services_[serviceId].fn = fn;
services_[serviceId].context = context;
exitCritical(primask);

return true;
}

void PendSV::clearService(uint8_t serviceId) {
if (serviceId >= kMaxServices)
return;

const uint32_t primask = enterCritical();
services_[serviceId].fn = nullptr;
services_[serviceId].context = nullptr;
pendingCount_[serviceId] = 0;
pendingMask_ &= ~(1u << serviceId);
exitCritical(primask);
}

void PendSV::setPending(uint8_t serviceId) {
if (serviceId >= kMaxServices)
return;

const uint32_t primask = enterCritical();
uint16_t &pendingCount = pendingCount_[serviceId];
if (pendingCount < UINT16_MAX)
++pendingCount;
pendingMask_ |= (1u << serviceId);
exitCritical(primask);

__DMB();
SCB->ICSR = SCB_ICSR_PENDSVSET_Msk;
}

void PendSV::dispatchPending() {
// Bound one PendSV entry so high-rate producers do not monopolize return to
// thread mode. Remaining work re-pends PendSV below.
uint8_t dispatched = 0;

while (dispatched < kDispatchBudget) {
uint32_t primask = enterCritical();
const uint32_t pending = pendingMask_;
if (pending == 0) {
exitCritical(primask);
return;
}

const uint8_t serviceId = static_cast<uint8_t>(__builtin_ctz(pending));
uint16_t &pendingCount = pendingCount_[serviceId];

if (pendingCount == 0) {
// Defensive scrub in case mask and count drift out of sync.
pendingMask_ &= ~(1u << serviceId);
exitCritical(primask);
continue;
}

--pendingCount;
if (pendingCount == 0)
pendingMask_ &= ~(1u << serviceId);

ServiceEntry entry = services_[serviceId];
exitCritical(primask);

// Pending work without a registered service is intentionally dropped.
// Producers are expected to register before calling setPending(), and
// clearService() cancels queued work for that service.
if (entry.fn != nullptr)
entry.fn(serviceId, entry.context);

++dispatched;
}

const uint32_t primask = enterCritical();
const bool hasRemaining = (pendingMask_ != 0);
exitCritical(primask);

if (hasRemaining) {
__DMB();
SCB->ICSR = SCB_ICSR_PENDSVSET_Msk;
}
}

extern "C" void PendSV_Handler(void) {
PendSV::instance().dispatchPending();
}
119 changes: 119 additions & 0 deletions cores/arduino/PendSV.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#pragma once

#include "PendSVChannelMap.h"
#include "sam.h"

#include <array>
#include <stdint.h>

#if defined(SERCOM0) || defined(SERCOM0_REGS)
#define PENDSV_HAS_SERCOM0 true
#else
#define PENDSV_HAS_SERCOM0 false
#endif
#if defined(SERCOM1) || defined(SERCOM1_REGS)
#define PENDSV_HAS_SERCOM1 true
#else
#define PENDSV_HAS_SERCOM1 false
#endif
#if defined(SERCOM2) || defined(SERCOM2_REGS)
#define PENDSV_HAS_SERCOM2 true
#else
#define PENDSV_HAS_SERCOM2 false
#endif
#if defined(SERCOM3) || defined(SERCOM3_REGS)
#define PENDSV_HAS_SERCOM3 true
#else
#define PENDSV_HAS_SERCOM3 false
#endif
#if defined(SERCOM4) || defined(SERCOM4_REGS)
#define PENDSV_HAS_SERCOM4 true
#else
#define PENDSV_HAS_SERCOM4 false
#endif
#if defined(SERCOM5) || defined(SERCOM5_REGS)
#define PENDSV_HAS_SERCOM5 true
#else
#define PENDSV_HAS_SERCOM5 false
#endif
#if defined(SERCOM6) || defined(SERCOM6_REGS)
#define PENDSV_HAS_SERCOM6 true
#else
#define PENDSV_HAS_SERCOM6 false
#endif
#if defined(SERCOM7) || defined(SERCOM7_REGS)
#define PENDSV_HAS_SERCOM7 true
#else
#define PENDSV_HAS_SERCOM7 false
#endif

#if defined(USB) || defined(USB_REGS)
#define PENDSV_HAS_USB true
#else
#define PENDSV_HAS_USB false
#endif

#if defined(GMAC) || defined(GMAC_REGS)
#define PENDSV_HAS_GMAC true
#else
#define PENDSV_HAS_GMAC false
#endif
#if defined(ADC) || defined(ADC_REGS) || defined(ADC0) || defined(ADC0_REGS)
#define PENDSV_HAS_ADC true
#else
#define PENDSV_HAS_ADC false
#endif
#if defined(AES) || defined(AES_REGS)
#define PENDSV_HAS_AES true
#else
#define PENDSV_HAS_AES false
#endif
#if defined(PUKCC) || defined(PUKCC_REGS) || defined(ID_PUKCC) || \
defined(PUKCC_INSTANCE_ID) || defined(PUKCC_IRQn)
#define PENDSV_HAS_PUKCC true
#else
#define PENDSV_HAS_PUKCC false
#endif
#if defined(TRNG) || defined(TRNG_REGS)
#define PENDSV_HAS_TRNG true
#else
#define PENDSV_HAS_TRNG false
#endif

using PendSVChannels = PendSVChannelMap<
PENDSV_HAS_SERCOM0, PENDSV_HAS_SERCOM1, PENDSV_HAS_SERCOM2,
PENDSV_HAS_SERCOM3, PENDSV_HAS_SERCOM4, PENDSV_HAS_SERCOM5,
PENDSV_HAS_SERCOM6, PENDSV_HAS_SERCOM7, PENDSV_HAS_USB, PENDSV_HAS_GMAC,
PENDSV_HAS_ADC, PENDSV_HAS_AES, PENDSV_HAS_PUKCC, PENDSV_HAS_TRNG>;

class PendSV {
public:
using ServiceFn = void (*)(uint8_t serviceId, void *context);
static constexpr uint8_t kMaxServices = PendSVChannels::kMaxServices;
static constexpr uint8_t kDispatchBudget = 16;
static_assert(kMaxServices <= 32, "PendSV pending mask supports at most 32 services");
static_assert(PendSVChannels::Count <= kMaxServices,
"PendSV channel allocation exceeds dispatcher capacity");

static PendSV &instance();

bool registerService(uint8_t serviceId, ServiceFn fn, void *context = nullptr);
// Cancels queued work that has not started dispatching. If a callback was
// already copied for dispatch, its context must remain valid until it returns.
void clearService(uint8_t serviceId);
void dispatchPending();
void setPending(uint8_t serviceId);

private:
static uint32_t enterCritical();
static void exitCritical(uint32_t primask);

struct ServiceEntry {
ServiceFn fn = nullptr;
void *context = nullptr;
};

std::array<ServiceEntry, kMaxServices> services_{};
std::array<uint16_t, kMaxServices> pendingCount_{};
volatile uint32_t pendingMask_ = 0;
};
57 changes: 57 additions & 0 deletions cores/arduino/PendSVChannelMap.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#pragma once

#include <stdint.h>

template <bool HasSercom0, bool HasSercom1, bool HasSercom2, bool HasSercom3,
bool HasSercom4, bool HasSercom5, bool HasSercom6, bool HasSercom7,
bool HasUsb, bool HasGmac, bool HasAdc, bool HasAes, bool HasPukcc,
bool HasTrng>
struct PendSVChannelMap {
static constexpr uint8_t kMaxServices = 32;
static constexpr uint8_t kUnavailable = 0xFF;

private:
static constexpr uint8_t assign(bool present, uint8_t next) {
return present ? next : kUnavailable;
}

static constexpr uint8_t advance(bool present, uint8_t next) {
return present ? static_cast<uint8_t>(next + 1) : next;
}

static constexpr uint8_t kBase = 0;
static constexpr uint8_t kAfterSercom0 = advance(HasSercom0, kBase);
static constexpr uint8_t kAfterSercom1 = advance(HasSercom1, kAfterSercom0);
static constexpr uint8_t kAfterSercom2 = advance(HasSercom2, kAfterSercom1);
static constexpr uint8_t kAfterSercom3 = advance(HasSercom3, kAfterSercom2);
static constexpr uint8_t kAfterSercom4 = advance(HasSercom4, kAfterSercom3);
static constexpr uint8_t kAfterSercom5 = advance(HasSercom5, kAfterSercom4);
static constexpr uint8_t kAfterSercom6 = advance(HasSercom6, kAfterSercom5);
static constexpr uint8_t kAfterSercom7 = advance(HasSercom7, kAfterSercom6);
static constexpr uint8_t kAfterUsb = advance(HasUsb, kAfterSercom7);
static constexpr uint8_t kAfterGmac = advance(HasGmac, kAfterUsb);
static constexpr uint8_t kAfterAdc = advance(HasAdc, kAfterGmac);
static constexpr uint8_t kAfterAes = advance(HasAes, kAfterAdc);
static constexpr uint8_t kAfterPukcc = advance(HasPukcc, kAfterAes);

public:
static constexpr uint8_t Sercom0 = assign(HasSercom0, kBase);
static constexpr uint8_t Sercom1 = assign(HasSercom1, kAfterSercom0);
static constexpr uint8_t Sercom2 = assign(HasSercom2, kAfterSercom1);
static constexpr uint8_t Sercom3 = assign(HasSercom3, kAfterSercom2);
static constexpr uint8_t Sercom4 = assign(HasSercom4, kAfterSercom3);
static constexpr uint8_t Sercom5 = assign(HasSercom5, kAfterSercom4);
static constexpr uint8_t Sercom6 = assign(HasSercom6, kAfterSercom5);
static constexpr uint8_t Sercom7 = assign(HasSercom7, kAfterSercom6);
static constexpr uint8_t Usb = assign(HasUsb, kAfterSercom7);
static constexpr uint8_t Gmac = assign(HasGmac, kAfterUsb);
static constexpr uint8_t Adc = assign(HasAdc, kAfterGmac);
static constexpr uint8_t Aes = assign(HasAes, kAfterAdc);
static constexpr uint8_t Pukcc = assign(HasPukcc, kAfterAes);
static constexpr uint8_t Trng = assign(HasTrng, kAfterPukcc);
static constexpr uint8_t Count = advance(HasTrng, kAfterPukcc);

static constexpr bool isAvailable(uint8_t channel) {
return channel != kUnavailable;
}
};
Loading
Loading