17#define SAPI_DUAL_CHANNEL_STALL_POLL_LIMIT 32U
29 uint32_t up_count = 0U;
32 for (i = 0U; i < channel->link_count; i++)
34 if (channel->link_up[i])
44 if (up_count == channel->link_count)
55 if (new_status != channel->last_status)
59 channel->last_status = new_status;
60 if (channel->status_callback != NULL)
62 channel->status_callback(new_status, old_status, channel->status_callback_ctx);
78 uint8_t raw_size = 0U;
79 uint32_t sequence = 0U;
86 &raw_size, &sequence);
91 if (raw_size < (uint8_t)
sizeof(header))
98 (void)memcpy(&header, raw,
sizeof(header));
104 uint8_t app_size = (uint8_t)(raw_size - (uint8_t)
sizeof(header));
109 (void)memcpy(channel->pending_data, &raw[
sizeof(header)], app_size);
110 channel->pending_data_size = app_size;
111 channel->pending_data_valid =
true;
123 channel->ack_timeout_ms, NULL);
133 if (raw_size < (uint8_t)
sizeof(ack))
137 (void)memcpy(&ack, raw,
sizeof(ack));
150 (void)memcpy(&channel->pending_state, raw,
sizeof(channel->pending_state));
151 channel->pending_state_valid =
true;
168 channel->ack_timeout_ms, NULL);
187 if ((channel == NULL) || (config == NULL))
197 if (config->
links[i] == NULL)
212 channel->link_up[i] =
false;
216 channel->link_up[i] =
false;
224 channel->pending_data_valid =
false;
225 channel->pending_data_size = 0U;
226 channel->pending_state_valid =
false;
227 (void)memset(&channel->pending_state, 0,
sizeof(channel->pending_state));
233 uint32_t *out_ack_link_count)
238 uint32_t ack_count = 0U;
248 bool saw_hard_fault =
false;
255 if ((payload == NULL) && (payload_size != 0U))
271 (void)memcpy(frame, &header,
sizeof(header));
272 if (payload_size > 0U)
274 (void)memcpy(&frame[
sizeof(header)], payload, payload_size);
276 frame_size = (uint8_t)(
sizeof(header) + payload_size);
279 for (i = 0U; i < channel->link_count; i++)
281 uint32_t sent_sequence = 0U;
283 bool link_now_up =
false;
289 saw_hard_fault =
true;
290 hard_fault_status = send_status;
306 uint32_t stall_polls = 0U;
333 saw_hard_fault =
true;
334 hard_fault_status = poll_status;
345 if (now_ms > start_ms)
388 channel->link_up[i] = link_now_up;
395 if (out_ack_link_count != NULL)
397 *out_ack_link_count = ack_count;
400 dual_channel_update_status(channel);
412 if ((channel == NULL) || (out_payload == NULL) || (out_size == NULL) || (max_size == 0U))
418 bool saw_hard_fault =
false;
421 if (!channel->pending_data_valid)
424 sapi_duration_ms_t per_link_timeout = (channel->link_count > 0U) ? (timeout_ms / channel->link_count) : 0U;
435 for (i = 0U; i < channel->link_count; i++)
449 saw_hard_fault =
true;
450 hard_fault_status = poll_status;
455 if (!channel->pending_data_valid)
460 if (channel->pending_data_size > max_size)
467 (void)memcpy(out_payload, channel->pending_data, channel->pending_data_size);
468 *out_size = channel->pending_data_size;
469 channel->pending_data_valid =
false;
478 uint32_t ack_count = 0U;
479 bool saw_hard_fault =
false;
499 (void)memset(&frame, 0,
sizeof(frame));
507 for (i = 0U; i < channel->link_count; i++)
509 uint32_t sent_sequence = 0U;
511 bool link_now_up =
false;
514 (uint8_t)
sizeof(frame), channel->ack_timeout_ms,
518 saw_hard_fault =
true;
519 hard_fault_status = send_status;
525 uint32_t stall_polls = 0U;
546 saw_hard_fault =
true;
547 hard_fault_status = poll_status;
553 if (poll_now_ms > start_ms)
573 channel->link_up[i] = link_now_up;
580 if (out_ack_link_count != NULL)
582 *out_ack_link_count = ack_count;
585 dual_channel_update_status(channel);
595 bool channel_degraded, uint64_t timestamp_ms)
599 uint32_t sent_count = 0U;
610 frame.
state = (uint8_t)state;
615 for (i = 0U; i < channel->link_count; i++)
618 (uint8_t)
sizeof(frame), channel->ack_timeout_ms, NULL);
631 if ((channel == NULL) || (out_frame == NULL))
636 if (!channel->pending_state_valid)
639 sapi_duration_ms_t per_link_timeout = (channel->link_count > 0U) ? (timeout_ms / channel->link_count) : 0U;
643 for (i = 0U; i < channel->link_count; i++)
651 if (!channel->pending_state_valid)
656 *out_frame = channel->pending_state;
657 channel->pending_state_valid =
false;
668 return channel->last_status;
673 if ((channel == NULL) || (link_index >= channel->link_count))
677 return channel->link_up[link_index];
sapi_status_t sapi_dual_channel_send(sapi_dual_channel_t *channel, const uint8_t *payload, uint8_t payload_size, uint32_t *out_ack_link_count)
Sends payload as a DATA frame on every configured redundant link - never gated by any negotiated stat...
#define SAPI_DUAL_CHANNEL_MAX_LINKS
Maximum number of redundant links one sapi_dual_channel_t may be configured with. Fixed,...
sapi_status_t sapi_dual_msgchannel_send(sapi_dual_msgchannel_t *channel, const uint8_t *payload, uint8_t payload_size, sapi_duration_ms_t timeout_ms, uint32_t *out_sequence)
Wraps payload in a sapi_vital_message_t (this channel's own sender_id and next sequence_number) and s...
#define SAPI_DUAL_MSGCHANNEL_MAX_PAYLOAD
Max payload bytes usable via sapi_dual_msgchannel_send()/ _receive() - bounded by sapi_vital_message_...
sapi_status_t sapi_dual_channel_receive_state_frame(sapi_dual_channel_t *channel, sapi_duration_ms_t timeout_ms, sapi_dual_state_frame_t *out_frame)
Returns the most recently staged inbound STATE frame, actively polling the configured links if none w...
sapi_dual_state_t
State of one instance in a dual (two-redundant-instance) relationship, as decided by sapi_dual_negoti...
sapi_status_t sapi_dual_msgchannel_init(sapi_dual_msgchannel_t *channel, const sapi_dual_msgchannel_config_t *config)
Initializes a sapi_dual_msgchannel_t: starts both sequence counters at 0.
sapi_dual_channel_status_t sapi_dual_channel_get_status(const sapi_dual_channel_t *channel)
Returns the aggregate connection status across every configured redundant link, as of the most recent...
sapi_dual_channel_status_t
Aggregate connection status of a sapi_dual_channel_t across all of its configured redundant links (AD...
#define SAPI_DUAL_CHANNEL_MAX_PAYLOAD
Max application payload bytes usable via sapi_dual_channel_send()/_receive() - Layer-1's own SAPI_DUA...
sapi_status_t sapi_dual_channel_receive(sapi_dual_channel_t *channel, uint8_t *out_payload, uint8_t max_size, sapi_duration_ms_t timeout_ms, uint8_t *out_size)
Returns the most recently staged inbound DATA frame, actively polling the configured links (each auto...
sapi_status_t sapi_dual_msgchannel_receive(sapi_dual_msgchannel_t *channel, uint8_t *out_payload, uint8_t payload_max_size, sapi_duration_ms_t timeout_ms, uint8_t *out_payload_size, uint32_t *out_sequence)
Receives one frame over config->link, blocking at most timeout_ms, verifies its CRC-64 and sequence c...
sapi_status_t sapi_dual_channel_init(sapi_dual_channel_t *channel, const sapi_dual_channel_config_t *config)
Initializes a sapi_dual_channel_t: initializes every configured redundant link (sapi_dual_msgchannel_...
sapi_dual_frame_kind_t
Which kind of Layer-2 frame a given Layer-1 payload holds. Stored as the first byte of every sapi_dua...
bool sapi_dual_channel_is_link_up(const sapi_dual_channel_t *channel, uint32_t link_index)
Returns whether one specific configured link is currently considered up, as of the most recent sapi_d...
sapi_status_t sapi_dual_channel_send_heartbeat(sapi_dual_channel_t *channel, uint32_t *out_ack_count)
Sends a sapi_dual_state_frame_t (sapi_dual_frames.h) on every configured redundant link - fire-and-fo...
@ SAPI_DUAL_CHANNEL_STATUS_FULL
@ SAPI_DUAL_CHANNEL_STATUS_DOWN
@ SAPI_DUAL_CHANNEL_STATUS_DEGRADED
@ SAPI_DUAL_FRAME_KIND_STATE
@ SAPI_DUAL_FRAME_KIND_HEARTBEAT
@ SAPI_DUAL_FRAME_KIND_ACK
@ SAPI_DUAL_FRAME_KIND_DATA
sapi_status_t
Common result/status codes.
@ SAPI_STATUS_HARDWARE_FAULT
@ SAPI_STATUS_INVALID_PARAM
@ SAPI_STATUS_DATA_CORRUPTION
sapi_status_t sapi_timer_now(sapi_timestamp_ms_t *out_now_ms)
Returns the current monotonic time base used by all timers.
uint32_t sapi_duration_ms_t
uint64_t sapi_timestamp_ms_t
#define SAPI_DUAL_CHANNEL_STALL_POLL_LIMIT
Cap on consecutive sapi_dual_channel_send() ACK-wait polls that may complete without sapi_timer_now()...
static sapi_status_t dual_channel_poll_link_once(sapi_dual_channel_t *channel, uint32_t link_index, sapi_duration_ms_t timeout_ms, dual_poll_result_t *out_result)
One receive attempt on one link, dispatched by frame kind: DATA is auto-ACKed and staged for sapi_dua...
"DualChannel" layer of ADR-020: wraps 1..N redundant sapi_dual_msgchannel_t links for fault-tolerant,...
OS Abstraction Layer - Timer service.
Result of one dual_channel_poll_link_once() attempt.
sapi_dual_frame_kind_t kind
SAPI_DUAL_FRAME_KIND_ACK payload.
Configuration for sapi_dual_channel_init().
sapi_duration_ms_t ack_timeout_ms
uint32_t expected_peer_id
sapi_dual_channel_status_callback_t status_callback
sapi_netlink_handle_t links[SAPI_DUAL_CHANNEL_MAX_LINKS]
void * status_callback_ctx
One sapi_dual_channel_t instance's state. Caller-owned storage; every field is private - reach it onl...
SAPI_DUAL_FRAME_KIND_HEARTBEAT payload - connection maintenance heartbeat.
Configuration for sapi_dual_msgchannel_init().
uint32_t expected_peer_id
sapi_netlink_handle_t link
SAPI_DUAL_FRAME_KIND_STATE payload - sapi_dual_negotiator_t's own periodic beacon.