Skip to content

Commit ea75329

Browse files
committed
Introduced new memory related types:
- `CanardMemoryAllocate` - `CanardMemoryDeallocate` - `CanardMemoryDeleter` - `CanardMemoryResource`
1 parent 14e373a commit ea75329

File tree

5 files changed

+102
-64
lines changed

5 files changed

+102
-64
lines changed

libcanard/canard.c

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -296,15 +296,15 @@ CANARD_PRIVATE size_t txRoundFramePayloadSizeUp(const size_t x)
296296
}
297297

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

339339
/// Returns the number of frames enqueued or error (i.e., =1 or <0).
340340
CANARD_PRIVATE int32_t txPushSingleFrame(CanardTxQueue* const que,
341-
CanardInstance* const ins,
342341
const CanardMicrosecond deadline_usec,
343342
const uint32_t can_id,
344343
const CanardTransferID transfer_id,
345344
const size_t payload_size,
346345
const void* const payload)
347346
{
348-
CANARD_ASSERT(ins != NULL);
347+
CANARD_ASSERT(que != NULL);
349348
CANARD_ASSERT((payload != NULL) || (payload_size == 0));
350349
const size_t frame_payload_size = txRoundFramePayloadSizeUp(payload_size + 1U);
351350
CANARD_ASSERT(frame_payload_size > payload_size);
352351
const size_t padding_size = frame_payload_size - payload_size - 1U;
353352
CANARD_ASSERT((padding_size + payload_size + 1U) == frame_payload_size);
354353
int32_t out = 0;
355354
TxItem* const tqi =
356-
(que->size < que->capacity) ? txAllocateQueueItem(ins, can_id, deadline_usec, frame_payload_size) : NULL;
355+
(que->size < que->capacity) ? txAllocateQueueItem(que, can_id, deadline_usec, frame_payload_size) : NULL;
357356
if (tqi != NULL)
358357
{
359358
if (payload_size > 0U) // The check is needed to avoid calling memcpy() with a NULL pointer, it's an UB.
@@ -384,15 +383,15 @@ CANARD_PRIVATE int32_t txPushSingleFrame(CanardTxQueue* const que,
384383
}
385384

386385
/// Produces a chain of Tx queue items for later insertion into the Tx queue. The tail is NULL if OOM.
387-
CANARD_PRIVATE TxChain txGenerateMultiFrameChain(CanardInstance* const ins,
386+
CANARD_PRIVATE TxChain txGenerateMultiFrameChain(CanardTxQueue* const que,
388387
const size_t presentation_layer_mtu,
389388
const CanardMicrosecond deadline_usec,
390389
const uint32_t can_id,
391390
const CanardTransferID transfer_id,
392391
const size_t payload_size,
393392
const void* const payload)
394393
{
395-
CANARD_ASSERT(ins != NULL);
394+
CANARD_ASSERT(que != NULL);
396395
CANARD_ASSERT(presentation_layer_mtu > 0U);
397396
CANARD_ASSERT(payload_size > presentation_layer_mtu); // Otherwise, a single-frame transfer should be used.
398397
CANARD_ASSERT(payload != NULL);
@@ -410,7 +409,7 @@ CANARD_PRIVATE TxChain txGenerateMultiFrameChain(CanardInstance* const ins,
410409
((payload_size_with_crc - offset) < presentation_layer_mtu)
411410
? txRoundFramePayloadSizeUp((payload_size_with_crc - offset) + 1U) // Padding in the last frame only.
412411
: (presentation_layer_mtu + 1U);
413-
TxItem* const tqi = txAllocateQueueItem(ins, can_id, deadline_usec, frame_payload_size_with_tail);
412+
TxItem* const tqi = txAllocateQueueItem(que, can_id, deadline_usec, frame_payload_size_with_tail);
414413
if (NULL == out.head)
415414
{
416415
out.head = tqi;
@@ -485,15 +484,14 @@ CANARD_PRIVATE TxChain txGenerateMultiFrameChain(CanardInstance* const ins,
485484

486485
/// Returns the number of frames enqueued or error.
487486
CANARD_PRIVATE int32_t txPushMultiFrame(CanardTxQueue* const que,
488-
CanardInstance* const ins,
489487
const size_t presentation_layer_mtu,
490488
const CanardMicrosecond deadline_usec,
491489
const uint32_t can_id,
492490
const CanardTransferID transfer_id,
493491
const size_t payload_size,
494492
const void* const payload)
495493
{
496-
CANARD_ASSERT((ins != NULL) && (que != NULL));
494+
CANARD_ASSERT(que != NULL);
497495
CANARD_ASSERT(presentation_layer_mtu > 0U);
498496
CANARD_ASSERT(payload_size > presentation_layer_mtu); // Otherwise, a single-frame transfer should be used.
499497

@@ -503,7 +501,7 @@ CANARD_PRIVATE int32_t txPushMultiFrame(CanardTxQueue* const que,
503501
CANARD_ASSERT(num_frames >= 2);
504502
if ((que->size + num_frames) <= que->capacity) // Bail early if we can see that we won't fit anyway.
505503
{
506-
const TxChain sq = txGenerateMultiFrameChain(ins,
504+
const TxChain sq = txGenerateMultiFrameChain(que,
507505
presentation_layer_mtu,
508506
deadline_usec,
509507
can_id,
@@ -535,7 +533,7 @@ CANARD_PRIVATE int32_t txPushMultiFrame(CanardTxQueue* const que,
535533
while (head != NULL)
536534
{
537535
CanardTxQueueItem* const next = head->next_in_transfer;
538-
ins->memory_free(ins, head, head->allocated_size);
536+
que->memory.deallocate(que->memory.user_reference, head->allocated_size, head);
539537
head = next;
540538
}
541539
}
@@ -698,7 +696,7 @@ CANARD_PRIVATE int8_t rxSessionWritePayload(CanardInstance* const ins,
698696
if ((NULL == rxs->payload) && (extent > 0U))
699697
{
700698
CANARD_ASSERT(rxs->payload_size == 0);
701-
rxs->payload = ins->memory_allocate(ins, extent);
699+
rxs->payload = ins->memory.allocate(ins->memory.user_reference, extent);
702700
}
703701

704702
int8_t out = 0;
@@ -738,7 +736,7 @@ CANARD_PRIVATE void rxSessionRestart(CanardInstance* const ins,
738736
{
739737
CANARD_ASSERT(ins != NULL);
740738
CANARD_ASSERT(rxs != NULL);
741-
ins->memory_free(ins, rxs->payload, allocated_size); // May be NULL, which is OK.
739+
ins->memory.deallocate(ins->memory.user_reference, allocated_size, rxs->payload); // May be NULL, which is OK.
742740
rxs->total_payload_size = 0U;
743741
rxs->payload_size = 0U;
744742
rxs->payload = NULL;
@@ -938,7 +936,8 @@ CANARD_PRIVATE int8_t rxAcceptFrame(CanardInstance* const ins,
938936
if ((NULL == subscription->sessions[frame->source_node_id]) && frame->start_of_transfer)
939937
{
940938
CanardInternalRxSession* const rxs =
941-
(CanardInternalRxSession*) ins->memory_allocate(ins, sizeof(CanardInternalRxSession));
939+
(CanardInternalRxSession*) ins->memory.allocate(ins->memory.user_reference,
940+
sizeof(CanardInternalRxSession));
942941
subscription->sessions[frame->source_node_id] = rxs;
943942
if (rxs != NULL)
944943
{
@@ -977,7 +976,7 @@ CANARD_PRIVATE int8_t rxAcceptFrame(CanardInstance* const ins,
977976
// independent of the input data and the memory shall be free-able.
978977
const size_t payload_size =
979978
(subscription->extent < frame->payload_size) ? subscription->extent : frame->payload_size;
980-
void* const payload = ins->memory_allocate(ins, payload_size);
979+
void* const payload = ins->memory.allocate(ins->memory.user_reference, payload_size);
981980
if (payload != NULL)
982981
{
983982
rxInitTransferMetadataFromFrame(frame, &out_transfer->metadata);
@@ -1030,34 +1029,36 @@ const uint8_t CanardCANLengthToDLC[65] = {
10301029
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, // 49-64
10311030
};
10321031

1033-
CanardInstance canardInit(const CanardMemoryAllocate memory_allocate, const CanardMemoryFree memory_free)
1032+
CanardInstance canardInit(const struct CanardMemoryResource memory)
10341033
{
1035-
CANARD_ASSERT(memory_allocate != NULL);
1036-
CANARD_ASSERT(memory_free != NULL);
1034+
CANARD_ASSERT(memory.allocate != NULL);
1035+
CANARD_ASSERT(memory.deallocate != NULL);
10371036
const CanardInstance out = {
10381037
.user_reference = NULL,
10391038
.node_id = CANARD_NODE_ID_UNSET,
1040-
.memory_allocate = memory_allocate,
1041-
.memory_free = memory_free,
1039+
.memory = memory,
10421040
.rx_subscriptions = {NULL, NULL, NULL},
10431041
};
10441042
return out;
10451043
}
10461044

1047-
CanardTxQueue canardTxInit(const size_t capacity, const size_t mtu_bytes)
1045+
CanardTxQueue canardTxInit(const size_t capacity, const size_t mtu_bytes, const struct CanardMemoryResource memory)
10481046
{
1047+
CANARD_ASSERT(memory.allocate != NULL);
1048+
CANARD_ASSERT(memory.deallocate != NULL);
10491049
CanardTxQueue out = {
10501050
.capacity = capacity,
10511051
.mtu_bytes = mtu_bytes,
10521052
.size = 0,
10531053
.root = NULL,
1054+
.memory = memory,
10541055
.user_reference = NULL,
10551056
};
10561057
return out;
10571058
}
10581059

10591060
int32_t canardTxPush(CanardTxQueue* const que,
1060-
CanardInstance* const ins,
1061+
const CanardInstance* const ins,
10611062
const CanardMicrosecond tx_deadline_usec,
10621063
const CanardTransferMetadata* const metadata,
10631064
const size_t payload_size,
@@ -1073,7 +1074,6 @@ int32_t canardTxPush(CanardTxQueue* const que,
10731074
if (payload_size <= pl_mtu)
10741075
{
10751076
out = txPushSingleFrame(que,
1076-
ins,
10771077
tx_deadline_usec,
10781078
(uint32_t) maybe_can_id,
10791079
metadata->transfer_id,
@@ -1084,7 +1084,6 @@ int32_t canardTxPush(CanardTxQueue* const que,
10841084
else
10851085
{
10861086
out = txPushMultiFrame(que,
1087-
ins,
10881087
pl_mtu,
10891088
tx_deadline_usec,
10901089
(uint32_t) maybe_can_id,
@@ -1245,9 +1244,9 @@ int8_t canardRxUnsubscribe(CanardInstance* const ins,
12451244
{
12461245
if (sub->sessions[i] != NULL)
12471246
{
1248-
ins->memory_free(ins, sub->sessions[i]->payload, sub->extent);
1247+
ins->memory.deallocate(ins->memory.user_reference, sub->extent, sub->sessions[i]->payload);
12491248
}
1250-
ins->memory_free(ins, sub->sessions[i], sizeof(*sub->sessions[i]));
1249+
ins->memory.deallocate(ins->memory.user_reference, sizeof(*sub->sessions[i]), sub->sessions[i]);
12511250
sub->sessions[i] = NULL;
12521251
}
12531252
}

libcanard/canard.h

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,47 @@ typedef struct
234234
CanardTransferID transfer_id;
235235
} CanardTransferMetadata;
236236

237+
/// A pointer to the memory allocation function. The semantics are similar to malloc():
238+
/// - The returned pointer shall point to an uninitialized block of memory that is at least "amount" bytes large.
239+
/// - If there is not enough memory, the returned pointer shall be NULL.
240+
/// - The memory shall be aligned at least at max_align_t.
241+
/// - The execution time should be constant (O(1)).
242+
/// - The worst-case memory fragmentation should be bounded and easily predictable.
243+
/// If the standard dynamic memory manager of the target platform does not satisfy the above requirements,
244+
/// consider using O1Heap: https://github.com/pavel-kirienko/o1heap.
245+
///
246+
/// The value of the user reference is taken from the corresponding field of the memory resource structure.
247+
typedef void* (*CanardMemoryAllocate)(void* const user_reference, const size_t size);
248+
249+
/// The counterpart of the above -- this function is invoked to return previously allocated memory to the allocator.
250+
/// The semantics are similar to free(), but with additional `amount` parameter:
251+
/// - The pointer was previously returned by the allocation function.
252+
/// - The pointer may be NULL, in which case the function shall have no effect.
253+
/// - The execution time should be constant (O(1)).
254+
/// - The amount is the same as it was during allocation.
255+
///
256+
/// The value of the user reference is taken from the corresponding field of the memory resource structure.
257+
typedef void (*CanardMemoryDeallocate)(void* const user_reference, const size_t size, void* const pointer);
258+
259+
/// A kind of memory resource that can only be used to free memory previously allocated by the user.
260+
/// Instances are mostly intended to be passed by value.
261+
struct CanardMemoryDeleter
262+
{
263+
void* user_reference; ///< Passed as the first argument.
264+
CanardMemoryDeallocate deallocate; ///< Shall be a valid pointer.
265+
};
266+
267+
/// A memory resource encapsulates the dynamic memory allocation and deallocation facilities.
268+
/// Note that the library allocates a large amount of small fixed-size objects for bookkeeping purposes;
269+
/// allocators for them can be implemented using fixed-size block pools to eliminate extrinsic memory fragmentation.
270+
/// Instances are mostly intended to be passed by value.
271+
struct CanardMemoryResource
272+
{
273+
void* user_reference; ///< Passed as the first argument.
274+
CanardMemoryDeallocate deallocate; ///< Shall be a valid pointer.
275+
CanardMemoryAllocate allocate; ///< Shall be a valid pointer.
276+
};
277+
237278
/// Prioritized transmission queue that keeps CAN frames destined for transmission via one CAN interface.
238279
/// Applications with redundant interfaces are expected to have one instance of this type per interface.
239280
/// Applications that are not interested in transmission may have zero queues.
@@ -263,6 +304,13 @@ typedef struct CanardTxQueue
263304
/// The root of the priority queue is NULL if the queue is empty. Do not modify this field!
264305
CanardTreeNode* root;
265306

307+
/// The memory resource used by this queue for allocating the enqueued items (CAN frames).
308+
/// There is exactly one allocation per enqueued item, each allocation contains both the CanardTxQueueItem
309+
/// and its payload, hence the size is variable.
310+
/// In a simple application, there would be just one memory resource shared by all parts of the library.
311+
/// If the application knows its MTU, it can use block allocation to avoid extrinsic fragmentation.
312+
struct CanardMemoryResource memory;
313+
266314
/// This field can be arbitrarily mutated by the user. It is never accessed by the library.
267315
/// Its purpose is to simplify integration with OOP interfaces.
268316
void* user_reference;
@@ -351,24 +399,6 @@ typedef struct CanardRxTransfer
351399
size_t allocated_size;
352400
} CanardRxTransfer;
353401

354-
/// A pointer to the memory allocation function. The semantics are similar to malloc():
355-
/// - The returned pointer shall point to an uninitialized block of memory that is at least "amount" bytes large.
356-
/// - If there is not enough memory, the returned pointer shall be NULL.
357-
/// - The memory shall be aligned at least at max_align_t.
358-
/// - The execution time should be constant (O(1)).
359-
/// - The worst-case memory fragmentation should be bounded and easily predictable.
360-
/// If the standard dynamic memory manager of the target platform does not satisfy the above requirements,
361-
/// consider using O1Heap: https://github.com/pavel-kirienko/o1heap.
362-
typedef void* (*CanardMemoryAllocate)(CanardInstance* ins, size_t amount);
363-
364-
/// The counterpart of the above -- this function is invoked to return previously allocated memory to the allocator.
365-
/// The semantics are similar to free(), but with additional `amount` parameter:
366-
/// - The pointer was previously returned by the allocation function.
367-
/// - The pointer may be NULL, in which case the function shall have no effect.
368-
/// - The execution time should be constant (O(1)).
369-
/// - The amount is the same as it was during allocation.
370-
typedef void (*CanardMemoryFree)(CanardInstance* ins, void* pointer, size_t amount);
371-
372402
/// This is the core structure that keeps all of the states and allocated resources of the library instance.
373403
struct CanardInstance
374404
{
@@ -390,8 +420,7 @@ struct CanardInstance
390420
/// The following API functions may allocate memory: canardRxAccept(), canardTxPush().
391421
/// The following API functions may deallocate memory: canardRxAccept(), canardRxSubscribe(), canardRxUnsubscribe().
392422
/// The exact memory requirement and usage model is specified for each function in its documentation.
393-
CanardMemoryAllocate memory_allocate;
394-
CanardMemoryFree memory_free;
423+
struct CanardMemoryResource memory;
395424

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

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

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

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

0 commit comments

Comments
 (0)