Skip to content
오늘을살자
Go back

lwIP에서 RX는 되는데 payload가 가끔 깨진다: DMA 버퍼와 D-Cache 순서를 같이 봐야 한다

Edit page

lwIP를 Cortex-M7이나 A 계열 코어에 포팅하면
이상하게 패킷 내용만 가끔 틀어지는 경우가 있다.

이럴 때 pbuf나 프로토콜 파서부터 오래 파기 쉽다.
하지만 먼저 볼 곳은 종종 더 아래다.

오늘 메모는 lwIP zero-copy 또는 DMA 버퍼 경로에서 D-Cache 일관성이 깨질 때 보이는 전형적인 증상이다.

결론부터

DMA와 CPU가 같은 메모리를 같이 만질 때는
“메모리에 썼다”와 “CPU가 그 값을 본다”가 같은 말이 아니다.

대략 이런 규칙을 먼저 떠올리는 편이 빠르다.

RX:
DMA가 메모리에 쓴 뒤
CPU가 읽기 전에 cache invalidate 필요

TX:
CPU가 메모리에 쓴 뒤
DMA가 읽기 전에 cache clean 필요

이 순서가 빠지면 현상은 네트워크 버그처럼 보이지만,
실제로는 캐시된 오래된 바이트를 읽는 문제일 수 있다.

왜 “가끔만” 터지나

캐시 이슈는 항상 100% 재현되지 않는다.

그래서 처음에는 DMA 드라이버보다
“상대가 이상한 값을 보냈나?” 쪽으로 의심이 가기 쉽다.

전형적인 RX 증상

수신에서는 보통 이런 모양으로 나타난다.

흔한 흐름은 이렇다.

DMA writes RX buffer
-> CPU reads cached old line
-> pbuf payload가 가끔 깨진 것처럼 보임

이 경우 pbuf->len, tot_len, 프로토콜 파싱은 모두 맞아도
바이트 값 자체가 틀려 보인다.

전형적인 TX 증상

송신 쪽은 반대로 이런 식이다.

흐름으로 보면:

CPU updates TX buffer
-> cache line stays dirty in CPU
-> DMA reads old memory image
-> wire에는 이전 값이 섞여 나감

이때 lwIP API는 정상처럼 보인다.
udp_send()low_level_output() 반환값도 멀쩡하다.

그래서 송신 성공 여부보다
DMA가 실제로 읽은 메모리 이미지를 의심해야 한다.

pbuf 문제와 어떻게 구분하나

둘 다 “랜덤하게 깨진다”로 보여서 헷갈린다.

대략 이렇게 나눠 보면 빠르다.

즉 이번 이슈는
메모리 ownership보다 메모리 가시성 쪽에 가깝다.

어디서 순서를 놓치기 쉬운가

1. RX descriptor 완료 직후 invalidate를 안 한다

드라이버가 DMA 완료 플래그만 보고
곧바로 CPU가 payload를 읽게 하면 위험하다.

ETH RX done
-> descriptor 확인
-> invalidate 없이 payload parse
-> 가끔 이전 cache line 값 사용

2. TX enqueue 뒤 clean을 늦게 하거나 빼먹는다

앱이 버퍼를 채운 뒤
DMA start 전에 clean이 필요할 수 있다.

특히 scatter-gather나 chained buffer에서는
한 조각만 clean 하고 끝내는 실수도 잘 나온다.

3. cache line 정렬을 무시한다

invalidate/clean을 했는데도 문제가 남는 경우가 있다.
이때는 범위 정렬이 흔한 원인이다.

즉 “API를 불렀다”보다
어느 주소부터 어느 길이까지 처리했는지가 더 중요하다.

구현 쪽에서 무난한 패턴

프로젝트마다 HAL 이름은 다르지만,
경계는 보통 이렇게 두는 편이 낫다.

RX path:
DMA complete
-> invalidate aligned buffer range
-> pbuf 또는 app parser에 전달

TX path:
app fills buffer
-> clean aligned buffer range
-> DMA kick

그리고 이 규칙은 lwIP 바깥까지 포함해서 한 군데에 모아두는 편이 좋다.

세 군데가 각자 제각각 처리하면
몇 주 뒤 다시 같은 버그가 돌아온다.

로그를 이렇게 남기면 빨라진다

이 이슈는 패킷 캡처와 메모리 dump를 같이 봐야 풀린다.

  1. DMA 완료 시각
  2. invalidate/clean 호출 시각
  3. 버퍼 주소와 길이
  4. wire 상 payload 일부
  5. CPU가 읽은 payload 일부

이 다섯 줄이 있으면
“상대가 잘못 보냈다”와
“내 CPU가 stale cache를 읽었다”를 빨리 가를 수 있다.

빠른 체크리스트

  1. RX 버퍼를 CPU가 읽기 전에 cache invalidate를 하는지 확인
  2. TX 버퍼를 DMA가 읽기 전에 cache clean을 하는지 확인
  3. invalidate/clean 범위가 cache line 기준으로 정렬되는지 확인
  4. zero-copy RX/TX 경로와 복사 경로가 서로 다른 캐시 규칙을 쓰지 않는지 확인
  5. 패킷 길이 문제와 바이트 내용 문제를 분리해서 로그에 남기는지 확인

한 줄 요약

lwIP에서 길이는 맞는데 payload 내용만 가끔 깨지면 pbuf보다 먼저 DMA 버퍼와 D-Cache 일관성을 보고, RX 전 invalidate와 TX 전 clean 순서를 cache line 기준으로 맞추는 편이 빠르다.

추천 키워드

lwIP, DMA, D-Cache, RX buffer, zero-copy, embedded network


DevBJ | 오늘을살자, Log Today


Edit page
Share this post on:

Previous Post
Wireshark Reassembled TCP Segments 의미: 여러 TCP 조각이 하나의 메시지로 보일 때
Next Post
DoIP에서 UDS를 ISO-TP처럼 자르면 안 된다: Diagnostic Message 경계를 먼저 봐야 한다