Alignment fault

I am using FreeRTOS on NXP’s S32K3 series chips, the portable is GCC/ARM_CM4_MPU, and a memfault is triggered when a non-privileged task accesses a variable

(For the convenience of description, I defined a variable with no access restrictions and data type uint8 for testing)


图片

CFSR indicates a data access violation, MMFAR is the address of Test_HwSfty_Wdg_GetSignature, and DFSR shows an Alignment fault
The following are the Task code and Task settings



Although I think uint8 variables do not require special alignment instructions, I still tried multiple alignment methods, but still can’t solve the problem. I have a new discovery, as long as the task is set as a privileged task (/*.uxPriority = */ ((tskIDLE_PRIORITY + 2u) | portPRIVILEGE_BIT),) there will be no problem. I am confused because I confirmed that the address of this variable does not require privileges to access. Can you help me see where my problem is?

The alignment is needed because of the ARMv7 MPU hardware requirements:

  1. The size of a MPU region is a power of 2.
  2. Smallest supported region size is 32 bytes.
  3. Start address of a region must be aligned to an integer multiple of the region size. For example, if the region size is 4 KB(0x1000), the starting address must be N x 0x1000, where N is an integer.

Here is an example on how to declare a memory region to use with MPU - FreeRTOS/FreeRTOS/Demo/CORTEX_MPU_M7_NUCLEO_H743ZI2_GCC_IAR_Keil/Demo/mpu_demo.c at main · FreeRTOS/FreeRTOS · GitHub.

I noticed this and I calculated it carefully using a calculator. This is my setting. Please help me see if there is anything wrong:

FLASH_segment_start = 0x00400000;
FLASH_segment_end = 0x00500000;

SRAM_segment_start = 0x20000000;
SRAM_segment_end = 0x22000000;

privileged_functions_start = 0x00460000;
privileged_functions_end = 0x00480000;

privileged_data_start = 0x20004000;
privileged_data_end = 0x20008000;

Where are you declaring this variable? Is it properly aligned? Also, did you grant the unprivileged task access to this variable?

The data type of this variable is uint8 (for rigor, I used attribute( ( aligned( 4 ) ) )). The address of this variable is between SRAM_segment_start and SRAM_segment_end, but not between privileged_data_start and privileged_data_end, so I think all tasks can access it.
图片

Correction: Rechecking the ARM manual, DFSR 1 does not mean an Alignment fault has occurred, it just shows that the chip has been halted.

That is not true. An unprivileged task only has access to its own stack by default. You need to grant explicit access to any other memory region that your unprivileged task needs to access. We use MPU regions to grant these access and therefore, these memory regions needs to satisfy MPU constraints.

As I mentioned before, the smallest MPU region size is 32 bytes. You cannot grant an unprivileged task access to just one byte (i.e. uint8_t). Here is an example to grant your unprivileged task access to a 32 byte array:

/* We will grant our unprivileged task access to the unprivAccessibleMemory
 * array. Therefore, this memory region must satisfy all the MPU constraints:
 * 1. The size of a MPU region is a power of 2.
 * 2. Smallest supported region size is 32 bytes.
 * 3. Start address of a region must be aligned to an integer multiple of the
 *    region size. For example, if the region size is 4 KB(0x1000), the starting
 *    address must be N x 0x1000, where N is an integer.
 */
#define ARRAY_SIZE 32
static uint8_t unprivAccessibleMemory[ ARRAY_SIZE ] __attribute__( ( aligned( ARRAY_SIZE ) ) );

/* We use an MPU region to grant unprivileged task access to its stack, therefore,
 * stack memory must satisfy MPU constraints. */
static StackType_t UnprivTaskStack[ configMINIMAL_STACK_SIZE ] __attribute__( ( aligned( configMINIMAL_STACK_SIZE * sizeof( StackType_t ) ) ) );

/* Grant unprivileged task Read-Write access to unprivAccessibleMemory. */
TaskParameters_t UnprivTaskParameters =
{
    .pvTaskCode      = UnprivTask,
    .pcName          = "Unpriv",
    .usStackDepth    = configMINIMAL_STACK_SIZE,
    .pvParameters    = NULL,
    .uxPriority      = tskIDLE_PRIORITY,
    .puxStackBuffer  = UnprivTaskStack,
    .xRegions        =    {
                            { unprivAccessibleMemory, ARRAY_SIZE, portMPU_REGION_READ_WRITE | portMPU_REGION_EXECUTE_NEVER },
                            { 0,                      0,          0                                                        },
                            { 0,                      0,          0                                                        },
                            { 0,                      0,          0                                                        },
                            { 0,                      0,          0                                                        },
                            { 0,                      0,          0                                                        },
                            { 0,                      0,          0                                                        },
                            { 0,                      0,          0                                                        },
                            { 0,                      0,          0                                                        },
                            { 0,                      0,          0                                                        },
                            { 0,                      0,          0                                                        }
                        }
};

xTaskCreateRestricted( &( UnprivTaskParameters ), NULL );

M7 has 16 sets of MPU registers, but only 11 sets are available for user configuration when creating a task, because the other 5 sets are used by the OS. You mentioned in another post that the MPU registers are reset every time a task is switched, so I mistakenly thought that portPRIVILEGED_RAM_REGION and portGENERAL_PERIPHERALS_REGION would be set when switching non-privileged tasks. So, are these 5 sets only set when switching privileged tasks? Or are these 5 sets not included at all?


You are not mistaken but you are exactly right. These kernel regions are set when you start the scheduler and remain set forever. Only per-task regions are re-configured on every context switch.

  • portPRIVILEGED_RAM_REGION - This region is used to ensure that kernel data is only accessible to the privileged code. In other words, unprivileged code cannot read/write kernel data.
  • portGENERAL_PERIPHERALS_REGION - This region is used to grant access to the memory range 0x40000000UL - 0x5FFFFFFFUL to all the tasks.

I get it.Thank you! :smile:

Sorry, I have another problem. When a non-privileged task accesses a normal peripheral register, a busfault will occur. The following is my configuration. The circled configuration is additional (according to your reply, the setting of portGENERAL_PERIPHERALS_REGION should be enough)


Still, the fault disappeared when it was set as a privileged task. The following is my test code

STM寄存器地址为0x40274000

Please help me find what’s wrong with my settings

A bus fault does not occur because of incorrect MPU settings - it is MemFault. Can you find out the faulting instruction?

This looks correct.

When testing with STM, CFSR is 0x400, which means Imprecise data access error has occurred, so there is no more useful information. I changed a register to test as follows. It can be clearly seen that the error is caused by accessing this register. The error disappeared after changing to a privileged task.



Can you share the generated assembly? Which STM32 board are you using?

Sorry
Sorry, I replied late. I think I found a deeper reason. This chip of NXP uses hardware locks for some peripheral registers, which must be accessed in privileged mode. For this reason, a Trust function is defined in the peripheral driver provided by NXP to access these registers in user mode. This Trust function performs the following operations:

  1. Go To Supervisor
  2. Access registers
  3. Go to user
    When executing step 1 in user mode, an SVC exception will be triggered. Only when this operation is allowed in SVC_Handler can it continue
    NXP provides an implementation of SVC_Handler, but vPortSVCHandler also exists in FreeRTOS. Although I added the following definition in the configuration, the function defined in FreeRTOS is finally used
    Can these two SVC_Handlers be combined into one? How to do it

What SVC number does the NXP code use? FreeRTOS currently uses the following SVC numbers -

  1. 0-99 : for system call.
  2. 100: start the scheduler.
  3. 101: yield operation.
  4. 102: raise privilege (MPU V1 only).
  5. 103: system call exit.

Assuming the NXP code uses some other SVC number, you can call the NXP’s SVC handler function from here. It will be helpful if you can share the NXP’s SVC_Handler definition (or a link to it).

This is the SVCHandler defined by NXP

I tried to redefine this macro and set configENFORCE_SYSTEM_CALLS_FROM_KERNEL_ONLY = 0, hoping to execute case portSVC_RAISE_PRIVILEGE when entering prvSVCHandler, but it triggered MemManage_Handler

Are you using mpu_wrappers v1 or v2?

It looks like V1 is used.
At the same time, I checked the value of the MPU_CTRL register, which is 5, so the MPU should not be enabled when entering an abnormal interrupt

The useful information when MemFault is triggered is as follows

Try the following:

  1. Do not change the definition of SVC_GoToSupoervisor.
  2. Write the following function in one your source files (may be the one containing SVCHandler_main):
void Application_SVC_Handler( uint8_t svcNumber )
{
    switch( svcNumber )
    {
        case 0:
        {
            ASM_KEYWORD( "mov r0, #0x00" );
            ASM_KEYWORD( "msr CONTROL, r0" );
        }
        break;

        case 1:
        {
            ASM_KEYWORD( "mov r0, #0x01" );
            ASM_KEYWORD( "msr CONTROL, r0" );
        }
        break;

        case 2:
        {
            Resume_Interrupts();
        }
        break;

        case 3:
        {
            Suspend_Interrupts();
        }
        break;

        default:
        {
            /* Unknown SVC call. */
        }
        break;
    }
}
  1. Add the following lines here:
    default: /* Unknown SVC call. */
    {
        extern Application_SVC_Handler( uint8_t svcNumber );
        Application_SVC_Handler( ucSVCNumber );
    }
    break;

Unfortunately, this method doesn’t work and a hardfault will occur.
If the definition of SVC_GoToSupoervisor is not modified, it will obviously not match the defult, so I tried the following:


The result is the same, a hardfault occurs