Contrary to popular belief, it is possible to achieve acceptable memory efficiency with the Armv7-M MPU. This article discusses methods to do so. There are two main problems to overcome: inter-block gaps and block tails.
Background
Since the Armv7-M MPU requires regions to be powers-of-two bytes in size and aligned on their sizes, it is generally believed that using it will result in an excessive amounts of wasted memory. As a result, the MPU adoption rate has been low despite the security benefits that it offers. This is a problem because the Cortex-M0, M3, M4, and M7 microcontrollers, which use this MPU, are the “workhorse chips” of the industry and are still being shipped in large quantities. Cortex-M accounts for 68% of all microcontrollers being shipped and Armv8M is only a small share of that.
Inter-block Gaps
Inter-block gaps occur when there is unused memory between region blocks. A region block is a block, defined by the linker command file, that is used as an MPU region. As described in Part 1 of this series [ref 1], a typical region block is defined in the linker command file as follows:

The region size, cpdsz, must be a power of two. (A power of two, in hexadecimal, has only one digit and it must be 1, 2, 4, or 8). In this case, the actual cp_data region size is 0x550. cpdsz = 0x800 so the subregion size is 0x100 and 6 subregions = 0x600 is sufficient to hold cp_data, hence the 6/8 fraction. So subregions 6 and 7 are not used and are disabled for the cp_data region block. Hence, the memory they cover can be used to hold smaller region blocks.
MPUPacker™ was developed by us to suggest the best ordering of regions in a memory area such as SRAM. The diagram above shows two equal size regions: region 1 has subregions 5-7 disabled and region 2 has subregion 7 disabled, followed by two smaller regions, 3 and 4. MPUPacker suggests putting region 4 into the subregion 5 gap of region 1 and region 3 into the subregion 6 &7 gap of region 1. As a result, there is no wasted memory, external to the regions, and the equivalent of 4 out of 20 subregions has been saved – a 20% memory reduction.
Plug Blocks
After MpuPacker has done its job, there may still be some large gaps between region blocks. This can be remedied by defining plug blocks. These are blocks that are not used for regions and thus can be of any size and aligned on word boundaries. It is recommended to put all non-runtime code and data into two plug blocks, as follows:

In this example, some non-runtime code and constants have been put into .pb1.text and .pb1.rodata, and ro brings in the remaining code that is not in code region blocks. Similarly some non-runtime data has been put into .pb1.bss and .pb1.data, and rw brings in all remaining data that is not in data region blocks. pb1_code and pb1_data can then be split into smaller plug blocks that fit tightly into inter-region gaps, for example:

where two plug blocks, pb1_code and pb2_code, have been put between three region blocks, sys_code, lcd_code, and fpu_code. Note the with fixed order command that controls this order. Clearly, this takes effort and is best postponed until the project is done, unless it is unavoidable due to tight memory[1]. Other methods to reduce block gaps are discussed after following block tail discussion.
Block Tails
As previously noted, block tails are the other main problem causing memory inefficiency. They are wasted memory inside of block regions, as can be seen in the following map file[2] snippet:

As shown, the block tail is 0xb0 = 176 bytes wasted out of 0x600 = 1536, = 11%, which is not bad, since regions typically grow during development. If cp_data were to grow to 0x610, for example, the linker would complain and the fraction could be increased from 6/8 to 7/8. If 7/8 were exceeded, then 8/8 exceeded, cpdsz would be increased to 0x1000 and the fraction would be reduced to 5/8. If a block shrinks, MPUMapper warns when its tail is greater than a subregion, so the fraction can be reduced or, in some cases, the region size can be reduced[3].
Reasonable size block tails are not a bad thing because they allow room for expansion if a region is updated[4]. For this reason, it is better to have spare memory spread among regions than clustered at the end of RAM or ROM.
If memory permits, it is best to employ the following fixes after development is done, since memory usage and region assignments tend to vary significantly during development.
Reducing Block Tails
Large regions tend to waste the most memory, so it is best to tackle them first. For example, if a region is 0x20000 bytes then its subregions are 0x8000 = 32 KB each. It may happen that the actual region size is slightly over a subregion boundary. A small subregion spillover would result in nearly 32 KB of wasted memory in this example! There is nothing the linker can do about this, but the following techniques can be used to fix it:
- For a small spillover, the region is a good candidate for size reduction. See Other Techniques below.
- In the case of a larger spillover, if an unused slot is available in every partition using the region, the region can be split into two smaller regions. This results in smaller subregions, which reduces waste. It also enables adjusting what is in each region in order to get a better fits.
- Defining child tasks within a partition results in more and smaller regions. Thus subregion spillovers have less impact.
- If two portions of a region are mutually exclusive (i.e. only one is needed at a time), it may be possible to split it into two regions, put one into an auxiliary slot, and swap the regions as needed [ref 2].
Included Regions
It often is desirable to put one region block inside of another. For example:

In this case, ucom_code is put inside of sys_code so it is reachable from pmode as well as from umode. It is important to put included regions first and in decreasing order of size in order to minimize internal block gaps. Sections require only 4-byte alignment, so they can either follow blocks or fill internal block gaps like plug blocks fill external block gaps.
Other Techniques
If easier approaches do not achieve the size reduction needed, it may be necessary to make code changes. As previously described, this can work well if there are only small subregion spillovers. For code regions, a few ideas to consider are:
- Refactoring functions to reduce their sizes.
- Reducing repeated code in functions by defining subroutines.
- Replacing macros with subroutines. Be sure to use _pragma(“inline=never”) ahead of short subroutines.
- Removing code not actually needed in a region by using section pragmas. This often happens for initialization and other non-runtime code just because it is in the same file.
- Merging similar subroutines, in different regions, into a single subroutine in ucom_code that is accessed directly from umode, or in sys_code that is accessed via the SVC Handler from umode and directly from pmode.
For data regions, consider:
- Reducing buffer sizes where reduced performance is acceptable.
- Removing data not actually needed in a region by using section pragmas.
- Moving task data to task local storage, TLS, which reduces the stack block tail. To do so, group static task variables into a structure that is accessed via a pointer.
- Allocating buffers and structures from the main heap or a local heap, as needed, rather than statically defining them.
Conclusion
The above approaches provide practical methods to improve MPU memory efficiency. The fear of wasted memory is not a good reason to avoid the security benefits of Cortex-M MPUs. Code showing the above techniques and MPUPacker and MPUMapper are available at www.github.com/Micro-Digital/SecureSMX.
References
1. “Memory Protection Units, Part 1: Basics”, Ralph Moore, LinkedIn, March 2025.
2. “Memory Protection Units, Part 2: Advanced Concepts”, Ralph Moore, LinkedIn, March 2025.
Notes
[1] For this reason it is best to do development with larger memories than will be in the final product.
[2] This map file was produced by MPUMapper™, developed by us, which shows what is in each region.
[3] Normally, this need not done during development unless memory is tight.
[4] Since portals conceal internal addresses from other regions, it is possible to do partition-only updates, while the rest of the system continues running.