Skip to content

Commit

Permalink
Introduced new memory related types:
Browse files Browse the repository at this point in the history
- `CanardMemoryAllocate`
- `CanardMemoryDeallocate`
- `CanardMemoryDeleter`
- `CanardMemoryResource`
  • Loading branch information
serges147 committed Nov 15, 2024
1 parent 14e373a commit ea75329
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 64 deletions.
55 changes: 27 additions & 28 deletions libcanard/canard.c
Original file line number Diff line number Diff line change
Expand Up @@ -296,15 +296,15 @@ CANARD_PRIVATE size_t txRoundFramePayloadSizeUp(const size_t x)
}

/// The item is only allocated and initialized, but NOT included into the queue! The caller needs to do that.
CANARD_PRIVATE TxItem* txAllocateQueueItem(CanardInstance* const ins,
CANARD_PRIVATE TxItem* txAllocateQueueItem(CanardTxQueue* const que,
const uint32_t id,
const CanardMicrosecond deadline_usec,
const size_t payload_size)
{
CANARD_ASSERT(ins != NULL);
CANARD_ASSERT(que != NULL);
CANARD_ASSERT(payload_size > 0U);
const size_t tx_item_size = (sizeof(TxItem) - CANARD_MTU_MAX) + payload_size;
TxItem* const out = (TxItem*) ins->memory_allocate(ins, tx_item_size);
TxItem* const out = (TxItem*) que->memory.allocate(que->memory.user_reference, tx_item_size);
if (out != NULL)
{
out->base.allocated_size = tx_item_size;
Expand Down Expand Up @@ -338,22 +338,21 @@ CANARD_PRIVATE int8_t txAVLPredicate(void* const user_reference, // NOSONAR Cav

/// Returns the number of frames enqueued or error (i.e., =1 or <0).
CANARD_PRIVATE int32_t txPushSingleFrame(CanardTxQueue* const que,
CanardInstance* const ins,
const CanardMicrosecond deadline_usec,
const uint32_t can_id,
const CanardTransferID transfer_id,
const size_t payload_size,
const void* const payload)
{
CANARD_ASSERT(ins != NULL);
CANARD_ASSERT(que != NULL);
CANARD_ASSERT((payload != NULL) || (payload_size == 0));
const size_t frame_payload_size = txRoundFramePayloadSizeUp(payload_size + 1U);
CANARD_ASSERT(frame_payload_size > payload_size);
const size_t padding_size = frame_payload_size - payload_size - 1U;
CANARD_ASSERT((padding_size + payload_size + 1U) == frame_payload_size);
int32_t out = 0;
TxItem* const tqi =
(que->size < que->capacity) ? txAllocateQueueItem(ins, can_id, deadline_usec, frame_payload_size) : NULL;
(que->size < que->capacity) ? txAllocateQueueItem(que, can_id, deadline_usec, frame_payload_size) : NULL;
if (tqi != NULL)
{
if (payload_size > 0U) // The check is needed to avoid calling memcpy() with a NULL pointer, it's an UB.
Expand Down Expand Up @@ -384,15 +383,15 @@ CANARD_PRIVATE int32_t txPushSingleFrame(CanardTxQueue* const que,
}

/// Produces a chain of Tx queue items for later insertion into the Tx queue. The tail is NULL if OOM.
CANARD_PRIVATE TxChain txGenerateMultiFrameChain(CanardInstance* const ins,
CANARD_PRIVATE TxChain txGenerateMultiFrameChain(CanardTxQueue* const que,
const size_t presentation_layer_mtu,
const CanardMicrosecond deadline_usec,
const uint32_t can_id,
const CanardTransferID transfer_id,
const size_t payload_size,
const void* const payload)
{
CANARD_ASSERT(ins != NULL);
CANARD_ASSERT(que != NULL);
CANARD_ASSERT(presentation_layer_mtu > 0U);
CANARD_ASSERT(payload_size > presentation_layer_mtu); // Otherwise, a single-frame transfer should be used.
CANARD_ASSERT(payload != NULL);
Expand All @@ -410,7 +409,7 @@ CANARD_PRIVATE TxChain txGenerateMultiFrameChain(CanardInstance* const ins,
((payload_size_with_crc - offset) < presentation_layer_mtu)
? txRoundFramePayloadSizeUp((payload_size_with_crc - offset) + 1U) // Padding in the last frame only.
: (presentation_layer_mtu + 1U);
TxItem* const tqi = txAllocateQueueItem(ins, can_id, deadline_usec, frame_payload_size_with_tail);
TxItem* const tqi = txAllocateQueueItem(que, can_id, deadline_usec, frame_payload_size_with_tail);
if (NULL == out.head)
{
out.head = tqi;
Expand Down Expand Up @@ -485,15 +484,14 @@ CANARD_PRIVATE TxChain txGenerateMultiFrameChain(CanardInstance* const ins,

/// Returns the number of frames enqueued or error.
CANARD_PRIVATE int32_t txPushMultiFrame(CanardTxQueue* const que,
CanardInstance* const ins,
const size_t presentation_layer_mtu,
const CanardMicrosecond deadline_usec,
const uint32_t can_id,
const CanardTransferID transfer_id,
const size_t payload_size,
const void* const payload)
{
CANARD_ASSERT((ins != NULL) && (que != NULL));
CANARD_ASSERT(que != NULL);
CANARD_ASSERT(presentation_layer_mtu > 0U);
CANARD_ASSERT(payload_size > presentation_layer_mtu); // Otherwise, a single-frame transfer should be used.

Expand All @@ -503,7 +501,7 @@ CANARD_PRIVATE int32_t txPushMultiFrame(CanardTxQueue* const que,
CANARD_ASSERT(num_frames >= 2);
if ((que->size + num_frames) <= que->capacity) // Bail early if we can see that we won't fit anyway.
{
const TxChain sq = txGenerateMultiFrameChain(ins,
const TxChain sq = txGenerateMultiFrameChain(que,
presentation_layer_mtu,
deadline_usec,
can_id,
Expand Down Expand Up @@ -535,7 +533,7 @@ CANARD_PRIVATE int32_t txPushMultiFrame(CanardTxQueue* const que,
while (head != NULL)
{
CanardTxQueueItem* const next = head->next_in_transfer;
ins->memory_free(ins, head, head->allocated_size);
que->memory.deallocate(que->memory.user_reference, head->allocated_size, head);
head = next;
}
}
Expand Down Expand Up @@ -698,7 +696,7 @@ CANARD_PRIVATE int8_t rxSessionWritePayload(CanardInstance* const ins,
if ((NULL == rxs->payload) && (extent > 0U))
{
CANARD_ASSERT(rxs->payload_size == 0);
rxs->payload = ins->memory_allocate(ins, extent);
rxs->payload = ins->memory.allocate(ins->memory.user_reference, extent);
}

int8_t out = 0;
Expand Down Expand Up @@ -738,7 +736,7 @@ CANARD_PRIVATE void rxSessionRestart(CanardInstance* const ins,
{
CANARD_ASSERT(ins != NULL);
CANARD_ASSERT(rxs != NULL);
ins->memory_free(ins, rxs->payload, allocated_size); // May be NULL, which is OK.
ins->memory.deallocate(ins->memory.user_reference, allocated_size, rxs->payload); // May be NULL, which is OK.
rxs->total_payload_size = 0U;
rxs->payload_size = 0U;
rxs->payload = NULL;
Expand Down Expand Up @@ -938,7 +936,8 @@ CANARD_PRIVATE int8_t rxAcceptFrame(CanardInstance* const ins,
if ((NULL == subscription->sessions[frame->source_node_id]) && frame->start_of_transfer)
{
CanardInternalRxSession* const rxs =
(CanardInternalRxSession*) ins->memory_allocate(ins, sizeof(CanardInternalRxSession));
(CanardInternalRxSession*) ins->memory.allocate(ins->memory.user_reference,
sizeof(CanardInternalRxSession));
subscription->sessions[frame->source_node_id] = rxs;
if (rxs != NULL)
{
Expand Down Expand Up @@ -977,7 +976,7 @@ CANARD_PRIVATE int8_t rxAcceptFrame(CanardInstance* const ins,
// independent of the input data and the memory shall be free-able.
const size_t payload_size =
(subscription->extent < frame->payload_size) ? subscription->extent : frame->payload_size;
void* const payload = ins->memory_allocate(ins, payload_size);
void* const payload = ins->memory.allocate(ins->memory.user_reference, payload_size);
if (payload != NULL)
{
rxInitTransferMetadataFromFrame(frame, &out_transfer->metadata);
Expand Down Expand Up @@ -1030,34 +1029,36 @@ const uint8_t CanardCANLengthToDLC[65] = {
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, // 49-64
};

CanardInstance canardInit(const CanardMemoryAllocate memory_allocate, const CanardMemoryFree memory_free)
CanardInstance canardInit(const struct CanardMemoryResource memory)
{
CANARD_ASSERT(memory_allocate != NULL);
CANARD_ASSERT(memory_free != NULL);
CANARD_ASSERT(memory.allocate != NULL);
CANARD_ASSERT(memory.deallocate != NULL);
const CanardInstance out = {
.user_reference = NULL,
.node_id = CANARD_NODE_ID_UNSET,
.memory_allocate = memory_allocate,
.memory_free = memory_free,
.memory = memory,
.rx_subscriptions = {NULL, NULL, NULL},
};
return out;
}

CanardTxQueue canardTxInit(const size_t capacity, const size_t mtu_bytes)
CanardTxQueue canardTxInit(const size_t capacity, const size_t mtu_bytes, const struct CanardMemoryResource memory)
{
CANARD_ASSERT(memory.allocate != NULL);
CANARD_ASSERT(memory.deallocate != NULL);
CanardTxQueue out = {
.capacity = capacity,
.mtu_bytes = mtu_bytes,
.size = 0,
.root = NULL,
.memory = memory,
.user_reference = NULL,
};
return out;
}

int32_t canardTxPush(CanardTxQueue* const que,
CanardInstance* const ins,
const CanardInstance* const ins,
const CanardMicrosecond tx_deadline_usec,
const CanardTransferMetadata* const metadata,
const size_t payload_size,
Expand All @@ -1073,7 +1074,6 @@ int32_t canardTxPush(CanardTxQueue* const que,
if (payload_size <= pl_mtu)
{
out = txPushSingleFrame(que,
ins,
tx_deadline_usec,
(uint32_t) maybe_can_id,
metadata->transfer_id,
Expand All @@ -1084,7 +1084,6 @@ int32_t canardTxPush(CanardTxQueue* const que,
else
{
out = txPushMultiFrame(que,
ins,
pl_mtu,
tx_deadline_usec,
(uint32_t) maybe_can_id,
Expand Down Expand Up @@ -1245,9 +1244,9 @@ int8_t canardRxUnsubscribe(CanardInstance* const ins,
{
if (sub->sessions[i] != NULL)
{
ins->memory_free(ins, sub->sessions[i]->payload, sub->extent);
ins->memory.deallocate(ins->memory.user_reference, sub->extent, sub->sessions[i]->payload);
}
ins->memory_free(ins, sub->sessions[i], sizeof(*sub->sessions[i]));
ins->memory.deallocate(ins->memory.user_reference, sizeof(*sub->sessions[i]), sub->sessions[i]);
sub->sessions[i] = NULL;
}
}
Expand Down
77 changes: 53 additions & 24 deletions libcanard/canard.h
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,47 @@ typedef struct
CanardTransferID transfer_id;
} CanardTransferMetadata;

/// A pointer to the memory allocation function. The semantics are similar to malloc():
/// - The returned pointer shall point to an uninitialized block of memory that is at least "amount" bytes large.
/// - If there is not enough memory, the returned pointer shall be NULL.
/// - The memory shall be aligned at least at max_align_t.
/// - The execution time should be constant (O(1)).
/// - The worst-case memory fragmentation should be bounded and easily predictable.
/// If the standard dynamic memory manager of the target platform does not satisfy the above requirements,
/// consider using O1Heap: https://github.com/pavel-kirienko/o1heap.
///
/// The value of the user reference is taken from the corresponding field of the memory resource structure.
typedef void* (*CanardMemoryAllocate)(void* const user_reference, const size_t size);

/// The counterpart of the above -- this function is invoked to return previously allocated memory to the allocator.
/// The semantics are similar to free(), but with additional `amount` parameter:
/// - The pointer was previously returned by the allocation function.
/// - The pointer may be NULL, in which case the function shall have no effect.
/// - The execution time should be constant (O(1)).
/// - The amount is the same as it was during allocation.
///
/// The value of the user reference is taken from the corresponding field of the memory resource structure.
typedef void (*CanardMemoryDeallocate)(void* const user_reference, const size_t size, void* const pointer);

/// A kind of memory resource that can only be used to free memory previously allocated by the user.
/// Instances are mostly intended to be passed by value.
struct CanardMemoryDeleter
{
void* user_reference; ///< Passed as the first argument.
CanardMemoryDeallocate deallocate; ///< Shall be a valid pointer.
};

/// A memory resource encapsulates the dynamic memory allocation and deallocation facilities.
/// Note that the library allocates a large amount of small fixed-size objects for bookkeeping purposes;
/// allocators for them can be implemented using fixed-size block pools to eliminate extrinsic memory fragmentation.
/// Instances are mostly intended to be passed by value.
struct CanardMemoryResource
{
void* user_reference; ///< Passed as the first argument.
CanardMemoryDeallocate deallocate; ///< Shall be a valid pointer.
CanardMemoryAllocate allocate; ///< Shall be a valid pointer.
};

/// 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.
Expand Down Expand Up @@ -263,6 +304,13 @@ typedef struct CanardTxQueue
/// The root of the priority queue is NULL if the queue is empty. Do not modify this field!
CanardTreeNode* root;

/// The memory resource used by this queue for allocating the enqueued items (CAN frames).
/// There is exactly one allocation per enqueued item, each allocation contains both the CanardTxQueueItem
/// and its payload, hence the size is variable.
/// In a simple application, there would be just one memory resource shared by all parts of the library.
/// If the application knows its MTU, it can use block allocation to avoid extrinsic fragmentation.
struct CanardMemoryResource memory;

/// This field can be arbitrarily mutated by the user. It is never accessed by the library.
/// Its purpose is to simplify integration with OOP interfaces.
void* user_reference;
Expand Down Expand Up @@ -351,24 +399,6 @@ typedef struct CanardRxTransfer
size_t allocated_size;
} CanardRxTransfer;

/// A pointer to the memory allocation function. The semantics are similar to malloc():
/// - The returned pointer shall point to an uninitialized block of memory that is at least "amount" bytes large.
/// - If there is not enough memory, the returned pointer shall be NULL.
/// - The memory shall be aligned at least at max_align_t.
/// - The execution time should be constant (O(1)).
/// - The worst-case memory fragmentation should be bounded and easily predictable.
/// If the standard dynamic memory manager of the target platform does not satisfy the above requirements,
/// consider using O1Heap: https://github.com/pavel-kirienko/o1heap.
typedef void* (*CanardMemoryAllocate)(CanardInstance* ins, size_t amount);

/// The counterpart of the above -- this function is invoked to return previously allocated memory to the allocator.
/// The semantics are similar to free(), but with additional `amount` parameter:
/// - The pointer was previously returned by the allocation function.
/// - The pointer may be NULL, in which case the function shall have no effect.
/// - The execution time should be constant (O(1)).
/// - The amount is the same as it was during allocation.
typedef void (*CanardMemoryFree)(CanardInstance* ins, void* pointer, size_t amount);

/// This is the core structure that keeps all of the states and allocated resources of the library instance.
struct CanardInstance
{
Expand All @@ -390,8 +420,7 @@ struct CanardInstance
/// The following API functions may allocate memory: canardRxAccept(), canardTxPush().
/// The following API functions may deallocate memory: canardRxAccept(), canardRxSubscribe(), canardRxUnsubscribe().
/// The exact memory requirement and usage model is specified for each function in its documentation.
CanardMemoryAllocate memory_allocate;
CanardMemoryFree memory_free;
struct CanardMemoryResource memory;

/// Read-only DO NOT MODIFY THIS
CanardTreeNode* rx_subscriptions[CANARD_NUM_TRANSFER_KINDS];
Expand All @@ -413,13 +442,13 @@ typedef struct CanardFilter

/// Construct a new library instance.
/// The default values will be assigned as specified in the structure field documentation.
/// If any of the pointers are NULL, the behavior is undefined.
/// If any of the memory resource pointers are NULL, the behavior is undefined.
///
/// The instance does not hold any resources itself except for the allocated memory.
/// To safely discard it, simply remove all existing subscriptions, and don't forget about the TX queues.
///
/// The time complexity is constant. This function does not invoke the dynamic memory manager.
CanardInstance canardInit(const CanardMemoryAllocate memory_allocate, const CanardMemoryFree memory_free);
CanardInstance canardInit(const struct CanardMemoryResource memory);

/// Construct a new transmission queue instance with the specified values for capacity and mtu_bytes.
/// No memory allocation is going to take place until the queue is actually pushed to.
Expand All @@ -429,7 +458,7 @@ CanardInstance canardInit(const CanardMemoryAllocate memory_allocate, const Cana
/// To safely discard it, simply pop all items from the queue.
///
/// The time complexity is constant. This function does not invoke the dynamic memory manager.
CanardTxQueue canardTxInit(const size_t capacity, const size_t mtu_bytes);
CanardTxQueue canardTxInit(const size_t capacity, const size_t mtu_bytes, const struct CanardMemoryResource memory);

/// This function serializes a transfer into a sequence of transport frames and inserts them into the prioritized
/// transmission queue at the appropriate position. Afterwards, the application is supposed to take the enqueued frames
Expand Down Expand Up @@ -477,7 +506,7 @@ CanardTxQueue canardTxInit(const size_t capacity, const size_t mtu_bytes);
/// allocation; a multi-frame transfer of N frames takes N allocations. The size of each allocation is
/// (sizeof(CanardTxQueueItem) + MTU).
int32_t canardTxPush(CanardTxQueue* const que,
CanardInstance* const ins,
const CanardInstance* const ins,
const CanardMicrosecond tx_deadline_usec,
const CanardTransferMetadata* const metadata,
const size_t payload_size,
Expand Down
Loading

0 comments on commit ea75329

Please sign in to comment.