Skip to content

Add centrally arbitrated DMA leases to dmdma - #2

Merged
JohnAmadis merged 2 commits into
mainfrom
feature/dma-leases
Sep 27, 2026
Merged

JohnAmadis merged 2 commits into
mainfrom
feature/dma-leases

Conversation

@JaxCalder

@JaxCalder JaxCalder commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Closes choco-technologies/dmod-ecosystem#10.

Summary

  • New dmdma_lease.h: dmdma_lease_acquire/_release/_start/_abort/_is_busy/_get_remaining/_set_callback, exposed as a plain Module API (dmod's README, "Module API" section) - dmdma is the one and only implementation of it, so consumers just call these functions directly (e.g. dmdma_lease_acquire(controller, stream)), patched by the loader against dmdma's own registration exactly the way dmdma_port_stream_start() already works. No runtime lookup ceremony, no Dmod_GetModuleContext()/Dmod_GetDifFunction() - that's DIF, which is for a 1:N "multiple modules implement the same interface, discovered at runtime" relationship (e.g. dmramfs/dmffs both implementing dmfsi), which doesn't apply here. (An earlier version of this branch used DIF for this and needed a hand-written registration workaround to make it work at all - that was the wrong tool for a 1:1 relationship; it's been replaced with the plain Module API pattern, which needs no such workaround.)
  • Lease reservations and /dev/dmdmaN/M device-node opens share the exact same stream-slot bookkeeping (dmdma_slot_owner_t tag) - the same stream can never be acquired twice through any API combination, and a slot opened one way can't be driven through the other API.
  • New dmdma_transfer_config_t.timeout_ms: arms a real dmosi_timer watchdog that auto-aborts the stream and fires a new dmdma_event_timeout if the transfer doesn't finish in time - shared by both the lease and the existing device-node ioctl path.
  • New dmdma_event_aborted: fires (synchronously, only if a transfer was genuinely in flight) when a caller explicitly aborts, on both paths.
  • Transfer validation (direction/request-line consistency, address alignment, non-zero length) is now centralized in validate_transfer_config() and shared by both start paths, instead of only partially checked in the ioctl path.

Real-hardware verification (STM32F746G-DISCO, DMA2 stream 0)

Built a full dmod-boot firmware image against this branch and ran a new on-target smoke test (tests/dmdma_test_lease.c) through the actual shell console - not just host-side unit tests. All of the following passed on real silicon:

  • Acquire, and double-acquire on the same stream correctly rejected
  • Aborting an idle stream fires no callback
  • A real 16 KiB memory-to-memory transfer completes, data verifies byte-for-byte, and the callback fires with the correct event and user_ptr
  • Malformed configs (zero length, request line on a mem-to-mem transfer) rejected before touching hardware
  • Abort while a transfer is genuinely in flight stops it and notifies the owner
  • The dmosi_timer watchdog fires dmdma_event_timeout and auto-stops the stream
  • Release returns the stream to the free pool cleanly (re-acquire succeeds)

One real bug only surfaced through this hardware run (fixed in this branch, not visible from compilation alone): the watchdog was being disarmed on every dmdma_event_complete, but that flag fires at every wrap of a circular transfer, not just a true final completion - so a circular transfer's own watchdog was being cancelled after its first lap. Fixed by tracking whether the active transfer is circular and only treating complete as terminal when it isn't.

Test plan

  • Host cross-compile clean for both stm32f4 and stm32f7 CPU families (arm-none-eabi-gcc)
  • Existing dmdma_test_dev/dmdma_test_port on-target smoke tests still compile against the updated dmdma_transfer_config_t (added explicit timeout_ms = 0)
  • New dmdma_test_lease on-target smoke test passes end-to-end on real STM32F746G-DISCO hardware (see above), re-verified after switching from DIF to the plain Module API pattern

JaxCalder and others added 2 commits September 27, 2026 09:54
Lets peripheral drivers (dmuart, dmspi, dmsdio, ...) reserve and drive a
DMA stream directly by calling dmdma as a DIF (Dmod_GetModuleContext +
Dmod_GetDifFunction), instead of going through a /dev/dmdmaN/M device node -
acquire/release, configure+start/abort, status, and callback registration.

Both reservation paths (dmdrvi_open() and dmdma_lease_acquire()) share the
same stream-slot bookkeeping, so a stream open as a device node can't also
be leased and vice versa, and the same stream can never be acquired twice
through any API combination.

Also adds a real per-transfer watchdog: dmdma_transfer_config_t.timeout_ms
arms a dmosi_timer that auto-aborts the stream and fires dmdma_event_timeout
if it elapses, shared by both the lease and device-node ioctl paths. A new
dmdma_event_aborted fires (synchronously, only if something was actually in
flight) when a caller explicitly aborts. Transfer validation (direction vs.
request-line consistency, address alignment, non-zero length) is now shared
between both start paths via validate_transfer_config().

Verified on real STM32F746G-DISCO hardware (DMA2 stream 0): acquire, double-
acquire rejection, a real mem-to-mem transfer with data verification and
correct callback/user_ptr delivery, malformed-config rejection, abort while
busy, the dmosi_timer watchdog firing dmdma_event_timeout, and clean
release+re-acquire. Two real bugs only surfaced this way and are fixed here:
dmod_dmdma_dif_api_declaration() never emits a runtime-visible registration
when the implementing module is also the DIF-defining module (dmdma
implementing its own "dmdma" DIF family) - worked around with an explicit
registration macro - and the watchdog was being disarmed on every circular
wrap instead of only on a genuinely terminal event, since dmdma_event_complete
fires on every wrap for a circular transfer, not just at the end.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
dmdma is the one and only implementation of its lease functions - DIF is
for a 1:N "multiple modules implement the same interface, discovered at
runtime" relationship (e.g. dmramfs/dmffs both implementing dmfsi), which
doesn't apply here. The previous DIF-based design needed a hand-written
registration workaround because dmod_dmdma_dif_api_declaration() only
registers when the implementing module differs from the DIF-defining one -
a symptom of forcing the wrong tool onto a 1:1 relationship dmod's own
"Module API" pattern already covers directly, with no such caveat.

Consumers now just call dmdma_lease_acquire()/etc. directly - no
Dmod_GetModuleContext()/Dmod_GetDifFunction() lookup - patched by the
loader against dmdma's own registration exactly like dmdma_port_stream_*()
already is. Whether a board needs DMA at all becomes a build-time choice
(is dmdma bundled into that firmware image), not a per-call runtime check.

Re-verified end to end on real STM32F746G-DISCO hardware after the switch;
all dmdma_test_lease checks still pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@JohnAmadis
JohnAmadis merged commit b2167df into main Sep 27, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[dmsdio 5/9] Add centrally arbitrated DMA leases to dmdma

2 participants