문제 상황
DMA에서 지속적으로 데이터를 쓰는 버퍼가 있다고 할 떄, 프로세서는 해당하는 메모리의 데이터를 이미 캐싱했을 수 있다.
즉, 실제 메모리에 저장된 값과 프로세서가 캐싱하여 처리하는 데이터가 다를 수 있다.
이는 프로세서에서 데이터를 처리할 경우 문제를 야기할 수 있게된다.
이러한 문제는 일반적인 멀티프로세싱 상태와는 다르게, DMA가 데이터를 메모리에 직접 쓰더라도 CPU 캐시를 무효화하거나 갱신하지 않기 때문에 발생한다.
DMA 작업 이후에도 CPU 캐시가 이전 데이터를 유지하고 있어, 메모리에 저장된 최신 값과 CPU가 참조하는 데이터가 불일치할 수 있다.
해결 방안
기존 방식
void ProcessFrame_Line(uint8_t **src, uint32_t xsize) {
ProcessSingleLine(*src, xsize);
*src += xsize * 2;
}
void ProcessSingleLine(uint8_t *srcLine, uint32_t xsize) {
for (uint32_t j = 0; j < xsize; j += 2) {
uint8_t y1 = srcLine[0];
uint8_t u = srcLine[1];
uint8_t y2 = srcLine[2];
uint8_t v = srcLine[3];
uint16_t rgb1, rgb2;
CONVERT_YUV_TO_RGB565(y1, u, v, &rgb1);
CONVERT_YUV_TO_RGB565(y2, u, v, &rgb2);
uint16_t *src16 = (uint16_t *)srcLine;
src16[0] = rgb1;
src16[1] = rgb2;
srcLine += 4;
}
}위와 같이 아무런 처리도 없이 DMA에 쓴 버퍼를 그대로 처리에 사용하고 있다. 빈번한 사용으로 인해서 해당 라인은 캐시에 올라가는 경우가 많아지고, 이에 따라서 데이터 처리에 일부 문제가 생겼다.
변경 방식
void ProcessFrame_Line(uint8_t **src, uint32_t xsize) {
ProcessSingleLine(*src, xsize);
*src += xsize * 2;
}
void ProcessSingleLine(uint8_t *srcLine, uint32_t xsize) {
// 캐시 일관성 보장
uintptr_t addr = (uintptr_t)srcLine;
uintptr_t alignedAddr = addr & ~(CACHE_LINE_SIZE - 1);
size_t alignedSize = ((xsize * 2 + CACHE_LINE_SIZE - 1) / CACHE_LINE_SIZE) * CACHE_LINE_SIZE;
SCB_InvalidateDCache_by_Addr((uint32_t *)alignedAddr, alignedSize);
//////////////////////////
for (uint32_t j = 0; j < xsize; j += 2) {
uint8_t y1 = srcLine[0];
uint8_t u = srcLine[1];
uint8_t y2 = srcLine[2];
uint8_t v = srcLine[3];
uint16_t rgb1, rgb2;
CONVERT_YUV_TO_RGB565(y1, u, v, &rgb1);
CONVERT_YUV_TO_RGB565(y2, u, v, &rgb2);
uint16_t *src16 = (uint16_t *)srcLine;
src16[0] = rgb1;
src16[1] = rgb2;
srcLine += 4;
}
}위와 같이 SCB_InvalidateDCache_by_Addr명령어를 사용하여 캐시를 유효하지 않게 하여 메모리에서 다시 데이터를 가져올 수 있도록 처리했다.
일반적으로 사용할 수 있는 방식은 아래와 같을 것이다.
캐시 일관성 보장 방법:
- DMA → CPU: 데이터 무효화(Invalidate).
- CPU → DMA: 데이터 쓰기 반영(Flush).