ether2ser 0.1.0
Ethernet <-> synchronous V.24 bridge firmware for RP2040 + W5500
Loading...
Searching...
No Matches
tx_queue.c
Go to the documentation of this file.
1
2/*
3 * ether2ser - Ethernet <-> synchronous V.24 (RS-232/V.28) bridge
4 *
5 * File: src/drivers/tx_queue.c
6 * Purpose: TX queue storage and HDLC frame enqueue/dequeue.
7 *
8 * SPDX-License-Identifier: Apache-2.0
9 *
10 * Copyright (c) 2026 Florian <f.leuze@outlook.de>
11 */
12
13// Related headers
14#include "tx_queue.h"
15
16// Standard library headers
17#include <stdbool.h>
18#include <stdint.h>
19#include <stdio.h>
20#include <stdlib.h>
21#include <string.h>
22
23// Library Headers
24#include "hardware/pio.h"
25
26// Project Headers
30#include "system/common.h"
31#include "system/error.h"
32#include "system/ringbuffer.h"
33
34// Generated headers
35
36#define TX_QUEUE_TX_INPROG_MS 1000U
37
38e2s_error_t tx_queue_init(TX_QUEUE_T* queue, uint8_t* buffer_data)
39{
40 RbInit(&(queue->queue_buffer), buffer_data, TX_FRAME_QUEUE_SIZE, sizeof(TX_QUEUE_ENTRY_T));
41 queue->queue_touched = false;
42 queue->tx_wire_bytes = 0;
44 queue->current_frame_cts_seq_valid = false;
45 return E2S_OK;
46}
47
48#include "pico/time.h"
50{
51#define QUEUE_FILLING_UP_THRESHOLD 5U
52 if (!queue)
53 {
55 }
56 bool queue_filling_up =
58 if (queue->queue_touched || queue_filling_up)
59 {
60 if (queue_filling_up)
61 {
62 LOG_TRACE("TX Queue Stats: %zu / %zu frames used\r\n", queue->queue_buffer.count,
63 queue->queue_buffer.capacity);
64 }
65 else
66 {
67 LOG_INFO("TX Queue Stats: %zu / %zu frames used\r\n", queue->queue_buffer.count,
68 queue->queue_buffer.capacity);
69 }
70 queue->queue_touched = false;
71 }
72 return E2S_OK;
73}
74
76 size_t bytes_to_drain, size_t* bytes_drained)
77{
78 // LOG_DEBUG("TX: Draining up to %zu bytes from entry (length: %zu, offset: %zu)\r\n",
79 // bytes_to_drain, entry->frame.length, entry->offset);
80 if (!entry || !bytes_drained)
81 {
82 // LOG_DEBUG("TX: Entry is NULL\r\n");
84 }
85 size_t effective_bytes_to_drain = bytes_to_drain >= entry->frame.length - entry->offset
86 ? entry->frame.length - entry->offset
87 : bytes_to_drain;
88 *bytes_drained = 0;
89 for (size_t i = 0; i < effective_bytes_to_drain; i++)
90 {
91 if (pio_sm_is_tx_fifo_full(pio0, 0))
92 {
93 // LOG_DEBUG("TX: TX FIFO is full, stopping drain\r\n");
94 // break;
95 }
96 if (tx_put(entry->frame.payload[entry->offset + i]))
97 {
98 // LOG_DEBUG("TX: Wrote byte %02X\r\n", entry->frame.payload[entry->offset + i]);
99 (*bytes_drained)++;
100 }
101 else
102 {
103 // LOG_DEBUG("TX: Failed to write byte %02X\r\n", entry->frame.payload[entry->offset +
104 // i]);
105 break;
106 }
107 }
108 entry->offset += *bytes_drained;
109 queue->tx_wire_bytes += *bytes_drained;
110 // LOG_DEBUG("TX: Drained %zu bytes, new offset is %zu\r\n", bytes_drained, entry->offset);
111 return E2S_OK;
112}
113size_t tx_queue_get_count(const TX_QUEUE_T* queue)
114{
115 if (!queue)
116 {
117 return 0;
118 }
119 return queue->queue_buffer.count;
120}
121
123{
124 // Queue is empty only if ring buffer is empty AND current entry is fully drained
125 return queue->queue_buffer.count == 0 &&
127}
128
129e2s_error_t tx_queue_drain(TX_QUEUE_T* queue, size_t bytes_to_drain, size_t* bytes_drained)
130{
131 if (!queue || !bytes_drained)
132 {
134 }
135 *bytes_drained = 0U;
136
141 uint32_t cts_seq = tx_clock_get_cts_toggle_seq();
142 bool frame_mid_send = queue->current_entry.offset > 0 &&
144 if (frame_mid_send && queue->current_frame_cts_seq_valid &&
145 cts_seq != queue->current_frame_cts_seq_start)
146 {
148 queue->current_entry.offset = 0U; // resend whole HDLC frame
149 queue->current_frame_cts_seq_valid = false;
150 return E2S_OK;
151 }
152
153 if (queue->current_entry.offset >= queue->current_entry.frame.length)
154 {
155 size_t completed_offset = queue->current_entry.offset;
156 size_t completed_length = queue->current_entry.frame.length;
157
158 if (RbPopFront(&(queue->queue_buffer), &queue->current_entry) < 0)
159 {
160 // TODO: This has to be tested on target
161 queue->current_frame_cts_seq_valid = false;
162 return E2S_OK;
163 }
164 LOG_DEBUG("TX FRAME COMPLETE: offset=%zu length=%zu\n", completed_offset, completed_length);
165
167 queue->current_entry.frame.capacity = sizeof(queue->current_entry.payload);
168 queue->current_frame_cts_seq_valid = false;
169 }
170 size_t offset_before_drain = queue->current_entry.offset;
171 tx_queue_drain_bytes(queue, &queue->current_entry, bytes_to_drain, bytes_drained);
172 if (!queue->current_frame_cts_seq_valid && offset_before_drain == 0U && *bytes_drained > 0U)
173 {
174 /*
175 * Capture baseline after first bytes were actually queued to TX.
176 * This avoids false "mid-frame CTS toggle" caused by RTS->CTS settling
177 * edges that can happen during the first drain call.
178 */
180 queue->current_frame_cts_seq_valid = true;
181 }
182 if (queue->current_entry.offset >= queue->current_entry.frame.length)
183 {
184 queue->current_frame_cts_seq_valid = false;
185 }
186 if (queue->current_entry.offset > 0 &&
188 {
189 static uint32_t last_log = 0;
190 uint32_t now = to_ms_since_boot(get_absolute_time());
191 if (now - last_log > TX_QUEUE_TX_INPROG_MS)
192 { // Log once per second
193 LOG_TRACE("TX IN PROGRESS: offset=%zu/%zu\r\n", queue->current_entry.offset,
194 queue->current_entry.frame.length);
195 last_log = now;
196 }
197 }
198 return E2S_OK;
199}
200
202{
203 if (!queue || !frame)
204 {
206 }
207 if (queue->queue_buffer.count == queue->queue_buffer.capacity)
208 {
210 }
211 TX_QUEUE_ENTRY_T tx_entry = {0};
212 tx_entry.frame.payload = tx_entry.payload;
213 tx_entry.frame.capacity = sizeof(tx_entry.payload);
214 tx_entry.frame.payload = tx_entry.payload; // Fix pointer to point to OUR payload
215
216 if (!hdlc_encode(frame->payload, frame->length, &tx_entry.frame, true))
217 {
219 }
220 LOG_DEBUG("TX: Enqueued %zu bytes\r\n", tx_entry.frame.length);
221 if (RbPushBack(&(queue->queue_buffer), &tx_entry) < 0)
222 {
224 }
225 queue->queue_touched = true;
226 return E2S_OK;
227}
#define LOG_INFO(...)
Definition common.h:164
#define LOG_TRACE(...)
Definition common.h:166
#define LOG_DEBUG(...)
Definition common.h:165
e2s_error_t
Common error codes returned by ether2ser modules.
Definition error.h:27
@ E2S_OK
Definition error.h:28
@ E2S_ERR_TX_QUEUE_FULL
Definition error.h:76
@ E2S_ERR_HDLC_ENCODE_FAILED
Definition error.h:53
@ E2S_ERR_TX_QUEUE_NOT_INITIALIZED
Definition error.h:77
bool hdlc_encode(const uint8_t *payload, const size_t payload_length, HDLC_FRAME_T *frame, bool lsb_first)
Encode payload into an HDLC bit-stuffed frame.
bool tx_put(uint8_t data)
Queue one byte to the TX PIO FIFO.
void tx_clock_hard_reset(void)
Hard-reset TX SM state to discard queued/in-flight bytes.
uint32_t tx_clock_get_cts_toggle_seq(void)
Read current CTS edge sequence counter.
int RbInit(Ringbuffer *bufferStruct, void *bufferPointer, size_t capacity, size_t itemSizeInByte)
Initialize ring buffer instance.
Definition ringbuffer.c:27
int RbPopFront(Ringbuffer *bufferStruct, void *element)
Pop one element from front.
Definition ringbuffer.c:60
int RbPushBack(Ringbuffer *bufferStruct, const void *element)
Push one element at tail/head end.
Definition ringbuffer.c:44
size_t capacity
Definition hdlc_common.h:44
size_t length
Definition hdlc_common.h:42
uint8_t * payload
Definition hdlc_common.h:40
size_t capacity
Definition ringbuffer.h:34
size_t count
Definition ringbuffer.h:36
One queued HDLC frame plus drain offset state.
Definition tx_queue.h:60
size_t offset
Definition tx_queue.h:66
uint8_t payload[TX_FRAME_MAX_SIZE_BYTE]
Definition tx_queue.h:62
HDLC_FRAME_T frame
Definition tx_queue.h:64
TX queue runtime state.
Definition tx_queue.h:73
TX_QUEUE_ENTRY_T current_entry
Definition tx_queue.h:75
uint64_t tx_wire_bytes
Definition tx_queue.h:81
bool current_frame_cts_seq_valid
Definition tx_queue.h:85
Ringbuffer queue_buffer
Definition tx_queue.h:77
bool queue_touched
Definition tx_queue.h:79
uint32_t current_frame_cts_seq_start
Definition tx_queue.h:83
UDP payload container.
size_t length
uint8_t * payload
static e2s_error_t tx_queue_drain_bytes(TX_QUEUE_T *queue, TX_QUEUE_ENTRY_T *entry, size_t bytes_to_drain, size_t *bytes_drained)
Definition tx_queue.c:75
#define QUEUE_FILLING_UP_THRESHOLD
e2s_error_t poll_queue_stats(TX_QUEUE_T *queue)
Emit queue usage statistics when needed.
Definition tx_queue.c:49
bool tx_queue_is_empty(TX_QUEUE_T *queue)
Check whether queue and active entry are fully drained.
Definition tx_queue.c:122
size_t tx_queue_get_count(const TX_QUEUE_T *queue)
Get current number of queued entries.
Definition tx_queue.c:113
#define TX_QUEUE_TX_INPROG_MS
Definition tx_queue.c:36
e2s_error_t tx_queue_init(TX_QUEUE_T *queue, uint8_t *buffer_data)
Initialize TX queue and ring buffer storage.
Definition tx_queue.c:38
e2s_error_t tx_queue_enqueue_udp_frame(TX_QUEUE_T *queue, const UDP_FRAME_T *frame)
Encode UDP frame to HDLC and append it to TX queue.
Definition tx_queue.c:201
e2s_error_t tx_queue_drain(TX_QUEUE_T *queue, size_t bytes_to_drain, size_t *bytes_drained)
Drain up to bytes_to_drain bytes from queue into TX FIFO.
Definition tx_queue.c:129
#define TX_FRAME_QUEUE_SIZE
Number of frame entries available in the TX queue ring buffer.
Definition tx_queue.h:32