mirror of
https://github.com/halleysfifthinc/AVCLAN-Mockingboard.git
synced 2026-08-11 08:22:52 +00:00
Redesign (simplify) top-level avclan interface to use expected/nullable unique_ptr
- Peripheral::read/send now return `expected<unique_ptr<Frame>, Error>` for a unified success/error interface. - Peripheral::route returns `expected<unique_ptr<Frame>, Error>` to distinguish intent: (intentional) non-response vs unable to respond - Other functions with optional message semantics (handle,poll,react) return a unique_ptr whose ownership-state indicates response intent (i.e. send new message) Bundled necessary changes: - Switch Frame allocation from global to local (enabled via member function new/delete) - Add new header-library tl::expected to shim avr-libstdcpp Unrelated: Defensive `continue` added after `route`, to reduce Bus activity check latency Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ set(CMAKE_CXX_STANDARD 23)
|
|||||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
set(BUILD_TESTING OFF CACHE BOOL "" FORCE)
|
||||||
|
|
||||||
add_compile_options(
|
add_compile_options(
|
||||||
-Wall -Wswitch-enum -Werror
|
-Wall -Wswitch-enum -Werror
|
||||||
@@ -76,6 +77,75 @@ target_include_directories(avclan PUBLIC
|
|||||||
# flashing target. Added after the targets above so it can extend them.
|
# flashing target. Added after the targets above so it can extend them.
|
||||||
add_subdirectory(src/avclan/target/${AVCLAN_TARGET})
|
add_subdirectory(src/avclan/target/${AVCLAN_TARGET})
|
||||||
|
|
||||||
|
# --- Polyfill headers for std facilities the toolchain may lack -------------
|
||||||
|
# tl::expected / tl::optional back-fill std::expected / std::optional (with
|
||||||
|
# C++23 monadic ops) when the build's stdlib predates them. Runs after the port
|
||||||
|
# subdirectory to provide a port-supplied freestanding stdlib via
|
||||||
|
# CMAKE_REQUIRED_INCLUDES, if necessary.
|
||||||
|
include(CheckCXXSourceCompiles)
|
||||||
|
include(FetchContent)
|
||||||
|
find_program(GIT_EXECUTABLE git REQUIRED)
|
||||||
|
|
||||||
|
if(TARGET libstdcpp)
|
||||||
|
get_target_property(_stdlib_incs libstdcpp INTERFACE_INCLUDE_DIRECTORIES)
|
||||||
|
set(CMAKE_REQUIRED_INCLUDES ${_stdlib_incs})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
check_cxx_source_compiles("
|
||||||
|
#include <version>
|
||||||
|
#if !defined(__cpp_lib_expected) || __cpp_lib_expected < 202211L
|
||||||
|
#error no std::expected
|
||||||
|
#endif
|
||||||
|
int main() { return 0; }" HAVE_STD_EXPECTED)
|
||||||
|
# check_cxx_source_compiles("
|
||||||
|
# #include <version>
|
||||||
|
# #if !defined(__cpp_lib_optional) || __cpp_lib_optional < 202110L
|
||||||
|
# #error no C++23 std::optional
|
||||||
|
# #endif
|
||||||
|
# int main() { return 0; }" HAVE_STD_OPTIONAL)
|
||||||
|
unset(CMAKE_REQUIRED_INCLUDES)
|
||||||
|
|
||||||
|
if(NOT HAVE_STD_EXPECTED)
|
||||||
|
FetchContent_Declare(
|
||||||
|
tl_expected
|
||||||
|
GIT_REPOSITORY https://github.com/TartanLlama/expected.git
|
||||||
|
GIT_TAG 1770e3559f2f6ea4a5fb4f577ad22aeb30fbd8e4
|
||||||
|
PATCH_COMMAND ${CMAKE_COMMAND}
|
||||||
|
-DGIT_EXECUTABLE=${GIT_EXECUTABLE}
|
||||||
|
-DPATCH_FILE=${CMAKE_CURRENT_SOURCE_DIR}/cmake/tl-expected-no-exception-header.patch
|
||||||
|
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/apply-patch.cmake
|
||||||
|
SYSTEM
|
||||||
|
)
|
||||||
|
set(EXPECTED_BUILD_TESTS OFF)
|
||||||
|
set(EXPECTED_BUILD_PACKAGE OFF)
|
||||||
|
FetchContent_MakeAvailable(tl_expected)
|
||||||
|
add_library(tl_expected_hdr INTERFACE)
|
||||||
|
target_include_directories(tl_expected_hdr
|
||||||
|
INTERFACE ${tl_expected_SOURCE_DIR}/include)
|
||||||
|
target_link_libraries(avclan PUBLIC tl_expected_hdr)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# std/tl::optional not currently used
|
||||||
|
# if(NOT HAVE_STD_OPTIONAL)
|
||||||
|
# FetchContent_Declare(
|
||||||
|
# tl_optional
|
||||||
|
# GIT_REPOSITORY https://github.com/TartanLlama/optional.git
|
||||||
|
# GIT_TAG 3a1209de8370bf5fe16362934956144b49591565
|
||||||
|
# PATCH_COMMAND ${CMAKE_COMMAND}
|
||||||
|
# -DGIT_EXECUTABLE=${GIT_EXECUTABLE}
|
||||||
|
# -DPATCH_FILE=${CMAKE_CURRENT_SOURCE_DIR}/cmake/tl-optional-no-exception-header.patch
|
||||||
|
# -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/apply-patch.cmake
|
||||||
|
# SYSTEM
|
||||||
|
# )
|
||||||
|
# set(OPTIONAL_BUILD_TESTS OFF)
|
||||||
|
# set(OPTIONAL_BUILD_PACKAGE OFF)
|
||||||
|
# FetchContent_MakeAvailable(tl_optional)
|
||||||
|
# add_library(tl_optional_hdr INTERFACE)
|
||||||
|
# target_include_directories(tl_optional_hdr
|
||||||
|
# INTERFACE ${tl_optional_SOURCE_DIR}/include)
|
||||||
|
# target_link_libraries(avclan PUBLIC tl_optional_hdr)
|
||||||
|
# endif()
|
||||||
|
|
||||||
add_executable(mockingboard
|
add_executable(mockingboard
|
||||||
src/sniffer.cc
|
src/sniffer.cc
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Idempotent patch driver for FetchContent PATCH_COMMAND steps.
|
||||||
|
#
|
||||||
|
# Usage: cmake -DGIT_EXECUTABLE=<git> -DPATCH_FILE=<patch> -P apply-patch.cmake
|
||||||
|
# (run with CWD = the populated source directory, as PATCH_COMMAND does).
|
||||||
|
#
|
||||||
|
# FetchContent re-runs PATCH_COMMAND after its update step on reconfigure, so a
|
||||||
|
# bare `git apply` fails the second time around. Skip if the patch is already
|
||||||
|
# applied (the reverse-apply dry run succeeds); otherwise apply it for real.
|
||||||
|
execute_process(
|
||||||
|
COMMAND "${GIT_EXECUTABLE}" apply --reverse --check --ignore-whitespace
|
||||||
|
"${PATCH_FILE}"
|
||||||
|
RESULT_VARIABLE _already_applied
|
||||||
|
OUTPUT_QUIET ERROR_QUIET)
|
||||||
|
if(NOT _already_applied EQUAL 0)
|
||||||
|
execute_process(
|
||||||
|
COMMAND "${GIT_EXECUTABLE}" apply --ignore-whitespace "${PATCH_FILE}"
|
||||||
|
COMMAND_ERROR_IS_FATAL ANY)
|
||||||
|
endif()
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
diff --git a/include/tl/expected.hpp b/include/tl/expected.hpp
|
||||||
|
index 59e59aa..75857bd 100644
|
||||||
|
--- a/include/tl/expected.hpp
|
||||||
|
+++ b/include/tl/expected.hpp
|
||||||
|
@@ -20,7 +20,7 @@
|
||||||
|
#define TL_EXPECTED_VERSION_MINOR 3
|
||||||
|
#define TL_EXPECTED_VERSION_PATCH 1
|
||||||
|
|
||||||
|
-#include <exception>
|
||||||
|
+#include <cstdlib>
|
||||||
|
#include <functional>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
@@ -222,7 +222,7 @@ static constexpr unexpect_t unexpect{};
|
||||||
|
#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED
|
||||||
|
#define TL_EXPECTED_THROW_EXCEPTION(e) throw((e));
|
||||||
|
#else
|
||||||
|
-#define TL_EXPECTED_THROW_EXCEPTION(e) std::terminate();
|
||||||
|
+#define TL_EXPECTED_THROW_EXCEPTION(e) std::abort();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
@@ -1249,11 +1249,11 @@ template <class T, class E> struct expected_default_ctor_base<T, E, false> {
|
||||||
|
};
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
-template <class E> class bad_expected_access : public std::exception {
|
||||||
|
+template <class E> class bad_expected_access {
|
||||||
|
public:
|
||||||
|
explicit bad_expected_access(E e) : m_val(std::move(e)) {}
|
||||||
|
|
||||||
|
- virtual const char *what() const noexcept override {
|
||||||
|
+ virtual const char *what() const noexcept {
|
||||||
|
return "Bad expected access";
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
diff --git a/include/tl/optional.hpp b/include/tl/optional.hpp
|
||||||
|
index e9c59c2..1ff3d4a 100644
|
||||||
|
--- a/include/tl/optional.hpp
|
||||||
|
+++ b/include/tl/optional.hpp
|
||||||
|
@@ -21,7 +21,6 @@
|
||||||
|
#define TL_OPTIONAL_VERSION_MINOR 1
|
||||||
|
#define TL_OPTIONAL_VERSION_PATCH 0
|
||||||
|
|
||||||
|
-#include <exception>
|
||||||
|
#include <functional>
|
||||||
|
#include <new>
|
||||||
|
#include <type_traits>
|
||||||
|
@@ -664,7 +663,7 @@ struct nullopt_t {
|
||||||
|
static constexpr nullopt_t nullopt{nullopt_t::do_not_use{},
|
||||||
|
nullopt_t::do_not_use{}};
|
||||||
|
|
||||||
|
-class bad_optional_access : public std::exception {
|
||||||
|
+class bad_optional_access {
|
||||||
|
public:
|
||||||
|
bad_optional_access() = default;
|
||||||
|
const char *what() const noexcept { return "Optional has no value"; }
|
||||||
@@ -121,6 +121,12 @@ enum AVCLAN_ENUM_CLASS MediaAction : uint8_t {
|
|||||||
};
|
};
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
enum class Device : uint8_t;
|
||||||
|
|
||||||
|
// Sentinel for a frame with no owning device.
|
||||||
|
// Compile-time collision check lives in device.hpp
|
||||||
|
inline constexpr Device NoDevice = Device{0xFF};
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
struct Error {
|
struct Error {
|
||||||
#endif
|
#endif
|
||||||
@@ -140,6 +146,7 @@ struct Error {
|
|||||||
STARTBIT_TOO_SHORT,
|
STARTBIT_TOO_SHORT,
|
||||||
STARTBIT_TOO_LONG,
|
STARTBIT_TOO_LONG,
|
||||||
BAD_STARTBIT,
|
BAD_STARTBIT,
|
||||||
|
POOL_EMPTY, // non-bus error
|
||||||
};
|
};
|
||||||
|
|
||||||
enum AVCLAN_ENUM_CLASS Send : uint8_t {
|
enum AVCLAN_ENUM_CLASS Send : uint8_t {
|
||||||
@@ -160,6 +167,12 @@ struct Error {
|
|||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct SendError {
|
||||||
|
Device owning_device;
|
||||||
|
uint8_t reaction;
|
||||||
|
Error::Send err;
|
||||||
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
enum AVCLAN_ENUM_CLASS Bit : uint8_t {
|
enum AVCLAN_ENUM_CLASS Bit : uint8_t {
|
||||||
|
|||||||
+17
-3
@@ -31,11 +31,14 @@
|
|||||||
|
|
||||||
#include <concepts>
|
#include <concepts>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <memory>
|
||||||
|
#include <new>
|
||||||
|
|
||||||
#include "avclan.h"
|
#include "avclan.h"
|
||||||
#include "bus.hpp"
|
#include "bus.hpp"
|
||||||
#include "frame.hpp"
|
#include "frame.hpp"
|
||||||
#include "hal/phy.h"
|
#include "hal/phy.h"
|
||||||
|
#include "stdshim.hpp"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
using Read = avclan::detail::Error::Read;
|
using Read = avclan::detail::Error::Read;
|
||||||
@@ -191,7 +194,8 @@ void Bus::mute(bool mute) {
|
|||||||
muted_ = mute; // Only update muted_ *AFTER* hardware has finished muting
|
muted_ = mute; // Only update muted_ *AFTER* hardware has finished muting
|
||||||
};
|
};
|
||||||
|
|
||||||
auto Bus::read(uint16_t address, Frame *in, Frame::Print print) -> Read {
|
auto Bus::read(uint16_t address, Frame::Print print)
|
||||||
|
-> expected<std::unique_ptr<Frame>, Error::Read> {
|
||||||
struct errtype {
|
struct errtype {
|
||||||
Read errno;
|
Read errno;
|
||||||
uint16_t val;
|
uint16_t val;
|
||||||
@@ -199,6 +203,12 @@ auto Bus::read(uint16_t address, Frame *in, Frame::Print print) -> Read {
|
|||||||
|
|
||||||
using enum Read;
|
using enum Read;
|
||||||
|
|
||||||
|
std::unique_ptr<Frame> in(new (std::nothrow) Frame);
|
||||||
|
if (!in) {
|
||||||
|
err.errno = POOL_EMPTY;
|
||||||
|
goto handle_err;
|
||||||
|
}
|
||||||
|
|
||||||
{ // bound handle lifetime
|
{ // bound handle lifetime
|
||||||
auto handle = get();
|
auto handle = get();
|
||||||
|
|
||||||
@@ -277,6 +287,7 @@ auto Bus::read(uint16_t address, Frame *in, Frame::Print print) -> Read {
|
|||||||
handle_err:;
|
handle_err:;
|
||||||
fputs("ERR(read): ", stdout);
|
fputs("ERR(read): ", stdout);
|
||||||
switch (err.errno) {
|
switch (err.errno) {
|
||||||
|
case POOL_EMPTY: puts("failed Frame alloc"); break;
|
||||||
case BAD_STARTBIT: fputs("bad start bit (other)", stdout); break;
|
case BAD_STARTBIT: fputs("bad start bit (other)", stdout); break;
|
||||||
case STARTBIT_TOO_SHORT: fputs("bad start bit (short)", stdout); break;
|
case STARTBIT_TOO_SHORT: fputs("bad start bit (short)", stdout); break;
|
||||||
case STARTBIT_TOO_LONG: fputs("bad start bit (long)", stdout); break;
|
case STARTBIT_TOO_LONG: fputs("bad start bit (long)", stdout); break;
|
||||||
@@ -297,7 +308,7 @@ auto Bus::read(uint16_t address, Frame *in, Frame::Print print) -> Read {
|
|||||||
printf("; read 0x%02X", err.val);
|
printf("; read 0x%02X", err.val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
putchar('\n');
|
puts(":");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only print if some data has been correctly received
|
// Only print if some data has been correctly received
|
||||||
@@ -307,7 +318,10 @@ auto Bus::read(uint16_t address, Frame *in, Frame::Print print) -> Read {
|
|||||||
in->print(print);
|
in->print(print);
|
||||||
}
|
}
|
||||||
|
|
||||||
return err.errno;
|
if (err.errno != Read{0})
|
||||||
|
return unexpected(err.errno);
|
||||||
|
|
||||||
|
return in;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Bus::send(const Frame *out, Frame::Print print) -> Send {
|
auto Bus::send(const Frame *out, Frame::Print print) -> Send {
|
||||||
|
|||||||
+4
-1
@@ -48,9 +48,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "avclan.h"
|
#include "avclan.h"
|
||||||
#include "frame.hpp"
|
#include "frame.hpp"
|
||||||
|
#include "stdshim.hpp"
|
||||||
|
|
||||||
namespace avclan {
|
namespace avclan {
|
||||||
|
|
||||||
@@ -77,7 +79,8 @@ public:
|
|||||||
void measure();
|
void measure();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Error::Read read(uint16_t address, Frame *in, Frame::Print print);
|
expected<std::unique_ptr<Frame>, Error::Read> read(uint16_t address,
|
||||||
|
Frame::Print print);
|
||||||
Error::Send send(const Frame *out, Frame::Print print);
|
Error::Send send(const Frame *out, Frame::Print print);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
+21
-9
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "avclan.h"
|
#include "avclan.h"
|
||||||
#include "cdchanger.hpp"
|
#include "cdchanger.hpp"
|
||||||
@@ -263,18 +264,22 @@ void CDChanger::handle(const Frame *in, Frame *out) {
|
|||||||
#pragma GCC diagnostic pop
|
#pragma GCC diagnostic pop
|
||||||
}
|
}
|
||||||
|
|
||||||
void CDChanger::react(Frame *out, detail::Error::Send err) {
|
std::unique_ptr<Frame> CDChanger::react(
|
||||||
auto resp = static_cast<reaction_t>(out->reaction);
|
expected<std::unique_ptr<Frame>, detail::SendError> exp) {
|
||||||
out->reaction = r_Nothing;
|
|
||||||
switch (resp) {
|
if (!exp) {
|
||||||
case r_StateReport:
|
if (exp.error().reaction == to_underlying(r_StateReport) &&
|
||||||
if (err == detail::Error::Send::NAK_ADDRESS &&
|
exp.error().err == detail::Error::Send::NAK_ADDRESS &&
|
||||||
++failedStatusReports > 1) {
|
++failedStatusReports > 1) {
|
||||||
failedStatusReports = 0;
|
failedStatusReports = 0;
|
||||||
stopPlaying(); // Disable periodic updates if e.g. no-one's
|
stopPlaying(); // Disable periodic updates if e.g. no-one's
|
||||||
// listening (car was turned off?)
|
// listening (car was turned off?)
|
||||||
}
|
}
|
||||||
break;
|
} else {
|
||||||
|
auto out = std::move(exp.value());
|
||||||
|
auto resp = static_cast<reaction_t>(out->reaction);
|
||||||
|
out->reaction = r_Nothing;
|
||||||
|
switch (resp) {
|
||||||
case r_Ejection: {
|
case r_Ejection: {
|
||||||
const uint8_t play[] = {0x00,
|
const uint8_t play[] = {0x00,
|
||||||
to_underlying(Device::COMM_CTRL),
|
to_underlying(Device::COMM_CTRL),
|
||||||
@@ -319,10 +324,17 @@ void CDChanger::react(Frame *out, detail::Error::Send err) {
|
|||||||
generateStatus(out, false, Device::STATUS);
|
generateStatus(out, false, Device::STATUS);
|
||||||
out->reaction = r_SendOnly;
|
out->reaction = r_SendOnly;
|
||||||
break;
|
break;
|
||||||
|
case r_StateReport: [[fallthrough]];
|
||||||
case r_SendOnly: [[fallthrough]];
|
case r_SendOnly: [[fallthrough]];
|
||||||
case r_Nothing: [[fallthrough]];
|
case r_Nothing: [[fallthrough]];
|
||||||
default: out->reaction = r_Nothing;
|
default: break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (out->reaction > r_Nothing)
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void CDChanger::enable(Frame *out) {
|
void CDChanger::enable(Frame *out) {
|
||||||
@@ -339,11 +351,11 @@ void CDChanger::enable(Frame *out) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool CDChanger::pending() { return cdtimer_pending(); }
|
bool CDChanger::pending() { return cdtimer_pending(); }
|
||||||
void CDChanger::resolvepending() { cdtimer_clear(); }
|
|
||||||
|
|
||||||
void CDChanger::emit(Frame *out) {
|
void CDChanger::emit(Frame *out) {
|
||||||
generateStatus(out, false, Device::STATUS);
|
generateStatus(out, false, Device::STATUS);
|
||||||
out->reaction = r_StateReport;
|
out->reaction = r_StateReport;
|
||||||
|
cdtimer_clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CDChanger::isPlaying() const { return playing; }
|
bool CDChanger::isPlaying() const { return playing; }
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "avclan.h"
|
#include "avclan.h"
|
||||||
#include "device.hpp"
|
#include "device.hpp"
|
||||||
@@ -61,11 +62,11 @@ public:
|
|||||||
void init();
|
void init();
|
||||||
|
|
||||||
void handle(const Frame *in, Frame *out);
|
void handle(const Frame *in, Frame *out);
|
||||||
void react(Frame *out, detail::Error::Send err);
|
std::unique_ptr<Frame>
|
||||||
|
react(expected<std::unique_ptr<Frame>, detail::SendError> exp);
|
||||||
void enable(Frame *out);
|
void enable(Frame *out);
|
||||||
void disable(Frame *out);
|
void disable(Frame *out);
|
||||||
static bool pending();
|
static bool pending();
|
||||||
static void resolvepending();
|
|
||||||
void emit(Frame *out);
|
void emit(Frame *out);
|
||||||
void incrementTime();
|
void incrementTime();
|
||||||
bool isPlaying() const;
|
bool isPlaying() const;
|
||||||
|
|||||||
+12
-6
@@ -5,9 +5,11 @@
|
|||||||
|
|
||||||
#include <concepts>
|
#include <concepts>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "avclan.h"
|
#include "avclan.h"
|
||||||
#include "frame.hpp"
|
#include "frame.hpp"
|
||||||
|
#include "stdshim.hpp"
|
||||||
|
|
||||||
namespace avclan {
|
namespace avclan {
|
||||||
|
|
||||||
@@ -46,15 +48,19 @@ enum class Device : uint8_t {
|
|||||||
};
|
};
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
concept DeviceInterface = requires {
|
concept DeviceInterface =
|
||||||
std::integral_constant<Device, T::id>{};
|
requires { std::integral_constant<Device, T::id>{}; } &&
|
||||||
} && requires(T dev, const Frame *in, Frame *out, detail::Error::Send err) {
|
requires(T dev, const Frame *in, Frame *out,
|
||||||
|
expected<std::unique_ptr<Frame>, detail::SendError> exp) {
|
||||||
dev.init();
|
dev.init();
|
||||||
dev.handle(in, out);
|
dev.handle(in, out);
|
||||||
dev.enable(out);
|
dev.enable(out);
|
||||||
dev.react(out, err);
|
{
|
||||||
|
dev.react(std::move(exp))
|
||||||
|
} -> std::same_as<std::unique_ptr<Frame>>;
|
||||||
|
|
||||||
{ dev.pending() } -> std::convertible_to<bool>;
|
{ dev.pending() } -> std::convertible_to<bool>;
|
||||||
dev.resolvepending();
|
// Devices must clear `pending()` after `emit()` is called
|
||||||
dev.emit(out);
|
dev.emit(out);
|
||||||
};
|
};
|
||||||
} // namespace avclan
|
} // namespace avclan
|
||||||
|
|||||||
@@ -9,12 +9,62 @@
|
|||||||
|
|
||||||
#include "frame.hpp"
|
#include "frame.hpp"
|
||||||
|
|
||||||
|
#if defined(AVCLAN_FRAME_POOL_N)
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <limits>
|
||||||
|
#include <new>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
template <class T, std::uint8_t N>
|
||||||
|
requires(N >= 1 && N <= std::numeric_limits<uint8_t>::max())
|
||||||
|
class Pool {
|
||||||
|
public:
|
||||||
|
constexpr Pool() {
|
||||||
|
for (uint8_t i = 0; i < N; ++i)
|
||||||
|
ptrs_[i] = &storage_[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
T *acquire() {
|
||||||
|
if (top_ == 0)
|
||||||
|
return nullptr;
|
||||||
|
return ptrs_[--top_];
|
||||||
|
}
|
||||||
|
|
||||||
|
void release(T *ptr) {
|
||||||
|
// Properly would need an origin check/confirmation if this was used more
|
||||||
|
// generally
|
||||||
|
ptrs_[top_++] = ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::array<T, N> storage_;
|
||||||
|
std::array<T *, N> ptrs_;
|
||||||
|
uint8_t top_ = N;
|
||||||
|
};
|
||||||
|
|
||||||
|
Pool<avclan::Frame, AVCLAN_FRAME_POOL_N> pool;
|
||||||
|
} // namespace
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
using Error = avclan::detail::Error;
|
using Error = avclan::detail::Error;
|
||||||
using enum Error::Parse;
|
using enum Error::Parse;
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace avclan {
|
namespace avclan {
|
||||||
|
|
||||||
|
#if defined(AVCLAN_FRAME_POOL_N)
|
||||||
|
void *Frame::operator new(std::size_t /*count*/,
|
||||||
|
const std::nothrow_t & /*tag*/) noexcept {
|
||||||
|
return pool.acquire();
|
||||||
|
}
|
||||||
|
// NOLINTNEXTLINE(misc-new-delete-overloads) false-positive
|
||||||
|
void Frame::operator delete(void *ptr) noexcept {
|
||||||
|
pool.release(static_cast<Frame *>(ptr));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void Frame::print(Frame::Print print) const {
|
void Frame::print(Frame::Print print) const {
|
||||||
if (print.binary) {
|
if (print.binary) {
|
||||||
uint8_t buffer[8];
|
uint8_t buffer[8];
|
||||||
|
|||||||
+19
-3
@@ -9,6 +9,11 @@
|
|||||||
|
|
||||||
#include "avclan.h"
|
#include "avclan.h"
|
||||||
|
|
||||||
|
#if defined(AVCLAN_FRAME_POOL_N)
|
||||||
|
#include <cstddef>
|
||||||
|
#include <new>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace avclan {
|
namespace avclan {
|
||||||
enum class Device : uint8_t;
|
enum class Device : uint8_t;
|
||||||
|
|
||||||
@@ -25,13 +30,24 @@ struct Frame {
|
|||||||
Error::Parse parse(const uint8_t *bytes, uint8_t len);
|
Error::Parse parse(const uint8_t *bytes, uint8_t len);
|
||||||
void print(Print print) const;
|
void print(Print print) const;
|
||||||
|
|
||||||
uint8_t reaction;
|
#if defined(AVCLAN_FRAME_POOL_N)
|
||||||
Device owning_device;
|
// O(1) heapless pooled allocation. Only `new (std::nothrow) Frame` is
|
||||||
|
// supported.
|
||||||
|
static void *operator new(std::size_t) = delete;
|
||||||
|
static void *operator new(std::size_t, const std::nothrow_t &) noexcept;
|
||||||
|
// NOLINTNEXTLINE(misc-new-delete-overloads) false-positive
|
||||||
|
static void operator delete(void *) noexcept;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// reaction has a Device-defined interpretation, with the sole invariant that
|
||||||
|
// 0 == inactive/non-sendable frame
|
||||||
|
uint8_t reaction = 0;
|
||||||
|
Device owning_device = NoDevice;
|
||||||
bool is_unicast;
|
bool is_unicast;
|
||||||
uint16_t controller_addr; // formerly "master"
|
uint16_t controller_addr; // formerly "master"
|
||||||
uint16_t peripheral_addr; // formerly "slave"
|
uint16_t peripheral_addr; // formerly "slave"
|
||||||
uint8_t control = 0xF;
|
uint8_t control = 0xF;
|
||||||
uint8_t length;
|
uint8_t length = 0;
|
||||||
uint8_t data[MAXLENGTH];
|
uint8_t data[MAXLENGTH];
|
||||||
};
|
};
|
||||||
} // namespace avclan
|
} // namespace avclan
|
||||||
|
|||||||
+81
-41
@@ -4,16 +4,20 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
#include <new>
|
||||||
|
#include <tuple>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include "avclan.h"
|
#include "avclan.h"
|
||||||
#include "bus.hpp"
|
#include "bus.hpp"
|
||||||
#include "device.hpp"
|
#include "device.hpp"
|
||||||
#include "frame.hpp"
|
#include "frame.hpp"
|
||||||
|
#include "stdshim.hpp"
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstring>
|
|
||||||
#include <tuple>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
namespace avclan {
|
namespace avclan {
|
||||||
|
|
||||||
@@ -25,6 +29,14 @@ template <DeviceInterface... Devs> class Peripheral {
|
|||||||
public:
|
public:
|
||||||
using Error = detail::Error;
|
using Error = detail::Error;
|
||||||
|
|
||||||
|
// lack of static reflection until C++26 limits our methods of doing a
|
||||||
|
// compile-time collision check. Within Peripheral, we can at least assert no
|
||||||
|
// collision with Devices *used in a particular instantiation* but it is not a
|
||||||
|
// universal check against all Device enum values
|
||||||
|
static_assert(((Devs::id != NoDevice) && ...),
|
||||||
|
"a registered Device id collides with the NoDevice sentinel; "
|
||||||
|
"update the sentinel value in avclan.h");
|
||||||
|
|
||||||
Peripheral(Bus &bus, uint16_t address) : bus{bus}, address_{address} {
|
Peripheral(Bus &bus, uint16_t address) : bus{bus}, address_{address} {
|
||||||
bus.init();
|
bus.init();
|
||||||
(std::get<Devs>(devices_).init(), ...);
|
(std::get<Devs>(devices_).init(), ...);
|
||||||
@@ -43,32 +55,46 @@ public:
|
|||||||
Bus &get_bus() { return bus; }
|
Bus &get_bus() { return bus; }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Error::Read read(Frame *in, Frame::Print print) {
|
expected<std::unique_ptr<Frame>, Error::Read>
|
||||||
return bus.read(address_, in, print);
|
read(Frame::Print print = Frame::Print{}) {
|
||||||
|
return bus.read(address_, print);
|
||||||
};
|
};
|
||||||
Error::Send send(Frame *out, Frame::Print print) {
|
|
||||||
|
expected<std::unique_ptr<Frame>, detail::SendError>
|
||||||
|
send(std::unique_ptr<Frame> out, Frame::Print print = Frame::Print{}) {
|
||||||
// To "forge" a controller_addr, instantiate a new/different Peripheral
|
// To "forge" a controller_addr, instantiate a new/different Peripheral
|
||||||
stamp<Sender>(out);
|
stamp<Sender>(out.get());
|
||||||
out->control = 0xF;
|
out->control = 0xF;
|
||||||
return bus.send(out, print);
|
auto err = bus.send(out.get(), print);
|
||||||
|
if (err != Error::Send{0})
|
||||||
|
return unexpected{
|
||||||
|
detail::SendError{out->owning_device, out->reaction, err}};
|
||||||
|
|
||||||
|
return out;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define PACK3(a, b, c) (((uint32_t)(a) << 16) | ((uint32_t)(b) << 8) | (c))
|
#define PACK3(a, b, c) (((uint32_t)(a) << 16) | ((uint32_t)(b) << 8) | (c))
|
||||||
|
|
||||||
void route(const Frame *in, Frame *out) {
|
// expected needed to distinguish don't vs can't respond
|
||||||
|
expected<std::unique_ptr<Frame>, Error::Read> route(const Frame *in) {
|
||||||
using enum Device;
|
using enum Device;
|
||||||
using enum Action;
|
using enum Action;
|
||||||
out->reaction = 0;
|
|
||||||
|
|
||||||
if (is_muted() || in->length < 3)
|
if (is_muted() || in->length < 3)
|
||||||
return;
|
return {};
|
||||||
|
|
||||||
|
std::unique_ptr<Frame> out(new (std::nothrow) Frame);
|
||||||
|
if (!out) {
|
||||||
|
puts("!! failed Frame alloc in route !!");
|
||||||
|
return unexpected{Error::Read::POOL_EMPTY};
|
||||||
|
}
|
||||||
|
|
||||||
// 0xFF placeholders are variant bytes filled by writing directly to
|
// 0xFF placeholders are variant bytes filled by writing directly to
|
||||||
// out->data[N] after memcpy.
|
// out->data[N] after memcpy.
|
||||||
static const uint8_t lancheck_resp[] = {0x00, to_underlying(COMM_CTRL),
|
static const uint8_t lancheck_resp[] = {0x00, to_underlying(COMM_CTRL),
|
||||||
to_underlying(LAN), 0xFF, 0xFF};
|
to_underlying(LAN), 0xFF, 0xFF};
|
||||||
|
|
||||||
stamp<Recipient>(out);
|
stamp<Recipient>(out.get());
|
||||||
|
|
||||||
const uint8_t *data = in->data;
|
const uint8_t *data = in->data;
|
||||||
const uint8_t b0 = *data++;
|
const uint8_t b0 = *data++;
|
||||||
@@ -114,7 +140,7 @@ public:
|
|||||||
to_underlying(Advertise_Function)): {
|
to_underlying(Advertise_Function)): {
|
||||||
auto enable_d = [](auto &d, auto &out) { d.enable(out); };
|
auto enable_d = [](auto &d, auto &out) { d.enable(out); };
|
||||||
((Devs::id == static_cast<Device>(b3)
|
((Devs::id == static_cast<Device>(b3)
|
||||||
? originate(std::get<Devs>(devices_), out, enable_d)
|
? originate(std::get<Devs>(devices_), out.get(), enable_d)
|
||||||
: void()),
|
: void()),
|
||||||
...);
|
...);
|
||||||
break;
|
break;
|
||||||
@@ -135,7 +161,7 @@ public:
|
|||||||
case PACK3(COMMUNICATION_V2, COMM_CTRL,
|
case PACK3(COMMUNICATION_V2, COMM_CTRL,
|
||||||
to_underlying(List_Functions_Req)): {
|
to_underlying(List_Functions_Req)): {
|
||||||
controller_ = in->controller_addr;
|
controller_ = in->controller_addr;
|
||||||
stamp<Recipient>(out); // re-stamp now that controller_ is known
|
stamp<Recipient>(out.get()); // re-stamp now that controller_ is known
|
||||||
out->is_unicast = true;
|
out->is_unicast = true;
|
||||||
const uint8_t list_functions_resp[] = {
|
const uint8_t list_functions_resp[] = {
|
||||||
0x00, to_underlying(COMM_CTRL), from,
|
0x00, to_underlying(COMM_CTRL), from,
|
||||||
@@ -151,40 +177,54 @@ public:
|
|||||||
} else if (in->peripheral_addr == address_ && b0 == 0x00) {
|
} else if (in->peripheral_addr == address_ && b0 == 0x00) {
|
||||||
auto handle_d = [&](auto &d, auto &out) { d.handle(in, out); };
|
auto handle_d = [&](auto &d, auto &out) { d.handle(in, out); };
|
||||||
((Devs::id == static_cast<Device>(b2)
|
((Devs::id == static_cast<Device>(b2)
|
||||||
? originate(std::get<Devs>(devices_), out, handle_d)
|
? originate(std::get<Devs>(devices_), out.get(), handle_d)
|
||||||
: void()),
|
: void()),
|
||||||
...);
|
...);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (out->reaction > 0)
|
||||||
|
return out;
|
||||||
|
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef PACK3
|
#undef PACK3
|
||||||
|
|
||||||
void react(Frame *out, Error::Send err) {
|
std::unique_ptr<Frame>
|
||||||
if (((Devs::id == out->owning_device) || ...))
|
react(expected<std::unique_ptr<Frame>, detail::SendError> exp) {
|
||||||
((Devs::id == out->owning_device
|
const Device from =
|
||||||
? void(std::get<Devs>(devices_).react(out, err))
|
exp ? exp.value()->owning_device : exp.error().owning_device;
|
||||||
: void()),
|
|
||||||
|
std::unique_ptr<Frame> next;
|
||||||
|
((Devs::id == from &&
|
||||||
|
(next = std::get<Devs>(devices_).react(std::move(exp)))) ||
|
||||||
...);
|
...);
|
||||||
else
|
return next;
|
||||||
out->reaction = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool pending() const { return (std::get<Devs>(devices_).pending() || ...); }
|
|
||||||
|
|
||||||
// Service ready devices in round-robin order
|
// Service ready devices in round-robin order
|
||||||
bool emit(Frame *out) {
|
std::unique_ptr<Frame> poll() {
|
||||||
auto does_emit = [&](DeviceInterface auto &dev) -> bool {
|
using U = std::unique_ptr<Frame>;
|
||||||
|
auto does_emit = [&](DeviceInterface auto &dev) -> U {
|
||||||
if (!dev.pending())
|
if (!dev.pending())
|
||||||
return false;
|
return {};
|
||||||
originate(dev, out, [](auto &d, auto &out) { d.emit(out); });
|
|
||||||
dev.resolvepending();
|
U out(new (std::nothrow) Frame);
|
||||||
return true;
|
if (!out) {
|
||||||
|
puts("!! failed Frame alloc in poll !!");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
originate(dev, out.get(), [](auto &d, auto &out) { d.emit(out); });
|
||||||
|
return out;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Runtime tuple index helper
|
// Runtime tuple index helper
|
||||||
auto does_index_emit = [&](std::size_t t) -> bool {
|
auto does_index_emit = [&](std::size_t t) -> U {
|
||||||
return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
|
return [&]<std::size_t... Is>(std::index_sequence<Is...>) -> U {
|
||||||
return (((Is == t) && does_emit(std::get<Is>(devices_))) || ...);
|
U out;
|
||||||
|
((Is == t && (out = does_emit(std::get<Is>(devices_)))) || ...);
|
||||||
|
return out;
|
||||||
}(std::index_sequence_for<Devs...>{});
|
}(std::index_sequence_for<Devs...>{});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -195,16 +235,16 @@ public:
|
|||||||
static uint8_t rr_ = 0; // round-robin cursor
|
static uint8_t rr_ = 0; // round-robin cursor
|
||||||
const std::size_t start = rr_;
|
const std::size_t start = rr_;
|
||||||
for (std::size_t t = start; t < N; ++t) // [start, N)
|
for (std::size_t t = start; t < N; ++t) // [start, N)
|
||||||
if (does_index_emit(t)) {
|
if (auto out = does_index_emit(t)) {
|
||||||
rr_ = (t + 1 == N) ? 0 : t + 1;
|
rr_ = (t + 1 == N) ? 0 : t + 1;
|
||||||
return true;
|
return out;
|
||||||
}
|
}
|
||||||
for (std::size_t t = 0; t < start; ++t) // [0, start); t+1 <= start < N
|
for (std::size_t t = 0; t < start; ++t) // [0, start); t+1 <= start < N
|
||||||
if (does_index_emit(t)) {
|
if (auto out = does_index_emit(t)) {
|
||||||
rr_ = t + 1;
|
rr_ = t + 1;
|
||||||
return true;
|
return out;
|
||||||
}
|
}
|
||||||
return false;
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Copyright (C) 2026 Allen Hill <allenofthehills@gmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <version>
|
||||||
|
|
||||||
|
#if !defined(__cpp_lib_expected) || __cpp_lib_expected < 202211L
|
||||||
|
#include <tl/expected.hpp>
|
||||||
|
|
||||||
|
namespace avclan {
|
||||||
|
template <class T, class E> using expected = tl::expected<T, E>;
|
||||||
|
template <class E> using unexpected = tl::unexpected<E>;
|
||||||
|
using tl::unexpect;
|
||||||
|
using tl::unexpect_t;
|
||||||
|
} // namespace avclan
|
||||||
|
#else
|
||||||
|
#include <expected>
|
||||||
|
|
||||||
|
namespace avclan {
|
||||||
|
template <class T, class E> using expected = std::expected<T, E>;
|
||||||
|
template <class E> using unexpected = std::unexpected<E>;
|
||||||
|
using std::unexpect;
|
||||||
|
using std::unexpect_t;
|
||||||
|
} // namespace avclan
|
||||||
|
#endif
|
||||||
@@ -76,6 +76,9 @@ set_property(CACHE USART_RXMODE PROPERTY STRINGS
|
|||||||
# tick. 1000 = no correction; set per-board in CMakeUserPresets.json.
|
# tick. 1000 = no correction; set per-board in CMakeUserPresets.json.
|
||||||
set(RTC_STATUS_PERIOD_MS 1000 CACHE STRING "Measured ms per nominal RTC status period (1000 = no correction)")
|
set(RTC_STATUS_PERIOD_MS 1000 CACHE STRING "Measured ms per nominal RTC status period (1000 = no correction)")
|
||||||
|
|
||||||
|
# --- Firmware configuration options ----------------------------------------
|
||||||
|
set(AVCLAN_FRAME_POOL_N 32 CACHE STRING "Frame pool depth (pooled targets)")
|
||||||
|
|
||||||
try_compile(LIBC_VERSION_TEST
|
try_compile(LIBC_VERSION_TEST
|
||||||
SOURCES "${CMAKE_SOURCE_DIR}/cmake/libc-version-test.cpp"
|
SOURCES "${CMAKE_SOURCE_DIR}/cmake/libc-version-test.cpp"
|
||||||
COMPILE_DEFINITIONS -mmcu=${AVR_MCU}
|
COMPILE_DEFINITIONS -mmcu=${AVR_MCU}
|
||||||
@@ -85,11 +88,14 @@ FetchContent_Declare(
|
|||||||
avr_libstdcpp
|
avr_libstdcpp
|
||||||
GIT_REPOSITORY https://github.com/modm-io/avr-libstdcpp.git
|
GIT_REPOSITORY https://github.com/modm-io/avr-libstdcpp.git
|
||||||
GIT_TAG 5354296040a2289c911062daa82336762231e897
|
GIT_TAG 5354296040a2289c911062daa82336762231e897
|
||||||
|
SYSTEM
|
||||||
)
|
)
|
||||||
FetchContent_MakeAvailable(avr_libstdcpp)
|
FetchContent_MakeAvailable(avr_libstdcpp)
|
||||||
add_library(libstdcpp INTERFACE)
|
add_library(libstdcpp STATIC ${avr_libstdcpp_SOURCE_DIR}/src/new_handler.cc)
|
||||||
target_include_directories(libstdcpp SYSTEM
|
target_include_directories(libstdcpp
|
||||||
INTERFACE ${avr_libstdcpp_SOURCE_DIR}/include)
|
PUBLIC ${avr_libstdcpp_SOURCE_DIR}/include)
|
||||||
|
# Let --gc-sections drop the unused set/get_new_handler functions.
|
||||||
|
target_compile_options(libstdcpp PRIVATE -ffunction-sections -fdata-sections)
|
||||||
target_link_libraries(avclan PUBLIC libstdcpp)
|
target_link_libraries(avclan PUBLIC libstdcpp)
|
||||||
|
|
||||||
if(NOT LIBC_VERSION_TEST)
|
if(NOT LIBC_VERSION_TEST)
|
||||||
@@ -142,6 +148,7 @@ target_compile_definitions(avclan PUBLIC
|
|||||||
__CLK_PRESCALE_DIV=__${CLK_PRESCALE_DIV}
|
__CLK_PRESCALE_DIV=__${CLK_PRESCALE_DIV}
|
||||||
TCB_CLKSEL=${TCB_CLKSEL}
|
TCB_CLKSEL=${TCB_CLKSEL}
|
||||||
RTC_STATUS_PERIOD_MS=${RTC_STATUS_PERIOD_MS}
|
RTC_STATUS_PERIOD_MS=${RTC_STATUS_PERIOD_MS}
|
||||||
|
AVCLAN_FRAME_POOL_N=${AVCLAN_FRAME_POOL_N}
|
||||||
)
|
)
|
||||||
target_compile_options(avclan PUBLIC
|
target_compile_options(avclan PUBLIC
|
||||||
--param=min-pagesize=0
|
--param=min-pagesize=0
|
||||||
|
|||||||
+47
-24
@@ -8,33 +8,44 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
template <class T, auto N> struct Deleter;
|
template <class T, auto N> struct Deleter;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T, std::integral auto N, bool Owning = false>
|
template <class T, std::integral auto N, bool Owning = false,
|
||||||
requires((N & (N - 1)) == 0 && N <= std::numeric_limits<uint8_t>::max())
|
class Deleter = std::default_delete<T>>
|
||||||
|
requires((N & (N - 1)) == 0 && N <= std::numeric_limits<uint8_t>::max() &&
|
||||||
|
// push() drops the passed-in deleter and pop() fabricates a fresh
|
||||||
|
// one, so a deleter must either derive its state from the queue
|
||||||
|
// itself (the pool Deleter) or carry no state at all
|
||||||
|
(std::is_same_v<Deleter, detail::Deleter<T, N>> ||
|
||||||
|
(std::is_empty_v<Deleter> &&
|
||||||
|
std::is_nothrow_default_constructible_v<Deleter>)))
|
||||||
class Queue {
|
class Queue {
|
||||||
using Deleter = detail::template Deleter<T, N>;
|
friend detail::Deleter<T, N>;
|
||||||
friend Deleter;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Only full and copy-convert-from-full construction is allowed
|
// Only empty construction is allowed for non-Owning, non-pool deleters
|
||||||
Queue() = delete;
|
constexpr Queue()
|
||||||
|
requires(!Owning && !std::is_same_v<Deleter, detail::Deleter<T, N>>)
|
||||||
|
: owner{nullptr} {}
|
||||||
Queue(const Queue &) = delete;
|
Queue(const Queue &) = delete;
|
||||||
|
|
||||||
// Moving is unsupported due to being self-referential
|
// Moving is unsupported due to being self-referential
|
||||||
Queue(Queue &&) = delete;
|
Queue(Queue &&) = delete;
|
||||||
Queue &operator=(Queue &&) = delete;
|
Queue &operator=(Queue &&) = delete;
|
||||||
|
|
||||||
|
// Construct from pre-defined storage
|
||||||
constexpr Queue(T (&items)[N])
|
constexpr Queue(T (&items)[N])
|
||||||
requires(Owning)
|
requires(Owning) && std::same_as<Deleter, detail::Deleter<T, N>>
|
||||||
: owner{this}, write{N} {
|
: owner{this}, write{N} {
|
||||||
for (uint8_t i = 0; i < N; ++i)
|
for (uint8_t i = 0; i < N; ++i)
|
||||||
buf[i] = &items[i];
|
buf[i] = &items[i];
|
||||||
}
|
}
|
||||||
constexpr Queue(Queue<T, N, true> &queue)
|
// Construct non-Owning empty from Owning queue
|
||||||
|
constexpr Queue(Queue<T, N, true, Deleter> &queue)
|
||||||
requires(!Owning)
|
requires(!Owning)
|
||||||
: owner{&queue} {}
|
: owner{&queue} {}
|
||||||
|
|
||||||
@@ -46,14 +57,22 @@ public:
|
|||||||
uint8_t push(std::unique_ptr<T, Deleter> x)
|
uint8_t push(std::unique_ptr<T, Deleter> x)
|
||||||
requires(!Owning)
|
requires(!Owning)
|
||||||
{
|
{
|
||||||
// structurally unnecessary; empty construction from same size parent
|
if constexpr (!std::is_same_v<Deleter, detail::Deleter<T, N>>) {
|
||||||
// guarantees
|
// structurally unnecessary for detail::Deleter, where empty construction
|
||||||
// !isFull assert(!isFull());
|
// is only from same size parent
|
||||||
|
if (isFull())
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (!x || !x.get_deleter().is_owned_by(owner))
|
if (!x)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
reclaim(x.release());
|
if constexpr (std::is_same_v<Deleter, detail::Deleter<T, N>>) {
|
||||||
|
if (!x.get_deleter().is_owned_by(owner))
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
claim(x.release());
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -69,35 +88,39 @@ public:
|
|||||||
if (isEmpty())
|
if (isEmpty())
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
if constexpr (Owning)
|
if constexpr (!std::is_same_v<Deleter, detail::Deleter<T, N>>)
|
||||||
return {buf[mask(read++)], {this}};
|
return {buf[mask(read++)], Deleter{}};
|
||||||
|
else if constexpr (Owning)
|
||||||
|
return {buf[mask(read++)], Deleter{this}};
|
||||||
else
|
else
|
||||||
return {buf[mask(read++)], {owner}};
|
return {buf[mask(read++)], Deleter{owner}};
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint8_t mask(uint8_t pos) const { return pos & (N - 1); }
|
uint8_t mask(uint8_t pos) const { return pos & (N - 1); }
|
||||||
void reclaim(T *x) { buf[mask(write++)] = x; }
|
void claim(T *x) { buf[mask(write++)] = x; }
|
||||||
|
|
||||||
std::array<T *, N> buf = {};
|
std::array<T *, N> buf = {};
|
||||||
Queue<T, N, true> *const owner;
|
Queue<T, N, true, Deleter> *const owner;
|
||||||
uint8_t read = 0;
|
uint8_t read = 0;
|
||||||
uint8_t write = 0;
|
uint8_t write = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <class T, auto N> Queue(Queue<T, N, true> &) -> Queue<T, N, false>;
|
template <class T, auto N, class D>
|
||||||
template <class T, auto N> Queue(T (&items)[N]) -> Queue<T, N, true>;
|
Queue(Queue<T, N, true, D> &) -> Queue<T, N, false, D>;
|
||||||
|
template <class T, auto N>
|
||||||
|
Queue(T (&items)[N]) -> Queue<T, N, true, detail::Deleter<T, N>>;
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
template <class T, auto N> struct Deleter {
|
template <class T, auto N> struct Deleter {
|
||||||
Deleter() = default;
|
Deleter() = default;
|
||||||
constexpr Deleter(Queue<T, N, true> *owner) : owner{owner} {}
|
constexpr Deleter(Queue<T, N, true, Deleter> *owner) : owner{owner} {}
|
||||||
void operator()(T *x) const { owner->reclaim(x); }
|
void operator()(T *x) const { owner->claim(x); }
|
||||||
constexpr bool is_owned_by(const Queue<T, N, true> *parent) const {
|
constexpr bool is_owned_by(const Queue<T, N, true, Deleter> *parent) const {
|
||||||
return owner == parent;
|
return owner == parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Queue<T, N, true> *const owner = nullptr;
|
Queue<T, N, true, Deleter> *const owner = nullptr;
|
||||||
};
|
};
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|||||||
+32
-38
@@ -7,6 +7,8 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <new>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include "cdchanger.hpp"
|
#include "cdchanger.hpp"
|
||||||
#include "frame.hpp"
|
#include "frame.hpp"
|
||||||
@@ -14,6 +16,7 @@
|
|||||||
#include "hal/stdio.h"
|
#include "hal/stdio.h"
|
||||||
#include "peripheral.hpp"
|
#include "peripheral.hpp"
|
||||||
#include "queue.hpp"
|
#include "queue.hpp"
|
||||||
|
#include "stdshim.hpp"
|
||||||
|
|
||||||
const char *const offon[] = {"OFF", "ON"};
|
const char *const offon[] = {"OFF", "ON"};
|
||||||
|
|
||||||
@@ -22,11 +25,8 @@ constexpr uint8_t CACHE_SIZE = 32;
|
|||||||
using namespace avclan;
|
using namespace avclan;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
Frame frames[CACHE_SIZE];
|
constinit Queue<Frame, CACHE_SIZE> incoming;
|
||||||
|
constinit Queue<Frame, CACHE_SIZE> outgoing;
|
||||||
constinit Queue cache(frames);
|
|
||||||
constinit Queue incoming = cache;
|
|
||||||
constinit Queue outgoing = cache;
|
|
||||||
|
|
||||||
uint8_t hexChars[2];
|
uint8_t hexChars[2];
|
||||||
uint8_t hexDigit = 0; // current digit being written to hexChars
|
uint8_t hexDigit = 0; // current digit being written to hexChars
|
||||||
@@ -66,7 +66,6 @@ int main() {
|
|||||||
using enum Action;
|
using enum Action;
|
||||||
using enum Device;
|
using enum Device;
|
||||||
Peripheral<CDChanger> peripheral(phy, 0x360);
|
Peripheral<CDChanger> peripheral(phy, 0x360);
|
||||||
using Error = decltype(peripheral)::Error;
|
|
||||||
using Print = Frame::Print;
|
using Print = Frame::Print;
|
||||||
|
|
||||||
Setup();
|
Setup();
|
||||||
@@ -74,40 +73,31 @@ int main() {
|
|||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (peripheral.bus_is_active()) {
|
if (peripheral.bus_is_active()) {
|
||||||
if (auto msg = cache.pop()) {
|
if (auto msg = peripheral.read(Print{.print = printAllFrames,
|
||||||
auto err = peripheral.read(msg.get(), Print{.print = printAllFrames,
|
|
||||||
.binary = printBinary,
|
.binary = printBinary,
|
||||||
.verbose = verbose});
|
.verbose = verbose}))
|
||||||
if (err == Error::Read{0x00})
|
incoming.push(std::move(*msg));
|
||||||
incoming.push(std::move(msg));
|
|
||||||
} else {
|
|
||||||
puts("!! Dropping an incoming message; cache is empty !!");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (const auto *in = incoming.peek()) {
|
if (const auto *in = incoming.peek()) {
|
||||||
if (auto out = cache.pop()) {
|
if (auto resp = peripheral.route(*in)) {
|
||||||
peripheral.route(in, out.get());
|
|
||||||
incoming.pop();
|
incoming.pop();
|
||||||
|
if (*resp) {
|
||||||
if (out->reaction > 0)
|
outgoing.push(std::move(*resp));
|
||||||
outgoing.push(std::move(out));
|
continue; // route can be long; re-check the bus before poll/send
|
||||||
} else {
|
}
|
||||||
puts("!! Unable to respond; cache is empty !!");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (peripheral.pending()) {
|
if (auto msg = peripheral.poll())
|
||||||
if (auto out = cache.pop(); out && peripheral.emit(out.get()))
|
outgoing.push(std::move(msg));
|
||||||
outgoing.push(std::move(out));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto out = outgoing.pop()) {
|
if (auto out = outgoing.pop()) {
|
||||||
auto err = peripheral.send(
|
auto result =
|
||||||
out.get(), Print{.print = printAllFrames, .binary = printBinary});
|
peripheral.send(std::move(out), Print{.print = printAllFrames,
|
||||||
peripheral.react(out.get(), err);
|
.binary = printBinary});
|
||||||
if (out->reaction > 0)
|
if (auto next = peripheral.react(std::move(result)))
|
||||||
outgoing.push(std::move(out));
|
outgoing.push(std::move(next));
|
||||||
}
|
}
|
||||||
|
|
||||||
// stdin must be non-blocking: yielding EOF when idle/empty
|
// stdin must be non-blocking: yielding EOF when idle/empty
|
||||||
@@ -129,7 +119,7 @@ int main() {
|
|||||||
case 'x': set_flag(&printBinary, false, "Binary:"); break;
|
case 'x': set_flag(&printBinary, false, "Binary:"); break;
|
||||||
|
|
||||||
case 'E': // Beep
|
case 'E': // Beep
|
||||||
if (auto out = cache.pop()) {
|
if (auto out = std::unique_ptr<Frame>(new (std::nothrow) Frame)) {
|
||||||
out->is_unicast = true;
|
out->is_unicast = true;
|
||||||
out->peripheral_addr = peripheral.controller();
|
out->peripheral_addr = peripheral.controller();
|
||||||
{
|
{
|
||||||
@@ -141,10 +131,10 @@ int main() {
|
|||||||
out->reaction = 1;
|
out->reaction = 1;
|
||||||
outgoing.push(std::move(out));
|
outgoing.push(std::move(out));
|
||||||
} else
|
} else
|
||||||
puts("!! Cache empty; unable to queue beep request");
|
puts("!! failed Frame alloc for Beep !! ");
|
||||||
break;
|
break;
|
||||||
case 'P':
|
case 'P':
|
||||||
if (auto out = cache.pop()) {
|
if (auto out = std::unique_ptr<Frame>(new (std::nothrow) Frame)) {
|
||||||
out->is_unicast = true;
|
out->is_unicast = true;
|
||||||
out->peripheral_addr = peripheral.controller();
|
out->peripheral_addr = peripheral.controller();
|
||||||
{
|
{
|
||||||
@@ -159,7 +149,8 @@ int main() {
|
|||||||
}
|
}
|
||||||
out->reaction = CDChanger::reaction_t::r_Ejection;
|
out->reaction = CDChanger::reaction_t::r_Ejection;
|
||||||
outgoing.push(std::move(out));
|
outgoing.push(std::move(out));
|
||||||
}
|
} else
|
||||||
|
puts("!! failed Frame alloc for Play !! ");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
@@ -218,19 +209,21 @@ int main() {
|
|||||||
if (readSeq && seqIdx > 0) {
|
if (readSeq && seqIdx > 0) {
|
||||||
if (readBinary) {
|
if (readBinary) {
|
||||||
if (data_tmp[seqIdx - 1] == 0x17) {
|
if (data_tmp[seqIdx - 1] == 0x17) {
|
||||||
if (auto out = cache.pop()) {
|
if (auto out =
|
||||||
|
std::unique_ptr<Frame>(new (std::nothrow) Frame)) {
|
||||||
if (out->parse(data_tmp, --seqIdx) ==
|
if (out->parse(data_tmp, --seqIdx) ==
|
||||||
Frame::Error::Parse{0}) {
|
Frame::Error::Parse{0}) {
|
||||||
out->reaction = 1;
|
out->reaction = 1;
|
||||||
outgoing.push(std::move(out));
|
outgoing.push(std::move(out));
|
||||||
}
|
}
|
||||||
}
|
} else
|
||||||
|
puts("!! failed Frame alloc for input message !!");
|
||||||
readSeq = readBinary = false;
|
readSeq = readBinary = false;
|
||||||
} else
|
} else
|
||||||
goto DEFAULT; // reading binary and this is a real data byte;
|
goto DEFAULT; // reading binary and this is a real data byte;
|
||||||
// fall through to default
|
// fall through to default
|
||||||
} else if (seqIdx <= Frame::MAXLENGTH) {
|
} else if (seqIdx <= Frame::MAXLENGTH) {
|
||||||
if (auto out = cache.pop()) {
|
if (auto out = std::unique_ptr<Frame>(new (std::nothrow) Frame)) {
|
||||||
out->is_unicast = seqIsUnicast;
|
out->is_unicast = seqIsUnicast;
|
||||||
out->peripheral_addr =
|
out->peripheral_addr =
|
||||||
seqIsUnicast ? peripheral.controller() : 0x1FF;
|
seqIsUnicast ? peripheral.controller() : 0x1FF;
|
||||||
@@ -238,7 +231,8 @@ int main() {
|
|||||||
memcpy(out->data, data_tmp, seqIdx);
|
memcpy(out->data, data_tmp, seqIdx);
|
||||||
out->reaction = 1;
|
out->reaction = 1;
|
||||||
outgoing.push(std::move(out));
|
outgoing.push(std::move(out));
|
||||||
}
|
} else
|
||||||
|
puts("!! failed Frame alloc for input message !!");
|
||||||
printAllFrames = lastPrintAllFrames;
|
printAllFrames = lastPrintAllFrames;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
Reference in New Issue
Block a user