Cortex-Rx MPU - Reprogramming Active MPU Region Potentially Creates Abort

Hello,

I’m looking at the “Cortex-Rx” MPU port (for a Cortex-R5F target), specifically where we program the MPU regions in portASM.S around portable/GCC/ARM_CRx_MPU/portASM.S#L118

    MOV     R5, #portFIRST_CONFIGURABLE_REGION
    123:
        LDMIA   R1!, { R2-R4 }  /* R2 = ulRegionSize, R3 = ulRegionAttribute, R4 = ulRegionBaseAddress. */

        MCR     p15, #0, R5, c6, c2, #0 /* MPU Region Number Register. */
        MCR     p15, #0, R4, c6, c1, #0 /* MPU Region Base Address Register. */
        MCR     p15, #0, R3, c6, c1, #4 /* MPU Region Access Control Register. */
        MCR     p15, #0, R2, c6, c1, #2 /* MPU Region Size and Enable Register. */

        ADD     R5, R5, #1
        CMP     R5, #portNUM_CONFIGURABLE_REGIONS
        BLE     123b

Here we load and program the MPU regions as part of the context restore. The FreeRTOS port allows us to specify a per-task configuration of some MPU regions that are loaded above at run-time. This means the multiple MPU registers we are reprogramming hold a ‘stale’ value from the last executing task.

I noticed we do not disable each MPU entry while we reprogram it, so we are potentially writing the MPU registers for a region that is already active. It’s then possible a partially written MPU entry creates an abort.

Consider this scenario: we execute out of 1 MiB RAM in the 0x70000000 address range, and have say 1 MiB of peripheral memory at address 0x50000000. We might want to disallow execution of the 1 MiB of peripheral space (make it RW, no X) and allow execution of the RAM (make it RWX). Maybe each task configures two of its MPU regions to facilitate this.

If ever, during a context switch we begin writing the “RAM RWX” entry into the same MPU region that previously had the “peripheral RW” entry, the above operations program the 0x70000000 base address while we have the active but ‘stale’ peripheral permissions of “read-write, no-execute” in the access control register. We are then executing out of memory marked as no-execute and should encounter a prefetch abort.

I believe I am observing an equivalent of this scenario in a real R5F target.

Zeroing the MPU region enable register before reprogramming the MPU region avoids this, but affects throughput/performance.

Is there a FreeRTOS “best practice” or recommendation that would avoid this? i.e. does this break the rules? Or could this motivate a patch?

Thanks,
Brandon

Shouldn’t all of the FreeRTOS code be in a segment that part of the fixed blocks that FreeRTOS allocates, and NOT part of the task configured address space? It should be marked RX to avoid code changing the code in the kernel.

This memory segment can also include unchangeable user code, the only user RWX memory should be the limited space needed for self-modifying code, or perhaps if you have a mass-store available for the use of “overlays” (which I wouldn’t expect in “Real-Time” code).

Could you protect the code which is working with the MPU against an interruption like a critical section? This way you’ll ensure that each MPU modification is complete and none remainder of partially written registers is possible when starting next MPU interaction.
For best real-time performance, you may prepare all registers data before entering critical section. Then enter a critical section and write MPU register by prepared data as fast as possible. Exit critical section. Will such scenario work in your environment?

Another possible approach - concentrate all MPU-related code in single dispatching task (or even an dedicated interrupt servicing routine). When a modification is needed, an application is asking for it, for example, by placing a request into an MPU queue. Such a request could contain requester task handle to notify the task about MPU modification completion. Being triggered by a request from a queue MPU code performs the modifications and notifies the requesting task back. The task is happily unblocking and continue the operation with new MPU configuration.

When are you observing this scenario? Can you share your task creation code?

Do you mean disable MPU region before programming it? This is a safe thing to do and we should do to ensure that MPU does not get into an inconsistent state as you described.