🌱 MPU và DMA trong STM32 Hoạt Động cùng nhau Như Thế Nào?

🌱 MPU và DMA trong STM32 Hoạt Động cùng nhau Như Thế Nào?

    Ở những bài viết trước mình đã từng nói về MPU - Memory Protection Unit. MPU được biết đến như một ngoại vi sử dụng để bảo vệ việc truy cập bộ nhớ. Tuy nhiên, việc hạn chế truy cập đó là đối với Core, còn đối với những master khác (Ví dụ như DMA) thì MPU có thể hạn chế truy cập của chúng đến memory hay không?

    Câu trả lời là không! Và bên dưới mình sẽ giải thích và Demo vấn đề này.

Xem thêm:

👉 Về mặt lý thuyết

    Rất đơn giản khi xét về mặt lý thuyết, MPU là một Core Peripheral, vì vậy nó sẽ chỉ tác dụng đối với Core. Chúng ta có thể nhìn vào sơ đồ hoạt động của MPU như hình sau:

MPU Core
Hình 1: Sơ đồ hoạt động của MPU trong ARM Cortex

    Có thể thấy MPU nằm trong processor, và là trung gian giữa Core và Memory. Trong khi đó, DMA không nằm trong processor. Vì vậy, dễ thấy DMA và MPU không có liên quan gì đến nhau.

👉 Về mặt thực hành

    Bài toán của mình đặt ra để kiểm tra hoạt động này của DMA và MPU. Bài toán sử dụng MPU với 2 Region config như sau:

Region Start Address End Address Access Right
0 0x0000.0000 0xFFFF.FFFF Full Access
1 0x2000.7000 0x2000.FFFF Read Only

    Vùng RAM (Region 1) sẽ dùng để test hoạt động của DMA.

  • Sử dụng DMA transfer từ một vùng RAM khác (địa chỉ 0x2001.6000, data = 0x5A5A.5A5A) sang địa chỉ thuộc Region 1 (địa chỉ 0x2000.7F00).
    👉 Kết quả mong muốn: Transfer thành công do DMA không bị ảnh hưởng bởi MPU.
  • Sử dụng Core ghi trực tiếp data vào địa chỉ thuộc Region 1 (địa chỉ 0x2000.7F00).
    👉 Kết quả mong muốn: Chương trình nhảy vào Fault (MemManage) do Core bị MPU hạn chế quyền write vào Region 1.

💬 Source Code

#include "stm32f4xx.h"

#define SRCADDR  0x20016000UL
#define DESTADDR 0x20007F00UL

// MPU Configuration
void MPU_Init(void) {
    // Enable MPU
    MPU->CTRL = MPU_CTRL_ENABLE_Msk | MPU_CTRL_PRIVDEFENA_Msk;

    // Region 0: Full access to entire memory
    MPU->RNR  = 0; // Select Region 0
    MPU->RBAR = 0x00000000; // Base address
    MPU->RASR = (0x1F << MPU_RASR_SIZE_Pos) | // Size: 4GB (2^(31+1))
                (0x3 << MPU_RASR_AP_Pos) |    // Full access (RW)
                MPU_RASR_ENABLE_Msk;          // Enable region

    // Region 1: Read-only SRAM (0x20007000 - 0x2000FFFF)
    MPU->RNR  = 1; // Select Region 1
    MPU->RBAR = 0x20007000; // Base address
    MPU->RASR = (0x0F << MPU_RASR_SIZE_Pos) | // Size: 32KB (2^(15+1))
                (0x1 << MPU_RASR_AP_Pos) |    // Read-only (Privileged RO)
                MPU_RASR_ENABLE_Msk;          // Enable region
}

// DMA Configuration (Memory-to-Memory)
void DMA_Init(void) {
    RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN; // Enable DMA2 clock
    DMA2_Stream0->CR = 0; // Disable stream before config

    DMA2_Stream0->CR = (0 << DMA_SxCR_CHSEL_Pos) | // Channel 0
                       (0b10 << DMA_SxCR_DIR_Pos) | // Memory-to-Memory
                       DMA_SxCR_MINC |             // Memory increment
                       DMA_SxCR_PINC |             // Peripheral increment
                       (0b10 << DMA_SxCR_MSIZE_Pos) | // Memory size: 32-bit
                       (0b10 << DMA_SxCR_PSIZE_Pos) | // Peripheral size: 32-bit
                       (0b01 << DMA_SxCR_PL_Pos);    // Priority: Medium
}

void DMA_Config(uint32_t src, uint32_t dest, uint32_t size) {
    DMA2_Stream0->NDTR = size; // Number of data items
    DMA2_Stream0->PAR  = src;  // Source address
    DMA2_Stream0->M0AR = dest; // Destination address
    DMA2_Stream0->CR |= DMA_SxCR_EN; // Enable DMA
}

int main(void) {
    // Initialize MPU and DMA
    MPU_Init();
    DMA_Init();

    // Write to Source Data - SRAM
    *(volatile uint32_t *)SRCADDR = 0x5A5A5A5A;

    // DMA Transfer: Should succeed
    DMA_Config(SRCADDR, DESTADDR, 1);

    // Wait for DMA transfer to complete
    while (!(DMA2->LISR & DMA_LISR_TCIF0));
    DMA2->LIFCR |= DMA_LIFCR_CTCIF0; // Clear flag

    // Core Write: Should trigger MemManage Fault
    *(volatile uint32_t *)DESTADDR = 0x12345678;

    while (1); // Check memory in debugger
}

// MemManage Fault Handler
void MemManage_Handler(void) {
    while (1); // Trap here for debugging
}
    

Giải thích:

  • MPU_Init: Cấu hình 2 region: Region 0 (Full access toàn bộ 4GB), Region 1 (Read-only SRAM 32KB từ 0x20007000).
  • DMA_Init: Cấu hình DMA2 Stream 0 cho Memory-to-Memory.
  • DMA_Config: Truyền 1 word (4 byte) từ 0x20016000 sang 0x20007F00.
  • Core Write: Thử ghi trực tiếp vào 0x20007F00, sẽ gây MemManage Fault.
  • Handler: Bẫy lỗi MemManage để debug.

💬 Kết quả

    Khi transfer bằng DMA - Data transfer thành công.

DMA Transfer Success
Hình 2: DMA chuyển dữ liệu thành công vào Region 1

    Khi ghi trực tiếp bằng Core - Giá trị ô nhớ không thay đổi, và chương trình nhảy vào fault.

Core Write Fault
Hình 3: Core ghi trực tiếp gây MemManage Fault

👉 Video Demo

    Video Demo về hoạt động của DMA và MPU trên vi điều khiển STM32

///...

👉 Kết luận

    MPU là một công cụ tốt để protect memory khỏi các truy cập của Core, tuy nhiên với các Non-Core Master thì MPU không chắc có tác dụng hạn chế quyền truy cập (Cần kiểm chứng thêm với các Non-Core Master khác ngoài DMA - Ví dụ Debugger). Đối với DMA đã được kiểm chứng hoạt động, nó không bị hạn chế bởi các Attribute (Access Right) của MPU. Vì vậy, để bảo vệ memory khỏi các Non-Core Master như DMA, chúng ta cần các cơ chế khác.

>>>>>> Follow ngay <<<<<<<

Để nhận được những bài học miễn phí mới nhất nhé 😊
Chúc các bạn học tập tốt 😊

Nguyễn Văn Nghĩa

Mình là một người thích học hỏi và chia sẻ các kiến thức về Nhúng IOT.

Đăng nhận xét

Mới hơn Cũ hơn
//