Skip to content
Merged
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
138 changes: 77 additions & 61 deletions v6/modules/interrupts.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,83 @@ import hal;
*/

namespace hal::inline v6 {
export template<std::size_t SlotCount>
class edge_triggered_interrupt_dispatcher;

/**
* @brief One subscriber's view of a pin shared by
* `hal::edge_triggered_interrupt_dispatcher`
* @ingroup Interrupt_Utilities
*
* Only a dispatcher can create slots; get them from its `operator[]`. Every
* dispatcher, whatever its slot count, uses this one type.
*
* A slot starts with the trigger setting `edge_trigger::rising`. Configuring
* a slot changes only which edges reach its subscriber, not the real pin.
*/
export class edge_triggered_interrupt_slot final
: public edge_triggered_interrupt
{
public:
edge_triggered_interrupt_slot(edge_triggered_interrupt_slot const&) = delete;
edge_triggered_interrupt_slot& operator=(
edge_triggered_interrupt_slot const&) = delete;
edge_triggered_interrupt_slot(edge_triggered_interrupt_slot&&) = delete;
edge_triggered_interrupt_slot& operator=(edge_triggered_interrupt_slot&&) =
delete;
~edge_triggered_interrupt_slot() = default;

private:
template<std::size_t SlotCount>
friend class edge_triggered_interrupt_dispatcher;

edge_triggered_interrupt_slot() = default;

void dispatch(bool p_state) noexcept
{
// Read the callback once: a subscriber can be removed between edges.
auto* const callback = m_callback;
if (callback == nullptr) {
return;
}
if (m_trigger == edge_trigger::both ||
(m_trigger == edge_trigger::rising && p_state) ||
(m_trigger == edge_trigger::falling && not p_state)) {
callback->callback(p_state);
}
}

void driver_configure(settings const& p_settings) override
{
m_trigger = p_settings.trigger;
}

void driver_on_trigger(edge_triggered_callback& p_callback) override
{
if (m_callback != nullptr) {
throw subscription_busy(this);
}
m_callback = &p_callback;
}

void driver_clear_trigger() noexcept override
{
m_callback = nullptr;
}

edge_triggered_callback* m_callback = nullptr;
edge_trigger m_trigger = edge_trigger::rising;
};

/**
* @brief Shares one edge triggered interrupt among several subscribers
* @ingroup Interrupt_Utilities
*
* `hal::edge_triggered_interrupt` accepts one subscriber at a time. The
* dispatcher subscribes once to a real pin and provides `SlotCount` slots.
* Each slot is a `hal::edge_triggered_interrupt` with its own subscriber and
* its own trigger setting.
* Each slot is a `hal::edge_triggered_interrupt_slot`, a
* `hal::edge_triggered_interrupt` with its own subscriber and its own trigger
* setting.
*
* The dispatcher configures the real pin to trigger on both edges. When an
* edge occurs, it calls, in slot order, each slot's subscriber whose trigger
Expand All @@ -59,61 +128,6 @@ class edge_triggered_interrupt_dispatcher final
: private edge_triggered_callback
{
public:
/**
* @brief One subscriber's view of the shared pin
*
* A slot starts with the trigger setting `edge_trigger::rising`. Configuring
* a slot changes only which edges reach its subscriber, not the real pin.
*/
class slot final : public edge_triggered_interrupt
{
public:
slot() = default;
slot(slot const&) = delete;
slot& operator=(slot const&) = delete;
slot(slot&&) = delete;
slot& operator=(slot&&) = delete;
~slot() = default;

private:
friend class edge_triggered_interrupt_dispatcher;

void dispatch(bool p_state) noexcept
{
// Read the callback once: a subscriber can be removed between edges.
auto* const callback = m_callback;
if (callback == nullptr) {
return;
}
if (m_trigger == edge_trigger::both ||
(m_trigger == edge_trigger::rising && p_state) ||
(m_trigger == edge_trigger::falling && not p_state)) {
callback->callback(p_state);
}
}

void driver_configure(settings const& p_settings) override
{
m_trigger = p_settings.trigger;
}

void driver_on_trigger(edge_triggered_callback& p_callback) override
{
if (m_callback != nullptr) {
throw subscription_busy(this);
}
m_callback = &p_callback;
}

void driver_clear_trigger() noexcept override
{
m_callback = nullptr;
}

edge_triggered_callback* m_callback = nullptr;
edge_trigger m_trigger = edge_trigger::rising;
};

/**
* @brief Subscribe to a pin and provide its slots
*
Expand Down Expand Up @@ -148,10 +162,12 @@ public:
* @brief Access a slot
*
* @param p_index - index of the slot
* @return slot& - the slot, which must not outlive the dispatcher
* @return edge_triggered_interrupt_slot& - the slot, which must not outlive
* the dispatcher
* @throws hal::out_of_range - if p_index is not less than SlotCount
*/
[[nodiscard]] slot& operator[](std::size_t p_index) & [[clang::lifetimebound]]
[[nodiscard]] edge_triggered_interrupt_slot& operator[](
std::size_t p_index) & [[clang::lifetimebound]]
{
if (p_index >= SlotCount) {
throw hal::out_of_range(this,
Expand All @@ -161,7 +177,7 @@ public:
}

/// A slot of a temporary dispatcher would dangle immediately.
slot& operator[](std::size_t p_index) && = delete;
edge_triggered_interrupt_slot& operator[](std::size_t p_index) && = delete;

/// @return std::size_t - number of slots
[[nodiscard]] static constexpr std::size_t size() noexcept
Expand All @@ -183,7 +199,7 @@ private:
}
}

std::array<slot, SlotCount> m_slots{};
std::array<edge_triggered_interrupt_slot, SlotCount> m_slots{};
// Last, so the real pin stops calling the dispatcher before the slots are
// destroyed.
subscription m_subscription;
Expand Down
7 changes: 7 additions & 0 deletions v6/tests/compile_checks/dispatcher_rejects.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ void copy_dispatcher(fake_pin& p_pin)
dispatcher copy = shared;
}

// A slot only works inside a dispatcher, so only a dispatcher creates one.
void construct_edge_slot()
{
// expected-error@+1 {{private constructor}}
hal::edge_triggered_interrupt_slot slot;
}

using can_dispatcher = hal::can_interrupt_dispatcher<2>;

void index_temporary_can_dispatcher(fake_can_interrupt& p_interrupt)
Expand Down
14 changes: 13 additions & 1 deletion v6/tests/interrupts.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.

#include <cstddef>
#include <optional>
#include <type_traits>
#include <utility>
#include <vector>

#include <boost/ut.hpp>
Expand All @@ -21,6 +24,14 @@ import hal;
import hal.util;

namespace {

// Every dispatcher hands out the same slot type, whatever its slot count.
template<std::size_t SlotCount>
using slot_of = std::remove_reference_t<
decltype(std::declval<
hal::edge_triggered_interrupt_dispatcher<SlotCount>&>()[0])>;
static_assert(std::is_same_v<slot_of<2>, hal::edge_triggered_interrupt_slot>);
static_assert(std::is_same_v<slot_of<7>, hal::edge_triggered_interrupt_slot>);
struct fake_pin final : hal::edge_triggered_interrupt
{
void trigger(bool p_state)
Expand Down Expand Up @@ -167,7 +178,8 @@ void edge_triggered_interrupt_dispatcher_test()
hal::edge_triggered_interrupt_dispatcher<2> dispatcher(pin);

// Exercise + Verify
expect(throws<hal::out_of_range>([&]() { (void)dispatcher[2]; }));
expect(
throws<hal::out_of_range>([&]() { static_cast<void>(dispatcher[2]); }));
expect(that % dispatcher.size() == 2);
};
}
Expand Down
Loading