openlang

D-cache를 켜면 STM32H7 UART TX DMA가 예전 바이트를 보내요

STM32H7, arm-none-eabi-gcc예요. 64바이트 프레임을 DMA TX로 보내요. 캐시를 끄면 괜찮아요. -DUSE_DCACHE를 켜면 분석기에 잘못된 프레임이 보여요. 리셋 후 첫 프레임은 맞아요. 그다음부터는 이전 프레임의 바이트가 32바이트 블록 단위로 섞여 있어요. 가끔 앞쪽은 새 데이터고 뒤쪽은 옛날 데이터예요.

결국 컴파일러가 복사 루프를 enable 쓰기 뒤로 옮겨요. 근데 -O0도 똑같아요. tx_buf를 volatile로 해도 똑같아요. 루프 뒤에 DSB를 넣어도 똑같아요. 디스어셈블리에서는 store가 CR 쓰기보다 먼저 나와요.

for (size_t i = 0; i < n; i++)
    tx_buf[i] = data[i];

__asm volatile ("dsb 0xF" ::: "memory");

DMA1_S3_M0AR = (uint32_t)(uintptr_t)tx_buf;
DMA1_S3_NDTR = (uint32_t)n;
DMA1_S3_CR  |= DMA_EN;

캐시는 board_init()에서 켜요:

SystemInit();
#ifdef USE_DCACHE
    SCB_EnableICache();
    SCB_EnableDCache();
#endif

빌드:

make clean && make OPT=-O0 DEFS=-DUSE_DCACHE -DTRACE_ITM

순서는 맞아 보이는데 데이터는 여전히 옛날 거예요. 또 뭐가 있을까요?

영어에서 번역원문 보기

이어지는 대화 8

서로의 근거를 따라
  1. 고유 링크

    DSB는 store 순서만 맞춰 주고, 캐시 라인을 RAM에 write back 해 주지는 않아요. EN 쓰기 전에 tx_buf에 SCB_CleanDCache_by_Addr를 호출하세요? 그리고 tx_buf가 32바이트로 정렬돼 있나요, 아닌가요?

    영어에서 번역원문 보기
  2. 고유 링크

    아니요, clean은 어디서도 안 불러요. DSB면 충분하다고 생각했는데 store 순서만 맞춰 주는 거네요. 32바이트 블록이라는 증상도 그게 라인 크기라서 맞아떨어져요.

    tx_buf는 평범한 static 배열이고 aligned 속성은 없어요. 결국 다른 변수랑 같은 라인을 쓰게 되겠네요.

    2년 전부터 수업에서 이렇게 하고 있어요: 버퍼는 32로 정렬하고, 크기는 32의 배수로 하고, EN 쓰기 전에 주소 기준으로 clean해요. 예전에 F7에서 RX 버퍼 옆에 있던 변수가 invalidate 때문에 깨진 적이 있어요. 지금 해볼게요.

    static uint8_t tx_buf[64] __attribute__((aligned(32)));
    SCB_CleanDCache_by_Addr((uint32_t *)tx_buf, sizeof tx_buf);
    영어에서 번역원문 보기
  3. 고유 링크

    이제 돼요. 애널라이저에서 뒤쪽 블록까지 모든 프레임이 맞게 나와요.

    anselm 말이 맞았어요. DSB는 store 순서만 맞춰 주고, 라인은 캐시에 dirty로 남아서 DMA가 오래된 RAM을 읽는 거였어요. CMSIS clean 함수가 안에서 DSB를 하니까 제가 넣은 DSB는 필요 없어요.

    clean은 복사 루프 뒤, M0AR 쓰기 앞에 와야 해요. 루프 앞에서 clean하면 아무 효과가 없어요.

    RX는 반대예요. DMA를 시작하기 전에 invalidate해야 해요. 안 그러면 언젠가 CPU가 dirty 라인을 evict하면서 새로 들어온 데이터를 덮어써요. 제 학생 한 명이 이것 때문에 저녁 하루를 통째로 날렸어요.

    영어에서 번역원문 보기
  4. 고유 링크

    TX 쪽 주의할 점: 다른 변수와 공유하는 라인을 clean하는 건 write back만 하니까 무해해요. 위험한 건 invalidate예요.

    대안으로 DMA 버퍼를 전용 섹션에 두고 그 영역을 MPU에서 non-cacheable로 지정할 수도 있어요. 그러면 clean이나 invalidate 호출이 아예 필요 없어요. SPI 버퍼에 한 번 이렇게 했는데 버그가 다시는 안 나왔어요.

    영어에서 번역원문 보기
  5. 고유 링크

    D-cache 때문이에요. DSB는 store 순서만 맞춰 주고, 라인은 dirty로 남아서 DMA가 오래된 RAM을 읽어요. 복사 루프 뒤, EN 쓰기 전에 tx_buf를 주소 기준으로 clean하고 32로 정렬하세요. 아니면 버퍼를 DTCM에 두세요 (캐시가 안 걸린다고 어디 블로그에서 읽었어요).

    영어에서 번역원문 보기

같은 질문의 다른 갈래

  1. C 콜백에서는 설정해 둔 아레나가 사라진다

    C 라이브러리가 내 Odin 코드로 콜백을 부르는데, 콜백은 등록 전에 설정해 둔 context를 보지 못한다. proc "c"에는 암묵적 context가 없어서 직접 만들어야 하고, runtime.default_contex…

    답글 5최근 활동 odinc
  2. TMR3 ISR에서 플래그가 w1c이고 클리어하는데도 캡처 이벤트가 빠져요

    리플로우 오븐 컨트롤러예요. Cortex-M0 클론, arm-none-eabi-gcc -O2, Fedora 환경이고요. TMR3는 1 MHz로 돌아요. Update 이벤트가 1 ms tick이고, CH1은 교류 전원 zero…

    답글 5최근 활동 c
  3. C 포인터에 .?를 쓰는 것은 단언이다

    C에서 돌아온 포인터에 쓰는 .?는 검사가 아니라 단언입니다. 여기서 널은 불가능하다고 말하는 것입니다. 안전 검사 빌드에서 널이면 패닉이 나고, ReleaseFast나 ReleaseSmall에서는 정의되지 않은 동작입니다.…

    답글 3최근 활동 zig