Skip to content

fix(adc): migrate dmaInit() to ownership-checked dmaAllocate() - #1389

Merged
nerdCopter merged 2 commits into
emuflight:masterfrom
nerdCopter:fix/dmainit-adc-migration
Aug 17, 2026
Merged

fix(adc): migrate dmaInit() to ownership-checked dmaAllocate()#1389
nerdCopter merged 2 commits into
emuflight:masterfrom
nerdCopter:fix/dmainit-adc-migration

Conversation

@nerdCopter

@nerdCopter nerdCopter commented Aug 15, 2026

Copy link
Copy Markdown
Member

AI Generated [pull-request]

Summary

adcInit() (F4/F7/H7) claims its DMA stream via unchecked dmaInit(), which
unconditionally overwrites dmaDescriptors[index].owner regardless of the
current owner. bus_spi.c and the UART drivers already use the
ownership-checked dmaAllocate() for this. This PR converts all 3 ADC files
to the same pattern, matching Betaflight 4.5-maintenance's adc_stm32*xx.c
exactly: if (!dmaAllocate(...)) { return; } followed by dmaEnable(...).

Stage 1 of 5 for #1363 (fleet-wide dmaInit() -> dmaAllocate() migration).
Remaining stages (transponder, LED strip, motors/DSHOT, SD card) tracked as
separate follow-up PRs against the same issue.

Why this is safe

  • adcInit()'s DMA claim is boot-time, single call site, always executed
    before spiInitBusDMA() in fc_init.c — no other code re-invokes it at
    runtime, so dmaAllocate() cannot structurally fail on any current target.
    This mirrors the same collision-free argument already established for the
    other 12 remaining dmaInit() sites in Fleet-wide dmaInit() -> dmaAllocate() migration for the 13 remaining call sites (finish BF parity) #1363's investigation.
  • adcInit() is void in both EF and BF, so the early-return-on-failure
    pattern needed zero signature or caller changes anywhere.
  • dmaEnable() is required alongside dmaAllocate()dmaAllocate() only
    sets ownership bookkeeping, it never enables the DMA controller's
    peripheral clock (found the hard way in PR fix: check DMA stream ownership before UART reopen claims it #1356, same fix applied here
    from the start).

Verification

  • Build: HELIOSPRING (F4), FOXEERF722V4 (F7), STELLARH7DEV (H7) — all real
    aircraft targets — plus TUNERCF405, SKYSTARSF405AIO, PYRODRONEF7,
    FOXEERF405, APEXF7, TMOTORF7, SITL. 10/10 succeeded, no warnings.
  • Host unit tests: 47/47 test binaries pass, no change from baseline.
  • Hardware flight-verification: complete. Live dma CLI output on all 3
    real aircraft matches each target's own static DMA-stream macro exactly
    (HELIOSPRING DMA2 Stream 4, FOXEERF722V4 DMA2 Stream 0 on ADC3,
    STELLARH7DEV DMA2 Stream 1) — no regression, since the migration only
    changes whether the claim is checked, never which stream gets picked.
    FOXEERF722V4 also showed a correct 22.9V battery reading, confirming the
    DMA transfer itself is functioning end-to-end, not just the ownership
    claim. Clean boot, no fault, on all 3 boards.

Related

Summary by CodeRabbit

  • Bug Fixes
    • Improved ADC initialization across STM32F4, STM32F7, and STM32H7 platforms.
    • Added safer DMA resource allocation and handling when resources are unavailable.
    • Ensured allocated DMA resources are properly enabled during ADC setup.

adcInit() unconditionally overwrote DMA stream ownership via dmaInit(),
unlike bus_spi.c and the UART drivers, which already use dmaAllocate()'s
ownership check. Matches BF 4.5-maintenance's adc_stm32*xx.c, which
performs the identical dmaAllocate()+dmaEnable() early-return sequence.

Part of emuflight#1363 (stage 1 of 5).
@coderabbitai

coderabbitai Bot commented Aug 15, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c68b02d1-5191-4d36-a15a-565790f4502e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 49f5f54c-ead5-403a-856e-7f4b61d0013b

📥 Commits

Reviewing files that changed from the base of the PR and between 1bab1c3 and 39676e2.

📒 Files selected for processing (3)
  • src/main/drivers/adc_stm32f4xx.c
  • src/main/drivers/adc_stm32f7xx.c
  • src/main/drivers/adc_stm32h7xx.c
📜 Recent review details
⏰ Context from checks skipped due to timeout. (6)
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: build (targets-group-10)
  • GitHub Check: build (targets-group-9)
  • GitHub Check: build (targets-group-2)
  • GitHub Check: build (targets-group-1)
  • GitHub Check: build (targets-group-3)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-05-15T14:49:12.754Z
Learnt from: nerdCopter
Repo: emuflight/EmuFlight PR: 1189
File: src/main/drivers/bus_spi.c:631-631
Timestamp: 2026-05-15T14:49:12.754Z
Learning: In the EmuFlight repo (emuflight/EmuFlight), the STM32F303 (F303) targets currently do not compile on the master branch due to a pre-existing hardware RAM budget overflow (unrelated to any specific PR). When reviewing driver changes in shared driver files (e.g., src/main/drivers/bus_spi.c), avoid adding or fixing F303-specific driver behavior as part of the current PR; instead, defer those F303-targeted fixes to a dedicated F3-targeted PR and only revisit once the RAM overflow/build issue is resolved.

Applied to files:

  • src/main/drivers/adc_stm32h7xx.c
  • src/main/drivers/adc_stm32f7xx.c
  • src/main/drivers/adc_stm32f4xx.c
🔇 Additional comments (3)
src/main/drivers/adc_stm32f4xx.c (1)

248-251: LGTM!

src/main/drivers/adc_stm32f7xx.c (1)

266-269: LGTM!

src/main/drivers/adc_stm32h7xx.c (1)

317-320: LGTM!


📝 Walkthrough

Walkthrough

ADC DMA initialization for STM32F4, STM32F7, and STM32H7 now uses dmaAllocate and dmaEnable. Each driver stops initialization when DMA allocation fails.

Changes

ADC DMA ownership

Layer / File(s) Summary
Allocate and enable ADC DMA
src/main/drivers/adc_stm32f4xx.c, src/main/drivers/adc_stm32f7xx.c, src/main/drivers/adc_stm32h7xx.c
The drivers replace dmaInit with dmaAllocate and dmaEnable. ADC initialization returns when DMA allocation fails.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 39676

The ADC initialization paths now use ownership-checked DMA allocation while preserving DMA enablement and stream selection; no actionable merge-blocking risk remains after normal checks and review.

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the primary ADC DMA migration from dmaInit() to ownership-checked dmaAllocate().
Description check ✅ Passed The description explains the change, rationale, scope, related issue, implementation pattern, and verification results in sufficient detail.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@nerdCopter
nerdCopter marked this pull request as ready for review August 15, 2026 22:19
@nerdCopter

Copy link
Copy Markdown
Member Author

@coderabbitai review

Small, focused diff: 3 files (adc_stm32f4xx.c, adc_stm32f7xx.c, adc_stm32h7xx.c), 5 lines
each, converting unchecked dmaInit() to ownership-checked dmaAllocate()+dmaEnable() in
adcInit(). Please analyze for:

  • Correctness of the dmaAllocate()/dmaEnable() sequencing vs. bus_spi.c's established
    usage of the same primitives.
  • Any early-return-on-failure interaction with surrounding code in each of the 3 files,
    especially adc_stm32h7xx.c, which has multiple existing early-return paths in adcInit().
  • Whether the void return type (no signature change, matching both EF's and BF's own
    adcInit()) is handled correctly at the call site.

@coderabbitai

coderabbitai Bot commented Aug 15, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== PR diff ==='
git diff --unified=80 HEAD^ HEAD -- \
  src/main/drivers/adc_stm32f4xx.c \
  src/main/drivers/adc_stm32f7xx.c \
  src/main/drivers/adc_stm32h7xx.c

echo '=== Relevant file map ==='
fd -a '^(adc_stm32(f4|f7|h7)xx|bus_spi|dma_stm32.*)\.c$|^adc\.h$|^dma\.h$' src/main 2>/dev/null || true

echo '=== Symbol outlines ==='
ast-grep outline src/main/drivers/adc_stm32f4xx.c --items all --match 'adcInit|dmaAllocate|dmaEnable' 2>/dev/null || true
ast-grep outline src/main/drivers/adc_stm32f7xx.c --items all --match 'adcInit|dmaAllocate|dmaEnable' 2>/dev/null || true
ast-grep outline src/main/drivers/adc_stm32h7xx.c --items all --match 'adcInit|dmaAllocate|dmaEnable' 2>/dev/null || true
ast-grep outline src/main/drivers/bus_spi.c --items all --match 'spiInitBusDMA|dmaAllocate|dmaEnable' 2>/dev/null || true

echo '=== ADC implementations and SPI DMA paths ==='
rg -n -C 35 --type c \
  '^\s*(void|bool)\s+adcInit\s*\(|\bdmaAllocate\s*\(|\bdmaEnable\s*\(|\bdmaInit\s*\(' \
  src/main/drivers/adc_stm32f4xx.c \
  src/main/drivers/adc_stm32f7xx.c \
  src/main/drivers/adc_stm32h7xx.c \
  src/main/drivers/bus_spi.c

echo '=== DMA API declarations and implementations ==='
rg -n -C 12 --type c --type h \
  '^\s*(bool|void)\s+dma(Allocate|Enable|Init)\s*\(|\bdma(Allocate|Enable|Init)\s*\(' \
  src/main

echo '=== adcInit declarations and call sites ==='
rg -n -C 8 --glob '*.[ch]' \
  '^\s*(extern\s+)?void\s+adcInit\s*\(|\badcInit\s*\(' \
  src/main

Length of output: 134694


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== H7 ADC helper initialization ==='
sed -n '80,190p' src/main/drivers/adc_stm32h7xx.c
sed -n '235,355p' src/main/drivers/adc_stm32h7xx.c

echo '=== Flight-controller initialization order ==='
sed -n '380,455p' src/main/fc/fc_init.c
sed -n '610,680p' src/main/fc/fc_init.c
rg -n -C 10 --glob '*.[ch]' \
  '\bspiInitBusDMA\s*\(|USE_SPI_DMA_ENABLE_LATE|`#define`\s+USE_ADC_INTERNAL' \
  src/main

echo '=== H7 target configuration and ADC DMA resources ==='
rg -n -C 5 --glob '*.[ch]' \
  '`#define`\s+USE_ADC_INTERNAL|ADC_INSTANCE|dmaResource|DMA.*ADC|ADC.*DMA' \
  src/main/target src/main/drivers/adc_stm32h7xx.c

echo '=== ADC internal API consumers ==='
rg -n -C 5 --glob '*.[ch]' \
  '\badcInternal(IsBusy|StartConversion|ReadVrefint|ReadTempsensor)\s*\(' \
  src/main

Length of output: 173674


@nerdCopter I found one issue.

P2 — Preserve H7 internal ADC initialization when regular ADC DMA allocation fails

In src/main/drivers/adc_stm32h7xx.c, the new return after dmaAllocate() failure exits adcInit() before adcInitInternalInjected().

USE_ADC_INTERNAL is enabled for H7 targets. adcInternalProcess() uses this initialization for VREFINT and temperature samples. Before this change, dmaInit() could not fail on ownership conflict, so execution always reached adcInitInternalInjected() after regular ADC setup.

If the regular ADC DMA stream is already owned, the code now disables both regular ADC sampling and internal ADC initialization. The F4 and F7 paths do not have this behavior because they initialize internal ADC support before their DMA allocation checks.

Keep the failed allocation local to regular ADC DMA setup. Then continue to the existing adcInitInternalInjected() call. For example, wrap the regular DMA configuration and HAL_ADC_Start_DMA() in the successful-allocation branch instead of returning from adcInit().

The remaining requested checks are correct:

  • dmaAllocate() before dmaEnable() matches bus_spi.c.
  • dmaAllocate() rejects invalid identifiers and already-owned DMA resources. This also covers the existing DMA_NONE safety requirement.
  • dmaEnable() only enables the DMA controller clock. It does not start the stream. Its position before HAL or SPL stream setup is correct.
  • The F4 and F7 early returns stop only the regular ADC DMA path. Their internal ADC initialization already completed.
  • adcInit() remains void in src/main/drivers/adc.h. src/main/fc/fc_init.c calls it without a return value. This matches the existing early-return design and needs no call-site or signature change.

🧠 Learnings used
Learnt from: nerdCopter
Repo: emuflight/EmuFlight PR: 0
File: :0-0
Timestamp: 2026-04-29T23:22:24.191Z
Learning: In EmuFlight dma_stm32f4xx.c and dma_stm32f7xx.c (PR `#1130`, Stage M.3.d onward), dmaAllocate() and dmaEnable() must guard against `identifier == DMA_NONE` (which maps to index −1) before indexing into the dmaDescriptors array. Without this guard, DMA_NONE passed from spiInitBusDMA for buses without an available DMA channel causes an out-of-bounds array access. The fix applies identically to both the F4 and F7 implementations.

Learnt from: nerdCopter
Repo: emuflight/EmuFlight PR: 0
File: :0-0
Timestamp: 2026-08-04T12:13:37.036Z
Learning: For PR `#1348` in EmuFlight, `USE_TX_IRQ_HANDLER` is defined unconditionally when `STM32F4` or `STM32F7` is defined. Therefore, `spiInitBusDMA()` uses the TX-only DMA path when TX allocation succeeds and RX allocation fails on the F4/F7 targets enabled by the PR. The fallback that clears `bus->dmaTx` and `bus->dmaRx` without releasing TX ownership is unreachable for this PR’s enabled configurations.
⚠️ Action not completed

Already reviewed.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@nerdCopter
nerdCopter marked this pull request as draft August 15, 2026 23:08
adcInit()'s dmaAllocate() failure returned from the whole function,
skipping the later adcInitInternalInjected() call (VREFINT/temp sensor).
F4/F7 don't have this coupling since they init internal ADC before their
DMA claim; H7's own ordering has it after, so a regular-ADC DMA conflict
would have also disabled internal ADC monitoring. Scoped the DMA setup
into the successful-allocation branch instead of returning early, so a
claim failure only drops regular ADC sampling, matching F4/F7.

Found by CodeRabbit (github.com/emuflight/pull/1389).
@nerdCopter

Copy link
Copy Markdown
Member Author

@coderabbitai, AI generated comment

Fixed in 27305cf, addressing #1389 (comment) (P2 — H7 internal ADC init coupling).

dmaAllocate() failure for the regular ADC DMA stream now falls through
into the successful-allocation branch's scope only, instead of returning
from adcInit() entirely — adcInitInternalInjected() (VREFINT/temp
sensor) still runs on a regular-ADC DMA conflict, matching F4/F7's
decoupled ordering. Pre-existing HAL_DMA_Init/HAL_ADC_Start_DMA
failure returns were left untouched (out of this PR's scope, unaffected
by the restructure since a bare return; still exits the function from
the new nesting level).

Please verify.

@coderabbitai

coderabbitai Bot commented Aug 15, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== Commit ==='
git show --no-ext-diff --format=fuller --stat 27305cfcf

echo '=== Commit diff ==='
git show --no-ext-diff --format= --unified=50 27305cfcf -- src/main/drivers/adc_stm32h7xx.c

echo '=== adcInit outline ==='
ast-grep outline src/main/drivers/adc_stm32h7xx.c --items all --match 'adcInit|adcInitInternalInjected'

echo '=== adcInit implementation and internal-init call ==='
rg -n -C 70 --type c \
  '^\s*void\s+adcInit\s*\(|\bdmaAllocate\s*\(|\bHAL_DMA_Init\s*\(|\bHAL_ADC_Start_DMA\s*\(|\badcInitInternalInjected\s*\(' \
  src/main/drivers/adc_stm32h7xx.c

Length of output: 19850


@nerdCopter Verified.

In src/main/drivers/adc_stm32h7xx.c, dmaAllocate() now scopes only the regular ADC DMA setup. If allocation fails, execution reaches adcInitInternalInjected().

The existing HAL_DMA_Init() and HAL_ADC_Start_DMA() failure paths still return from adcInit(). The change does not alter their behavior.

The P2 finding is addressed.


🧠 Learnings used
Learnt from: nerdCopter
Repo: emuflight/EmuFlight PR: 0
File: :0-0
Timestamp: 2026-04-29T23:22:24.191Z
Learning: In EmuFlight dma_stm32f4xx.c and dma_stm32f7xx.c (PR `#1130`, Stage M.3.d onward), dmaAllocate() and dmaEnable() must guard against `identifier == DMA_NONE` (which maps to index −1) before indexing into the dmaDescriptors array. Without this guard, DMA_NONE passed from spiInitBusDMA for buses without an available DMA channel causes an out-of-bounds array access. The fix applies identically to both the F4 and F7 implementations.

If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

You are interacting with an AI system.

@nerdCopter

Copy link
Copy Markdown
Member Author

@coderabbitai, AI generated comment

Requesting a fresh full-diff analyze on the current HEAD (both commits: the
original ADC migration 39676e255 and the H7 scoping fix 27305cfcf).
Specifically want a second look at:

  1. Whether adc_stm32h7xx.c's restructured dmaAllocate() block (commit
    27305cfcf) correctly preserves the pre-existing HAL_DMA_Init/
    HAL_ADC_Start_DMA early-return behavior unchanged, while only
    decoupling the new dmaAllocate() failure path from
    adcInitInternalInjected().
  2. Whether adc_stm32f4xx.c/adc_stm32f7xx.c's unchanged return; on
    dmaAllocate() failure is safe given their adcInitInternalInjected()
    call happens earlier in the function, before the DMA claim (verified
    directly: F4 line 232 vs claim at 248; F7 lines 247/249 vs claim at
    266) — so no equivalent coupling exists there.
  3. Any other spot in the 3-file diff with a similar ordering-dependency
    risk not yet caught.

@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== Current HEAD and requested commits ==='
git status --short
git log --oneline -8
git merge-base --is-ancestor 39676e255 HEAD && echo '39676e255 is an ancestor of HEAD'
git merge-base --is-ancestor 27305cfcf HEAD && echo '27305cfcf is an ancestor of HEAD'
BASE="$(git rev-parse 39676e255^)"
echo "BASE=$BASE"
echo

echo '=== Full requested diff ==='
git diff --no-ext-diff --unified=80 "$BASE"..HEAD -- \
  src/main/drivers/adc_stm32f4xx.c \
  src/main/drivers/adc_stm32f7xx.c \
  src/main/drivers/adc_stm32h7xx.c

echo '=== Changed lines, including the H7 follow-up ==='
git show --no-ext-diff --format='commit %H%n%s' --unified=50 39676e255 -- \
  src/main/drivers/adc_stm32f4xx.c \
  src/main/drivers/adc_stm32f7xx.c \
  src/main/drivers/adc_stm32h7xx.c
git show --no-ext-diff --format='commit %H%n%s' --unified=80 27305cfcf -- \
  src/main/drivers/adc_stm32h7xx.c

echo '=== ADC implementation outlines ==='
ast-grep outline src/main/drivers/adc_stm32f4xx.c --items all --match 'adcInit|adcInitInternalInjected'
ast-grep outline src/main/drivers/adc_stm32f7xx.c --items all --match 'adcInit|adcInitInternalInjected'
ast-grep outline src/main/drivers/adc_stm32h7xx.c --items all --match 'adcInit|adcInitInternalInjected'

echo '=== Current ADC initialization control flow ==='
rg -n -C 85 --type c \
  '^\s*void\s+adcInit\s*\(|\badcInitInternalInjected\s*\(|\bdmaAllocate\s*\(|\bdmaEnable\s*\(|\bdmaInit\s*\(|\bHAL_DMA_Init\s*\(|\bHAL_ADC_Start_DMA\s*\(|\breturn\s*;' \
  src/main/drivers/adc_stm32f4xx.c \
  src/main/drivers/adc_stm32f7xx.c \
  src/main/drivers/adc_stm32h7xx.c

echo '=== SPI reference sequence and DMA API contracts ==='
fd -a '^(bus_spi|dma_stm32.*)\.(c|h)$' src/main | sort
rg -n -C 28 --glob '*.[ch]' \
  '\bdmaAllocate\s*\(|\bdmaEnable\s*\(|\bdmaInit\s*\(|^\s*(bool|void)\s+dma(Allocate|Enable|Init)\s*\(' \
  src/main/drivers/bus_spi.c src/main/drivers

echo '=== ADC declaration and call sites ==='
rg -n -C 12 --glob '*.[ch]' \
  '^\s*(extern\s+)?void\s+adcInit\s*\(|\badcInit\s*\(' \
  src/main

Length of output: 243070


@nerdCopter Fresh full-diff review complete. I found no new findings.

  1. H7 control flow is correct.
    In src/main/drivers/adc_stm32h7xx.c, a failed dmaAllocate() now skips only regular ADC DMA setup. Execution then reaches adcInitInternalInjected().

    The existing return; statements after HAL_DMA_Init() and HAL_ADC_Start_DMA() remain function-level returns. The new nesting does not change their behavior. They still skip adcInitInternalInjected(), as they did before the migration.

    The pre-existing HAL_ADC_ConfigChannel() failure return also remains unchanged. It still exits before internal ADC initialization.

  2. F4 and F7 allocation returns are safe for internal ADC initialization.
    src/main/drivers/adc_stm32f4xx.c calls adcInitInternalInjected() at Line 232, before the DMA allocation check at Line 248.
    src/main/drivers/adc_stm32f7xx.c calls adcInitInternalInjected() at Lines 247 and 249, before the DMA allocation check at Line 266.

    Therefore, a regular ADC DMA ownership conflict disables only the regular ADC DMA path. It does not disable VREFINT or temperature-sensor initialization.

  3. DMA sequencing is consistent.
    Each target now uses successful dmaAllocate() followed by dmaEnable(). This matches src/main/drivers/bus_spi.c. The DMA implementations reject invalid identifiers, including DMA_NONE, and resources that already have an owner.

  4. No additional ordering dependency found.
    I found no other path in the three-file diff where the new DMA allocation failure changes the ordering of internal ADC initialization or changes a pre-existing HAL failure path.

The void adcInit() interface and its call site in src/main/fc/fc_init.c remain correct.


🧠 Learnings used
Learnt from: nerdCopter
Repo: emuflight/EmuFlight PR: 0
File: :0-0
Timestamp: 2026-04-29T23:22:24.191Z
Learning: In EmuFlight dma_stm32f4xx.c and dma_stm32f7xx.c (PR `#1130`, Stage M.3.d onward), dmaAllocate() and dmaEnable() must guard against `identifier == DMA_NONE` (which maps to index −1) before indexing into the dmaDescriptors array. Without this guard, DMA_NONE passed from spiInitBusDMA for buses without an available DMA channel causes an out-of-bounds array access. The fix applies identically to both the F4 and F7 implementations.

You are interacting with an AI system.

@nerdCopter
nerdCopter marked this pull request as ready for review August 17, 2026 16:47
@nerdCopter
nerdCopter merged commit 1ec15ae into emuflight:master Aug 17, 2026
18 checks passed
nerdCopter added a commit that referenced this pull request Aug 17, 2026
#1393)

* fix(transponder): migrate dmaInit() to ownership-checked dmaAllocate()

transponderIrHardwareInit() (stdperiph and HAL variants) claimed its
DMA stream via unchecked dmaInit(), which unconditionally overwrites
dmaDescriptors[index].owner even if another peripheral already holds
the stream. Converted to dmaAllocate() + dmaEnable(), matching BF
4.5-maintenance's transponder_ir_io_stdperiph.c/transponder_ir_io_hal.c
exactly.

transponderIrHardwareInit() is void in both EF and BF, so this is a
one-line-shape swap with no signature or caller changes, same pattern
as PR #1389 (ADC, stage 1 of IT#1363's 5-stage migration). Verified no
coupling to any later logic in the function: unlike the H7 ADC case
fixed in PR #1389, the DMA setup here isn't split from a separate
subsystem call — everything after the claim is the same timer/DMA
config, so the early return on allocation failure carries no ordering
risk.

Stage 2 of 5 (ADC done, transponder this PR, then LED strip,
motors/DSHOT, SD card).

* fix(transponder): assign dmaRef before dmaAllocate() check in stdperiph

transponderIrHardwareInit() (STM32F4 stdperiph variant) assigned the
file-scope static dmaRef after the new dmaAllocate() early-return, so
a claim failure left dmaRef at its NULL initializer. A later
transponderIrTransmit()/transponderIrDMAEnable() call would then pass
that NULL dmaRef into DMA_SetCurrDataCounter()/DMA_Cmd(), an invalid
DMA register write.

This failure path could not occur before dmaInit() was replaced with
dmaAllocate(), since dmaInit() never failed. BF 4.5-maintenance avoids
this by assigning dmaRef before its own dmaAllocate() check; moved the
assignment to match. transponderIrInit() still returns true regardless
of hardware-init success (matches BF's identical gap in
transponder_ir_io_stdperiph.c/transponder_ir_io_hal.c) — out of this
PR's scope, since it isn't specific to this migration.

* fix(transponder): propagate init success/failure from stdperiph driver

transponderIrInit() (STM32F4 stdperiph) always returned true regardless
of transponderIrHardwareInit()'s outcome. Before this migration, that
was harmless: dmaInit() never failed, so hardware init always completed.
Since dmaAllocate() can fail, a claim conflict now leaves the transponder
half-configured while transponderIrInit() still reports success -- the
io/transponder_ir.c call chain (transponderInit() -> transponderUpdate()/
transponderTransmitOnce()) gates on that return value, so a false success
lets transponderIrTransmit() run DMA_SetCurrDataCounter()/DMA_Cmd() on a
stream OWNER_TRANSPONDER never actually claimed.

Added a transponderInitialised flag matching the pattern already used in
transponder_ir_io_hal.c (this codebase's own HAL variant, ported from BF):
reset false at the start of transponderIrHardwareInit(), set true only
after every step succeeds, returned from transponderIrInit(), and checked
in transponderIrDMAEnable()/transponderIrDisable() as defense in depth.
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.

1 participant