DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
dma.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file dma.h
6 * @brief DMA peripheral ingest / egress (DETWS_ENABLE_DMA) - the v5 high-throughput
7 * hardware-ingest path.
8 *
9 * A DMA channel moves bytes between a peripheral (UART / I2C / SPI) and a static
10 * buffer while the CPU is free, then a DMA-complete event carries the result up.
11 * Two directions:
12 *
13 * - RX (ingress): peripheral -> DMA -> buffer. On completion the channel emits a
14 * @ref det_dma_event whose `data`/`len` point at the just-filled buffer. RX is
15 * **double-buffered (ping-pong)**: the completed buffer is handed to the callback
16 * while the engine fills the other, so there is a full transfer of headroom to
17 * consume it before it is reused.
18 * - TX (egress): buffer -> DMA -> peripheral. The caller submits bytes; on
19 * completion the channel emits a TX event (data == nullptr) so the producer knows
20 * the buffer is free to refill.
21 *
22 * The completion callback runs in ISR context on real silicon, so keep it tiny - the
23 * intended pattern is to post the event into the preempting work queue
24 * (services/preempt_queue) with detws_pq_post_from_isr(), letting a high-priority task
25 * do the real work off the interrupt. det_dma stays decoupled from the queue: it just
26 * hands you the event.
27 *
28 * **Simulator (DETWS_DMA_SIMULATE, default on).** With no physical loopback wire the
29 * transfers run through an in-memory model of the peripheral: det_dma_sim_feed() injects
30 * bytes as if they arrived on the RX line, det_dma_sim_capture() reads back what egress
31 * DMA "transmitted", and a channel opened with `loopback` feeds its own TX egress into
32 * its RX ingress (an internal jumper). det_dma_poll() advances the engine and fires the
33 * completions. This exercises the entire ingress -> event -> (queue) -> handler and
34 * produce -> egress pipeline identically on the host bench and on-device. It is the
35 * shipped, tested backend; a real silicon driver plugs into det_dma_hw_* when the flag
36 * is 0 (see docs/KNOWN_LIMITATIONS.md).
37 *
38 * Zero-heap: DETWS_DMA_CHANNELS channels, each with 2x DETWS_DMA_BUF_SIZE RX + one TX
39 * buffer + the simulator's ingress/egress staging, all static and compile-time sized.
40 * Fail-closed: a submit onto a busy channel or a feed past the staging capacity returns
41 * false rather than blocking or overrunning.
42 *
43 * @author Douglas Quigg (dstroy0)
44 * @date 2026
45 */
46
47#ifndef DETERMINISTICESPASYNCWEBSERVER_DET_DMA_H
48#define DETERMINISTICESPASYNCWEBSERVER_DET_DMA_H
49
50#include "ServerConfig.h"
51
52#if DETWS_ENABLE_DMA
53
54#include <stddef.h>
55#include <stdint.h>
56
57/** @brief Peripheral a channel is bound to (informational; selects the real backend). */
58enum class det_dma_periph : uint8_t
59{
60 DET_DMA_UART = 0,
61 DET_DMA_I2C = 1,
62 DET_DMA_SPI = 2,
63};
64
65/** @brief Transfer direction carried on a completion event. */
66enum class det_dma_dir : uint8_t
67{
68 DET_DMA_RX = 0, ///< ingress: peripheral -> buffer
69 DET_DMA_TX = 1, ///< egress: buffer -> peripheral
70};
71
72/**
73 * @brief A DMA-complete event handed to the channel callback.
74 *
75 * For RX, @ref data / @ref len point at the completed ping-pong buffer - valid only
76 * until that buffer is reused (a transfer or two later). Consume it inside the callback,
77 * or, if you hand the work to another task (e.g. post it to the preempting queue), copy
78 * the @ref len bytes into the posted item: a deferred consumer can lag past the buffer's
79 * reuse under load, so post the bytes, not this pointer. For TX, @ref data is nullptr and
80 * @ref len is the number of bytes drained.
81 */
82struct det_dma_event
83{
84 const uint8_t *data; ///< RX: received bytes (into a ping-pong buffer); TX: nullptr
85 uint32_t t_ms; ///< detws_millis() at completion (0 on host builds)
86 uint16_t len; ///< bytes transferred
87 uint16_t seq; ///< per-channel completion sequence (wraps)
88 uint8_t channel; ///< channel id [0, DETWS_DMA_CHANNELS)
89 det_dma_periph periph; ///< det_dma_periph
90 det_dma_dir dir; ///< det_dma_dir
91 uint8_t _pad;
92};
93
94/**
95 * @brief Called once per completed transfer (RX and TX). ISR context on real silicon,
96 * so keep it tiny (the canonical body posts @p ev to the preempting queue).
97 * @param ev the completion event (owned by the caller; copy what you need).
98 * @param ctx the opaque pointer from @ref det_dma_config.
99 */
100typedef void (*det_dma_cb)(const det_dma_event *ev, void *ctx);
101
102/** @brief Channel configuration passed to det_dma_open(). */
103struct det_dma_config
104{
105 uint8_t channel; ///< channel id [0, DETWS_DMA_CHANNELS).
106 det_dma_periph periph; ///< det_dma_periph the channel drives.
107 bool loopback; ///< simulator: this channel's TX egress feeds its own RX ingress.
108 det_dma_cb on_complete; ///< completion callback (required).
109 void *ctx; ///< opaque, forwarded to @ref on_complete.
110};
111
112/**
113 * @brief Configure a channel and arm its RX transfer.
114 * @return true if opened; false on a bad channel id / null callback / already open.
115 */
116bool det_dma_open(const det_dma_config *cfg);
117
118/**
119 * @brief Submit bytes for egress DMA on channel @p ch (copies up to DETWS_DMA_BUF_SIZE).
120 *
121 * Fail-closed: returns false if a TX is still in flight on the channel (wait for its
122 * TX-complete event), if the channel is closed, or on a null / oversize buffer.
123 * @return true if the transfer was accepted.
124 */
125bool det_dma_tx_submit(uint8_t ch, const uint8_t *buf, uint16_t len);
126
127/** @brief Close a channel and drop any in-flight transfer / staged simulator bytes. */
128void det_dma_close(uint8_t ch);
129
130/**
131 * @brief Advance the simulator engine: drain egress, run loopback, complete RX/TX and
132 * fire the callbacks. No-op on the real silicon backend (ISRs drive completion).
133 * This is how the host and the on-device self-test step the pipeline.
134 */
135void det_dma_poll(void);
136
137#if DETWS_DMA_SIMULATE
138
139/**
140 * @brief Simulator: inject @p len bytes as if they arrived on channel @p ch's RX line.
141 * Delivered as one or more RX events on the next det_dma_poll().
142 * @return true if staged; false if the ingress staging is full (fail-closed) or the
143 * channel is closed.
144 */
145bool det_dma_sim_feed(uint8_t ch, const uint8_t *bytes, uint16_t len);
146
147/**
148 * @brief Simulator: read back up to @p max bytes that channel @p ch transmitted via
149 * egress DMA. Consumes them from the capture buffer.
150 * @return number of bytes copied into @p out.
151 */
152uint16_t det_dma_sim_capture(uint8_t ch, uint8_t *out, uint16_t max);
153
154#endif // DETWS_DMA_SIMULATE
155
156#endif // DETWS_ENABLE_DMA
157
158#endif // DETERMINISTICESPASYNCWEBSERVER_DET_DMA_H
User-facing configuration for DeterministicESPAsyncWebServer.