From 47642c6f6fc20efcfaf293bce441b8c04c3705c2 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 28 Sep 2026 08:43:23 +0000 Subject: [PATCH] Add FIFO/burst/flow-controller stream options to the lease API dmdma_lease_start_ex() takes an optional dmdma_stream_options_t: FIFO mode and threshold, source/destination burst lengths and peripheral flow control, validated by the core (bursts need FIFO mode, the memory burst must fit the threshold, peripheral flow control only for non-circular memory<->peripheral transfers). The STM32F4/F7 port programs DMDIS/FTH, PBURST/MBURST and PFCTRL through the new dmdma_port_stream_start_ex(); dmdma_lease_start() and dmdma_port_stream_start() keep their behavior. FEIF is reported as an error in direct mode only, as FEIE is never enabled in FIFO mode. Needed by dmsdio: SDIO/SDMMC data requires DMA2 in FIFO mode with 4-beat bursts and peripheral flow control (RM0090 31.3.2 / RM0385). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TgkwHQKqf6c4DzypSKzZT7 --- docs/api.md | 37 +++++++ docs/port-api.md | 11 ++ include/dmdma_lease.h | 12 +++ include/dmdma_port.h | 5 + include/dmdma_types.h | 62 +++++++++++ src/dmdma.c | 79 +++++++++++++- src/port/stm32_common/stm32_common.c | 148 +++++++++++++++++++-------- tests/dmdma_test_lease.c | 74 +++++++++++++- 8 files changed, 379 insertions(+), 49 deletions(-) diff --git a/docs/api.md b/docs/api.md index 0eae4b0..cf30411 100644 --- a/docs/api.md +++ b/docs/api.md @@ -120,6 +120,43 @@ PAR/M0AR register pair: | `memory_to_peripheral` | memory buffer | peripheral register | | `memory_to_memory` | memory buffer | memory buffer | +### `dmdma_stream_options_t` (optional, `dmdma_lease_start_ex()`) + +```c +typedef struct +{ + dmdma_flow_controller_t flow_controller; /* dma (default) | peripheral */ + dmdma_fifo_threshold_t fifo_threshold; /* direct (default) | quarter | half | three_quarters | full */ + dmdma_burst_t source_burst; /* single (default) | 4 | 8 | 16 beats */ + dmdma_burst_t destination_burst; +} dmdma_stream_options_t; +``` + +A zero-initialized structure (or `NULL`) is exactly the behavior of +`dmdma_lease_start()`. Rules checked by the core before the port is touched +(`-EINVAL` otherwise): + +- bursts need FIFO mode; direct mode also needs equal source/destination widths, +- the memory-side burst (beats x memory element width) must fit the FIFO + threshold (4/8/12/16 bytes) an integral number of times, +- `dmdma_flow_controller_peripheral` is only valid for memory<->peripheral, + non-circular transfers; the hardware then ignores `element_count` and the + peripheral ends the transfer (e.g. SDIO/SDMMC). + +In FIFO mode the FIFO-error flag is not reported as `dmdma_event_error` +(the FIFO error interrupt is not enabled); in direct mode it still is. + +## Lease API (`dmdma_lease.h`) + +| Function | Description | +|----------|-------------| +| `dmdma_lease_acquire(controller, stream)` | Reserve a stream (shared pool with `/dev/dmdmaN/M`) | +| `dmdma_lease_release(lease)` | Abort, disable the IRQ, return the stream | +| `dmdma_lease_start(lease, config)` | Validate and start a transfer (direct mode, single transfers) | +| `dmdma_lease_start_ex(lease, config, options)` | Same, with `dmdma_stream_options_t` | +| `dmdma_lease_abort(lease)` / `_is_busy()` / `_get_remaining()` | Transfer control | +| `dmdma_lease_set_callback(lease, callback, user_ptr)` | Completion/error/timeout/abort callback | + ### `dmdma_event_t` ```c diff --git a/docs/port-api.md b/docs/port-api.md index b68d16d..635d631 100644 --- a/docs/port-api.md +++ b/docs/port-api.md @@ -65,6 +65,17 @@ completion/half-complete/error reporting are added automatically. Returns `element_count` is `0` or exceeds the hardware counter width) or `controller`/`stream` is out of range. +```c +int dmdma_port_stream_start_ex(dmdma_controller_t controller, dmdma_stream_t stream, + const dmdma_transfer_config_t *config, + const dmdma_stream_options_t *options); +``` +Same as `_stream_start()` plus FIFO mode/threshold, burst lengths and the +flow controller (`options` already validated by the core; `NULL` means the +`_stream_start()` defaults). On STM32F4/F7: `DMA_SxFCR.DMDIS`/`FTH`, +`DMA_SxCR.PBURST`/`MBURST` (mapped from source/destination by direction) +and `DMA_SxCR.PFCTRL`. `_stream_start()` is `_stream_start_ex(..., NULL)`. + ```c void dmdma_port_stream_stop(dmdma_controller_t controller, dmdma_stream_t stream); ``` diff --git a/include/dmdma_lease.h b/include/dmdma_lease.h index 1ee503a..ad3c0ec 100644 --- a/include/dmdma_lease.h +++ b/include/dmdma_lease.h @@ -79,6 +79,18 @@ dmod_dmdma_api(1.0, void, _lease_release, ( dmdma_lease_t lease ) ); */ dmod_dmdma_api(1.0, int, _lease_start, ( dmdma_lease_t lease, const dmdma_transfer_config_t *config ) ); +/** + * Same as dmdma_lease_start() with explicit stream options - FIFO mode and + * threshold, burst lengths and the flow controller (see + * dmdma_stream_options_t). options == NULL behaves exactly like + * dmdma_lease_start(). Invalid combinations (burst without FIFO, a memory + * burst that does not fit the FIFO threshold, peripheral flow control for a + * memory-to-memory or circular transfer, ...) are rejected with -EINVAL + * before the port is touched. + */ +dmod_dmdma_api(1.0, int, _lease_start_ex, ( dmdma_lease_t lease, const dmdma_transfer_config_t *config, + const dmdma_stream_options_t *options ) ); + /** * Abort whatever transfer is in flight; safe to call when idle. Fires the * lease's callback with dmdma_event_aborted, but only if a transfer was diff --git a/include/dmdma_port.h b/include/dmdma_port.h index f605114..08879d0 100644 --- a/include/dmdma_port.h +++ b/include/dmdma_port.h @@ -41,6 +41,11 @@ dmod_dmdma_port_api(1.0, void, _stream_release, ( dmdma_controller_t controller, dmod_dmdma_port_api(1.0, int, _stream_start, ( dmdma_controller_t controller, dmdma_stream_t stream, const dmdma_transfer_config_t *config ) ); +/* Like _stream_start(), with the stream options already validated by the + * core (options == NULL means the _stream_start() defaults). */ +dmod_dmdma_port_api(1.0, int, _stream_start_ex, ( dmdma_controller_t controller, dmdma_stream_t stream, + const dmdma_transfer_config_t *config, + const dmdma_stream_options_t *options ) ); dmod_dmdma_port_api(1.0, void, _stream_stop, ( dmdma_controller_t controller, dmdma_stream_t stream ) ); dmod_dmdma_port_api(1.0, bool, _stream_is_busy, ( dmdma_controller_t controller, dmdma_stream_t stream ) ); dmod_dmdma_port_api(1.0, size_t, _stream_get_remaining, ( dmdma_controller_t controller, dmdma_stream_t stream ) ); diff --git a/include/dmdma_types.h b/include/dmdma_types.h index 791994d..b343f2f 100644 --- a/include/dmdma_types.h +++ b/include/dmdma_types.h @@ -94,6 +94,68 @@ typedef struct * not reset it) rather than a per-element deadline. */ } dmdma_transfer_config_t; +/** + * @brief Who decides when a peripheral transfer ends + * + * With dmdma_flow_controller_peripheral the peripheral signals the last + * data item itself (e.g. SDIO/SDMMC, whose data length is programmed in the + * peripheral) and element_count is ignored by the hardware (STM32: NDTR is + * forced to 0xFFFF, RM0090/RM0385 "Flow controller"). Only valid for + * memory<->peripheral transfers and never together with circular mode. + */ +typedef enum +{ + dmdma_flow_controller_dma = 0, /**< DMA counts element_count (default) */ + dmdma_flow_controller_peripheral, /**< The peripheral ends the transfer */ +} dmdma_flow_controller_t; + +/** + * @brief Burst length (beats per request) on one side of a transfer + * + * Anything but single requires FIFO mode (fifo_threshold != direct). + */ +typedef enum +{ + dmdma_burst_single = 0, /**< One element per request (default) */ + dmdma_burst_4, /**< Incremental burst of 4 beats */ + dmdma_burst_8, /**< Incremental burst of 8 beats */ + dmdma_burst_16, /**< Incremental burst of 16 beats */ +} dmdma_burst_t; + +/** + * @brief FIFO usage of a stream + * + * dmdma_fifo_direct disables the FIFO (direct mode, source and destination + * widths must match). The other values enable the 16-byte FIFO and set the + * level that triggers a memory access: the memory-side burst (beats x + * memory element width) must fit the threshold an integral number of times + * (RM0090/RM0385 "FIFO threshold configurations"). + */ +typedef enum +{ + dmdma_fifo_direct = 0, /**< Direct mode, no FIFO (default) */ + dmdma_fifo_quarter, /**< FIFO threshold 1/4 (4 bytes) */ + dmdma_fifo_half, /**< FIFO threshold 1/2 (8 bytes) */ + dmdma_fifo_three_quarters, /**< FIFO threshold 3/4 (12 bytes) */ + dmdma_fifo_full, /**< FIFO threshold full (16 bytes) */ +} dmdma_fifo_threshold_t; + +/** + * @brief Optional stream options, see dmdma_lease_start_ex() + * + * A zero-initialized structure (or passing NULL) selects the behavior of + * dmdma_lease_start(): direct mode, single transfers, DMA flow control. + * Burst lengths are given per side of the transfer (source/destination), + * the port maps them to its peripheral/memory registers by direction. + */ +typedef struct +{ + dmdma_flow_controller_t flow_controller; /**< Who ends a peripheral transfer */ + dmdma_fifo_threshold_t fifo_threshold; /**< Direct mode or FIFO threshold */ + dmdma_burst_t source_burst; /**< Burst length at the source */ + dmdma_burst_t destination_burst; /**< Burst length at the destination */ +} dmdma_stream_options_t; + /* * Which address is "the peripheral one" depends on direction, exactly as it * does in the underlying hardware register pair (PAR/M0AR on STM32): diff --git a/src/dmdma.c b/src/dmdma.c index 9421285..f944a75 100644 --- a/src/dmdma.c +++ b/src/dmdma.c @@ -217,6 +217,63 @@ static int validate_transfer_config(dmdma_controller_t controller, const dmdma_t return 0; } +static uint32_t burst_beats(dmdma_burst_t burst) +{ + switch (burst) + { + case dmdma_burst_4: return 4U; + case dmdma_burst_8: return 8U; + case dmdma_burst_16: return 16U; + default: return 1U; + } +} + +/* FIFO bytes that trigger a memory access, 0 for direct mode. */ +static uint32_t fifo_threshold_bytes(dmdma_fifo_threshold_t threshold) +{ + return (threshold == dmdma_fifo_direct) ? 0U : 4U * (uint32_t)threshold; +} + +/* RM0090/RM0385 "FIFO threshold configurations": the memory-side burst has + * to fit the threshold level an integral number of times. */ +static int validate_memory_burst(uint32_t threshold, dmdma_burst_t burst, dmdma_data_width_t width) +{ + uint32_t bytes = burst_beats(burst) * (uint32_t)width; + if (burst == dmdma_burst_single) + { + return 0; + } + return (bytes <= threshold && (threshold % bytes) == 0U) ? 0 : -EINVAL; +} + +/* Checks dmdma_stream_options_t on top of validate_transfer_config(). */ +static int validate_stream_options(const dmdma_transfer_config_t *config, const dmdma_stream_options_t *options) +{ + if (options->flow_controller > dmdma_flow_controller_peripheral || + options->fifo_threshold > dmdma_fifo_full || + options->source_burst > dmdma_burst_16 || options->destination_burst > dmdma_burst_16) + { + return -EINVAL; + } + if (options->flow_controller == dmdma_flow_controller_peripheral && + (config->direction == dmdma_direction_memory_to_memory || config->circular)) + { + return -EINVAL; + } + uint32_t threshold = fifo_threshold_bytes(options->fifo_threshold); + if (threshold == 0U) + { + /* Direct mode: no bursts, both sides move the same element size. */ + bool bursts = options->source_burst != dmdma_burst_single || + options->destination_burst != dmdma_burst_single; + return (bursts || config->source_width != config->destination_width) ? -EINVAL : 0; + } + bool memory_is_source = (config->direction == dmdma_direction_memory_to_peripheral); + return memory_is_source + ? validate_memory_burst(threshold, options->source_burst, config->source_width) + : validate_memory_burst(threshold, options->destination_burst, config->destination_width); +} + /* [dmdma] is the fast path; anything else falls back to whichever section * comes first in the file, so e.g. [dma1]/[dma2]-named sections work too. */ static const char *detect_config_section(dmini_context_t config) @@ -764,8 +821,8 @@ dmod_dmdma_api_declaration(1.0, void, _lease_release, ( dmdma_lease_t lease )) slot->cb.lease.user_ptr = NULL; } -dmod_dmdma_api_declaration(1.0, int, _lease_start, - ( dmdma_lease_t lease, const dmdma_transfer_config_t *config )) +static int lease_start(dmdma_lease_t lease, const dmdma_transfer_config_t *config, + const dmdma_stream_options_t *options) { dmdma_stream_slot_t *slot = slot_from_lease(lease); if (slot == NULL) @@ -777,6 +834,10 @@ dmod_dmdma_api_declaration(1.0, int, _lease_start, dmdma_controller_t controller = context->config.controller; int rc = validate_transfer_config(controller, config); + if (rc == 0 && options != NULL) + { + rc = validate_stream_options(config, options); + } if (rc != 0) { DMOD_LOG_ERROR("dmdma: rejecting lease transfer config on controller %u (rc=%d)\n", (unsigned)controller, rc); @@ -784,7 +845,7 @@ dmod_dmdma_api_declaration(1.0, int, _lease_start, } slot->circular = config->circular; - rc = dmdma_port_stream_start(controller, slot->stream, config); + rc = dmdma_port_stream_start_ex(controller, slot->stream, config, options); if (rc == 0) { rc = arm_timeout(slot, config->timeout_ms); @@ -792,6 +853,18 @@ dmod_dmdma_api_declaration(1.0, int, _lease_start, return rc; } +dmod_dmdma_api_declaration(1.0, int, _lease_start, + ( dmdma_lease_t lease, const dmdma_transfer_config_t *config )) +{ + return lease_start(lease, config, NULL); +} + +dmod_dmdma_api_declaration(1.0, int, _lease_start_ex, + ( dmdma_lease_t lease, const dmdma_transfer_config_t *config, const dmdma_stream_options_t *options )) +{ + return lease_start(lease, config, options); +} + dmod_dmdma_api_declaration(1.0, void, _lease_abort, ( dmdma_lease_t lease )) { dmdma_stream_slot_t *slot = slot_from_lease(lease); diff --git a/src/port/stm32_common/stm32_common.c b/src/port/stm32_common/stm32_common.c index a4c1326..de79c68 100644 --- a/src/port/stm32_common/stm32_common.c +++ b/src/port/stm32_common/stm32_common.c @@ -144,71 +144,124 @@ dmod_dmdma_port_api_declaration(1.0, void, _stream_release, ( dmdma_controller_t /* ---- Transfer control ---- */ -dmod_dmdma_port_api_declaration(1.0, int, _stream_start, - ( dmdma_controller_t controller, dmdma_stream_t stream, const dmdma_transfer_config_t *config )) +/* Register-level view of one transfer: which side of source/destination is + * "the peripheral" depends on direction, exactly as in the PAR/M0AR pair - + * memory_to_peripheral swaps them, peripheral_to_memory and + * memory_to_memory both use PAR=source, M0AR=destination as-is. */ +typedef struct { - if (validate(controller, stream) != 0 || config == NULL) return -1; - if (config->element_count == 0U || config->element_count > 0xFFFFU) return -1; - if (config->destination_address == NULL) return -1; - if (config->direction != dmdma_direction_memory_to_memory && config->source_address == NULL) return -1; - - /* Which side of source/destination is "the peripheral" for register - * purposes depends on direction: memory_to_peripheral swaps PAR/M0AR - * relative to source/destination, peripheral_to_memory and - * memory_to_memory both use PAR=source, M0AR=destination as-is. */ - bool peripheral_is_destination = (config->direction == dmdma_direction_memory_to_peripheral); - - uint32_t peripheral_addr = (uint32_t)(uintptr_t)(peripheral_is_destination - ? config->destination_address : config->source_address); - uint32_t memory_addr = (uint32_t)(uintptr_t)(peripheral_is_destination - ? config->source_address : config->destination_address); - dmdma_data_width_t peripheral_width = peripheral_is_destination - ? config->destination_width : config->source_width; - dmdma_data_width_t memory_width = peripheral_is_destination - ? config->source_width : config->destination_width; - bool peripheral_inc = peripheral_is_destination - ? config->destination_increment : config->source_increment; - bool memory_inc = peripheral_is_destination - ? config->source_increment : config->destination_increment; - - volatile DMA_Stream_TypeDef *s = get_stream(controller, stream); - stream_disable_and_wait(s); - stream_clear_flags(controller, stream); + uint32_t peripheral_addr; + uint32_t memory_addr; + dmdma_data_width_t peripheral_width; + dmdma_data_width_t memory_width; + bool peripheral_inc; + bool memory_inc; + dmdma_burst_t peripheral_burst; + dmdma_burst_t memory_burst; +} stream_sides_t; + +static stream_sides_t map_sides(const dmdma_transfer_config_t *config, const dmdma_stream_options_t *options) +{ + bool swap = (config->direction == dmdma_direction_memory_to_peripheral); + const void *src = config->source_address; + const void *dst = config->destination_address; + dmdma_burst_t src_burst = (options != NULL) ? options->source_burst : dmdma_burst_single; + dmdma_burst_t dst_burst = (options != NULL) ? options->destination_burst : dmdma_burst_single; + + stream_sides_t sides; + sides.peripheral_addr = (uint32_t)(uintptr_t)(swap ? dst : src); + sides.memory_addr = (uint32_t)(uintptr_t)(swap ? src : dst); + sides.peripheral_width = swap ? config->destination_width : config->source_width; + sides.memory_width = swap ? config->source_width : config->destination_width; + sides.peripheral_inc = swap ? config->destination_increment : config->source_increment; + sides.memory_inc = swap ? config->source_increment : config->destination_increment; + sides.peripheral_burst = swap ? dst_burst : src_burst; + sides.memory_burst = swap ? src_burst : dst_burst; + return sides; +} - uint32_t cr = 0U; - switch (config->direction) +static uint32_t direction_bits(dmdma_direction_t direction) +{ + switch (direction) { - case dmdma_direction_peripheral_to_memory: cr |= DMA_SxCR_DIR_PERIPH_TO_MEM; break; - case dmdma_direction_memory_to_peripheral: cr |= DMA_SxCR_DIR_MEM_TO_PERIPH; break; + case dmdma_direction_peripheral_to_memory: return DMA_SxCR_DIR_PERIPH_TO_MEM; + case dmdma_direction_memory_to_peripheral: return DMA_SxCR_DIR_MEM_TO_PERIPH; case dmdma_direction_memory_to_memory: - default: cr |= DMA_SxCR_DIR_MEM_TO_MEM; break; + default: return DMA_SxCR_DIR_MEM_TO_MEM; } +} - cr |= width_to_size_field(peripheral_width) << DMA_SxCR_PSIZE_Pos; - cr |= width_to_size_field(memory_width) << DMA_SxCR_MSIZE_Pos; - if (peripheral_inc) cr |= DMA_SxCR_PINC; - if (memory_inc) cr |= DMA_SxCR_MINC; - if (config->circular) cr |= DMA_SxCR_CIRC | DMA_SxCR_HTIE; +static uint32_t build_cr(const dmdma_transfer_config_t *config, const stream_sides_t *sides, + const dmdma_stream_options_t *options) +{ + uint32_t cr = direction_bits(config->direction); + cr |= width_to_size_field(sides->peripheral_width) << DMA_SxCR_PSIZE_Pos; + cr |= width_to_size_field(sides->memory_width) << DMA_SxCR_MSIZE_Pos; + cr |= (uint32_t)sides->peripheral_burst << DMA_SxCR_PBURST_Pos; + cr |= (uint32_t)sides->memory_burst << DMA_SxCR_MBURST_Pos; + if (sides->peripheral_inc) cr |= DMA_SxCR_PINC; + if (sides->memory_inc) cr |= DMA_SxCR_MINC; + if (config->circular) cr |= DMA_SxCR_CIRC | DMA_SxCR_HTIE; cr |= ((uint32_t)config->priority & 0x3U) << DMA_SxCR_PL_Pos; cr |= DMA_SxCR_TCIE | DMA_SxCR_TEIE; /* Memory-to-memory ignores CHSEL and requires the DMA itself to be the - * flow controller - PFCTRL=0 (already the case, we never set it). */ + * flow controller - PFCTRL stays 0 (the core rejects anything else). */ if (config->direction != dmdma_direction_memory_to_memory) { cr |= ((uint32_t)config->request & 0x7U) << DMA_SxCR_CHSEL_Pos; + if (options != NULL && options->flow_controller == dmdma_flow_controller_peripheral) + { + cr |= DMA_SxCR_PFCTRL; + } } + return cr; +} - s->PAR = peripheral_addr; - s->M0AR = memory_addr; +/* Direct mode keeps the reset value; FIFO mode sets DMDIS and FTH + * (dmdma_fifo_quarter..full map to FTH 0..3). FEIE stays off - see + * stm32_dma_stream_irq(). */ +static uint32_t build_fcr(const dmdma_stream_options_t *options) +{ + if (options == NULL || options->fifo_threshold == dmdma_fifo_direct) + { + return DMA_SxFCR_RESET_VALUE; + } + uint32_t fth = (uint32_t)options->fifo_threshold - (uint32_t)dmdma_fifo_quarter; + return DMA_SxFCR_DMDIS | (fth << DMA_SxFCR_FTH_Pos); +} + +dmod_dmdma_port_api_declaration(1.0, int, _stream_start_ex, + ( dmdma_controller_t controller, dmdma_stream_t stream, + const dmdma_transfer_config_t *config, const dmdma_stream_options_t *options )) +{ + if (validate(controller, stream) != 0 || config == NULL) return -1; + if (config->element_count == 0U || config->element_count > 0xFFFFU) return -1; + if (config->destination_address == NULL) return -1; + if (config->direction != dmdma_direction_memory_to_memory && config->source_address == NULL) return -1; + + stream_sides_t sides = map_sides(config, options); + volatile DMA_Stream_TypeDef *s = get_stream(controller, stream); + stream_disable_and_wait(s); + stream_clear_flags(controller, stream); + + s->PAR = sides.peripheral_addr; + s->M0AR = sides.memory_addr; s->NDTR = (uint32_t)config->element_count; + s->FCR = build_fcr(options); nvic_enable_irq(dma_irqn[controller][stream]); - s->CR = cr | DMA_SxCR_EN; + s->CR = build_cr(config, &sides, options) | DMA_SxCR_EN; return 0; } +dmod_dmdma_port_api_declaration(1.0, int, _stream_start, + ( dmdma_controller_t controller, dmdma_stream_t stream, const dmdma_transfer_config_t *config )) +{ + return dmdma_port_stream_start_ex(controller, stream, config, NULL); +} + dmod_dmdma_port_api_declaration(1.0, void, _stream_stop, ( dmdma_controller_t controller, dmdma_stream_t stream )) { if (validate(controller, stream) != 0) return; @@ -267,7 +320,14 @@ void stm32_dma_stream_irq(dmdma_controller_t controller, dmdma_stream_t stream) uint32_t event_flags = 0U; if (sr & DMA_FLAG_TCIF(shift)) event_flags |= (uint32_t)dmdma_event_complete; if (sr & DMA_FLAG_HTIF(shift)) event_flags |= (uint32_t)dmdma_event_half_complete; - if (sr & (DMA_FLAG_TEIF(shift) | DMA_FLAG_DMEIF(shift) | DMA_FLAG_FEIF(shift))) + /* FEIF is an error in direct mode only. In FIFO mode it is merely + * informational while FEIE is off (RM0090/RM0385 "FIFO error"; ST's own + * SD driver ignores it too) - e.g. SDIO with peripheral flow control + * can raise it at the end of a transfer without any data loss. */ + uint32_t errors = DMA_FLAG_TEIF(shift) | DMA_FLAG_DMEIF(shift); + if ((get_stream(controller, stream)->FCR & DMA_SxFCR_DMDIS) == 0U) + errors |= DMA_FLAG_FEIF(shift); + if (sr & errors) event_flags |= (uint32_t)dmdma_event_error; stream_clear_flags(controller, stream); diff --git a/tests/dmdma_test_lease.c b/tests/dmdma_test_lease.c index ac452cb..22ffca0 100644 --- a/tests/dmdma_test_lease.c +++ b/tests/dmdma_test_lease.c @@ -77,8 +77,71 @@ static bool wait_until_idle(dmdma_lease_t lease) return false; } -static uint8_t s_src[DMDMA_TEST_BYTES]; -static uint8_t s_dst[DMDMA_TEST_BYTES]; +static uint32_t s_src_words[DMDMA_TEST_BYTES / 4U]; +static uint32_t s_dst_words[DMDMA_TEST_BYTES / 4U]; +#define s_src ((uint8_t *)s_src_words) /* word aligned for the FIFO/burst run */ +#define s_dst ((uint8_t *)s_dst_words) + +/* Invalid stream option combinations must be rejected before touching the port. */ +static bool check_option_rejections(dmdma_lease_t lease, const dmdma_transfer_config_t *words) +{ + dmdma_stream_options_t opt = { dmdma_flow_controller_dma, dmdma_fifo_direct, dmdma_burst_4, dmdma_burst_4 }; + if (dmdma_lease_start_ex(lease, words, &opt) == 0) + { + Dmod_Printf("FAIL: burst in direct mode was accepted\n"); + return false; + } + opt.fifo_threshold = dmdma_fifo_half; /* 4 words x 4 bytes = 16 > 8 */ + if (dmdma_lease_start_ex(lease, words, &opt) == 0) + { + Dmod_Printf("FAIL: memory burst larger than the FIFO threshold was accepted\n"); + return false; + } + opt.fifo_threshold = dmdma_fifo_full; + opt.flow_controller = dmdma_flow_controller_peripheral; + if (dmdma_lease_start_ex(lease, words, &opt) == 0) + { + Dmod_Printf("FAIL: peripheral flow control for memory-to-memory was accepted\n"); + return false; + } + Dmod_Printf("PASS: invalid stream options correctly rejected\n"); + return true; +} + +/* FIFO mode, full threshold, 4-beat word bursts on both sides (the setup + * SDIO/SDMMC needs, minus the peripheral flow control mem-to-mem can't use). */ +static bool check_fifo_burst_transfer(dmdma_lease_t lease, const dmdma_transfer_config_t *bytes) +{ + dmdma_transfer_config_t words = *bytes; + words.source_width = dmdma_data_width_word; + words.destination_width = dmdma_data_width_word; + words.element_count = DMDMA_TEST_BYTES / 4U; + for (uint32_t i = 0; i < DMDMA_TEST_BYTES / 4U; i++) + { + s_src_words[i] = (i * 2654435761U) ^ 0xA5A5A5A5U; + s_dst_words[i] = 0; + } + if (!check_option_rejections(lease, &words)) + { + return false; + } + dmdma_stream_options_t opt = { dmdma_flow_controller_dma, dmdma_fifo_full, dmdma_burst_4, dmdma_burst_4 }; + if (dmdma_lease_start_ex(lease, &words, &opt) != 0 || !wait_until_idle(lease)) + { + Dmod_Printf("FAIL: FIFO/burst transfer did not start or complete\n"); + return false; + } + for (uint32_t i = 0; i < DMDMA_TEST_BYTES / 4U; i++) + { + if (s_src_words[i] != s_dst_words[i]) + { + Dmod_Printf("FAIL: FIFO/burst mismatch at word %u\n", i); + return false; + } + } + Dmod_Printf("PASS: FIFO mode with 4-beat bursts copied %u byte(s) correctly\n", DMDMA_TEST_BYTES); + return true; +} int main(int argc, char *argv[]) { @@ -229,6 +292,13 @@ int main(int argc, char *argv[]) } Dmod_Printf("PASS: malformed transfer configs correctly rejected\n"); + /* --- 5b. Stream options: FIFO mode and bursts (dmdma_lease_start_ex) --- */ + + if (!check_fifo_burst_transfer(lease, &transfer)) + { + goto cleanup_lease; + } + /* --- 6. Abort while a transfer is genuinely in flight notifies the owner --- */ /* * Circular transfers never self-clear their enable bit - is_busy()