@@ -470,13 +470,14 @@ static bool header_deserialize(const udpard_bytes_mut_t dgram_payload,
470470// ---------------------------------------------------------------------------------------------------------------------
471471
472472/// This may be allocated in the NIC DMA region so we keep overheads tight.
473- /// An alternative approach would be to have a flex array of tx_frame_t* pointers in the tx_transfer_t.
474473typedef struct tx_frame_t
475474{
476475 struct tx_frame_t * next ;
477476 byte_t data [];
478477} tx_frame_t ;
479478
479+ static size_t tx_frame_size (const size_t mtu ) { return sizeof (tx_frame_t ) + mtu + HEADER_SIZE_BYTES ; }
480+
480481typedef struct tx_transfer_t
481482{
482483 /// Various indexes this transfer is a member of.
@@ -492,8 +493,8 @@ typedef struct tx_transfer_t
492493 tx_frame_t * cursor ;
493494
494495 /// Retransmission state.
495- uint16_t retries ;
496- udpard_us_t next_attempt_at ;
496+ uint_fast8_t retries ;
497+ udpard_us_t next_attempt_at ;
497498
498499 /// All frames except for the last one share the same MTU, so there's no use keeping dedicated size per frame.
499500 size_t mtu ;
@@ -511,7 +512,7 @@ typedef struct tx_transfer_t
511512
512513static bool tx_validate_mem_resources (const udpard_tx_mem_resources_t memory )
513514{
514- return (memory .meta .alloc != NULL ) && (memory .meta .free != NULL ) && //
515+ return (memory .transfer .alloc != NULL ) && (memory .transfer .free != NULL ) && //
515516 (memory .payload .alloc != NULL ) && (memory .payload .free != NULL );
516517}
517518
@@ -527,63 +528,63 @@ static int32_t tx_cavl_compare_deadline(const void* const user, const udpard_tre
527528 return ((* (const udpard_us_t * )user ) >= CAVL2_TO_OWNER (node , tx_frame_t , index_deadline )-> deadline ) ? +1 : -1 ;
528529}
529530
530- static tx_frame_t * tx_frame_new (const udpard_tx_mem_resources_t memory , const size_t payload_size )
531- {
532- tx_frame_t * const out = mem_alloc (memory .payload , sizeof (tx_frame_t ) + payload_size );
533- if (out != NULL ) {
534- out -> next = NULL ; // Last by default.
535- }
536- return out ;
537- }
538-
539- typedef struct
540- {
541- tx_frame_t * head ;
542- tx_frame_t * tail ;
543- size_t count ;
544- } tx_chain_t ;
545-
546- /// The tail is NULL if OOM. The caller is responsible for freeing the memory allocated for the chain.
547- static tx_chain_t tx_spool (const udpard_tx_mem_resources_t memory ,
548- const size_t mtu ,
549- const meta_t meta ,
550- const udpard_bytes_t payload )
531+ /// Returns the head of the transfer chain; NULL on OOM.
532+ static tx_frame_t * tx_spool (const udpard_tx_mem_resources_t memory ,
533+ const size_t mtu ,
534+ const meta_t meta ,
535+ const udpard_bytes_t payload )
551536{
552537 UDPARD_ASSERT (mtu > 0 );
553538 UDPARD_ASSERT ((payload .data != NULL ) || (payload .size == 0U ));
554- uint32_t prefix_crc = CRC_INITIAL ;
555- tx_chain_t out = { NULL , NULL , 0 };
556- size_t offset = 0U ;
539+ uint32_t prefix_crc = CRC_INITIAL ;
540+ tx_frame_t * head = NULL ;
541+ tx_frame_t * tail = NULL ;
542+ size_t frame_index = 0U ;
543+ size_t offset = 0U ;
544+ // Run the O(n) copy loop, where n is the payload size.
545+ // The client doesn't have to ensure that the payload data survives beyond this function call.
557546 do {
558- const size_t progress = smaller (payload .size - offset , mtu );
559- tx_frame_t * const item = tx_frame_new (memory , progress + HEADER_SIZE_BYTES );
560- if (NULL == out .head ) {
561- out .head = item ;
562- } else {
563- out .tail -> next = item ;
547+ // Compute the size of the next frame, allocate it and link it up in the chain.
548+ const size_t progress = smaller (payload .size - offset , mtu );
549+ {
550+ tx_frame_t * const item = mem_alloc (memory .payload , sizeof (tx_frame_t ) + progress + HEADER_SIZE_BYTES );
551+ if (NULL == head ) {
552+ head = item ;
553+ } else {
554+ tail -> next = item ;
555+ }
556+ tail = item ;
564557 }
565- out .tail = item ;
566- if (NULL == out .tail ) {
558+ // On OOM, deallocate the entire chain and quit.
559+ if (NULL == tail ) {
560+ while (head != NULL ) {
561+ tx_frame_t * const next = head -> next ;
562+ mem_free (memory .payload , tx_frame_size ((head == tail ) ? progress : mtu ), head );
563+ head = next ;
564+ }
567565 break ;
568566 }
567+ // Populate the frame contents.
568+ tail -> next = NULL ;
569569 const byte_t * const read_ptr = ((const byte_t * )payload .data ) + offset ;
570570 prefix_crc = crc_add (prefix_crc , progress , read_ptr );
571571 byte_t * const write_ptr =
572- header_serialize (item -> data , meta , (uint32_t )out . count , (uint32_t )offset , prefix_crc ^ CRC_OUTPUT_XOR );
572+ header_serialize (tail -> data , meta , (uint32_t )frame_index , (uint32_t )offset , prefix_crc ^ CRC_OUTPUT_XOR );
573573 (void )memcpy (write_ptr , read_ptr , progress ); // NOLINT(*DeprecatedOrUnsafeBufferHandling)
574+ // Advance the state.
575+ ++ frame_index ;
574576 offset += progress ;
575577 UDPARD_ASSERT (offset <= payload .size );
576- out .count ++ ;
577578 } while (offset < payload .size );
578- UDPARD_ASSERT ((offset == payload .size ) || (out . tail == NULL ));
579- return out ;
579+ UDPARD_ASSERT ((offset == payload .size ) || (( head == NULL ) && ( tail == NULL ) ));
580+ return head ;
580581}
581582
582- /// Derives the ack timeout for an outgoing transfer using an empirical formula .
583+ /// Derives the ack timeout for an outgoing transfer.
583584/// The number of retries is initially zero when the transfer is sent for the first time.
584- static udpard_us_t tx_ack_timeout (const udpard_us_t baseline , const udpard_prio_t prio , const uint16_t retries )
585+ static udpard_us_t tx_ack_timeout (const udpard_us_t baseline , const udpard_prio_t prio , const uint_fast8_t retries )
585586{
586- return baseline * (1L << smaller ((uint16_t )prio + retries , 15 )); // NOLINT(*-signed-bitwise)
587+ return baseline * (1L << smaller ((size_t )prio + retries , UDPARD_TX_RETRY_MAX )); // NOLINT(*-signed-bitwise)
587588}
588589
589590static uint32_t tx_push (udpard_tx_t * const tx ,
@@ -594,39 +595,41 @@ static uint32_t tx_push(udpard_tx_t* const tx,
594595 void * const user_transfer_reference )
595596{
596597 UDPARD_ASSERT (tx != NULL );
597- uint32_t out = 0 ; // The number of frames enqueued; zero on error (error counters incremented).
598- const size_t mtu = larger (tx -> mtu , UDPARD_MTU_MIN );
599- const size_t frame_count = larger (1 , (payload .size + mtu - 1U ) / mtu );
600- if ((tx -> queue_size + frame_count ) > tx -> queue_capacity ) {
598+ uint32_t out = 0 ; // The number of frames enqueued; zero on error (error counters incremented).
599+ const size_t payload_size = payload .size ;
600+ const size_t mtu = larger (tx -> mtu , UDPARD_MTU_MIN );
601+ const size_t mtu_last = ((payload_size % mtu ) != 0U ) ? (payload_size % mtu ) : mtu ;
602+ const size_t n_frames = larger (1 , (payload_size + mtu - 1U ) / mtu );
603+ if ((tx -> queue_size + n_frames ) > tx -> queue_capacity ) {
601604 tx -> errors_capacity ++ ;
602605 } else {
603- const tx_chain_t chain = tx_spool (tx -> memory , mtu , deadline , meta , endpoint , payload , user_transfer_reference );
604- if (chain .tail != NULL ) { // Insert the head into the tx index. Only the head, the rest is linked-listed.
605- tx_frame_t * const head = chain .head ;
606- UDPARD_ASSERT (frame_count == chain .count );
607- const udpard_tree_t * res = cavl2_find_or_insert (
608- & tx -> index_order , & head -> priority , & tx_cavl_compare_prio , & head -> index_order , & cavl2_trivial_factory );
609- UDPARD_ASSERT (res == & head -> index_order );
610- (void )res ;
611- res = cavl2_find_or_insert (& tx -> index_deadline ,
612- & head -> deadline ,
613- & tx_cavl_compare_deadline ,
614- & head -> index_deadline ,
615- & cavl2_trivial_factory );
616- UDPARD_ASSERT (res == & head -> index_deadline );
617- (void )res ;
618- tx -> queue_size += chain .count ;
619- UDPARD_ASSERT (tx -> queue_size <= tx -> queue_capacity );
620- out = (uint32_t )chain .count ;
621- } else { // The queue is large enough but we ran out of heap memory, so we have to unwind the chain.
622- tx -> errors_oom ++ ;
623- tx_frame_t * head = chain .head ;
624- while (head != NULL ) {
625- tx_frame_t * const next = head -> next ;
626- mem_free (tx -> memory .payload , head -> datagram_payload .size , head -> datagram_payload .data );
627- mem_free (tx -> memory .fragment , sizeof (tx_frame_t ), head );
628- head = next ;
606+ tx_transfer_t * const tr = mem_alloc (tx -> memory .transfer , sizeof (tx_transfer_t ));
607+ if (tr != NULL ) {
608+ mem_zero (sizeof (* tr ), tr );
609+ tr -> retries = 0 ;
610+ tr -> next_attempt_at = BIG_BANG ; // TODO: we can implement time-triggered comms here.
611+ tr -> mtu = mtu ;
612+ tr -> mtu_last = mtu_last ;
613+ tr -> topic_hash = meta .topic_hash ;
614+ tr -> transfer_id = meta .transfer_id ;
615+ tr -> deadline = deadline ;
616+ tr -> reliable = meta .flag_ack ;
617+ tr -> priority = meta .priority ;
618+ tr -> destination = endpoint ;
619+ tr -> user_transfer_reference = user_transfer_reference ;
620+ tr -> head = tr -> cursor = tx_spool (tx -> memory , mtu , meta , payload );
621+ if (tr -> head != NULL ) {
622+ // TODO: insert
623+ // Finalize
624+ tx -> queue_size += n_frames ;
625+ UDPARD_ASSERT (tx -> queue_size <= tx -> queue_capacity );
626+ out = (uint32_t )n_frames ;
627+ } else { // The queue is large enough but we ran out of heap memory.
628+ tx -> errors_oom ++ ;
629+ mem_free (tx -> memory .transfer , sizeof (tx_transfer_t ), tr );
629630 }
631+ } else { // The queue is large enough but we couldn't allocate the transfer metadata object.
632+ tx -> errors_oom ++ ;
630633 }
631634 }
632635 return out ;
0 commit comments