wtorek, 27 sierpnia 2024

STM32H7 - MPU - Memmory Protection Unit

W tym poście chciałbym opisać działanie i zastosowanie MPU (Memmory Protection Unit).


MPU jest mechanizmem, który pozwala na zdefiniowanie praw dostępu do odpowiednich pamięci w urządzeniu. Dzięki temu możemy ograniczyć dostęp do pamięci SRAM, FLASH czy rejestrów. MPU monitoruje każde próby dostępu do pamięci. Można zdefiniować sposoby dostępu do maksymalnie 16 regionów pamięci. Każdy niedozwolony zapis spowoduje wygenerowanie błędów Exception fault. 

MPU ogranicza dostęp do danych dla procesora. Dostęp za pomocą DMA nie może zostać ograniczony. Wykorzystuje się go podczas uruchamiania Ethernetu wraz z stosem LWIP czy podczas konfiguracji DMA.

Poniżej przykładowy kod pracujący z wykorzystaniem DMA. Program uruchomię na układzie STM32H723

Konfiguracja MPU:

  1. void MPU_Config(void)
  2. {
  3.   MPU_Region_InitTypeDef MPU_InitStruct = {0};
  4.  
  5.   /* Disables the MPU */
  6.   HAL_MPU_Disable();
  7.  
  8.   /** Initializes and configures the Region and the memory to be protected
  9.   */
  10.   MPU_InitStruct.Enable = MPU_REGION_ENABLE;
  11.   MPU_InitStruct.Number = MPU_REGION_NUMBER0;
  12.   MPU_InitStruct.BaseAddress = 0x0;
  13.   MPU_InitStruct.Size = MPU_REGION_SIZE_4GB;
  14.   MPU_InitStruct.SubRegionDisable = 0x87;
  15.   MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
  16.   MPU_InitStruct.AccessPermission = MPU_REGION_NO_ACCESS;
  17.   MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
  18.   MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
  19.   MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
  20.   MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
  21.  
  22.   HAL_MPU_ConfigRegion(&MPU_InitStruct);
  23.  
  24.   /** Initializes and configures the Region and the memory to be protected
  25.   */
  26.   MPU_InitStruct.Number = MPU_REGION_NUMBER1;
  27.   MPU_InitStruct.BaseAddress = 0x30000200;
  28.   MPU_InitStruct.Size = MPU_REGION_SIZE_32KB;
  29.   MPU_InitStruct.SubRegionDisable = 0x0;
  30.   MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
  31.   MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  32.  
  33.   HAL_MPU_ConfigRegion(&MPU_InitStruct);
  34.  
  35.   /** Initializes and configures the Region and the memory to be protected
  36.   */
  37.   MPU_InitStruct.Number = MPU_REGION_NUMBER2;
  38.   MPU_InitStruct.BaseAddress = 0x30000000;
  39.   MPU_InitStruct.SubRegionDisable = 0x00;
  40.   MPU_InitStruct.AccessPermission = portMPU_REGION_PRIVILEGED_READ_ONLY;
  41.   MPU_InitStruct.Size = MPU_REGION_SIZE_512B;
  42.   MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
  43.   MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
  44.   MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
  45.   MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
  46.   MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
  47.  
  48.   HAL_MPU_ConfigRegion(&MPU_InitStruct);
  49.   /* Enables the MPU */
  50.   HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
  51. }

Jest to zmodyfikowana wersja MPU dla LWIP. Zmianie uległy prawa dostępu dla regionu 2. Został on ustawiony jako tylko do odczytu. Weryfikację poprawności ustawień można wykonać przez wykonanie próbnego zapisu do buffora (bez użycia DMA). 

  1. uint8_t ReadOnlyDataRxRamD2[32] __attribute__((section(".RxDecripSection")));
  2.  
  3. int main(void)
  4. {
  5.   MPU_Config();
  6.   SCB_EnableICache();
  7.   SCB_EnableDCache();
  8.   HAL_Init();
  9.   SystemClock_Config();
  10.   MX_GPIO_Init();
  11.   MX_DMA_Init();
  12.   MX_USART3_UART_Init();
  13.  
  14.   /* USER CODE BEGIN 2 */
  15.   testRead =  ReadOnlyDataRxRamD2[0];   //Bez Errora, poprawny odczyt
  16.   ReadOnlyDataRxRamD2[0] = 0xA5;        //MemMenagementFault
  17.   /* USER CODE END 2 */
  18.  
  19.   /* Infinite loop */
  20.   /* USER CODE BEGIN WHILE */
  21.   while (1)
  22.   {

Próbra takiego zapisu spowoduje wygenerowanie MemMenagementFault. Odczyt danych wykona się poprawnie. 



W celu wykonania zapisu danych do pamięci należy posłużyć się DMA:

Konfiguracja DMA:

  1. static void MX_DMA_Init(void)
  2. {
  3.  
  4.   /* DMA controller clock enable */
  5.   __HAL_RCC_DMA1_CLK_ENABLE();
  6.  
  7.   /* Configure DMA request hdma_memtomem_dma1_stream1 on DMA1_Stream1 */
  8.   hdma_memtomem_dma1_stream1.Instance = DMA1_Stream1;
  9.   hdma_memtomem_dma1_stream1.Init.Request = DMA_REQUEST_MEM2MEM;
  10.   hdma_memtomem_dma1_stream1.Init.Direction = DMA_MEMORY_TO_MEMORY;
  11.   hdma_memtomem_dma1_stream1.Init.PeriphInc = DMA_PINC_ENABLE;
  12.   hdma_memtomem_dma1_stream1.Init.MemInc = DMA_MINC_ENABLE;
  13.   hdma_memtomem_dma1_stream1.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
  14.   hdma_memtomem_dma1_stream1.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
  15.   hdma_memtomem_dma1_stream1.Init.Mode = DMA_NORMAL;
  16.   hdma_memtomem_dma1_stream1.Init.Priority = DMA_PRIORITY_LOW;
  17.   hdma_memtomem_dma1_stream1.Init.FIFOMode = DMA_FIFOMODE_ENABLE;
  18.   hdma_memtomem_dma1_stream1.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
  19.   hdma_memtomem_dma1_stream1.Init.MemBurst = DMA_MBURST_SINGLE;
  20.   hdma_memtomem_dma1_stream1.Init.PeriphBurst = DMA_PBURST_SINGLE;
  21.   if (HAL_DMA_Init(&hdma_memtomem_dma1_stream1) != HAL_OK)
  22.   {
  23.     Error_Handler( );
  24.   }
  25.  
  26. }

Sposób wykonania zapisu danych:

  1. uint8_t ReadOnlyDataRxRamD2[32] __attribute__((section(".RxDecripSection")));
  2. uint8_t saveDMAData[32] = {20,21,22,23,24,25,26,27,28,29,30};
  3.  
  4. int main(void)
  5. {
  6.     //..
  7.     //..
  8.    HAL_DMA_Start(&hdma_memtomem_dma1_stream1, (uint32_t)saveDMAData, (uint32_t)ReadOnlyDataRxRamD2, 32);
  9. }

Powyższy kod wprowadza dane do ReadOnlyDataRxRamD2 tylko gdy zostay one wprowadzone do bufora saveDMAData podczas inicjalizacji. Jeśli wprowadzimy dane podczas działania programu np. wykorzystując pętlę for to nie zostaną przesłane. 

Aby działało to z edycją danych podczas pracy programu należy wykonać następujące operacje:

  1.  
  2. //...
  3. //...
  4. for(uint8_t i=0;i<32;i++) {
  5.     saveDMAData[i] = i + 20;
  6. }
  7. //..
  8. //..
  9. SCB_CleanDCache_by_Addr((uint32_t*)saveDMAData, sizeof(saveDMAData));
  10.  
  11. HAL_DMA_Start(&hdma_memtomem_dma1_stream1, (uint32_t)saveDMAData, (uint32_t)ReadOnlyDataRxRamD2, 32);
  12. HAL_DMA_PollForTransfer(&hdma_memtomem_dma1_stream1, HAL_DMA_FULL_TRANSFER, HAL_MAX_DELAY);
  13.  
  14. SCB_InvalidateDCache_by_Addr((uint32_t*)ReadOnlyDataRxRamD2, sizeof(ReadOnlyDataRxRamD2));

Źródła:


https://www.st.com/resource/en/application_note/an4838-introduction-to-memory-protection-unit-management-on-stm32-mcus-stmicroelectronics.pdf