임베디드 일기장

SocketCAN CAN FD 실전 설정 — ip link 구성, write() EILSEQ 에러, ID 마스크 필터링까지 본문

Linux

SocketCAN CAN FD 실전 설정 — ip link 구성, write() EILSEQ 에러, ID 마스크 필터링까지

jaeuuu 2026. 7. 11. 13:52
반응형

리눅스에서 CAN FD를 쓰다 보면 세 군데서 순서대로 막힌다. 인터페이스 설정, write()가 뱉는 정체불명의 에러, 그리고 수신 필터. 세 가지를 실전 순서대로 정리한다. 환경은 i.MX8MP(FlexCAN)이지만 SocketCAN 공통 내용이라 다른 컨트롤러에도 그대로 적용된다.

1. 인터페이스 설정: 중재 구간과 데이터 구간은 따로 논다

CAN FD는 한 프레임 안에서 속도가 두 번 바뀐다. 중재(arbitration) 구간은 클래식 CAN과 호환되는 속도로, 데이터 구간은 BRS 비트가 켜져 있으면 고속으로 달린다. 그래서 설정도 두 벌이다.

ip link set can0 down
ip link set can0 type can \
    bitrate 500000  sample-point 0.875 \
    dbitrate 2000000 dsample-point 0.75 \
    fd on
ip link set can0 txqueuelen 1000
ip link set can0 up

dbitrate/dsample-point가 데이터 구간용이다. 샘플포인트는 중재 구간은 CiA 권장 87.5%, 데이터 구간은 보통 더 낮게(70~80%) 잡는다. txqueuelen을 같이 만지는 이유는 기본값이 10에 불과해서다. 다채널 동시 송신 구성이면 큐가 순식간에 차서 ENOBUFS가 터진다.

설정했으면 반드시 실제 적용값을 확인해야 한다.

ip -details link show can0

BRP와 타임 세그먼트가 정수라서 요청한 샘플포인트와 실제 적용값이 다를 수 있다. 특히 FlexCAN처럼 클럭 소스가 정해져 있는 컨트롤러는 도달 가능한 조합이 제한된다. 버스 위 노드끼리 샘플포인트가 어긋나면 간헐 에러라는 최악의 형태로 나타나므로, 모든 노드에서 이 명령으로 실측값을 맞추는 게 정석이다.

부팅 시 자동 설정이 필요하면 systemd oneshot 서비스로 위 스크립트를 감싸는 방법이 systemd 버전 의존성 없이 가장 이식성이 좋다. Yocto 양산 이미지라면 해당 유닛과 스크립트를 레시피(SYSTEMD_SERVICE:${PN})로 rootfs에 굽는다.

2. write()가 EILSEQ를 뱉을 때

CAN FD로 넘어가면서 가장 많이 만나는 에러가 이것이다.

write(): Invalid or incomplete multibyte or wide character

"멀티바이트 문자"라는 메시지에 당황하게 되는데, SocketCAN에서 EILSEQ는 프레임 검증 실패로 재활용된 에러 코드다. 체크 순서:

① 전송 길이가 구조체 크기와 정확히 일치하는가. write()에 넘기는 길이는 sizeof(struct can_frame)(16) 또는 sizeof(struct canfd_frame)(72) 둘 중 하나여야 한다. 어중간한 길이는 거부된다.

② FD 프레임인데 소켓에 FD를 안 켰는가. canfd_frame을 쓰려면 소켓 옵션이 필요하다.

int enable = 1;
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &enable, sizeof(enable));

③ len 값이 유효한가. 클래식 CAN은 0 ~ 8. CAN FD는 아무 값이나 되는 게 아니라 8, 12, 16, 20, 24, 32, 48, 64만 유효하다. 페이로드가 9바이트면 len=9가 아니라 len=12로 올리고 나머지를 패딩해야 한다. 이 이산적인 길이 체계를 모르면 특정 크기에서만 실패하는 미스터리를 겪는다.

④ 인터페이스가 FD로 올라와 있는가. ip link에서 MTU를 보면 즉시 판별된다 — 클래식은 16, FD는 72. MTU 16인 인터페이스에 FD 프레임을 보내면 커널의 프레임 체크에서 걸린다.

3. 수신 필터: 매칭식 하나만 이해하면 된다

수신 측에서 필요한 ID만 골라 받으려면 CAN_RAW_FILTER를 쓴다. 동작은 매칭식 하나로 요약된다.

(수신 ID & can_mask) == (can_id & can_mask)

mask에서 1로 세운 비트만 비교하고 0인 비트는 무시한다. 이 원리에서 두 가지 사용 패턴이 나온다.

정확히 한 ID만 (SFF 기준)

struct can_filter f = {
    .can_id   = 0x123,
    .can_mask = CAN_SFF_MASK | CAN_EFF_FLAG,  /* 0x7FF + EFF 차단 */
};
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &f, sizeof(f));

정렬된 2ⁿ 블록 (예: 0x40~0x7F, 64개)

struct can_filter f = {
    .can_id   = 0x40,                   /* 구간 base */
    .can_mask = 0x7C0 | CAN_EFF_FLAG,   /* 하위 6비트 무시 */
};

블록 필터의 전제는 base & mask == base가 성립하는 정렬된 구간이라는 것이다. 임의 범위(예: 0x45~0x92)는 마스크 하나로 표현이 안 되므로 필터 배열을 여러 개 등록해 조합한다.

mask에 왜 CAN_EFF_FLAG(0x80000000)를 OR하는지가 실전 포인트다. 이 비트를 mask에 세우고 can_id에는 세우지 않으면, 매칭식상 수신 프레임의 EFF 비트도 0이어야 통과한다. 즉 29비트 확장 프레임이 하위 11비트 우연 일치로 새어 들어오는 것을 차단한다. 이걸 빼먹으면 확장 ID 트래픽이 섞인 버스에서 유령 수신이 생긴다.

4. 정리

  1. CAN FD 설정은 중재/데이터 구간 두 벌이고, ip -details로 실제 적용값을 전 노드에서 대조하라
  2. txqueuelen 기본 10은 다채널 구성에서 거의 확실히 부족하다
  3. write의 EILSEQ는 인코딩 에러가 아니라 프레임 검증 실패다 — 구조체 크기, CAN_RAW_FD_FRAMES, FD의 이산 len 체계, 인터페이스 MTU 순으로 점검
  4. 필터는 매칭식 (recv & mask) == (id & mask) 하나로 이해하고, SFF만 받을 거면 mask에 CAN_EFF_FLAG를 반드시 포함시켜라

i.MX8MP FlexCAN, 커널 5.15.71 기준 실측 내용입니다.

반응형