From df974af8b7ed49c137b019bd1d9cc408ff974114 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Wed, 4 Dec 2024 18:45:05 +0200 Subject: [PATCH] canardTxPoll --- libcanard/canard.c | 74 ++++++++++++++++++++++++++++++++++++++++------ libcanard/canard.h | 48 ++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 9 deletions(-) diff --git a/libcanard/canard.c b/libcanard/canard.c index 098d362..b17e5ca 100644 --- a/libcanard/canard.c +++ b/libcanard/canard.c @@ -592,6 +592,25 @@ CANARD_PRIVATE int32_t txPushMultiFrame(struct CanardTxQueue* const que, return out; } +CANARD_PRIVATE void txPopAndFreeTransfer(struct CanardTxQueue* const que, + const struct CanardInstance* const ins, + struct CanardTxQueueItem* tx_item, + const bool drop_whole_transfer) +{ + struct CanardTxQueueItem* tx_item_to_free = NULL; + while (NULL != (tx_item_to_free = canardTxPop(que, tx_item))) + { + tx_item = tx_item_to_free->next_in_transfer; + canardTxFree(que, ins, tx_item_to_free); + + if (!drop_whole_transfer) + { + break; + } + que->stats.dropped_frames++; + } +} + /// Flushes expired transfers by comparing deadline timestamps of the pending transfers with the current time. CANARD_PRIVATE void txFlushExpiredTransfers(struct CanardTxQueue* const que, const struct CanardInstance* const ins, @@ -609,15 +628,8 @@ CANARD_PRIVATE void txFlushExpiredTransfers(struct CanardTxQueue* const q break; } - // All frames of the transfer are released at once b/c they all have the same deadline. - struct CanardTxQueueItem* tx_item_to_free = NULL; - while (NULL != (tx_item_to_free = canardTxPop(que, tx_item))) - { - tx_item = tx_item_to_free->next_in_transfer; - canardTxFree(que, ins, tx_item_to_free); - - que->stats.dropped_frames++; - } + // All frames of the transfer are dropped at once b/c they all have the same deadline. + txPopAndFreeTransfer(que, ins, tx_item, true); // drop the whole transfer } } @@ -1242,6 +1254,50 @@ void canardTxFree(struct CanardTxQueue* const que, } } +int8_t canardTxPoll(struct CanardTxQueue* const que, + const struct CanardInstance* const ins, + const CanardMicrosecond now_usec, + void* const user_reference, + const CanardTxFrameHandler frame_handler) +{ + int8_t out = 0; + + // Before peeking a frame to transmit, we need to try to flush any expired transfers. + // This will not only ensure asap freeing of the queue capacity, but also makes sure that the following + // `canardTxPeek` will return a not expired item (if any), so we don't need to check the deadline again. + // The flushing is done by comparing deadline timestamps of the pending transfers with the current time, + // which makes sense only if the current time is known (bigger than zero). + if (now_usec > 0) + { + txFlushExpiredTransfers(que, ins, now_usec); + } + + if (frame_handler != NULL) + { + struct CanardTxQueueItem* const tx_item = canardTxPeek(que); + if (tx_item != NULL) + { + // No need to check the deadline again, as we have already flushed all expired transfers. + out = frame_handler(user_reference, tx_item->tx_deadline_usec, &tx_item->frame); + + // We gonna release (pop and free) the frame if the handler returned: + // - either a positive value - the frame has been successfully accepted by the handler; + // - or a negative value - the frame has been rejected by the handler due to failure. + // Zero value means that the handler cannot accept the frame at the moment, so we keep it in the queue. + if (out != 0) + { + // In case of a failure, it makes sense to drop the whole transfer immediately + // b/c at least one this frame has been rejected, so the whole transfer is useless. + const bool drop_whole_transfer = (out < 0); + txPopAndFreeTransfer(que, ins, tx_item, drop_whole_transfer); + } + } + } + + CANARD_ASSERT(out <= 1); + return out; +} + int8_t canardRxAccept(struct CanardInstance* const ins, const CanardMicrosecond timestamp_usec, const struct CanardFrame* const frame, diff --git a/libcanard/canard.h b/libcanard/canard.h index 8ef472e..905e74e 100644 --- a/libcanard/canard.h +++ b/libcanard/canard.h @@ -313,6 +313,29 @@ struct CanardTxQueueStats size_t dropped_frames; }; +/// Defines the signature of the TX frame handler function. +/// +/// The handler function is intended to be invoked from Canard TX polling (see details for the `canardTxPoll()`). +/// +/// @param user_reference The user reference passed to `canardTxPoll()`. +/// @param deadline_usec The deadline of the frame that is being handled. +/// @param frame The mutable frame that is being handled. +/// @return The result of the handling operation: +/// - Any positive value: the frame was successfully handled. +/// This indicates that the frame payload was accepted (and its ownership could be potentially moved, +/// see `canardTxPeek` for the details), and the frame can be safely removed from the queue. +/// - Zero: the frame was not handled, and so the frame should be kept in the queue. +/// It will be retried on some future `canardTxPoll()` call according to the queue state in the future. +/// This case is useful when TX hardware is busy, and the frame should be retried later. +/// - Any negative value: the frame was rejected due to an unrecoverable failure. +/// This indicates to the caller (`canardTxPoll`) that the frame should be dropped from the queue, +/// as well as all other frames belonging to the same transfer. The `dropped_frames` counter in the TX queue stats +/// will be incremented for each frame dropped in this way. +/// +typedef int8_t (*CanardTxFrameHandler)(void* const user_reference, + const CanardMicrosecond deadline_usec, + struct CanardMutableFrame* frame); + /// Prioritized transmission queue that keeps CAN frames destined for transmission via one CAN interface. /// Applications with redundant interfaces are expected to have one instance of this type per interface. /// Applications that are not interested in transmission may have zero queues. @@ -613,6 +636,31 @@ void canardTxFree(struct CanardTxQueue* const que, const struct CanardInstance* const ins, struct CanardTxQueueItem* const item); +/// This is a helper that combines several Canard TX calls (`canardTxPeek`, `canardTxPop` and `canardTxFree`) +/// into one "polling" algorythm. It simplifies the whole process of transmitting frames to just two function calls: +/// - `canardTxPush` to enqueue the frames +/// - `canardTxPoll` to dequeue, transmit and free a single frame +/// +/// The algorythm implements a typical pattern of de-queuing, transmitting and freeing a TX queue item, +/// as well as handling transmission failures, retries, and deadline timeouts. +/// +/// The function is intended to be periodically called, most probably on a signal that the previous TX frame +/// transmission has been completed, and so the next TX frame (if any) could be polled from the TX queue. +/// +/// @param que The TX queue to poll. +/// @param ins The Canard instance. +/// @param now_usec The current time in microseconds. It is used to determine if the frame has timed out. +/// @param user_reference The user reference to be passed to the frame handler. +/// @param frame_handler The frame handler function that will be called to transmit the frame. +/// @return Zero if the queue is empty or there is no frame handler (NULL). +/// Otherwise, the result from the frame handler call. See `CanardTxFrameHandler` documentation. +/// +int8_t canardTxPoll(struct CanardTxQueue* const que, + const struct CanardInstance* const ins, + const CanardMicrosecond now_usec, + void* const user_reference, + const CanardTxFrameHandler frame_handler); + /// This function implements the transfer reassembly logic. It accepts a transport frame from any of the redundant /// interfaces, locates the appropriate subscription state, and, if found, updates it. If the frame completed a /// transfer, the return value is 1 (one) and the out_transfer pointer is populated with the parameters of the