Sitelet https://github.com/betaflight/betaflight/pull/15441
Skip to content

STM32: align MPU regions to actual size - #15441

Merged
blckmn merged 2 commits into
betaflight:masterfrom
morrob955:fix/h743-sdcard-mpu-region-size
Jul 21, 2026
Merged

blckmn merged 2 commits into
betaflight:masterfrom
morrob955:fix/h743-sdcard-mpu-region-size

Conversation

@morrob955

@morrob955 morrob955 commented Jul 16, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • align dynamic STM32 MPU regions using the actual PMSAv7 RASR byte size, 2^(Size + 1)
  • expand the aligned-down region until it fully covers the requested start to end range
  • document why incomplete DMA RAM coverage can leave SD/blackbox buffers cacheable and corrupt writes

Validation

  • make TARGET=STM32H743 V=0
  • field-tested on an H743 board with onboard SD blackbox logging: two back-to-back logs, 107.8 MB total, 0 failed frames

Addresses #14736.

Summary by CodeRabbit

  • Bug Fixes
    • Improved memory protection region alignment on STM32 devices.
    • Ensured configured regions fully cover their intended memory ranges, including cases with misaligned base addresses.
    • Reduced the risk of uncovered memory at region boundaries.

PMSAv7 requires MPU base addresses to be aligned to the actual configured region size. RASR.SIZE encodes region bytes as 2^(Size + 1), so using 1 << Size can still leave the base misaligned and let the MPU snap the protected window down.

Use the actual region byte count when aligning and expanding dynamic MPU regions so DMA RAM remains fully covered by the non-cacheable mapping used for cache maintenance decisions.

Addresses betaflight#14736.
@github-actions

Copy link
Copy Markdown

Do you want to test this code? You can flash it directly from the Betaflight App:

  • Simply put #15441 (this pull request number) in the Select commit field in the Firmware Flasher tab (you need to Enable expert mode, Show release candidates and Development).

WARNING: It may be unstable. Use only for testing!

@coderabbitai

coderabbitai Bot commented Jul 16, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The STM32 memory-protection configuration now uses power-of-two region bytes and iterative base realignment to ensure the MPU region covers the configured endpoint. PMSAv7 behavior and cache-maintenance considerations are documented in the updated comments.

Changes

STM32 MPU alignment

Layer / File(s) Summary
Region sizing and alignment calculation
src/platform/STM32/memprot_hal.c
memProtConfigure now grows regionBytes, recalculates alignedStart, and widens the MPU region until it covers region->end.

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

Possibly related PRs

Suggested labels: RN: IMPROVEMENT

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: aligning STM32 MPU regions to the actual size.
Description check ✅ Passed The description includes a summary, validation steps, and the linked issue, matching the repository template well.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

An error occurred during the review process. Please try again later.


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.

1 similar comment
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

An error occurred during the review process. Please try again later.


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.

@morrob955

Copy link
Copy Markdown
Contributor Author

Should fix Matek f743 as well as Geprc taker h743 (tested on the taker h743).

@blckmn

blckmn commented Jul 17, 2026

Copy link
Copy Markdown
Member

@coderabbitai please review

@coderabbitai

coderabbitai Bot commented Jul 17, 2026 •

Copy link
Copy Markdown
Contributor

@blckmn I’ll review the MPU alignment changes in #15441, with particular attention to PMSAv7 sizing, range coverage, overflow/edge cases, and the STM32H7/F7 call sites.

✅ Action performed

Review finished.

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/platform/STM32/memprot_hal.c (1)

83-93: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Prevent undefined behavior and infinite loops on large region sizes.

The calculation 1U << (msbpos + 1) invokes undefined behavior if msbpos >= 31, which can occur if length wraps around due to linker misconfiguration. Furthermore, alignedStart + regionBytes can overflow a 32-bit integer, resulting in a wrap-around to zero and causing an infinite loop.

Use 64-bit arithmetic and cap msbpos to 31 to prevent these overflows and ensure the MPU's 5-bit SIZE field remains valid.

🔒️ Proposed fix to use 64-bit arithmetic and cap the region size
-            uint32_t regionBytes = 1U << (msbpos + 1);
-            uint32_t alignedStart = start & ~(regionBytes - 1);
+            if (msbpos > 31) {
+                msbpos = 31;
+            }
+
+            uint64_t regionBytes = 1ULL << (msbpos + 1);
+            uint64_t alignedStart = (uint64_t)start & ~(regionBytes - 1);
 
             while (alignedStart + regionBytes < region->end) {
                 msbpos += 1;
                 regionBytes <<= 1;
-                alignedStart = start & ~(regionBytes - 1);
+                alignedStart = (uint64_t)start & ~(regionBytes - 1);
             }
 
-            MPU_InitStruct.BaseAddress = alignedStart;
+            MPU_InitStruct.BaseAddress = (uint32_t)alignedStart;
             MPU_InitStruct.Size = msbpos;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/platform/STM32/memprot_hal.c` around lines 83 - 93, Update the
region-size calculation and growth loop around msbpos, regionBytes, and
alignedStart to use uint64_t arithmetic, preventing shift and alignedStart +
regionBytes overflow; cap msbpos at 31 before shifting or incrementing so the
MPU_InitStruct.Size 5-bit field remains valid and the loop always terminates.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/platform/STM32/memprot_hal.c`:
- Around line 83-93: Update the region-size calculation and growth loop around
msbpos, regionBytes, and alignedStart to use uint64_t arithmetic, preventing
shift and alignedStart + regionBytes overflow; cap msbpos at 31 before shifting
or incrementing so the MPU_InitStruct.Size 5-bit field remains valid and the
loop always terminates.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f38f2e35-0056-43c2-886e-045822480264

📥 Commits

Reviewing files that changed from the base of the PR and between 45c3501 and 086df05.

📒 Files selected for processing (1)
  • src/platform/STM32/memprot_hal.c

@morrob955

Copy link
Copy Markdown
Contributor Author

@coderabbitai

clamping a malformed MPU request to a gigantic region? You sure?

Analyze the actual execution path in Betaflight, the possible values of length, msbpos, and regionBytes, and determine whether msbpos + 1 can realistically reach an undefined shift or overflow on supported hardware. If not, explain why. If so, propose the best fix.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '== candidate files =='
fd -t f 'memprot_hal\.c|memprot.*\.h|.*mpu.*\.(c|h)$' src 2>/dev/null || true

printf '%s\n' '== outline =='
ast-grep outline src/platform/STM32/memprot_hal.c --items all 2>/dev/null || true

printf '%s\n' '== definitions and call sites =='
rg -n -C 5 --glob '*.{c,h}' \
  '\bmemProtConfigure\s*\(|\bmsbpos\s*\(|\bregionBytes\b|\bMPU_REGION_SIZE\b|\bMPU_RASR_SIZE\b' \
  src 2>/dev/null || true

printf '%s\n' '== relevant target configuration references =='
rg -n -C 3 --glob '*.{c,h}' \
  '\bMEMPROT\b|\bMPU\b|DMA_RAM|dmaRam|blackbox' \
  src/main/target src/platform 2>/dev/null || true

Length of output: 48016


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '== generic implementation and region type =='
nl -ba src/platform/STM32/memprot_hal.c | sed -n '27,108p'
nl -ba src/main/drivers/memprot.h | sed -n '1,80p'

printf '%s\n' '== ARMv7 generic-path descriptor definitions =='
for f in src/platform/STM32/memprot_stm32g4xx.c src/platform/STM32/memprot_stm32h7xx.c; do
  printf '\n-- %s --\n' "$f"
  nl -ba "$f" | sed -n '1,160p'
done

printf '%s\n' '== linker-script candidates and DMA symbol definitions =='
fd -t f -e ld -e ld.in -e lds -e S . | while IFS= read -r f; do
  if rg -n -C 4 '_dmaram_(start|end)__|dma_ram_(r|w|rw)_(start|end)|DMA_RAM' "$f" >/dev/null; then
    printf '\n-- %s --\n' "$f"
    rg -n -C 4 '_dmaram_(start|end)__|dma_ram_(r|w|rw)_(start|end)|DMA_RAM' "$f"
  fi
done

printf '%s\n' '== MPU size encoding limits from vendored headers =='
rg -n -C 3 'MPU_REGION_SIZE_(32B|4GB)|MPU_RASR_SIZE_Msk|MPU_REGION_SIZE_Msk' \
  lib src --glob '*.{h,c}' 2>/dev/null || true

Length of output: 1370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

show_range() {
  file="$1"
  start="$2"
  end="$3"
  printf '\n== %s (lines %s-%s) ==\n' "$file" "$start" "$end"
  sed -n "${start},${end}{=;p;}" "$file"
}

show_range src/platform/STM32/memprot_hal.c 27 108
show_range src/main/drivers/memprot.h 1 80
show_range src/platform/STM32/memprot_stm32g4xx.c 1 160
show_range src/platform/STM32/memprot_stm32h7xx.c 1 160

printf '%s\n' '== linker-script DMA symbol definitions =='
fd -t f -e ld -e ld.in -e lds -e S . -x sh -c '
  if rg -n -C 4 "_dmaram_(start|end)__|dma_ram_(r|w|rw)_(start|end)|DMA_RAM" "$1" >/dev/null; then
    printf "\n-- %s --\n" "$1"
    rg -n -C 4 "_dmaram_(start|end)__|dma_ram_(r|w|rw)_(start|end)|DMA_RAM" "$1"
  fi
' sh {}

printf '%s\n' '== MPU encoding constants =='
rg -n -C 3 'MPU_REGION_SIZE_(32B|4GB)|MPU_RASR_SIZE_Msk|MPU_REGION_SIZE_Msk' \
  lib src --glob '*.{h,c}' 2>/dev/null || true

Length of output: 71511


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '== all MPU-region array definitions and generic implementation selection =='
rg -n -C 5 --glob '*.{c,h,mk,cmake,txt}' \
  '\bmpuRegions\s*\[\s*\]|memprot_hal\.c|memprot_stm32(f7|h7|g4)' \
  src make CMakeLists.txt 2>/dev/null || true

printf '%s\n' '== H743/G4 relevant linker memory declarations and DMA_RW_AXI placement =='
for f in src/platform/STM32/link/stm32_ram_h743.ld src/platform/STM32/link/stm32_flash_g4_split.ld; do
  printf '\n-- %s --\n' "$f"
  rg -n -C 8 '^(MEMORY|[[:space:]]*[A-Za-z0-9_]+[[:space:]]*\([^)]*\)[[:space:]]*:)|DMA_RW_AXI|DMA_RW_D2|\.DMA_RAM' "$f"
done

printf '%s\n' '== F7 system MPU setup and generic HAL references =='
rg -n -C 6 --glob '*.{c,h}' \
  '\bHAL_MPU_ConfigRegion\s*\(|\bMPU_ConfigRegion\s*\(|\bmemProtConfigure\s*\(' \
  src/platform/STM32 src/main 2>/dev/null || true

printf '%s\n' '== flsl implementation/contract =='
rg -n -C 5 --glob '*.{c,h}' '\bflsl\s*\(' src 2>/dev/null || true

Length of output: 21636


@morrob955 You’re right to challenge the “clamp” idea. I traced the actual path, and a malformed request must not be converted into a giant permissive MPU region. I withdraw that suggestion.

For this PR, the undefined-shift/overflow case is not reachable on supported Betaflight builds:

  • memProtConfigure() in src/platform/STM32/memprot_hal.c is compiled only for STM32H7 and STM32G4 (STM32H7.mk / STM32G4.mk), not STM32F7. The F7 HAL headers exist in-tree, but F7 does not build or call this implementation.
  • It runs once during startup and receives only the static mpuRegions[] descriptors. There is no CLI, configuration, or other runtime-controlled MPU request path.
  • Those descriptors use linker symbols for DMA sections:
    • H7: dmaram_* and dmarwaxi_*, both placed in the 512 KiB AXI RAM bank in the H743 linker script.
    • G4: DMA sections are likewise linker-bounded SRAM sections, far below the problematic range.

The sizing values are:

Case length msbpos regionBytes
Empty/tiny section clamped to 32 B 5 64 B
Largest possible H743 linker range ≤ 512 KiB ≤ 19 ≤ 1 MiB

msbpos is ceil(log2(length)); regionBytes is deliberately 2^(msbpos + 1) because the PMSAv7 RASR.SIZE encoding represents 2^(SIZE + 1) bytes. The one-size-larger value is correct and necessary for alignment/coverage.

The first undefined shift would require msbpos + 1 >= 32, which requires a derived length >= 2 GiB (or a malformed wrapped subtraction from end < start). Neither can arise from these linker-defined SRAM ranges. The growth loop also cannot reach that size: at most it selects a 1 MiB enclosing region for the H743’s 512 KiB AXI RAM.

So: no clamp and no fix needed for the real supported path in this PR.

If we ever choose to harden this internal helper against a future broken linker descriptor, the right policy is to call memProtConfigError() on an invalid/reversed range or an unrepresentable size—not silently widen the MPU mapping. A fully general implementation would also perform the temporary coverage arithmetic in uint64_t, since PMSAv7 can encode a 4 GiB region while uint32_t regionBytes cannot represent it.

🐇🔍

@blckmn
blckmn merged commit 2879a18 into betaflight:master Jul 21, 2026
53 checks passed
@github-project-automation github-project-automation Bot moved this from Bugfix to Done in 2026.6.0 Jul 21, 2026
gwlim pushed a commit to gwlim/betaflight that referenced this pull request Jul 25, 2026
PMSAv7 requires MPU base addresses to be aligned to the actual configured region size. RASR.SIZE encodes region bytes as 2^(Size + 1), so using 1 << Size can still leave the base misaligned and let the MPU snap the protected window down.

Use the actual region byte count when aligning and expanding dynamic MPU regions so DMA RAM remains fully covered by the non-cacheable mapping used for cache maintenance decisions.

Addresses betaflight#14736.

Co-authored-by: morrob955 <196944784+morrob955@users.noreply.github.com>
Co-authored-by: blckmn <blckmn@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants