Port Queue to C++

Some knock-on changes to keep a simple design:
- Bake the data array into AVCLAN_frame_t
- Bundle the response/reaction with AVCLAN_frame_t
    - (Needed to maintain correct/simple ownership semantics with the
      cache and outgoing/incoming queues)
- Switch status frame update ticks to use a regular
  cache-originating/owned frame
This commit is contained in:
Allen Hill
2026-06-24 17:40:30 -07:00
parent 77ff4652fd
commit 02b370dd06
10 changed files with 251 additions and 309 deletions
+8 -6
View File
@@ -1,13 +1,16 @@
cmake_minimum_required(VERSION 3.24) cmake_minimum_required(VERSION 3.24)
project(avclan-mockingboard VERSION 1 LANGUAGES C CXX ASM) project(avclan-mockingboard VERSION 1.1 LANGUAGES C CXX)
set(CMAKE_C_STANDARD 23) set(CMAKE_C_STANDARD 23)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_compile_options( add_compile_options(
-Wall -Wswitch-enum -Werror -Wall -Wswitch-enum -Werror
"$<$<CONFIG:Debug>:-fanalyzer>") $<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions>
$<$<COMPILE_LANGUAGE:CXX>:-fno-rtti>
$<$<CONFIG:Debug>:-fanalyzer>)
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "13") if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "13")
@@ -33,10 +36,9 @@ add_library(avclan STATIC
src/avclan/cdchanger.c) src/avclan/cdchanger.c)
add_executable(mockingboard add_executable(mockingboard
src/sniffer.c src/sniffer.cc
src/com232.c src/com232.c)
src/queue.c)
# avclan exports its public generic headers (src/avclan) to consumers and # avclan exports its public generic headers (src/avclan) to consumers and
# reaches into src/ for sibling headers (com232.h, board.h) during its own # reaches into src/ for sibling headers (com232.h, board.h) during its own
# build. The selected target adds its own per-target include path. # build. The selected target adds its own per-target include path.
+2 -1
View File
@@ -116,12 +116,13 @@ typedef struct print_struct {
} log_t; } log_t;
typedef struct AVCLAN_frame_struct { typedef struct AVCLAN_frame_struct {
uint8_t reaction;
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; uint8_t control;
uint8_t length; uint8_t length;
uint8_t *data; uint8_t data[MAXMSGLEN];
} AVCLAN_frame_t; } AVCLAN_frame_t;
// A single bus symbol. bit_zero/bit_one carry data (and double as parity // A single bus symbol. bit_zero/bit_one carry data (and double as parity
+39 -41
View File
@@ -25,11 +25,11 @@ static const uint8_t cdloading_resp[] = {dev_CD_CHANGER,
0x01, 0x01,
0x02}; 0x02};
response_t AVCLAN_handleframe(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) { void AVCLAN_handleframe(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) {
response_t respond = r_Nothing; out->reaction = r_Nothing;
if (AVCLAN_ismuted() || in->length < 3) if (AVCLAN_ismuted() || in->length < 3)
return respond; return;
// 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.
@@ -60,7 +60,7 @@ response_t AVCLAN_handleframe(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) {
memcpy(out->data, lancheck_resp, sizeof(lancheck_resp)); memcpy(out->data, lancheck_resp, sizeof(lancheck_resp));
out->data[3] = Lancheck_Scan_Resp; out->data[3] = Lancheck_Scan_Resp;
out->data[4] = 0x01; out->data[4] = 0x01;
respond = r_Handled; out->reaction = r_SendOnly;
break; break;
case PACK3(dev_LAN, dev_COMM_CTRL, Lancheck_Req): case PACK3(dev_LAN, dev_COMM_CTRL, Lancheck_Req):
out->length = sizeof(lancheck_resp); out->length = sizeof(lancheck_resp);
@@ -69,7 +69,7 @@ response_t AVCLAN_handleframe(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) {
memcpy(out->data, lancheck_resp, sizeof(lancheck_resp)); memcpy(out->data, lancheck_resp, sizeof(lancheck_resp));
out->data[3] = Lancheck_Resp; out->data[3] = Lancheck_Resp;
out->data[4] = 0x00; out->data[4] = 0x00;
respond = r_Handled; out->reaction = r_SendOnly;
break; break;
case PACK3(dev_LAN, dev_COMM_CTRL, Lancheck_End_Req): case PACK3(dev_LAN, dev_COMM_CTRL, Lancheck_End_Req):
out->is_unicast = true; out->is_unicast = true;
@@ -77,7 +77,7 @@ response_t AVCLAN_handleframe(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) {
out->length = sizeof(lancheck_resp) - 1; out->length = sizeof(lancheck_resp) - 1;
memcpy(out->data, lancheck_resp, out->length); memcpy(out->data, lancheck_resp, out->length);
out->data[3] = Lancheck_End_Resp; out->data[3] = Lancheck_End_Resp;
respond = r_Handled; out->reaction = r_SendOnly;
break; break;
case PACK3(dev_COMM_v1, dev_COMM_CTRL, Current_Function): case PACK3(dev_COMM_v1, dev_COMM_CTRL, Current_Function):
case PACK3(dev_COMM_v2, dev_COMM_CTRL, Current_Function): case PACK3(dev_COMM_v2, dev_COMM_CTRL, Current_Function):
@@ -89,7 +89,7 @@ response_t AVCLAN_handleframe(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) {
cd_status.state = cd_SEEKING | cd_SEEKING_TRACK; cd_status.state = cd_SEEKING | cd_SEEKING_TRACK;
cd_status.flags2 = 0xc0; cd_status.flags2 = 0xc0;
AVCLAN_generateStatus(out, true, dev_STATUS); AVCLAN_generateStatus(out, true, dev_STATUS);
respond = r_StartPlaying; out->reaction = r_StartPlaying;
} }
break; break;
case PACK3(dev_COMM_v1, dev_COMM_CTRL, Ping_Req): case PACK3(dev_COMM_v1, dev_COMM_CTRL, Ping_Req):
@@ -100,7 +100,7 @@ response_t AVCLAN_handleframe(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) {
Ping_Resp, 0xFF, b3}; Ping_Resp, 0xFF, b3};
out->length = sizeof(ping_resp); out->length = sizeof(ping_resp);
memcpy(out->data, ping_resp, sizeof(ping_resp)); memcpy(out->data, ping_resp, sizeof(ping_resp));
respond = r_Handled; out->reaction = r_SendOnly;
break; break;
} }
case PACK3(dev_COMM_v1, dev_COMM_CTRL, List_Functions_Req): case PACK3(dev_COMM_v1, dev_COMM_CTRL, List_Functions_Req):
@@ -112,7 +112,7 @@ response_t AVCLAN_handleframe(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) {
dev_CD_CHANGER}; dev_CD_CHANGER};
out->length = sizeof(list_functions_resp); out->length = sizeof(list_functions_resp);
memcpy(out->data, list_functions_resp, sizeof(list_functions_resp)); memcpy(out->data, list_functions_resp, sizeof(list_functions_resp));
respond = r_Handled; out->reaction = r_SendOnly;
break; break;
} }
// case Restart_Lan: not handled // case Restart_Lan: not handled
@@ -130,7 +130,7 @@ response_t AVCLAN_handleframe(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) {
out->data[3] = Enable_Function_Resp; out->data[3] = Enable_Function_Resp;
cd_status.state = 0; cd_status.state = 0;
cd_status.flags2 = 0x80; cd_status.flags2 = 0x80;
respond = r_StatusReport; out->reaction = r_StatusReport;
break; break;
case PACK3(dev_COMM_v1, dev_CD_CHANGER, Disable_Function_Req): case PACK3(dev_COMM_v1, dev_CD_CHANGER, Disable_Function_Req):
[[fallthrough]]; [[fallthrough]];
@@ -145,7 +145,7 @@ response_t AVCLAN_handleframe(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) {
cd_status.flags2 = 0x80; cd_status.flags2 = 0x80;
out->is_unicast = true; out->is_unicast = true;
out->peripheral_addr = HU_ADDR; out->peripheral_addr = HU_ADDR;
respond = r_StatusReport; out->reaction = r_StatusReport;
} }
break; break;
case PACK3(dev_CMD_SW, dev_CD_CHANGER, Eject): { case PACK3(dev_CMD_SW, dev_CD_CHANGER, Eject): {
@@ -166,7 +166,7 @@ response_t AVCLAN_handleframe(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) {
out->length = sizeof(msg); out->length = sizeof(msg);
memcpy(out->data, msg, sizeof(msg)); memcpy(out->data, msg, sizeof(msg));
} }
respond = r_Handled; out->reaction = r_SendOnly;
} }
break; break;
} }
@@ -182,7 +182,7 @@ response_t AVCLAN_handleframe(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) {
0x10, 0x01, 0x01}; 0x10, 0x01, 0x01};
out->length = sizeof(cdinitreport_resp); out->length = sizeof(cdinitreport_resp);
memcpy(&out->data[1], cdinitreport_resp, sizeof(cdinitreport_resp)); memcpy(&out->data[1], cdinitreport_resp, sizeof(cdinitreport_resp));
respond = r_Handled; out->reaction = r_SendOnly;
break; break;
} }
case PACK3(dev_CMD_SW, dev_CD_CHANGER, Playback_Request): [[fallthrough]]; case PACK3(dev_CMD_SW, dev_CD_CHANGER, Playback_Request): [[fallthrough]];
@@ -195,7 +195,7 @@ response_t AVCLAN_handleframe(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) {
serializeCDStatus(&out->data[4]); serializeCDStatus(&out->data[4]);
out->is_unicast = true; out->is_unicast = true;
out->peripheral_addr = HU_ADDR; out->peripheral_addr = HU_ADDR;
respond = r_Handled; out->reaction = r_SendOnly;
break; break;
case PACK3(dev_CMD_SW, dev_CD_CHANGER, Loading_Request2): [[fallthrough]]; case PACK3(dev_CMD_SW, dev_CD_CHANGER, Loading_Request2): [[fallthrough]];
case PACK3(dev_STATUS, dev_CD_CHANGER, Loading_Request2): case PACK3(dev_STATUS, dev_CD_CHANGER, Loading_Request2):
@@ -206,7 +206,7 @@ response_t AVCLAN_handleframe(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) {
out->data[3] = Loading_Response2; out->data[3] = Loading_Response2;
out->is_unicast = true; out->is_unicast = true;
out->peripheral_addr = HU_ADDR; out->peripheral_addr = HU_ADDR;
respond = r_Handled; out->reaction = r_SendOnly;
break; break;
case PACK3(dev_CMD_SW, dev_CD_CHANGER, Track_Seek_Up): case PACK3(dev_CMD_SW, dev_CD_CHANGER, Track_Seek_Up):
cd_status.state = cd_SEEKING_TRACK; cd_status.state = cd_SEEKING_TRACK;
@@ -219,7 +219,7 @@ response_t AVCLAN_handleframe(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) {
cd_status.flags2 = 0xc0; cd_status.flags2 = 0xc0;
AVCLAN_generateStatus(out, true, dev_CMD_SW); AVCLAN_generateStatus(out, true, dev_CMD_SW);
AVCLAN_mediaFunction(MEDIA_SKIP_FORWARD); AVCLAN_mediaFunction(MEDIA_SKIP_FORWARD);
respond = r_TrackChange; out->reaction = r_TrackChange;
break; break;
case PACK3(dev_CMD_SW, dev_CD_CHANGER, Track_Seek_Down): case PACK3(dev_CMD_SW, dev_CD_CHANGER, Track_Seek_Down):
cd_status.state = cd_SEEKING_TRACK; cd_status.state = cd_SEEKING_TRACK;
@@ -235,7 +235,7 @@ response_t AVCLAN_handleframe(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) {
cd_status.flags2 = 0xc0; cd_status.flags2 = 0xc0;
AVCLAN_generateStatus(out, true, dev_CMD_SW); AVCLAN_generateStatus(out, true, dev_CMD_SW);
AVCLAN_mediaFunction(MEDIA_SKIP_BACKWARD); AVCLAN_mediaFunction(MEDIA_SKIP_BACKWARD);
respond = r_TrackChange; out->reaction = r_TrackChange;
break; break;
case PACK3(dev_CMD_SW, dev_CD_CHANGER, Track_Fast_Forward): { case PACK3(dev_CMD_SW, dev_CD_CHANGER, Track_Fast_Forward): {
cd_status.state |= cd_SEEKING; cd_status.state |= cd_SEEKING;
@@ -248,7 +248,7 @@ response_t AVCLAN_handleframe(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) {
AVCLAN_mediaFunction(MEDIA_SKIP_FORWARD); AVCLAN_mediaFunction(MEDIA_SKIP_FORWARD);
statustimer_reset(); // Skipped to a whole/round sec; ensure next tick statustimer_reset(); // Skipped to a whole/round sec; ensure next tick
// is ~1 sec from now // is ~1 sec from now
respond = r_Handled; out->reaction = r_SendOnly;
break; break;
} }
case PACK3(dev_CMD_SW, dev_CD_CHANGER, Track_Rewind): { case PACK3(dev_CMD_SW, dev_CD_CHANGER, Track_Rewind): {
@@ -268,67 +268,66 @@ response_t AVCLAN_handleframe(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) {
AVCLAN_mediaFunction(MEDIA_SKIP_BACKWARD); AVCLAN_mediaFunction(MEDIA_SKIP_BACKWARD);
statustimer_reset(); // Skipped to a whole/round sec; ensure next tick statustimer_reset(); // Skipped to a whole/round sec; ensure next tick
// is ~1 sec from now // is ~1 sec from now
respond = r_Handled; out->reaction = r_SendOnly;
break; break;
} }
case PACK3(dev_CMD_SW, dev_CD_CHANGER, CD_Enable_Random): case PACK3(dev_CMD_SW, dev_CD_CHANGER, CD_Enable_Random):
cd_status.flags |= cd_RANDOM; cd_status.flags |= cd_RANDOM;
AVCLAN_generateStatus(out, true, dev_CMD_SW); AVCLAN_generateStatus(out, true, dev_CMD_SW);
respond = r_StatusReport; out->reaction = r_StatusReport;
break; break;
case PACK3(dev_CMD_SW, dev_CD_CHANGER, CD_Disable_Random): case PACK3(dev_CMD_SW, dev_CD_CHANGER, CD_Disable_Random):
cd_status.flags &= ~cd_RANDOM; cd_status.flags &= ~cd_RANDOM;
AVCLAN_generateStatus(out, true, dev_CMD_SW); AVCLAN_generateStatus(out, true, dev_CMD_SW);
respond = r_StatusReport; out->reaction = r_StatusReport;
break; break;
case PACK3(dev_CMD_SW, dev_CD_CHANGER, CD_Enable_Repeat): case PACK3(dev_CMD_SW, dev_CD_CHANGER, CD_Enable_Repeat):
cd_status.flags |= cd_REPEAT; cd_status.flags |= cd_REPEAT;
AVCLAN_generateStatus(out, true, dev_CMD_SW); AVCLAN_generateStatus(out, true, dev_CMD_SW);
respond = r_StatusReport; out->reaction = r_StatusReport;
break; break;
case PACK3(dev_CMD_SW, dev_CD_CHANGER, CD_Disable_Repeat): case PACK3(dev_CMD_SW, dev_CD_CHANGER, CD_Disable_Repeat):
cd_status.flags &= ~cd_REPEAT; cd_status.flags &= ~cd_REPEAT;
AVCLAN_generateStatus(out, true, dev_CMD_SW); AVCLAN_generateStatus(out, true, dev_CMD_SW);
respond = r_StatusReport; out->reaction = r_StatusReport;
break; break;
case PACK3(dev_CMD_SW, dev_CD_CHANGER, CD_Enable_Disk_Random): case PACK3(dev_CMD_SW, dev_CD_CHANGER, CD_Enable_Disk_Random):
cd_status.flags |= cd_DISK_RANDOM; cd_status.flags |= cd_DISK_RANDOM;
AVCLAN_generateStatus(out, true, dev_CMD_SW); AVCLAN_generateStatus(out, true, dev_CMD_SW);
respond = r_StatusReport; out->reaction = r_StatusReport;
break; break;
case PACK3(dev_CMD_SW, dev_CD_CHANGER, CD_Disable_Disk_Random): case PACK3(dev_CMD_SW, dev_CD_CHANGER, CD_Disable_Disk_Random):
cd_status.flags &= ~cd_DISK_RANDOM; cd_status.flags &= ~cd_DISK_RANDOM;
AVCLAN_generateStatus(out, true, dev_CMD_SW); AVCLAN_generateStatus(out, true, dev_CMD_SW);
respond = r_StatusReport; out->reaction = r_StatusReport;
break; break;
case PACK3(dev_CMD_SW, dev_CD_CHANGER, CD_Enable_Disk_Repeat): case PACK3(dev_CMD_SW, dev_CD_CHANGER, CD_Enable_Disk_Repeat):
cd_status.flags |= cd_DISK_REPEAT; cd_status.flags |= cd_DISK_REPEAT;
AVCLAN_generateStatus(out, true, dev_CMD_SW); AVCLAN_generateStatus(out, true, dev_CMD_SW);
respond = r_StatusReport; out->reaction = r_StatusReport;
break; break;
case PACK3(dev_CMD_SW, dev_CD_CHANGER, CD_Disable_Disk_Repeat): case PACK3(dev_CMD_SW, dev_CD_CHANGER, CD_Disable_Disk_Repeat):
cd_status.flags &= ~cd_DISK_REPEAT; cd_status.flags &= ~cd_DISK_REPEAT;
AVCLAN_generateStatus(out, true, dev_CMD_SW); AVCLAN_generateStatus(out, true, dev_CMD_SW);
respond = r_StatusReport; out->reaction = r_StatusReport;
break; break;
} }
} }
return respond;
} }
#undef PACK3 #undef PACK3
RFrame_t *AVCLAN_statemachine(RFrame_t *resp) { void AVCLAN_statemachine(AVCLAN_frame_t *out) {
AVCLAN_frame_t *out = resp->frame; reaction_t r = out->reaction;
switch (resp->r) { out->reaction = r_Nothing;
switch (r) {
case r_Ejection: { case r_Ejection: {
const uint8_t play[] = {0x00, dev_COMM_CTRL, dev_COMM_v1, const uint8_t play[] = {0x00, dev_COMM_CTRL, dev_COMM_v1,
Insertion, dev_CD_CHANGER, 0x01}; Insertion, dev_CD_CHANGER, 0x01};
out->length = sizeof(play); out->length = sizeof(play);
memcpy(out->data, play, sizeof(play)); memcpy(out->data, play, sizeof(play));
} }
resp->r = r_Report_Load; out->reaction = r_Report_Load;
break; break;
case r_Report_Load: case r_Report_Load:
out->is_unicast = false; out->is_unicast = false;
@@ -337,7 +336,7 @@ RFrame_t *AVCLAN_statemachine(RFrame_t *resp) {
memcpy(out->data, cdloading_resp, sizeof(cdloading_resp)); memcpy(out->data, cdloading_resp, sizeof(cdloading_resp));
out->data[1] = dev_STATUS; out->data[1] = dev_STATUS;
out->data[2] = Loading_Status_Report; out->data[2] = Loading_Status_Report;
resp->r = r_Handled; out->reaction = r_SendOnly;
break; break;
case r_TrackChange: case r_TrackChange:
AVCLAN_setTime(0x00, 0x00); AVCLAN_setTime(0x00, 0x00);
@@ -347,24 +346,23 @@ RFrame_t *AVCLAN_statemachine(RFrame_t *resp) {
case r_NormalizeState: case r_NormalizeState:
AVCLAN_normalizeState(); AVCLAN_normalizeState();
AVCLAN_generateStatus(out, true, dev_STATUS); AVCLAN_generateStatus(out, true, dev_STATUS);
resp->r = r_Handled; out->reaction = r_SendOnly;
break; break;
case r_StartPlaying: case r_StartPlaying:
AVCLAN_normalizeState(); AVCLAN_normalizeState();
AVCLAN_generateStatus(out, true, dev_STATUS); AVCLAN_generateStatus(out, true, dev_STATUS);
resp->r = r_BeganPlaying; out->reaction = r_BeganPlaying;
break; break;
case r_BeganPlaying: case r_BeganPlaying:
AVCLAN_startPlaying(); // only start PIT after normalizing state AVCLAN_startPlaying(); // only start PIT after normalizing state
resp->r = r_Nothing; out->reaction = r_Nothing;
break; break;
case r_StatusReport: case r_StatusReport:
AVCLAN_generateStatus(out, true, dev_STATUS); AVCLAN_generateStatus(out, true, dev_STATUS);
resp->r = r_Handled; out->reaction = r_SendOnly;
break; break;
case r_Handled: [[fallthrough]]; case r_SendOnly: [[fallthrough]];
case r_Nothing: [[fallthrough]]; case r_Nothing: [[fallthrough]];
default: resp->r = r_Nothing; default: out->reaction = r_Nothing;
} }
return resp;
} }
+4 -10
View File
@@ -17,12 +17,11 @@ extern "C" {
/// Message state machine /// Message state machine
// - r_Nothing (0x00) means don't send current message // - r_Nothing (0x00) means don't send current message
// - r_Handled means send current message and stop/finished state machine
// - All other instances mean send current message and imply the presence of // - All other instances mean send current message and imply the presence of
// follow-up messages within state machine // follow-up messages within state machine
typedef enum : uint8_t { typedef enum : uint8_t {
r_Nothing = 0x00, r_Nothing = 0x00,
r_Handled, // No follow-up needed r_SendOnly, // No further follow-up needed (beyond sending current)
r_StatusReport = 0x02, // Needs follow-up status report r_StatusReport = 0x02, // Needs follow-up status report
r_NormalizeState, // cd_status needs normalized and resent r_NormalizeState, // cd_status needs normalized and resent
r_StartPlaying, // ~equivalent to normalizeState, but cycles to BeganPlaying r_StartPlaying, // ~equivalent to normalizeState, but cycles to BeganPlaying
@@ -30,15 +29,10 @@ typedef enum : uint8_t {
r_TrackChange, // Time needs reset r_TrackChange, // Time needs reset
r_Ejection, r_Ejection,
r_Report_Load, r_Report_Load,
} response_t; } reaction_t;
typedef struct RFrame_struct { void AVCLAN_handleframe(const AVCLAN_frame_t *in, AVCLAN_frame_t *out);
response_t r; void AVCLAN_statemachine(AVCLAN_frame_t *out);
AVCLAN_frame_t *frame;
} RFrame_t;
response_t AVCLAN_handleframe(const AVCLAN_frame_t *in, AVCLAN_frame_t *out);
RFrame_t *AVCLAN_statemachine(RFrame_t *resp);
#ifdef __cplusplus #ifdef __cplusplus
} }
+5 -21
View File
@@ -75,30 +75,14 @@ void AVCLAN_setTime(uint8_t mins, uint8_t secs) {
cd_status.secs = secs; cd_status.secs = secs;
} }
// Only used for regularly scheduled periodic updates
AVCLAN_frame_t *AVCLAN_getStatusFrame() {
static uint8_t status_data[sizeof(AVCLAN_CD_Status_t) + 3] = {0};
static AVCLAN_frame_t status = {.is_unicast = false,
.controller_addr = DEVICE_ADDR,
.peripheral_addr = 0x1FF,
.control = 0xF,
.length = sizeof(status_data),
.data = status_data};
return &status;
}
// Used for changed status messages // Used for changed status messages
void AVCLAN_generateStatus(AVCLAN_frame_t *status, bool is_unicast, void AVCLAN_generateStatus(AVCLAN_frame_t *status, bool is_unicast,
devices to) { devices to) {
*status = (AVCLAN_frame_t){ status->is_unicast = is_unicast;
.is_unicast = is_unicast, status->controller_addr = DEVICE_ADDR;
.controller_addr = DEVICE_ADDR, status->peripheral_addr = (is_unicast) ? HU_ADDR : 0x1FF;
.peripheral_addr = (is_unicast) ? HU_ADDR : 0x1FF, status->control = 0xF;
.control = 0xF, status->length = sizeof(AVCLAN_CD_Status_t) + ((is_unicast) ? 4 : 3);
.length = sizeof(AVCLAN_CD_Status_t) + ((is_unicast) ? 4 : 3),
.data = status->data, // don't overwrite data pointer
};
uint8_t *data = status->data; uint8_t *data = status->data;
if (is_unicast) if (is_unicast)
@@ -1,6 +1,6 @@
# AVR / ATtiny3216 hardware target (port). # AVR / ATtiny3216 hardware target (port).
include(CMakeDependentOption) include(CMakeDependentOption)
include(FetchContent)
# --- Port implementation sources ------------------------------------------- # --- Port implementation sources -------------------------------------------
target_sources(avclan PRIVATE target_sources(avclan PRIVATE
@@ -79,8 +79,21 @@ try_compile(LIBC_VERSION_TEST
COMPILE_DEFINITIONS -mmcu=${AVR_MCU} COMPILE_DEFINITIONS -mmcu=${AVR_MCU}
) )
if (AVCLAN_TARGET STREQUAL "avr-attiny3216")
FetchContent_Declare(
avr_libstdcpp
GIT_REPOSITORY https://github.com/modm-io/avr-libstdcpp.git
GIT_TAG 5354296040a2289c911062daa82336762231e897
)
FetchContent_MakeAvailable(avr_libstdcpp)
add_library(libstdcpp INTERFACE)
target_include_directories(libstdcpp SYSTEM
INTERFACE ${avr_libstdcpp_SOURCE_DIR}/include)
target_link_libraries(avclan INTERFACE libstdcpp)
endif()
if(NOT LIBC_VERSION_TEST) if(NOT LIBC_VERSION_TEST)
include(FetchContent)
FetchContent_Declare( FetchContent_Declare(
attiny_atpack attiny_atpack
URL http://packs.download.atmel.com/Atmel.ATtiny_DFP.2.0.368.atpack URL http://packs.download.atmel.com/Atmel.ATtiny_DFP.2.0.368.atpack
-61
View File
@@ -1,61 +0,0 @@
// Copyright (C) 2026 Allen Hill <allenofthehills@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#include <stdint.h>
#include "avclandrv.h"
#include "queue.h"
void constructQueue(Queue_t *q, void **slots, void *items, uint8_t itemSize,
uint8_t len, bool constructFull) {
q->read = 0;
q->size = len;
q->buf = slots;
if (constructFull) {
q->write = len;
for (uint8_t i = 0; i < len; ++i) {
q->buf[i] = items;
items = (char *)items + itemSize;
}
} else {
q->write = 0;
}
}
void constructEmptyQueue(Queue_t *q, void **slots, uint8_t len) {
constructQueue(q, slots, nullptr, 0, len, false);
}
bool isEmpty(const Queue_t *q) { return (q->write == q->read); }
static inline bool isFull(const Queue_t *q) {
return ((q->write - q->read) == q->size);
}
static inline uint8_t qMask(const Queue_t *q, uint8_t pos) {
return pos & (q->size - 1);
}
uint8_t pushQueue(Queue_t *q, void *x) {
if (x == nullptr || isFull(q))
return 1;
q->buf[qMask(q, q->write++)] = x;
return 0;
}
void *peekQueue(const Queue_t *q) {
if (isEmpty(q))
return nullptr;
return q->buf[qMask(q, q->read)];
}
void *popQueue(Queue_t *q) {
if (isEmpty(q))
return nullptr;
return q->buf[qMask(q, q->read++)];
}
-30
View File
@@ -1,30 +0,0 @@
// Copyright (C) 2026 Allen Hill <allenofthehills@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct Queue_struct {
uint8_t write;
uint8_t read;
uint8_t size; // MUST be a power of 2 (qMask depends on it)
void **buf; // size entries, owned by caller
} Queue_t;
void constructQueue(Queue_t *q, void **slots, void *items, uint8_t itemSize,
uint8_t len, bool constructFull);
void constructEmptyQueue(Queue_t *q, void **slots, uint8_t len);
bool isEmpty(const Queue_t *q);
static inline void incrementRead(Queue_t *q) { q->read++; }
uint8_t pushQueue(Queue_t *q, void *x);
void *peekQueue(const Queue_t *q);
void *popQueue(Queue_t *q);
#ifdef __cplusplus
}
#endif
+103
View File
@@ -0,0 +1,103 @@
// Copyright (C) 2026 Allen Hill <allenofthehills@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include <concepts>
#include <cstdint>
#include <limits>
#include <memory>
namespace detail {
template <class T, auto N> struct Deleter;
}
template <class T, std::integral auto N, bool Owning = false>
requires((N & (N - 1)) == 0 && N <= std::numeric_limits<uint8_t>::max())
class Queue {
using Deleter = detail::template Deleter<T, N>;
friend Deleter;
public:
// Only full and copy-convert-from-full construction is allowed
Queue() = delete;
Queue(const Queue &) = delete;
// Moving is unsupported due to being self-referential
Queue(Queue &&) = delete;
Queue &operator=(Queue &&) = delete;
constexpr Queue(T (&items)[N])
requires(Owning)
: owner{this}, write{N} {
for (uint8_t i = 0; i < N; ++i)
buf[i] = &items[i];
}
constexpr Queue(Queue<T, N, true> &queue)
requires(!Owning)
: owner{&queue} {}
bool isEmpty() const { return write == read; }
uint8_t size() const { return write - read; }
uint8_t capacity() const { return N; }
bool isFull() const { return size() == capacity(); }
uint8_t push(std::unique_ptr<T, Deleter> x)
requires(!Owning)
{
// structurally unnecessary; empty construction from same size parent
// guarantees
// !isFull assert(!isFull());
if (!x || !x.get_deleter().is_owned_by(owner))
return 1;
reclaim(x.release());
return 0;
}
const T *peek() const {
if (isEmpty())
return nullptr;
return buf[mask(read)];
}
std::unique_ptr<T, Deleter> pop() {
if (isEmpty())
return nullptr;
if constexpr (Owning)
return {buf[mask(read++)], {this}};
else
return {buf[mask(read++)], {owner}};
}
private:
uint8_t mask(uint8_t pos) const { return pos & (N - 1); }
void reclaim(T *x) { buf[mask(write++)] = x; }
std::array<T *, N> buf = {};
Queue<T, N, true> *const owner;
uint8_t read = 0;
uint8_t write = 0;
};
template <class T, auto N> Queue(Queue<T, N, true> &) -> Queue<T, N, false>;
template <class T, auto N> Queue(T (&items)[N]) -> Queue<T, N, true>;
namespace detail {
template <class T, auto N> struct Deleter {
Deleter() = default;
constexpr Deleter(Queue<T, N, true> *owner) : owner{owner} {}
void operator()(T *x) const { owner->reclaim(x); }
constexpr bool is_owned_by(const Queue<T, N, true> *parent) const {
return owner == parent;
}
private:
Queue<T, N, true> *const owner = nullptr;
};
} // namespace detail
+75 -137
View File
@@ -3,55 +3,28 @@
// Copyright (C) 2015 Allen Hill <allenofthehills@gmail.com> // Copyright (C) 2015 Allen Hill <allenofthehills@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include <ctype.h> #include <cctype>
#include <stddef.h> #include <cstdint>
#include <stdint.h> #include <cstring>
#include <stdlib.h>
#include <string.h>
#include "avclandrv.h" #include "avclandrv.h"
#include "board.h" #include "board.h"
#include "com232.h" #include "com232.h"
#include "queue.h" #include "queue.hpp"
const char *const offon[] = {"OFF", "ON"}; const char *const offon[] = {"OFF", "ON"};
#define CACHE_SIZE 16 static constexpr uint8_t CACHE_SIZE = 32;
static_assert((CACHE_SIZE & (CACHE_SIZE - 1)) == 0,
"CACHE_SIZE must be a power of two (qMask depends on it)");
static AVCLAN_frame_t frames[CACHE_SIZE]; static AVCLAN_frame_t frames[CACHE_SIZE];
static RFrame_t responses[CACHE_SIZE];
static uint8_t framesdata[CACHE_SIZE][MAXMSGLEN];
static void *cacheSlots[CACHE_SIZE]; constinit static Queue cache(frames);
static void *rcacheSlots[CACHE_SIZE]; constinit static Queue incoming = cache;
static void *incomingSlots[CACHE_SIZE]; constinit static Queue outgoing = cache;
static void *outgoingSlots[CACHE_SIZE];
static Queue_t cache, rcache, incoming, outgoing;
void Setup(); void Setup();
void print_help(); void print_help();
static uint8_t return_resp(RFrame_t *resp) {
uint8_t err;
AVCLAN_frame_t *out = resp->frame;
if ((out >= frames) && (out < &frames[CACHE_SIZE]))
// only return cache-owned frames (e.g. not status, etc)
err = pushQueue(&cache, out);
err = pushQueue(&rcache, resp);
return err;
}
static void push_or_return_resp(RFrame_t *resp) {
// r_Nothing == don't respond/send; should never be added to outgoing
if (resp->r != r_Nothing && !pushQueue(&outgoing, resp))
return;
return_resp(resp);
}
static void toggle_flag(bool *flag, const char *msg) { static void toggle_flag(bool *flag, const char *msg) {
*flag = !*flag; *flag = !*flag;
RS232_Print(msg); RS232_Print(msg);
@@ -89,87 +62,65 @@ int main() {
uint8_t err = 0; uint8_t err = 0;
uint8_t failedStatusReports = 0; uint8_t failedStatusReports = 0;
// Temporary, direct access is questionable since cache has ownership
for (uint8_t i = 0; i < CACHE_SIZE; ++i) { for (uint8_t i = 0; i < CACHE_SIZE; ++i) {
frames[i].control = 0x0f; frames[i].control = 0x0f;
frames[i].data = framesdata[i];
} }
constructQueue(&cache, cacheSlots, frames, sizeof(AVCLAN_frame_t), CACHE_SIZE, const AVCLAN_frame_t *lastStatus = nullptr;
true);
constructEmptyQueue(&incoming, incomingSlots, CACHE_SIZE);
constructQueue(&rcache, rcacheSlots, responses, sizeof(RFrame_t), CACHE_SIZE,
true);
constructEmptyQueue(&outgoing, outgoingSlots, CACHE_SIZE);
Setup(); Setup();
print_help(); print_help();
while (true) { while (true) {
if (AVCLAN_busActive()) { if (AVCLAN_busActive()) {
if (AVCLAN_frame_t *msg = popQueue(&cache)) { if (auto msg = cache.pop()) {
err = AVCLAN_readframe(msg, (log_t){.print = printAllFrames, err = AVCLAN_readframe(msg.get(), (log_t){.print = printAllFrames,
.binary = printBinary, .binary = printBinary,
.verbose = verbose}); .verbose = verbose});
if (!err) if (!err)
err = pushQueue(&incoming, msg); incoming.push(std::move(msg));
if (err)
pushQueue(&cache, msg);
} else { } else {
RS232_Print("!! Dropping an incoming message; cache is empty !!\n"); RS232_Print("!! Dropping an incoming message; cache is empty !!\n");
} }
} }
if (AVCLAN_frame_t *in = peekQueue(&incoming)) { if (auto in = incoming.peek()) {
if (AVCLAN_frame_t *out = popQueue(&cache)) { if (auto out = cache.pop()) {
incrementRead(&incoming); // successful out = pop cache; claim the AVCLAN_handleframe(in, out.get());
// peeked incoming incoming.pop();
response_t respond = AVCLAN_handleframe(in, out);
pushQueue(&cache, in); // return in after use
if (respond) { if (out->reaction)
if (RFrame_t *resp = popQueue(&rcache)) { outgoing.push(std::move(out));
*resp = (RFrame_t){.r = respond, .frame = out};
push_or_return_resp(resp);
} else
pushQueue(&cache, out); // rcache exhausted — don't leak the frame
} else // no response needed; return to circulation
pushQueue(&cache, out);
} else { } else {
RS232_Print("!! Unable to respond; cache is empty !!\n"); RS232_Print("!! Unable to respond; cache is empty !!\n");
} }
} }
if (RFrame_t *resp = popQueue(&outgoing)) { if (statustimer_tickPending()) {
AVCLAN_frame_t *out = resp->frame; if (auto status = cache.pop()) {
lastStatus = status.get();
AVCLAN_generateStatus(status.get(), true, dev_STATUS);
status->reaction = r_SendOnly;
outgoing.push(std::move(status));
statustimer_clearTick();
}
}
if (auto out = outgoing.pop()) {
err = AVCLAN_sendframe( err = AVCLAN_sendframe(
out, (log_t){.print = printAllFrames, .binary = printBinary}); out.get(), (log_t){.print = printAllFrames, .binary = printBinary});
if (err || resp->r == r_Handled) { if (err || (reaction_t)out->reaction == r_SendOnly) {
if (err && out == AVCLAN_getStatusFrame() && if (err && out.get() == lastStatus && ++failedStatusReports > 1) {
++failedStatusReports > 1) {
failedStatusReports = 0; failedStatusReports = 0;
AVCLAN_stopPlaying(); // Disable periodic updates if e.g. no-one's AVCLAN_stopPlaying(); // Disable periodic updates if e.g. no-one's
// listening (car was turned off?) // listening (car was turned off?)
} }
return_resp(resp);
} else { } else {
resp = AVCLAN_statemachine(resp); AVCLAN_statemachine(out.get());
push_or_return_resp(resp); if (out->reaction)
outgoing.push(std::move(out));
} }
} else if (statustimer_tickPending()) {
AVCLAN_frame_t *status = AVCLAN_getStatusFrame();
AVCLAN_generateStatus(status, true, dev_STATUS);
if (RFrame_t *resp = (RFrame_t *)popQueue(&rcache)) {
*resp = (RFrame_t){.r = r_Handled, .frame = status};
err = pushQueue(&outgoing, resp);
if (err) {
RS232_Print("Outgoing queue full; unable to send status update\n");
pushQueue(&rcache, resp);
} else
statustimer_clearTick(); // Only clear if successful
}
// no further error handling needed; status isn't part of the cache
} }
// Key handler // Key handler
@@ -192,39 +143,33 @@ 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 (AVCLAN_frame_t *out = (AVCLAN_frame_t *)popQueue(&cache)) { if (auto out = cache.pop()) {
if (RFrame_t *resp = popQueue(&rcache)) { out->is_unicast = true;
out->is_unicast = true; out->controller_addr = DEVICE_ADDR;
out->controller_addr = DEVICE_ADDR; out->peripheral_addr = HU_ADDR;
out->peripheral_addr = HU_ADDR; {
{ const uint8_t beep[] = {0x00, dev_CD_CHANGER, dev_BEEP_SPEAKERS,
const uint8_t beep[] = {0x00, dev_CD_CHANGER, dev_BEEP_SPEAKERS, 0x60, 0x01};
0x60, 0x01}; out->length = sizeof(beep);
out->length = sizeof(beep); memcpy(out->data, beep, sizeof(beep));
memcpy(out->data, beep, sizeof(beep)); }
} out->reaction = r_SendOnly;
*resp = (RFrame_t){.r = r_Handled, .frame = out}; outgoing.push(std::move(out));
push_or_return_resp(resp);
} else
pushQueue(&cache, out);
} }
break; break;
case 'P': case 'P':
if (AVCLAN_frame_t *out = (AVCLAN_frame_t *)popQueue(&cache)) { if (auto out = cache.pop()) {
if (RFrame_t *resp = popQueue(&rcache)) { out->is_unicast = true;
out->is_unicast = true; out->controller_addr = DEVICE_ADDR;
out->controller_addr = DEVICE_ADDR; out->peripheral_addr = HU_ADDR;
out->peripheral_addr = HU_ADDR; {
{ const uint8_t play[] = {0x00, dev_COMM_CTRL, dev_COMM_v1,
const uint8_t play[] = {0x00, dev_COMM_CTRL, dev_COMM_v1, Ejection, dev_CD_CHANGER, 0x01};
Ejection, dev_CD_CHANGER, 0x01}; out->length = sizeof(play);
out->length = sizeof(play); memcpy(out->data, play, sizeof(play));
memcpy(out->data, play, sizeof(play)); }
} out->reaction = r_Ejection;
*resp = (RFrame_t){.r = r_Ejection, .frame = out}; outgoing.push(std::move(out));
push_or_return_resp(resp);
} else
pushQueue(&cache, out);
} }
break; break;
@@ -284,32 +229,25 @@ int main() {
if (readSeq) { if (readSeq) {
if (readBinary) { if (readBinary) {
if (data_tmp[seqIdx] == 0x17) { if (data_tmp[seqIdx] == 0x17) {
if (AVCLAN_frame_t *out = (AVCLAN_frame_t *)popQueue(&cache)) { if (auto out = cache.pop()) {
if (!AVCLAN_parseframe(data_tmp, --seqIdx, out)) { if (!AVCLAN_parseframe(data_tmp, --seqIdx, out.get())) {
if (RFrame_t *resp = popQueue(&rcache)) { out->reaction = r_SendOnly;
*resp = (RFrame_t){.r = r_Handled, .frame = out}; outgoing.push(std::move(out));
push_or_return_resp(resp); }
} else
pushQueue(&cache, out);
} else
pushQueue(&cache, out);
} }
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 { } else {
if (AVCLAN_frame_t *out = (AVCLAN_frame_t *)popQueue(&cache)) { if (auto out = cache.pop()) {
if (RFrame_t *resp = popQueue(&rcache)) { out->is_unicast = seqIsUnicast;
out->is_unicast = seqIsUnicast; out->controller_addr = DEVICE_ADDR;
out->controller_addr = DEVICE_ADDR; out->peripheral_addr = seqIsUnicast ? HU_ADDR : 0x1FF;
out->peripheral_addr = seqIsUnicast ? HU_ADDR : 0x1FF; out->length = seqIdx;
out->length = seqIdx; memcpy(out->data, data_tmp, seqIdx);
memcpy(out->data, data_tmp, seqIdx); out->reaction = r_SendOnly;
*resp = (RFrame_t){.r = r_Handled, .frame = out}; outgoing.push(std::move(out));
push_or_return_resp(resp);
} else
pushQueue(&cache, out);
} }
printAllFrames = lastPrintAllFrames; printAllFrames = lastPrintAllFrames;
} }