mirror of
https://github.com/halleysfifthinc/AVCLAN-Mockingboard.git
synced 2026-08-11 08:22:52 +00:00
Compare commits
14 Commits
681450f4ad
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| eab535db98 | |||
| f3d0d99ce4 | |||
| 76f5da0125 | |||
| 7871e92dbe | |||
| fb17b7e75b | |||
| ae539e27f6 | |||
| a99fe56aae | |||
| 909915730b | |||
| 5bbbb6c011 | |||
| 8950bd6917 | |||
| 56936aed10 | |||
| 5208e083f3 | |||
| 200a225add | |||
| 568c2af9e6 |
@@ -16,6 +16,8 @@ Checks: |
|
|||||||
-modernize-avoid-c-arrays,
|
-modernize-avoid-c-arrays,
|
||||||
-modernize-use-std-print,
|
-modernize-use-std-print,
|
||||||
-readability-magic-numbers,
|
-readability-magic-numbers,
|
||||||
|
-readability-function-cognitive-complexity,
|
||||||
|
-misc-non-private-member-variables-in-classes
|
||||||
WarningsAsErrors: ""
|
WarningsAsErrors: ""
|
||||||
ExcludeHeaderFilterRegex: 'out/build'
|
ExcludeHeaderFilterRegex: 'out/build'
|
||||||
HeaderFilterRegex: '^src/.*'
|
HeaderFilterRegex: '^src/.*'
|
||||||
|
|||||||
@@ -12,3 +12,4 @@ __pycache__/
|
|||||||
~*
|
~*
|
||||||
\#*
|
\#*
|
||||||
/CMakeUserPresets.json
|
/CMakeUserPresets.json
|
||||||
|
.cache/
|
||||||
|
|||||||
+91
-4
@@ -7,12 +7,17 @@ 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
|
||||||
$<$<COMPILE_LANGUAGE:CXX>:-fno-threadsafe-statics>
|
$<$<COMPILE_LANGUAGE:CXX>:-fno-threadsafe-statics>
|
||||||
$<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions>
|
$<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions>
|
||||||
$<$<COMPILE_LANGUAGE:CXX>:-fno-rtti>
|
$<$<COMPILE_LANGUAGE:CXX>:-fno-rtti>
|
||||||
|
# Debug must not be -O0: both the vendored usart.h and avr-libc's
|
||||||
|
# <util/delay.h> #warning when __OPTIMIZE__ is undefined, and -Werror makes
|
||||||
|
# that fatal.
|
||||||
|
$<$<CONFIG:Debug>:-Og>
|
||||||
$<$<CONFIG:Debug>:-fanalyzer>
|
$<$<CONFIG:Debug>:-fanalyzer>
|
||||||
$<$<CONFIG:Debug>:-Wno-analyzer-use-of-uninitialized-value>)
|
$<$<CONFIG:Debug>:-Wno-analyzer-use-of-uninitialized-value>)
|
||||||
|
|
||||||
@@ -71,13 +76,95 @@ add_library(avclan STATIC
|
|||||||
target_include_directories(avclan PUBLIC
|
target_include_directories(avclan PUBLIC
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/avclan)
|
${CMAKE_CURRENT_SOURCE_DIR}/src/avclan)
|
||||||
|
|
||||||
|
add_executable(mockingboard
|
||||||
|
src/sniffer.cc
|
||||||
|
)
|
||||||
|
target_link_libraries(mockingboard avclan)
|
||||||
|
|
||||||
|
# Report firmware section sizes on every build, when the toolchain provides a
|
||||||
|
# size tool (CMAKE_SIZE). A standalone custom target (rather than a POST_BUILD
|
||||||
|
# command on mockingboard) so it prints even when the link is up to date:
|
||||||
|
# custom targets with no output are always considered stale and re-run.
|
||||||
|
# add_dependencies orders it after the link.
|
||||||
|
if(CMAKE_SIZE)
|
||||||
|
add_custom_target(mockingboard-size ALL
|
||||||
|
COMMAND ${CMAKE_SIZE} $<TARGET_FILE:mockingboard>
|
||||||
|
COMMENT "Section sizes: mockingboard firmware"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
add_dependencies(mockingboard-size mockingboard)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Pull in the selected hardware target: its port sources, per-target headers,
|
# Pull in the selected hardware target: its port sources, per-target headers,
|
||||||
# hardware-specific compile options/definitions, device-pack handling, and the
|
# hardware-specific compile options/definitions, device-pack handling, and the
|
||||||
# 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})
|
||||||
|
|
||||||
add_executable(mockingboard
|
# --- Polyfill headers for std facilities the toolchain may lack -------------
|
||||||
src/sniffer.cc
|
# 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)
|
||||||
|
|
||||||
target_link_libraries(mockingboard avclan)
|
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()
|
||||||
|
|||||||
+39
-7
@@ -22,7 +22,7 @@
|
|||||||
"hidden": true,
|
"hidden": true,
|
||||||
"description": "AVR ATtiny3216 cross-compile toolchain + matching port",
|
"description": "AVR ATtiny3216 cross-compile toolchain + matching port",
|
||||||
"toolchainFile": "${sourceDir}/cmake/avr-gcc-toolchain.cmake",
|
"toolchainFile": "${sourceDir}/cmake/avr-gcc-toolchain.cmake",
|
||||||
"binaryDir": "${sourceDir}/out/build/attiny3216",
|
"binaryDir": "${sourceDir}/out/build/avr-attiny3216",
|
||||||
"cacheVariables": {
|
"cacheVariables": {
|
||||||
"AVCLAN_TARGET": "avr-attiny3216",
|
"AVCLAN_TARGET": "avr-attiny3216",
|
||||||
"FREQSEL": "20MHz",
|
"FREQSEL": "20MHz",
|
||||||
@@ -34,8 +34,6 @@
|
|||||||
"name": "debug-base",
|
"name": "debug-base",
|
||||||
"hidden": true,
|
"hidden": true,
|
||||||
"description": "Debug build settings",
|
"description": "Debug build settings",
|
||||||
"generator": "Unix Makefiles",
|
|
||||||
"binaryDir": "${sourceDir}/out/build",
|
|
||||||
"cacheVariables": {
|
"cacheVariables": {
|
||||||
"CMAKE_BUILD_TYPE": "Debug"
|
"CMAKE_BUILD_TYPE": "Debug"
|
||||||
}
|
}
|
||||||
@@ -49,13 +47,30 @@
|
|||||||
"CMAKE_BUILD_TYPE": "RelWithDebInfo"
|
"CMAKE_BUILD_TYPE": "RelWithDebInfo"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "attiny3216-relwithdebinfo",
|
||||||
|
"hidden": true,
|
||||||
|
"description": "ATtiny3216 RelWithDebInfo build,",
|
||||||
|
"inherits": [
|
||||||
|
"avr-attiny3216",
|
||||||
|
"relwithdebinfo-base"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "attiny3216-debug",
|
||||||
|
"hidden": true,
|
||||||
|
"description": "ATtiny3216 Debug build",
|
||||||
|
"inherits": [
|
||||||
|
"avr-attiny3216",
|
||||||
|
"debug-base"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "attiny3216-relwithdebinfo-usb0",
|
"name": "attiny3216-relwithdebinfo-usb0",
|
||||||
"displayName": "ATtiny3216 RelWithDebInfo (ttyUSB0)",
|
"displayName": "ATtiny3216 RelWithDebInfo (ttyUSB0)",
|
||||||
"description": "ATtiny3216 RelWithDebInfo build, program over /dev/ttyUSB0",
|
"description": "ATtiny3216 RelWithDebInfo build, program over /dev/ttyUSB0",
|
||||||
"inherits": [
|
"inherits": [
|
||||||
"avr-attiny3216",
|
"attiny3216-relwithdebinfo",
|
||||||
"relwithdebinfo-base",
|
|
||||||
"usb0"
|
"usb0"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -64,8 +79,25 @@
|
|||||||
"displayName": "ATtiny3216 RelWithDebInfo (ttyUSB1)",
|
"displayName": "ATtiny3216 RelWithDebInfo (ttyUSB1)",
|
||||||
"description": "ATtiny3216 RelWithDebInfo build, program over /dev/ttyUSB1",
|
"description": "ATtiny3216 RelWithDebInfo build, program over /dev/ttyUSB1",
|
||||||
"inherits": [
|
"inherits": [
|
||||||
"avr-attiny3216",
|
"attiny3216-relwithdebinfo",
|
||||||
"relwithdebinfo-base",
|
"usb1"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "attiny3216-debug-usb0",
|
||||||
|
"displayName": "ATtiny3216 Debug (ttyUSB0)",
|
||||||
|
"description": "ATtiny3216 Debug build, program over /dev/ttyUSB0",
|
||||||
|
"inherits": [
|
||||||
|
"attiny3216-debug",
|
||||||
|
"usb0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "attiny3216-debug-usb1",
|
||||||
|
"displayName": "ATtiny3216 Debug (ttyUSB1)",
|
||||||
|
"description": "ATtiny3216 Debug build, program over /dev/ttyUSB1",
|
||||||
|
"inherits": [
|
||||||
|
"attiny3216-debug",
|
||||||
"usb1"
|
"usb1"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -13,6 +13,9 @@
|
|||||||
##########################################################################
|
##########################################################################
|
||||||
find_program(AVR_CC avr-gcc REQUIRED)
|
find_program(AVR_CC avr-gcc REQUIRED)
|
||||||
find_program(AVR_CXX avr-g++ REQUIRED)
|
find_program(AVR_CXX avr-g++ REQUIRED)
|
||||||
|
# Section-size reporter; consumed by an optional POST_BUILD in the top-level
|
||||||
|
# CMakeLists (left unset -> no size report) so the top level stays HW-agnostic.
|
||||||
|
find_program(CMAKE_SIZE avr-size)
|
||||||
|
|
||||||
set(CMAKE_SYSTEM_NAME Generic)
|
set(CMAKE_SYSTEM_NAME Generic)
|
||||||
set(CMAKE_SYSTEM_PROCESSOR avr)
|
set(CMAKE_SYSTEM_PROCESSOR avr)
|
||||||
|
|||||||
@@ -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"; }
|
||||||
@@ -49,6 +49,7 @@ still bypasses it.
|
|||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
# ---- locations -------------------------------------------------------------
|
# ---- locations -------------------------------------------------------------
|
||||||
@@ -227,10 +228,16 @@ def find_address_spec(root):
|
|||||||
|
|
||||||
|
|
||||||
def iter_src_files(root):
|
def iter_src_files(root):
|
||||||
for dirpath, _dirs, files in os.walk(os.path.join(root, SRC_REL)):
|
"""List source files under src/, via `git ls-files` so paths excluded by
|
||||||
for fn in sorted(files):
|
.gitignore (e.g. the ESP32 port's CMake build directory, which vendors
|
||||||
if fn.endswith(SRC_EXTS):
|
FetchContent'd third-party code) are never walked into."""
|
||||||
yield os.path.join(dirpath, fn)
|
out = subprocess.run(
|
||||||
|
["git", "-C", root, "ls-files", "-z", "--cached", "--others",
|
||||||
|
"--exclude-standard", "--", SRC_REL],
|
||||||
|
capture_output=True, check=True, text=True,
|
||||||
|
).stdout
|
||||||
|
paths = sorted(p for p in out.split("\0") if p.endswith(SRC_EXTS))
|
||||||
|
return [os.path.join(root, p) for p in paths]
|
||||||
|
|
||||||
# ---- lint ------------------------------------------------------------------
|
# ---- lint ------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -471,8 +478,6 @@ def main():
|
|||||||
lua_path = os.path.join(root, LUA_REL)
|
lua_path = os.path.join(root, LUA_REL)
|
||||||
lua_lines = read_lines(lua_path)
|
lua_lines = read_lines(lua_path)
|
||||||
specs, have_address = build_specs(root)
|
specs, have_address = build_specs(root)
|
||||||
if not have_address:
|
|
||||||
print("note: no Address enum found in src/ -- addresses not synced")
|
|
||||||
|
|
||||||
# ---- lint (all modes) ----
|
# ---- lint (all modes) ----
|
||||||
lint_failed = False
|
lint_failed = False
|
||||||
@@ -487,6 +492,8 @@ def main():
|
|||||||
print_lint(spec, LUA_REL, problems)
|
print_lint(spec, LUA_REL, problems)
|
||||||
|
|
||||||
if args.lint:
|
if args.lint:
|
||||||
|
if not have_address:
|
||||||
|
print("note: no Address enum found in src/ -- addresses not synced")
|
||||||
if lint_failed:
|
if lint_failed:
|
||||||
print("lint: FAILED")
|
print("lint: FAILED")
|
||||||
return 1
|
return 1
|
||||||
@@ -503,6 +510,8 @@ def main():
|
|||||||
has_drift = any(not d.clean() for _s, _e, d in drifts)
|
has_drift = any(not d.clean() for _s, _e, d in drifts)
|
||||||
|
|
||||||
if args.fix:
|
if args.fix:
|
||||||
|
if not have_address:
|
||||||
|
print("note: no Address enum found in src/ -- addresses not synced")
|
||||||
if lint_failed:
|
if lint_failed:
|
||||||
print("fix aborted: resolve the dissector name problems above first")
|
print("fix aborted: resolve the dissector name problems above first")
|
||||||
return 1
|
return 1
|
||||||
@@ -525,16 +534,17 @@ def main():
|
|||||||
print("fix: applied" if has_drift else "fix: already in sync")
|
print("fix: applied" if has_drift else "fix: already in sync")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
# ---- default: report ----
|
# ---- default: report (silent on success -- this is the pre-commit hook) ----
|
||||||
|
if not (lint_failed or has_drift):
|
||||||
|
return 0
|
||||||
|
if not have_address:
|
||||||
|
print("note: no Address enum found in src/ -- addresses not synced")
|
||||||
for spec, _entries, drift in drifts:
|
for spec, _entries, drift in drifts:
|
||||||
if not drift.clean():
|
if not drift.clean():
|
||||||
print("%s drift:" % spec["name"])
|
print("%s drift:" % spec["name"])
|
||||||
print_drift(drift)
|
print_drift(drift)
|
||||||
if lint_failed or has_drift:
|
|
||||||
print("out of sync -- run: scripts/sync_avclan_enums.py --fix")
|
print("out of sync -- run: scripts/sync_avclan_enums.py --fix")
|
||||||
return 1
|
return 1
|
||||||
print("enums in sync with the dissector")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
+15
-1
@@ -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
|
||||||
@@ -137,9 +143,11 @@ struct Error {
|
|||||||
BAD_CONTROLLER_PARITY,
|
BAD_CONTROLLER_PARITY,
|
||||||
BAD_CONTROL_PARITY,
|
BAD_CONTROL_PARITY,
|
||||||
BAD_PARITY, // generic bad parity has max severity
|
BAD_PARITY, // generic bad parity has max severity
|
||||||
STARTBIT_TOO_SHORT,
|
STARTBIT_MISSED,
|
||||||
|
STARTBIT_MALFORMED,
|
||||||
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 +168,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 {
|
||||||
|
|||||||
+33
-16
@@ -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,8 +287,12 @@ 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_MISSED: fputs("missed start bit", stdout); break;
|
||||||
|
case STARTBIT_MALFORMED:
|
||||||
|
fputs("malformed start bit (external cause)", stdout);
|
||||||
|
break;
|
||||||
case STARTBIT_TOO_LONG: fputs("bad start bit (long)", stdout); break;
|
case STARTBIT_TOO_LONG: fputs("bad start bit (long)", stdout); break;
|
||||||
case BAD_CONTROLLER_PARITY:
|
case BAD_CONTROLLER_PARITY:
|
||||||
fputs("reading controller addr.", stdout);
|
fputs("reading controller addr.", stdout);
|
||||||
@@ -288,29 +302,32 @@ auto Bus::read(uint16_t address, Frame *in, Frame::Print print) -> Read {
|
|||||||
goto VERBOSE;
|
goto VERBOSE;
|
||||||
case BAD_CONTROL_PARITY: fputs("reading control", stdout); goto VERBOSE;
|
case BAD_CONTROL_PARITY: fputs("reading control", stdout); goto VERBOSE;
|
||||||
case BAD_LENGTH_PARITY: fputs("reading length", stdout); goto VERBOSE;
|
case BAD_LENGTH_PARITY: fputs("reading length", stdout); goto VERBOSE;
|
||||||
case BAD_LENGTH_RANGE: printf("bad length 0x%02X", err.val); break;
|
case BAD_LENGTH_RANGE: printf("bad length 0x%02X:", err.val); break;
|
||||||
case BAD_DATA_PARITY: fputs("reading data", stdout); goto VERBOSE;
|
case BAD_DATA_PARITY: fputs("reading data", stdout); goto VERBOSE;
|
||||||
case BAD_PARITY:
|
case BAD_PARITY:
|
||||||
__builtin_unreachable();
|
__builtin_unreachable();
|
||||||
VERBOSE:
|
VERBOSE:
|
||||||
if (print.verbose) {
|
if (print.verbose) {
|
||||||
printf("; read 0x%02X", err.val);
|
printf("; read 0x%02X:", err.val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
putchar('\n');
|
putchar('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only print if some data has been correctly received
|
// Only print if some data has been correctly received
|
||||||
if (print.print && (err.errno < STARTBIT_TOO_SHORT)) {
|
if (print.print && (err.errno < STARTBIT_MISSED)) {
|
||||||
if (err.errno > BAD_DATA_PARITY)
|
if (err.errno > BAD_DATA_PARITY)
|
||||||
in->length = 0;
|
in->length = 0;
|
||||||
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 {
|
||||||
struct errtype {
|
struct errtype {
|
||||||
// Error enum is ordered such that a lower numeric value corresponds to
|
// Error enum is ordered such that a lower numeric value corresponds to
|
||||||
// more success
|
// more success
|
||||||
@@ -334,35 +351,35 @@ auto Bus::send(const Frame *out, Frame::Print print) -> Send {
|
|||||||
goto handle_err;
|
goto handle_err;
|
||||||
}
|
}
|
||||||
|
|
||||||
handle.send<1>(static_cast<uint8_t>(out->is_unicast), no_parity);
|
handle.send<1>(static_cast<uint8_t>(out.is_unicast), no_parity);
|
||||||
|
|
||||||
handle.send<12>(out->controller_addr, with_parity);
|
handle.send<12>(out.controller_addr, with_parity);
|
||||||
|
|
||||||
if (auto serr =
|
if (auto serr =
|
||||||
handle.send<12>(out->peripheral_addr, with_ack, out->is_unicast);
|
handle.send<12>(out.peripheral_addr, with_ack, out.is_unicast);
|
||||||
serr == NAK) {
|
serr == NAK) {
|
||||||
err.errno = NAK_ADDRESS;
|
err.errno = NAK_ADDRESS;
|
||||||
goto handle_err;
|
goto handle_err;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto serr = handle.send<4>(out->control, with_ack, out->is_unicast);
|
if (auto serr = handle.send<4>(out.control, with_ack, out.is_unicast);
|
||||||
serr == NAK) {
|
serr == NAK) {
|
||||||
err.errno = NAK_CONTROL;
|
err.errno = NAK_CONTROL;
|
||||||
goto handle_err;
|
goto handle_err;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto serr = handle.send<8>(out->length, with_ack, out->is_unicast);
|
if (auto serr = handle.send<8>(out.length, with_ack, out.is_unicast);
|
||||||
serr == NAK) {
|
serr == NAK) {
|
||||||
err.errno = NAK_MESSAGE_LENGTH;
|
err.errno = NAK_MESSAGE_LENGTH;
|
||||||
goto handle_err;
|
goto handle_err;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (uint8_t i = 0; i < out->length; i++) {
|
for (uint8_t i = 0; i < out.length; i++) {
|
||||||
// Based on the µPD6708 datasheet, ACK bit for broadcast doesn't seem
|
// Based on the µPD6708 datasheet, ACK bit for broadcast doesn't seem
|
||||||
// necessary (i.e. This deviates from the previous broadcast specific
|
// necessary (i.e. This deviates from the previous broadcast specific
|
||||||
// function that sent an extra `1` bit after each byte/parity)
|
// function that sent an extra `1` bit after each byte/parity)
|
||||||
// Explanation for why audio-group broadcast state report isn't working?
|
// Explanation for why audio-group broadcast state report isn't working?
|
||||||
if (auto serr = handle.send<8>(out->data[i], with_ack, out->is_unicast);
|
if (auto serr = handle.send<8>(out.data[i], with_ack, out.is_unicast);
|
||||||
serr == NAK) {
|
serr == NAK) {
|
||||||
err.errno = NAK_DATA;
|
err.errno = NAK_DATA;
|
||||||
err.val = i;
|
err.val = i;
|
||||||
@@ -399,14 +416,14 @@ auto Bus::send(const Frame *out, Frame::Print print) -> Send {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (print.print)
|
if (print.print)
|
||||||
out->print(print);
|
out.print(print);
|
||||||
|
|
||||||
return err.errno;
|
return err.errno;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bus::Handle Bus::get() { return Handle{*this}; };
|
Bus::Handle Bus::get() { return Handle{*this}; };
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#if !defined(NDEBUG) && defined(MEASURE_BUS)
|
||||||
// Debug bit-timing measurement on the one physical bus; instance-scoped for the
|
// Debug bit-timing measurement on the one physical bus; instance-scoped for the
|
||||||
// same reason as is_active().
|
// same reason as is_active().
|
||||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||||
|
|||||||
+5
-2
@@ -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,8 +79,9 @@ 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,
|
||||||
Error::Send send(const Frame *out, Frame::Print print);
|
Frame::Print print);
|
||||||
|
Error::Send send(const Frame &out, Frame::Print print);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class Handle;
|
class Handle;
|
||||||
|
|||||||
+89
-75
@@ -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"
|
||||||
@@ -56,17 +57,17 @@ void CDChanger::init() {
|
|||||||
cdtimer_init(this, &incrementTime_callback, &isPlaying_callback);
|
cdtimer_init(this, &incrementTime_callback, &isPlaying_callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CDChanger::handle(const Frame *in, Frame *out) {
|
void CDChanger::handle(const Frame &in, Frame &out) {
|
||||||
if (in->length < 4)
|
if (in.length < 4)
|
||||||
return; // [Currently known] valid CDChanger frames have at least 4 bytes
|
return; // [Currently known] valid CDChanger frames have at least 4 bytes
|
||||||
|
|
||||||
const uint8_t *data = &in->data[1];
|
const uint8_t *data = &in.data[1];
|
||||||
const auto from = static_cast<Device>(*data++);
|
const auto from = static_cast<Device>(*data++);
|
||||||
/* const auto to = */ data++;
|
/* const auto to = */ data++;
|
||||||
const auto action = static_cast<Action>(*data++);
|
const auto action = static_cast<Action>(*data++);
|
||||||
|
|
||||||
static const uint8_t function_change_resp[] = {
|
static const uint8_t function_change_resp[] = {
|
||||||
0x00, to_underlying(Device::CD_CHANGER), to_underlying(from), 0xFF, 0x01};
|
0x00, to_underlying(Device::CD_CHANGER), 0xFF, 0xFF, 0x01};
|
||||||
|
|
||||||
using enum Action;
|
using enum Action;
|
||||||
#pragma GCC diagnostic push
|
#pragma GCC diagnostic push
|
||||||
@@ -74,26 +75,30 @@ void CDChanger::handle(const Frame *in, Frame *out) {
|
|||||||
// Unicast to CD changer: bytes are (0x00, from, to, action, [extra...]).
|
// Unicast to CD changer: bytes are (0x00, from, to, action, [extra...]).
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case Enable_Function_Req:
|
case Enable_Function_Req:
|
||||||
out->is_unicast = true;
|
out.is_unicast = true;
|
||||||
out->length = sizeof(function_change_resp);
|
out.length = sizeof(function_change_resp);
|
||||||
memcpy(out->data, function_change_resp, sizeof(function_change_resp));
|
memcpy(out.data, function_change_resp, sizeof(function_change_resp));
|
||||||
out->data[3] = to_underlying(Enable_Function_Resp);
|
out.data[2] = to_underlying(from);
|
||||||
|
out.data[3] = to_underlying(Enable_Function_Resp);
|
||||||
state = 0;
|
state = 0;
|
||||||
flags2 = 0x80;
|
flags2 = 0x80;
|
||||||
out->reaction = r_StatusReport;
|
out.reaction = r_StatusReport;
|
||||||
break;
|
break;
|
||||||
case Disable_Function_Req:
|
case Disable_Function_Req:
|
||||||
// No change/response needed if we're already not playing
|
// Head unit always expects a response, but the state change can be
|
||||||
|
// conditional
|
||||||
|
out.is_unicast = true;
|
||||||
|
out.length = sizeof(function_change_resp);
|
||||||
|
memcpy(out.data, function_change_resp, sizeof(function_change_resp));
|
||||||
|
out.data[2] = to_underlying(from);
|
||||||
|
out.data[3] = to_underlying(Disable_Function_Resp);
|
||||||
if (isPlaying()) {
|
if (isPlaying()) {
|
||||||
stopPlaying();
|
stopPlaying();
|
||||||
out->length = sizeof(function_change_resp);
|
|
||||||
memcpy(out->data, function_change_resp, sizeof(function_change_resp));
|
|
||||||
out->data[3] = to_underlying(Disable_Function_Resp);
|
|
||||||
state = 0;
|
state = 0;
|
||||||
flags2 = 0x80;
|
flags2 = 0x80;
|
||||||
out->is_unicast = true;
|
out.reaction = r_StatusReport;
|
||||||
out->reaction = r_StatusReport;
|
} else
|
||||||
}
|
out.reaction = r_SendOnly;
|
||||||
break;
|
break;
|
||||||
case Eject: {
|
case Eject: {
|
||||||
// "Eject" label is multiply wrong; proper meaning unclear:
|
// "Eject" label is multiply wrong; proper meaning unclear:
|
||||||
@@ -106,20 +111,20 @@ void CDChanger::handle(const Frame *in, Frame *out) {
|
|||||||
if (static_cast<bool>(state & SEEKING)) { // FF/RW button released
|
if (static_cast<bool>(state & SEEKING)) { // FF/RW button released
|
||||||
state &= ~SEEKING;
|
state &= ~SEEKING;
|
||||||
} else {
|
} else {
|
||||||
out->is_unicast = true;
|
out.is_unicast = true;
|
||||||
{
|
{
|
||||||
const uint8_t msg[] = {0x00, to_underlying(Device::CD_CHANGER),
|
const uint8_t msg[] = {0x00, to_underlying(Device::CD_CHANGER),
|
||||||
to_underlying(Device::CMD_SW),
|
to_underlying(Device::CMD_SW),
|
||||||
to_underlying(Insertion), 0x01};
|
to_underlying(Insertion), 0x01};
|
||||||
out->length = sizeof(msg);
|
out.length = sizeof(msg);
|
||||||
memcpy(out->data, msg, sizeof(msg));
|
memcpy(out.data, msg, sizeof(msg));
|
||||||
}
|
}
|
||||||
out->reaction = r_SendOnly;
|
out.reaction = r_SendOnly;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Initial_Report_Req: {
|
case Initial_Report_Req: {
|
||||||
out->is_unicast = true;
|
out.is_unicast = true;
|
||||||
// No knowledge/understanding of field meaning/interpretation
|
// No knowledge/understanding of field meaning/interpretation
|
||||||
const uint8_t cdinitreport_resp[] = {0x00,
|
const uint8_t cdinitreport_resp[] = {0x00,
|
||||||
to_underlying(Device::CD_CHANGER),
|
to_underlying(Device::CD_CHANGER),
|
||||||
@@ -130,29 +135,29 @@ void CDChanger::handle(const Frame *in, Frame *out) {
|
|||||||
0x10,
|
0x10,
|
||||||
0x01,
|
0x01,
|
||||||
0x01};
|
0x01};
|
||||||
out->length = sizeof(cdinitreport_resp);
|
out.length = sizeof(cdinitreport_resp);
|
||||||
memcpy(out->data, cdinitreport_resp, sizeof(cdinitreport_resp));
|
memcpy(out.data, cdinitreport_resp, sizeof(cdinitreport_resp));
|
||||||
out->reaction = r_SendOnly;
|
out.reaction = r_SendOnly;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Playback_Req:
|
case Playback_Req:
|
||||||
out->data[0] = 0x00;
|
out.data[0] = 0x00;
|
||||||
out->data[1] = to_underlying(Device::CD_CHANGER);
|
out.data[1] = to_underlying(Device::CD_CHANGER);
|
||||||
out->data[2] = to_underlying(from);
|
out.data[2] = to_underlying(from);
|
||||||
out->data[3] = to_underlying(Playback_Resp);
|
out.data[3] = to_underlying(Playback_Resp);
|
||||||
out->length = WIRE_SIZE + 4;
|
out.length = WIRE_SIZE + 4;
|
||||||
serialize(&out->data[4]);
|
serialize(&out.data[4]);
|
||||||
out->is_unicast = true;
|
out.is_unicast = true;
|
||||||
out->reaction = r_SendOnly;
|
out.reaction = r_SendOnly;
|
||||||
break;
|
break;
|
||||||
case Loading_Req:
|
case Loading_Req:
|
||||||
out->data[0] = 0x00;
|
out.data[0] = 0x00;
|
||||||
out->length = sizeof(cdloading_resp) + 1;
|
out.length = sizeof(cdloading_resp) + 1;
|
||||||
memcpy(&out->data[1], cdloading_resp, sizeof(cdloading_resp));
|
memcpy(&out.data[1], cdloading_resp, sizeof(cdloading_resp));
|
||||||
out->data[2] = to_underlying(from);
|
out.data[2] = to_underlying(from);
|
||||||
out->data[3] = to_underlying(Loading_Resp);
|
out.data[3] = to_underlying(Loading_Resp);
|
||||||
out->is_unicast = true;
|
out.is_unicast = true;
|
||||||
out->reaction = r_SendOnly;
|
out.reaction = r_SendOnly;
|
||||||
break;
|
break;
|
||||||
case Track_Seek_Up:
|
case Track_Seek_Up:
|
||||||
state = SEEKING_TRACK;
|
state = SEEKING_TRACK;
|
||||||
@@ -165,7 +170,7 @@ void CDChanger::handle(const Frame *in, Frame *out) {
|
|||||||
flags2 = 0xc0;
|
flags2 = 0xc0;
|
||||||
generateStatus(out, true, Device::CMD_SW);
|
generateStatus(out, true, Device::CMD_SW);
|
||||||
media_action(MediaAction::Track_Next);
|
media_action(MediaAction::Track_Next);
|
||||||
out->reaction = r_TrackChange;
|
out.reaction = r_TrackChange;
|
||||||
break;
|
break;
|
||||||
case Track_Seek_Down:
|
case Track_Seek_Down:
|
||||||
state = SEEKING_TRACK;
|
state = SEEKING_TRACK;
|
||||||
@@ -182,7 +187,7 @@ void CDChanger::handle(const Frame *in, Frame *out) {
|
|||||||
secs = 0x7f;
|
secs = 0x7f;
|
||||||
flags2 = 0xc0;
|
flags2 = 0xc0;
|
||||||
generateStatus(out, true, Device::CMD_SW);
|
generateStatus(out, true, Device::CMD_SW);
|
||||||
out->reaction = r_TrackChange;
|
out.reaction = r_TrackChange;
|
||||||
break;
|
break;
|
||||||
case Track_Fast_Forward: {
|
case Track_Fast_Forward: {
|
||||||
state |= SEEKING;
|
state |= SEEKING;
|
||||||
@@ -195,7 +200,7 @@ void CDChanger::handle(const Frame *in, Frame *out) {
|
|||||||
media_action(MediaAction::Skip_Forward);
|
media_action(MediaAction::Skip_Forward);
|
||||||
cdtimer_reset(); // Skipped to a whole/round sec; ensure next tick
|
cdtimer_reset(); // Skipped to a whole/round sec; ensure next tick
|
||||||
// is ~1 sec from now
|
// is ~1 sec from now
|
||||||
out->reaction = r_SendOnly;
|
out.reaction = r_SendOnly;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Track_Rewind: {
|
case Track_Rewind: {
|
||||||
@@ -215,66 +220,70 @@ void CDChanger::handle(const Frame *in, Frame *out) {
|
|||||||
media_action(MediaAction::Skip_Backward);
|
media_action(MediaAction::Skip_Backward);
|
||||||
cdtimer_reset(); // Skipped to a whole/round sec; ensure next tick
|
cdtimer_reset(); // Skipped to a whole/round sec; ensure next tick
|
||||||
// is ~1 sec from now
|
// is ~1 sec from now
|
||||||
out->reaction = r_SendOnly;
|
out.reaction = r_SendOnly;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case CD_Enable_Random:
|
case CD_Enable_Random:
|
||||||
flags |= RANDOM;
|
flags |= RANDOM;
|
||||||
generateStatus(out, true, Device::CMD_SW);
|
generateStatus(out, true, Device::CMD_SW);
|
||||||
out->reaction = r_StatusReport;
|
out.reaction = r_StatusReport;
|
||||||
break;
|
break;
|
||||||
case CD_Disable_Random:
|
case CD_Disable_Random:
|
||||||
flags &= ~RANDOM;
|
flags &= ~RANDOM;
|
||||||
generateStatus(out, true, Device::CMD_SW);
|
generateStatus(out, true, Device::CMD_SW);
|
||||||
out->reaction = r_StatusReport;
|
out.reaction = r_StatusReport;
|
||||||
break;
|
break;
|
||||||
case CD_Enable_Repeat:
|
case CD_Enable_Repeat:
|
||||||
flags |= REPEAT;
|
flags |= REPEAT;
|
||||||
generateStatus(out, true, Device::CMD_SW);
|
generateStatus(out, true, Device::CMD_SW);
|
||||||
out->reaction = r_StatusReport;
|
out.reaction = r_StatusReport;
|
||||||
break;
|
break;
|
||||||
case CD_Disable_Repeat:
|
case CD_Disable_Repeat:
|
||||||
flags &= ~REPEAT;
|
flags &= ~REPEAT;
|
||||||
generateStatus(out, true, Device::CMD_SW);
|
generateStatus(out, true, Device::CMD_SW);
|
||||||
out->reaction = r_StatusReport;
|
out.reaction = r_StatusReport;
|
||||||
break;
|
break;
|
||||||
case CD_Enable_Disk_Random:
|
case CD_Enable_Disk_Random:
|
||||||
flags |= DISK_RANDOM;
|
flags |= DISK_RANDOM;
|
||||||
generateStatus(out, true, Device::CMD_SW);
|
generateStatus(out, true, Device::CMD_SW);
|
||||||
out->reaction = r_StatusReport;
|
out.reaction = r_StatusReport;
|
||||||
break;
|
break;
|
||||||
case CD_Disable_Disk_Random:
|
case CD_Disable_Disk_Random:
|
||||||
flags &= ~DISK_RANDOM;
|
flags &= ~DISK_RANDOM;
|
||||||
generateStatus(out, true, Device::CMD_SW);
|
generateStatus(out, true, Device::CMD_SW);
|
||||||
out->reaction = r_StatusReport;
|
out.reaction = r_StatusReport;
|
||||||
break;
|
break;
|
||||||
case CD_Enable_Disk_Repeat:
|
case CD_Enable_Disk_Repeat:
|
||||||
flags |= DISK_REPEAT;
|
flags |= DISK_REPEAT;
|
||||||
generateStatus(out, true, Device::CMD_SW);
|
generateStatus(out, true, Device::CMD_SW);
|
||||||
out->reaction = r_StatusReport;
|
out.reaction = r_StatusReport;
|
||||||
break;
|
break;
|
||||||
case CD_Disable_Disk_Repeat:
|
case CD_Disable_Disk_Repeat:
|
||||||
flags &= ~DISK_REPEAT;
|
flags &= ~DISK_REPEAT;
|
||||||
generateStatus(out, true, Device::CMD_SW);
|
generateStatus(out, true, Device::CMD_SW);
|
||||||
out->reaction = r_StatusReport;
|
out.reaction = r_StatusReport;
|
||||||
break;
|
break;
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
#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),
|
||||||
@@ -303,12 +312,12 @@ void CDChanger::react(Frame *out, detail::Error::Send err) {
|
|||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
case r_NormalizeState:
|
case r_NormalizeState:
|
||||||
normalizeState();
|
normalizeState();
|
||||||
generateStatus(out, false, Device::STATUS);
|
generateStatus(*out, false, Device::STATUS);
|
||||||
out->reaction = r_SendOnly;
|
out->reaction = r_SendOnly;
|
||||||
break;
|
break;
|
||||||
case r_StartPlaying:
|
case r_StartPlaying:
|
||||||
normalizeState();
|
normalizeState();
|
||||||
generateStatus(out, false, Device::STATUS);
|
generateStatus(*out, false, Device::STATUS);
|
||||||
out->reaction = r_BeganPlaying;
|
out->reaction = r_BeganPlaying;
|
||||||
break;
|
break;
|
||||||
case r_BeganPlaying:
|
case r_BeganPlaying:
|
||||||
@@ -316,16 +325,23 @@ void CDChanger::react(Frame *out, detail::Error::Send err) {
|
|||||||
out->reaction = r_Nothing;
|
out->reaction = r_Nothing;
|
||||||
break;
|
break;
|
||||||
case r_StatusReport:
|
case r_StatusReport:
|
||||||
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) {
|
||||||
if (!isPlaying()) {
|
if (!isPlaying()) {
|
||||||
if (mins > TWODIGIT_MAX)
|
if (mins > TWODIGIT_MAX)
|
||||||
mins = 0;
|
mins = 0;
|
||||||
@@ -334,18 +350,16 @@ void CDChanger::enable(Frame *out) {
|
|||||||
state = SEEKING | SEEKING_TRACK;
|
state = SEEKING | SEEKING_TRACK;
|
||||||
flags2 = 0xc0;
|
flags2 = 0xc0;
|
||||||
generateStatus(out, false, Device::STATUS);
|
generateStatus(out, false, Device::STATUS);
|
||||||
out->reaction = r_StartPlaying;
|
out.reaction = r_StartPlaying;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CDChanger::pending() { return cdtimer_pending(); }
|
bool CDChanger::pending() { return cdtimer_pending(); }
|
||||||
void CDChanger::resolvepending() { cdtimer_clear(); }
|
|
||||||
|
|
||||||
void CDChanger::emit(Frame *out, uint16_t peripheral) {
|
void CDChanger::emit(Frame &out) {
|
||||||
out->owning_device = id; // so react() routes r_StateReport back here
|
|
||||||
out->peripheral_addr = peripheral;
|
|
||||||
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; }
|
||||||
@@ -401,15 +415,15 @@ void CDChanger::incrementTime() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Used for changed status messages
|
// Used for changed status messages
|
||||||
void CDChanger::generateStatus(Frame *status, bool is_unicast,
|
void CDChanger::generateStatus(Frame &status, bool is_unicast,
|
||||||
Device to) const {
|
Device to) const {
|
||||||
status->is_unicast = is_unicast;
|
status.is_unicast = is_unicast;
|
||||||
if (!is_unicast)
|
if (!is_unicast)
|
||||||
status->peripheral_addr = 0x1FF;
|
status.peripheral_addr = 0x1FF;
|
||||||
status->control = 0xF;
|
status.control = 0xF;
|
||||||
status->length = WIRE_SIZE + ((is_unicast) ? 4 : 3);
|
status.length = WIRE_SIZE + ((is_unicast) ? 4 : 3);
|
||||||
|
|
||||||
uint8_t *data = status->data;
|
uint8_t *data = status.data;
|
||||||
if (is_unicast)
|
if (is_unicast)
|
||||||
*data++ = 0x00;
|
*data++ = 0x00;
|
||||||
*data++ = to_underlying(Device::CD_CHANGER);
|
*data++ = to_underlying(Device::CD_CHANGER);
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -60,13 +61,13 @@ public:
|
|||||||
static constexpr Device id = Device::CD_CHANGER;
|
static constexpr Device id = Device::CD_CHANGER;
|
||||||
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>
|
||||||
void enable(Frame *out);
|
react(expected<std::unique_ptr<Frame>, detail::SendError> exp);
|
||||||
void disable(Frame *out);
|
void enable(Frame &out);
|
||||||
|
void disable(Frame &out);
|
||||||
static bool pending();
|
static bool pending();
|
||||||
static void resolvepending();
|
void emit(Frame &out);
|
||||||
void emit(Frame *out, uint16_t peripheral);
|
|
||||||
void incrementTime();
|
void incrementTime();
|
||||||
bool isPlaying() const;
|
bool isPlaying() const;
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
@@ -80,7 +81,7 @@ private:
|
|||||||
void stopPlaying();
|
void stopPlaying();
|
||||||
void serialize(uint8_t *dst) const;
|
void serialize(uint8_t *dst) const;
|
||||||
void setTime(uint8_t mins, uint8_t secs);
|
void setTime(uint8_t mins, uint8_t secs);
|
||||||
void generateStatus(Frame *status, bool is_unicast, Device to) const;
|
void generateStatus(Frame &status, bool is_unicast, Device to) const;
|
||||||
void normalizeState();
|
void normalizeState();
|
||||||
|
|
||||||
bool playing = false;
|
bool playing = false;
|
||||||
|
|||||||
+10
-5
@@ -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 {
|
||||||
|
|
||||||
@@ -48,14 +50,17 @@ enum class Device : uint8_t {
|
|||||||
template <class T>
|
template <class T>
|
||||||
concept DeviceInterface =
|
concept DeviceInterface =
|
||||||
requires { 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,
|
||||||
uint16_t peripheral) {
|
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, peripheral);
|
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
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ Bit phy_read_bits_u8(uint8_t *bits, uint8_t len);
|
|||||||
Bit phy_read_bits_u16(uint16_t *bits, int8_t len);
|
Bit phy_read_bits_u16(uint16_t *bits, int8_t len);
|
||||||
Bit phy_read_byte(uint8_t *byte);
|
Bit phy_read_byte(uint8_t *byte);
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#if !defined(NDEBUG) && defined(MEASURE_BUS)
|
||||||
// Sample and dump bus bit timing over the serial link (REPL `M`).
|
// Sample and dump bus bit timing over the serial link (REPL `M`).
|
||||||
void phy_measure(void);
|
void phy_measure(void);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+132
-49
@@ -4,20 +4,41 @@
|
|||||||
// 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 <cstdint>
|
|
||||||
#include <cstring>
|
|
||||||
#include <tuple>
|
|
||||||
|
|
||||||
namespace avclan {
|
namespace avclan {
|
||||||
|
namespace detail {
|
||||||
|
enum class Party : uint8_t { Sender, Recipient };
|
||||||
|
}
|
||||||
|
|
||||||
template <DeviceInterface... Devs> class Peripheral {
|
template <DeviceInterface... Devs> class Peripheral {
|
||||||
|
using Party = detail::Party;
|
||||||
|
using enum Party;
|
||||||
|
|
||||||
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(), ...);
|
||||||
@@ -36,41 +57,56 @@ 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
|
||||||
postmark(out);
|
stamp<Sender>(*out);
|
||||||
return bus.send(out, print);
|
out->control = 0xF;
|
||||||
|
auto err = bus.send(*out, 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};
|
||||||
|
|
||||||
out->peripheral_addr = controller_;
|
stamp<Recipient>(*out);
|
||||||
|
|
||||||
const uint8_t *data = in->data;
|
const uint8_t *data = in.data;
|
||||||
const uint8_t b0 = *data++;
|
const uint8_t b0 = *data++;
|
||||||
const uint8_t b1 = *data++;
|
const uint8_t b1 = *data++;
|
||||||
const uint8_t b2 = *data++;
|
const uint8_t b2 = *data++;
|
||||||
uint8_t b3 = 0;
|
|
||||||
if (in->length > 3) // the shortest known/valid messages are 3 bytes long
|
|
||||||
b3 = *data++;
|
|
||||||
|
|
||||||
if (!in->is_unicast) {
|
// the shortest known/valid messages are 3 bytes long
|
||||||
|
const uint8_t b3 = (in.length > 3) ? *data++ : 0;
|
||||||
|
|
||||||
|
if (!in.is_unicast) {
|
||||||
const auto from = b0;
|
const auto from = b0;
|
||||||
const auto to = b1;
|
const auto to = b1;
|
||||||
const auto action = b2;
|
const auto action = b2;
|
||||||
@@ -103,15 +139,14 @@ public:
|
|||||||
case PACK3(COMMUNICATION_V1, COMM_CTRL,
|
case PACK3(COMMUNICATION_V1, COMM_CTRL,
|
||||||
to_underlying(Advertise_Function)):
|
to_underlying(Advertise_Function)):
|
||||||
case PACK3(COMMUNICATION_V2, COMM_CTRL,
|
case PACK3(COMMUNICATION_V2, COMM_CTRL,
|
||||||
to_underlying(Advertise_Function)):
|
to_underlying(Advertise_Function)): {
|
||||||
|
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)
|
||||||
out->owning_device = Devs::id;
|
|
||||||
std::get<Devs>(devices_).enable(out);
|
|
||||||
}()
|
|
||||||
: void()),
|
: void()),
|
||||||
...);
|
...);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case PACK3(COMMUNICATION_V1, COMM_CTRL, to_underlying(Ping_Req)):
|
case PACK3(COMMUNICATION_V1, COMM_CTRL, to_underlying(Ping_Req)):
|
||||||
case PACK3(COMMUNICATION_V2, COMM_CTRL, to_underlying(Ping_Req)): {
|
case PACK3(COMMUNICATION_V2, COMM_CTRL, to_underlying(Ping_Req)): {
|
||||||
out->is_unicast = true;
|
out->is_unicast = true;
|
||||||
@@ -127,8 +162,8 @@ public:
|
|||||||
to_underlying(List_Functions_Req)):
|
to_underlying(List_Functions_Req)):
|
||||||
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;
|
||||||
out->peripheral_addr = controller_;
|
stamp<Recipient>(*out); // 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,
|
||||||
@@ -141,44 +176,92 @@ public:
|
|||||||
// case Restart_Lan: not handled
|
// case Restart_Lan: not handled
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
} 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); };
|
||||||
((Devs::id == static_cast<Device>(b2)
|
((Devs::id == static_cast<Device>(b2)
|
||||||
? device_preroute(std::get<Devs>(devices_), in, out),
|
? originate(std::get<Devs>(devices_), *out, handle_d)
|
||||||
0 : 0),
|
: 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 =
|
||||||
? std::get<Devs>(devices_).react(out, err),
|
exp ? exp.value()->owning_device : exp.error().owning_device;
|
||||||
0 : 0),
|
|
||||||
|
std::unique_ptr<Frame> next;
|
||||||
|
((Devs::id == from &&
|
||||||
|
(next = std::get<Devs>(devices_).react(std::move(exp)))) ||
|
||||||
...);
|
...);
|
||||||
else
|
return next;
|
||||||
out->reaction = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class F> void poll_devices(F &&fun) {
|
// Service ready devices in round-robin order
|
||||||
(poller(std::get<Devs>(devices_), fun), ...);
|
std::unique_ptr<Frame> poll() {
|
||||||
|
using U = std::unique_ptr<Frame>;
|
||||||
|
auto does_emit = [&](DeviceInterface auto &dev) -> U {
|
||||||
|
if (!dev.pending())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
U out(new (std::nothrow) Frame);
|
||||||
|
if (!out) {
|
||||||
|
puts("!! failed Frame alloc in poll !!");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
originate(dev, *out, [](auto &d, auto &out) { d.emit(out); });
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Runtime tuple index helper
|
||||||
|
auto does_index_emit = [&](std::size_t t) -> U {
|
||||||
|
return [&]<std::size_t... Is>(std::index_sequence<Is...>) -> U {
|
||||||
|
U out;
|
||||||
|
((Is == t && (out = does_emit(std::get<Is>(devices_)))) || ...);
|
||||||
|
return out;
|
||||||
|
}(std::index_sequence_for<Devs...>{});
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr std::size_t N = sizeof...(Devs);
|
||||||
|
if constexpr (N == 1) { // round-robin not needed
|
||||||
|
return does_emit(std::get<0>(devices_));
|
||||||
|
} else {
|
||||||
|
static uint8_t rr_ = 0; // round-robin cursor
|
||||||
|
const std::size_t start = rr_;
|
||||||
|
for (std::size_t t = start; t < N; ++t) // [start, N)
|
||||||
|
if (auto out = does_index_emit(t)) {
|
||||||
|
rr_ = (t + 1 == N) ? 0 : t + 1;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
for (std::size_t t = 0; t < start; ++t) // [0, start); t+1 <= start < N
|
||||||
|
if (auto out = does_index_emit(t)) {
|
||||||
|
rr_ = t + 1;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void postmark(Frame *out) const {
|
template <Party P> void stamp(Frame &out) const {
|
||||||
out->controller_addr = address_;
|
if constexpr (P == Sender)
|
||||||
out->control = 0xF;
|
out.controller_addr = address_;
|
||||||
|
else
|
||||||
|
out.peripheral_addr = controller_;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <DeviceInterface Dev, class F> void poller(Dev &dev, F &&fun) {
|
void originate(DeviceInterface auto &dev, Frame &out, auto &&fill) {
|
||||||
if (dev.pending() && fun(dev))
|
out.owning_device = std::remove_reference_t<decltype(dev)>::id;
|
||||||
dev.resolvepending();
|
stamp<Recipient>(out); // default set FIRST; fill() may override
|
||||||
}
|
fill(dev, out);
|
||||||
template <DeviceInterface Dev>
|
|
||||||
void device_preroute(Dev &dev, const Frame *in, Frame *out) {
|
|
||||||
out->owning_device = Dev::id;
|
|
||||||
dev.handle(in, out);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Bus &bus;
|
Bus &bus;
|
||||||
|
|||||||
@@ -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 24 CACHE STRING "Pool allocator capacity for avclan::Frame (must be <= incoming/outgoing queue size)")
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -344,6 +344,14 @@ void phy_init() {
|
|||||||
// error reporting; no printing happens here.
|
// error reporting; no printing happens here.
|
||||||
Read phy_read_startbit() {
|
Read phy_read_startbit() {
|
||||||
uint16_t startbitlen = TCB1.CNT = 0;
|
uint16_t startbitlen = TCB1.CNT = 0;
|
||||||
|
|
||||||
|
// Reset the ~atomic `pulsewidth` variable to detect the post-pulse update
|
||||||
|
// from the TCB0_INT_vect ISR
|
||||||
|
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
|
||||||
|
if (!BUS_IS_IDLE) // Only reset if bus is actively driven (i.e. current
|
||||||
|
pulsewidth = 0; // value is stale/already been used)
|
||||||
|
}
|
||||||
|
|
||||||
while (!BUS_IS_IDLE) {
|
while (!BUS_IS_IDLE) {
|
||||||
startbitlen = TCB1.CNT;
|
startbitlen = TCB1.CNT;
|
||||||
if (startbitlen > (uint16_t)AVCLAN_STARTBIT_LOGIC_0 * 1.2) {
|
if (startbitlen > (uint16_t)AVCLAN_STARTBIT_LOGIC_0 * 1.2) {
|
||||||
@@ -367,18 +375,34 @@ Read phy_read_startbit() {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `pulsewidth` updates once the TCB0_INT_vect ISR runs for this pulse.
|
||||||
|
TCB1.CNT = 0;
|
||||||
|
do {
|
||||||
|
if (TCB1.CNT > (uint16_t)AVCLAN_BIT0_LOGIC_1) // Wait a max of ~6μs for ISR
|
||||||
|
return BAD_STARTBIT; // ISR/other implementation bug; abort
|
||||||
|
|
||||||
|
// Read ~atomically, to prevent torn reads
|
||||||
|
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { startbitlen = pulsewidth; }
|
||||||
|
} while (startbitlen == 0);
|
||||||
|
|
||||||
if (startbitlen < (uint16_t)(AVCLAN_STARTBIT_LOGIC_0 * 0.8)) {
|
if (startbitlen < (uint16_t)(AVCLAN_STARTBIT_LOGIC_0 * 0.8)) {
|
||||||
// We missed the beginning of this message; wait for it to finish (bus
|
// Not a start bit; wait for the message to finish (bus continuously idle
|
||||||
// continuously idle for >1 bit length) before returning, so we don't have
|
// for >1 bit length) before returning, so we only report one error (instead
|
||||||
// multiple false-starts while the in-progress message keeps sending more
|
// of e.g. repeated "bad (short) start bit" errors)
|
||||||
// bits.
|
|
||||||
TCB1.CNT = 0;
|
TCB1.CNT = 0;
|
||||||
while (TCB1.CNT < (uint16_t)(AVCLAN_BIT_LENGTH_MAX * 1.2)) {
|
while (TCB1.CNT < (uint16_t)(AVCLAN_BIT_LENGTH_MAX * 1.2)) {
|
||||||
if (!BUS_IS_IDLE)
|
if (!BUS_IS_IDLE)
|
||||||
TCB1.CNT = 0;
|
TCB1.CNT = 0; // Reset counter after each bit pulse
|
||||||
}
|
}
|
||||||
return STARTBIT_TOO_SHORT;
|
// A pulse no wider than a normal bit means we merely tuned in mid-frame and
|
||||||
|
// this was a data bit; a wider-but-still-sub-start pulse means some other
|
||||||
|
// device emitted a wonky pulse.
|
||||||
|
return (startbitlen < (uint16_t)AVCLAN_BIT_LENGTH_MAX) ? STARTBIT_MISSED
|
||||||
|
: STARTBIT_MALFORMED;
|
||||||
}
|
}
|
||||||
|
if (startbitlen > (uint16_t)(AVCLAN_STARTBIT_LOGIC_0 * 1.2))
|
||||||
|
return STARTBIT_TOO_LONG;
|
||||||
return (Read)0; // that was a start bit
|
return (Read)0; // that was a start bit
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -432,7 +456,7 @@ void phy_guard_leave() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#if !defined(NDEBUG) && defined(MEASURE_BUS)
|
||||||
// Only used immediately below
|
// Only used immediately below
|
||||||
#define XSTR(x) #x
|
#define XSTR(x) #x
|
||||||
#define STR(x) XSTR(x)
|
#define STR(x) XSTR(x)
|
||||||
|
|||||||
+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
|
||||||
|
|||||||
+62
-49
@@ -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,44 +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 !!");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
peripheral.poll_devices([&](auto &dev) {
|
if (auto msg = peripheral.poll())
|
||||||
if (auto status = cache.pop()) {
|
outgoing.push(std::move(msg));
|
||||||
dev.emit(status.get(), peripheral.controller());
|
|
||||||
outgoing.push(std::move(status));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
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
|
||||||
@@ -133,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();
|
||||||
{
|
{
|
||||||
@@ -145,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();
|
||||||
{
|
{
|
||||||
@@ -163,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
|
||||||
@@ -189,7 +176,9 @@ int main() {
|
|||||||
while (peripheral.device<CDChanger>().media_busy()) {}
|
while (peripheral.device<CDChanger>().media_busy()) {}
|
||||||
puts("end");
|
puts("end");
|
||||||
break;
|
break;
|
||||||
|
#ifdef MEASURE_BUS
|
||||||
case 'M': peripheral.get_bus().measure(); break;
|
case 'M': peripheral.get_bus().measure(); break;
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
case 0x10: // Signals binary sequence incoming
|
case 0x10: // Signals binary sequence incoming
|
||||||
@@ -222,34 +211,58 @@ 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));
|
||||||
}
|
|
||||||
}
|
|
||||||
readSeq = readBinary = false;
|
readSeq = readBinary = false;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
puts("!! failed Frame alloc for input message !!");
|
||||||
} 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 { // ASCII message
|
||||||
if (auto out = cache.pop()) {
|
if (auto out = std::unique_ptr<Frame>(new (std::nothrow) Frame)) {
|
||||||
|
const uint8_t sendLen =
|
||||||
|
seqIdx <= Frame::MAXLENGTH ? seqIdx : Frame::MAXLENGTH;
|
||||||
out->is_unicast = seqIsUnicast;
|
out->is_unicast = seqIsUnicast;
|
||||||
out->peripheral_addr =
|
out->peripheral_addr =
|
||||||
seqIsUnicast ? peripheral.controller() : 0x1FF;
|
seqIsUnicast ? peripheral.controller() : 0x1FF;
|
||||||
out->length = seqIdx;
|
out->length = sendLen;
|
||||||
memcpy(out->data, data_tmp, seqIdx);
|
memcpy(out->data, data_tmp, sendLen);
|
||||||
out->reaction = 1;
|
out->reaction = 1;
|
||||||
outgoing.push(std::move(out));
|
outgoing.push(std::move(out));
|
||||||
}
|
|
||||||
|
if (seqIdx > Frame::MAXLENGTH)
|
||||||
|
printf("!! sequence too long (%u > %u), truncated !!\n",
|
||||||
|
static_cast<unsigned>(seqIdx),
|
||||||
|
static_cast<unsigned>(Frame::MAXLENGTH));
|
||||||
|
|
||||||
|
// Only leave hex-entry mode and restore logging once the
|
||||||
|
// message actually sent, so a failed alloc can be retried
|
||||||
|
// with '\n' instead of silently dropping the entry.
|
||||||
|
readSeq = false;
|
||||||
|
seqIdx = hexDigit = 0;
|
||||||
printAllFrames = lastPrintAllFrames;
|
printAllFrames = lastPrintAllFrames;
|
||||||
|
} else
|
||||||
|
puts("!! failed Frame alloc for input message !!");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
DEFAULT:
|
DEFAULT:
|
||||||
default:
|
default:
|
||||||
if (readSeq && seqIdx < (Frame::MAXLENGTH + sizeof(Frame))) {
|
if (readSeq) {
|
||||||
|
// Binary mode carries the full wire preamble; hex mode is payload
|
||||||
|
// only, so it stops one past MAXLENGTH to let '\n' report overflow.
|
||||||
|
if (seqIdx >= (readBinary ? sizeof(data_tmp)
|
||||||
|
: uint8_t{Frame::MAXLENGTH + 1})) {
|
||||||
|
puts("!! sequence buffer full, ignoring further input !!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (readBinary) {
|
if (readBinary) {
|
||||||
data_tmp[seqIdx++] = readkey;
|
data_tmp[seqIdx++] = readkey;
|
||||||
} else {
|
} else {
|
||||||
@@ -291,8 +304,8 @@ void Setup() {
|
|||||||
|
|
||||||
void print_help() {
|
void print_help() {
|
||||||
puts("AVCLAN Mockingboard v1");
|
puts("AVCLAN Mockingboard v1");
|
||||||
puts("U - begin reading for unicast message\n"
|
puts("U - begin reading for unicast message (send with Enter key)\n"
|
||||||
"B - begin reading for broadcast message\n"
|
"B - begin reading for broadcast message (send with Enter key)\n"
|
||||||
"m - Toggle mute for mockingboard bus activity\n"
|
"m - Toggle mute for mockingboard bus activity\n"
|
||||||
"v - Toggle verbose error logging\n"
|
"v - Toggle verbose error logging\n"
|
||||||
"l - Toggle message logging\n"
|
"l - Toggle message logging\n"
|
||||||
|
|||||||
Reference in New Issue
Block a user