CAN Bus (Controller Area Network)

CAN Bus와 Linux SocketCAN을 실시간 제어 네트워크 관점에서 심층 분석합니다. 클래식 CAN 2.0A/B와 CAN FD/CAN XL 프레임 구조, 비트 타이밍과 샘플 포인트 계산, 중재(Arbitration) 메커니즘, 오류 프레임 처리와 Fault Confinement, net_device 기반 드라이버 모델, SocketCAN RAW/BCM/ISOTP/J1939 소켓 프로토콜 활용, CAN Gateway 라우팅, 버스 오프 복구와 상태 모니터링, timestamp/queue 설정을 통한 지연시간 관리, 차량/산업 장비 환경에서의 진단 프레임 운용, candump/cansniffer/iproute2 기반 디버깅 절차까지 안정적 필드 운영을 위한 핵심 내용을 다룹니다.

전제 조건: 네트워크 스택디바이스 드라이버 문서를 먼저 읽으세요. CAN은 이더넷과 달리 메시지 기반 버스 토폴로지로, 일반 소켓 프로그래밍과 다른 전송 제약과 하드웨어 모델을 가집니다.
일상 비유: CAN 버스는 회의실의 발언 규칙과 비슷합니다. 모든 참석자(노드)가 동시에 발언(전송)을 시도할 수 있지만, 직급(CAN ID)이 높은 사람이 우선권을 가집니다. 발언 내용은 모든 사람에게 들리고(브로드캐스트), 실수(에러)를 반복하면 자동으로 퇴장(Bus-off)됩니다.

핵심 요약

  • CAN 프레임 — CAN 2.0은 최대 8바이트, CAN FD는 최대 64바이트, CAN XL은 최대 2048바이트 페이로드를 전송합니다.
  • SocketCAN — CAN을 리눅스 네트워크 스택에 통합하여 BSD 소켓 API(PF_CAN)로 통신합니다.
  • 비트 타이밍 — 전파 지연, 위상 세그먼트, 샘플 포인트를 조합하여 신뢰성 있는 비트 동기화를 달성합니다.
  • Fault Confinement — TEC/REC 에러 카운터로 결함 노드를 자동 격리하여 버스를 보호합니다.
  • 상위 프로토콜 — ISO-TP(분할 전송), J1939(상용차), CANopen(산업 자동화) 등이 CAN 위에서 동작합니다.

단계별 이해

  1. 물리 계층 이해
    CAN High/Low 차동 신호, 종단 저항, 버스 토폴로지를 먼저 이해합니다.
  2. 프레임 구조 학습
    CAN 2.0/FD/XL 프레임의 각 필드(ID, DLC, Data, CRC)를 파악합니다.
  3. SocketCAN 실습
    vcan으로 가상 인터페이스를 만들고, candump/cansend로 통신을 확인합니다.
  4. 프로토콜 확장
    ISO-TP, J1939 등 상위 프로토콜을 학습하고 실제 차량 진단에 적용합니다.
관련 문서: 네트워크 스택 (프로토콜 스택), 디바이스 드라이버 (CAN 컨트롤러), I2C/SPI/GPIO (MCP2515 SPI-CAN), USB (USB-CAN 어댑터), Network Device 드라이버 (net_device 구조)

개요

CAN(Controller Area Network)은 Robert Bosch GmbH가 1986년에 발표한 멀티마스터 시리얼 버스 프로토콜입니다. 원래 자동차 ECU(Electronic Control Unit) 간 통신을 위해 설계되었으나, 현재는 산업 자동화, 의료 기기, 항공우주, 해양, 철도 등 다양한 분야에서 사용됩니다.

CAN 버스 특성

특성설명비고
멀티마스터모든 노드가 버스 마스터 역할 가능중앙 컨트롤러 불필요
메시지 우선순위CAN ID로 결정 (낮은 ID = 높은 우선순위)비파괴적 비트 중재
브로드캐스트모든 노드가 모든 메시지 수신수신 필터로 선택 수신
에러 검출CRC, ACK, Bit Monitoring, Form Check, Stuff Rule5가지 에러 검출 메커니즘
Fault Confinement결함 노드 자동 격리 (Bus-off)TEC/REC 에러 카운터 기반
최대 노드 수이론적 제한 없음 (물리적으로 약 32~110개)트랜시버 구동 능력에 의존
버스 길이최대 약 5km (10 Kbps) ~ 40m (1 Mbps)속도와 반비례

CAN 프로토콜 버전 비교

항목CAN 2.0ACAN 2.0BCAN FDCAN XL
표준ISO 11898-1ISO 11898-1ISO 11898-1:2015CiA 610-1
ID 비트11-bit (SFF)29-bit (EFF)11/29-bit11/29-bit + 우선순위
최대 데이터8 bytes8 bytes64 bytes2048 bytes
DLC 값0~80~80~8,12,16,20,24,32,48,640~2048 (가변)
비트레이트최대 1 Mbps최대 1 Mbps중재: 1Mbps, 데이터: 8Mbps최대 10 Mbps
BRS없음없음있음 (Bit Rate Switch)있음
Linux 지원CAN_RAWCAN_RAWCAN_RAW (FD 모드)6.3+ (canxl_frame)

CAN 프로토콜 계층

계층기능표준
Application LayerCANopen, J1939, UDS, ISO-TP, DeviceNetCiA 301, SAE J1939, ISO 14229
Transport Layer분할 전송, 흐름 제어ISO 15765-2 (ISO-TP)
Data Link Layer프레임 생성/파싱, 중재, 에러 검출ISO 11898-1
Physical LayerCAN High/Low 차동 신호, 트랜시버ISO 11898-2 (고속), ISO 11898-3 (저속)
CAN 프로토콜 계층과 Linux 매핑 Application Layer CANopen / J1939 / UDS / DeviceNet Linux: CAN_J1939, CAN_ISOTP net/can/j1939/ , net/can/isotp.c Transport Layer ISO 15765-2 (ISO-TP) 분할/재조합 Linux: CAN_RAW, CAN_BCM, CAN_GW net/can/raw.c , net/can/bcm.c Data Link Layer CAN 2.0 / FD / XL 프레임, 중재, CRC Linux: SocketCAN Core net/can/af_can.c , include/linux/can.h Physical Layer ISO 11898-2/3, 차동 신호, 트랜시버 Linux: CAN Device Drivers drivers/net/can/ (vcan, slcan, m_can 등) CAN_H / CAN_L (차동 버스, 120 Ohm 종단 저항)

CAN 프레임 구조

CAN 프레임은 버스에서 전송되는 데이터의 기본 단위입니다. 프레임 유형에 따라 Data Frame, Remote Frame, Error Frame, Overload Frame으로 구분됩니다.

CAN 2.0A (Standard Frame Format) 비트 구조 SOF 1b Identifier CAN ID (우선순위) 11b RTR 1b IDE 1b r0 1b DLC 4b Data Field 페이로드 (0~8 bytes) 0~64b CRC 오류 검출 16b ACK 2b EOF 7b IFS 3b Arbitration Field Control Data 최소 44비트 (DLC=0) ~ 최대 108비트 (DLC=8) + 비트 스터핑 오버헤드 500Kbps 기준: 최소 88us ~ 최대 216us 전송 시간

CAN 2.0 프레임 (struct can_frame)

#include <linux/can.h>

struct can_frame {
    canid_t can_id;         /* 11-bit (Standard) or 29-bit (Extended) + 플래그 */
    union {
        __u8 len;            /* 프레임 데이터 길이 (커널 6.x 이후) */
        __u8 can_dlc;        /* Data Length Code (0~8, 호환용) */
    };
    __u8    __pad;          /* 패딩 */
    __u8    __res0;         /* 예약 */
    __u8    len8_dlc;       /* 원래 DLC 값 (9~15 보존용) */
    __u8    data[8]  __attribute__((aligned(8)));  /* 페이로드 */
};

/* CAN ID 플래그 (can_id 상위 3비트) */
#define CAN_EFF_FLAG  0x80000000U  /* Extended Frame Format (29-bit ID) */
#define CAN_RTR_FLAG  0x40000000U  /* Remote Transmission Request */
#define CAN_ERR_FLAG  0x20000000U  /* Error Message Frame */

/* ID 마스크 */
#define CAN_SFF_MASK  0x000007FFU  /* Standard Frame: 11-bit ID */
#define CAN_EFF_MASK  0x1FFFFFFFU  /* Extended Frame: 29-bit ID */
#define CAN_ERR_MASK  0x1FFFFFFFU  /* Error 프레임 클래스 마스크 */

/* MTU 상수 */
#define CAN_MTU     sizeof(struct can_frame)      /* 16 bytes */
#define CANFD_MTU   sizeof(struct canfd_frame)    /* 72 bytes */
#define CANXL_MTU   sizeof(struct canxl_frame)    /* 2080 bytes */
can_id 해석: can_id는 단순한 ID가 아닙니다. 상위 3비트가 플래그(EFF/RTR/ERR)이고, 나머지가 실제 ID입니다. can_id & CAN_SFF_MASK로 Standard ID를, can_id & CAN_EFF_MASK로 Extended ID를 추출합니다.

CAN FD 프레임 (struct canfd_frame)

CAN FD(Flexible Data-rate)는 ISO 11898-1:2015에서 표준화되었으며, 최대 64바이트 페이로드와 BRS(Bit Rate Switch)를 통한 데이터 영역 고속 전송을 지원합니다.

struct canfd_frame {
    canid_t can_id;         /* 29-bit CAN ID + EFF/RTR/ERR 플래그 */
    __u8    len;            /* 실제 데이터 길이 (0, 1, ..., 8, 12, 16, 20, 24, 32, 48, 64) */
    __u8    flags;          /* CANFD_BRS, CANFD_ESI, CANFD_FDF */
    __u8    __res0;         /* 예약 */
    __u8    __res1;         /* 예약 */
    __u8    data[64]  __attribute__((aligned(8)));  /* 페이로드 */
};

#define CANFD_BRS  0x01  /* Bit Rate Switch: 데이터 영역 고속 전송 */
#define CANFD_ESI  0x02  /* Error State Indicator: 송신 노드가 Error Passive 상태 */
#define CANFD_FDF  0x04  /* FD Format: CAN FD 프레임 표시 (6.x+) */

CAN FD DLC-to-Length 매핑

DLC0123456789101112131415
CAN 2.00123456788888888
CAN FD01234567812162024324864
/* DLC → 길이 변환 (커널 내부) */
static const u8 can_fd_dlc2len[] = {
    0, 1, 2, 3, 4, 5, 6, 7,
    8, 12, 16, 20, 24, 32, 48, 64
};

/* 길이 → DLC 변환 (가장 작은 DLC 선택) */
u8 can_fd_len2dlc(u8 len);   /* 예: 10 → 9 (12바이트로 패딩) */

CAN XL 프레임 (struct canxl_frame)

CAN XL(eXtra Long)은 CiA 610에서 정의된 차세대 CAN으로, 최대 2048바이트 페이로드와 10 Mbps 데이터 전송을 지원합니다. Linux 커널 6.3부터 지원됩니다.

/* include/uapi/linux/can.h (커널 6.3+) */
struct canxl_frame {
    canid_t prio;           /* 11-bit 우선순위 + CANXL_XLF 플래그 */
    __u8    flags;          /* CANXL_SEC 등 추가 플래그 */
    __u8    sdt;            /* SDU (Service Data Unit) Type */
    __u16   len;            /* 데이터 길이 (1~2048) */
    __u32   af;             /* Acceptance Field (32-bit) */
    __u8    data[2048]  __attribute__((aligned(8)));
};

#define CANXL_XLF     0x80  /* prio에 설정: XL Frame 식별 */
#define CANXL_SEC     0x01  /* flags: 보안(SecOC) 활성화 */
#define CANXL_MIN_DLEN  1   /* 최소 데이터 길이 */
#define CANXL_MAX_DLEN  2048 /* 최대 데이터 길이 */
CAN XL 활용: CAN XL은 차량 내 이더넷(100BASE-T1)과 기존 CAN FD 사이의 간극을 메우기 위해 설계되었습니다. 이더넷보다 낮은 비용과 배선 복잡도로 큰 데이터를 전송할 수 있어, ADAS(첨단 운전자 보조 시스템)와 OTA(Over-The-Air) 업데이트에 적합합니다.

SocketCAN 아키텍처

SocketCAN은 CAN 통신을 리눅스 네트워크 서브시스템에 통합하여 표준 BSD 소켓 API(PF_CAN)로 CAN 메시지를 송수신할 수 있게 합니다. 캐릭터 디바이스 방식의 기존 CAN 드라이버와 달리, net_device 기반으로 동작하여 ifconfig/ip 명령, 패킷 필터, 방화벽 등 기존 네트워크 도구와 통합됩니다.

SocketCAN 아키텍처 User Space candump/cansend Application (C) python-can isotpsend/j1939 cangw socket() / bind() / read() / write() / setsockopt() — 시스템 콜 경계 SocketCAN 프로토콜 계층 (net/can/) CAN_RAW raw.c SOCK_RAW CAN_BCM bcm.c SOCK_DGRAM CAN_ISOTP isotp.c SOCK_DGRAM CAN_J1939 j1939/ SOCK_DGRAM CAN_GW gw.c Netlink af_can.c PF_CAN 코어 모듈 Linux Network Layer (net_device / sk_buff) can_rx_register() / can_send() / dev_queue_xmit() CAN Device Drivers (drivers/net/can/) vcan vxcan slcan m_can c_can mcp251x Physical CAN Bus (CAN_H / CAN_L)

SocketCAN 프로토콜 패밀리

프로토콜상수소켓 타입용도커널 모듈
CAN_RAW1SOCK_RAWRaw CAN 프레임 송수신can-raw
CAN_BCM2SOCK_DGRAM주기적 전송, 변경 감지can-bcm
CAN_TP163-ISO 15765-3 (미구현)-
CAN_TP204-VW TP 2.0 (미구현)-
CAN_MCNET5-Bosch MCNet (미구현)-
CAN_ISOTP6SOCK_DGRAMISO 15765-2 분할 전송can-isotp
CAN_J19397SOCK_DGRAMSAE J1939 상용차 통신can-j1939

SocketCAN API

CAN RAW 소켓

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <net/if.h>

int main(void)
{
    int s;
    struct sockaddr_can addr;
    struct ifreq ifr;

    /* 1. CAN RAW 소켓 생성 */
    s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
    if (s < 0) {
        perror("socket");
        return 1;
    }

    /* 2. 인터페이스 이름 → 인덱스 변환 */
    strncpy(ifr.ifr_name, "can0", IFNAMSIZ - 1);
    if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) {
        perror("ioctl SIOCGIFINDEX");
        close(s);
        return 1;
    }

    /* 3. 소켓을 CAN 인터페이스에 바인딩 */
    memset(&addr, 0, sizeof(addr));
    addr.can_family = AF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;

    if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("bind");
        close(s);
        return 1;
    }

    /* 4. CAN 프레임 송신 */
    struct can_frame tx_frame = {
        .can_id  = 0x123,
        .len     = 4,
        .data    = {0xDE, 0xAD, 0xBE, 0xEF},
    };

    if (write(s, &tx_frame, sizeof(tx_frame)) != sizeof(tx_frame)) {
        perror("write");
        close(s);
        return 1;
    }

    /* 5. CAN 프레임 수신 */
    struct can_frame rx_frame;
    ssize_t nbytes = read(s, &rx_frame, sizeof(rx_frame));
    if (nbytes < 0) {
        perror("read");
        close(s);
        return 1;
    }

    printf("RX: ID=0x%03X [%d] ", rx_frame.can_id & CAN_SFF_MASK, rx_frame.len);
    for (int i = 0; i < rx_frame.len; i++)
        printf("%02X ", rx_frame.data[i]);
    printf("\\n");

    close(s);
    return 0;
}
모든 인터페이스 수신: addr.can_ifindex = 0으로 바인딩하면 시스템의 모든 CAN 인터페이스에서 프레임을 수신합니다. 이때 recvfrom()을 사용하여 어느 인터페이스에서 왔는지 확인할 수 있습니다.

CAN 필터링

/* CAN ID 필터 구조체 */
struct can_filter {
    canid_t can_id;    /* 매칭할 CAN ID */
    canid_t can_mask;  /* 매칭 마스크: (received_id & mask) == (can_id & mask) */
};

/* 필터 설정 예제 */
struct can_filter rfilter[3];

/* 필터 1: ID 0x100~0x1FF 범위 수신 */
rfilter[0].can_id   = 0x100;
rfilter[0].can_mask = 0x700;

/* 필터 2: ID 0x200 정확히 매칭 */
rfilter[1].can_id   = 0x200;
rfilter[1].can_mask = CAN_SFF_MASK;

/* 필터 3: Extended ID 0x18FEF100 매칭 */
rfilter[2].can_id   = 0x18FEF100 | CAN_EFF_FLAG;
rfilter[2].can_mask = CAN_EFF_MASK | CAN_EFF_FLAG;

setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));

/* 모든 프레임 수신 (필터 비활성화) */
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);

/* 역 필터 (지정된 ID를 제외하고 모두 수신) */
canid_t exclude_id = 0x123;
rfilter[0].can_id   = exclude_id | CAN_INV_FILTER;
rfilter[0].can_mask = CAN_SFF_MASK | CAN_INV_FILTER;
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter[0]));

CAN RAW 소켓 옵션

옵션타입설명기본값
CAN_RAW_FILTERstruct can_filter[]수신 필터 설정모두 수신
CAN_RAW_ERR_FILTERcan_err_mask_t에러 프레임 필터에러 수신 안 함
CAN_RAW_LOOPBACKint (0/1)로컬 루프백 활성화1 (활성)
CAN_RAW_RECV_OWN_MSGSint (0/1)자신이 보낸 메시지 수신0 (비활성)
CAN_RAW_FD_FRAMESint (0/1)CAN FD 프레임 활성화0 (비활성)
CAN_RAW_XL_FRAMESint (0/1)CAN XL 프레임 활성화 (6.3+)0 (비활성)
CAN_RAW_JOIN_FILTERSint (0/1)필터를 OR 대신 AND로 결합0 (OR)
/* 에러 프레임 수신 활성화 */
can_err_mask_t err_mask = (CAN_ERR_TX_TIMEOUT | CAN_ERR_BUSOFF |
                           CAN_ERR_BUSERROR | CAN_ERR_CRTL);
setsockopt(s, SOL_CAN_RAW, CAN_RAW_ERR_FILTER, &err_mask, sizeof(err_mask));

/* CAN FD 활성화 */
int enable_canfd = 1;
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &enable_canfd, sizeof(enable_canfd));

/* 루프백 비활성화 */
int loopback = 0;
setsockopt(s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback));

비트 타이밍과 동기화

CAN 버스의 신뢰성은 모든 노드가 동일한 시점에 비트를 샘플링하는 데 달려 있습니다. 비트 타이밍은 하나의 비트를 여러 시간 세그먼트로 나누어 전파 지연을 보상하고, 샘플 포인트를 정확히 설정합니다.

CAN 비트 타이밍 (1 Bit Time = N x tq) SYNC_SEG 1 tq 에지 동기화 PROP_SEG 1~8 tq 전파 지연 보상 PHASE_SEG1 1~8 tq SJW로 연장 가능 Sample Point PHASE_SEG2 1~8 tq SJW로 단축 가능 1 Bit Time = SYNC + PROP + PHASE1 + PHASE2 tq = prescaler / f_clk Sample Point (%) = (SYNC + PROP + PHASE1) / (SYNC + PROP + PHASE1 + PHASE2) x 100 권장 Sample Point: CAN 2.0 = 87.5%, CAN FD 중재 = 80%, CAN FD 데이터 = 75%

비트 타이밍 상수 (can_bittiming_const)

/* include/linux/can/bittiming.h */
struct can_bittiming_const {
    char name[16];          /* 컨트롤러 이름 */
    __u32 tseg1_min;         /* PROP_SEG + PHASE_SEG1 최솟값 */
    __u32 tseg1_max;         /* PROP_SEG + PHASE_SEG1 최댓값 */
    __u32 tseg2_min;         /* PHASE_SEG2 최솟값 */
    __u32 tseg2_max;         /* PHASE_SEG2 최댓값 */
    __u32 sjw_max;           /* Synchronization Jump Width 최댓값 */
    __u32 brp_min;           /* Baud Rate Prescaler 최솟값 */
    __u32 brp_max;           /* Baud Rate Prescaler 최댓값 */
    __u32 brp_inc;           /* Prescaler 증가 단위 */
};

struct can_bittiming {
    __u32 bitrate;           /* 비트레이트 (bps) */
    __u32 sample_point;      /* 샘플 포인트 (0.1% 단위, 예: 875 = 87.5%) */
    __u32 tq;                /* Time Quantum (ns) */
    __u32 prop_seg;          /* Propagation Segment (tq) */
    __u32 phase_seg1;        /* Phase Buffer Segment 1 (tq) */
    __u32 phase_seg2;        /* Phase Buffer Segment 2 (tq) */
    __u32 sjw;               /* Synchronization Jump Width (tq) */
    __u32 brp;               /* Baud Rate Prescaler */
};
# 비트 타이밍 설정 (커널이 자동 계산)
$ sudo ip link set can0 type can bitrate 500000

# 샘플 포인트 명시
$ sudo ip link set can0 type can bitrate 500000 sample-point 0.875

# CAN FD 듀얼 비트레이트 (Nominal + Data)
$ sudo ip link set can0 type can bitrate 500000 dbitrate 4000000 fd on

# 수동 비트 타이밍 설정 (TQ 직접 지정)
$ sudo ip link set can0 type can \
    tq 125 prop-seg 6 phase-seg1 7 phase-seg2 2 sjw 1

# 현재 비트 타이밍 확인
$ ip -details link show can0
    can state ERROR-ACTIVE (berr-counter tx 0 rx 0) restart-ms 0
          bitrate 500000 sample-point 0.875
          tq 125 prop-seg 6 phase-seg1 7 phase-seg2 2 sjw 1
          mcp251x: tseg1 3..16 tseg2 2..8 sjw 1..4 brp 1..64 brp-inc 1
          clock 8000000

비트 타이밍 계산 예제

파라미터설명
CAN Clock (f_clk)80 MHzCAN 컨트롤러 입력 클럭
Bitrate 목표500 Kbps원하는 전송 속도
Sample Point 목표87.5%권장 샘플 포인트
BRP (Prescaler)10tq = 10 / 80MHz = 125ns
Bit Time16 tq1 / 500K / 125ns = 16 tq
SYNC_SEG1 tq고정
PROP_SEG6 tq전파 지연 보상
PHASE_SEG17 tqSample Point 전
PHASE_SEG22 tqSample Point 후
SJW1 tq재동기화 점프 폭
실제 Sample Point(1+6+7)/16 = 87.5%목표 달성

에러 핸들링과 Fault Confinement

CAN 버스의 핵심 안전 기능은 Fault Confinement 메커니즘입니다. 결함이 있는 노드가 버스 전체를 교란하지 않도록, 에러 카운터 기반의 자동 격리 시스템이 동작합니다.

CAN Fault Confinement 상태 전이 Error Active TEC < 128 REC < 128 정상 동작 Error Warning TEC >= 96 또는 REC >= 96 경고 단계 Error Passive TEC >= 128 또는 REC >= 128 에러 프레임 축소 Bus Off TEC > 255 (버스 격리) TEC/REC >= 96 TEC/REC >= 128 TEC > 255 128 x 11 recessive bits (자동/수동 재시작)

CAN 에러 상태 (커널)

/* include/uapi/linux/can/netlink.h */
enum can_state {
    CAN_STATE_ERROR_ACTIVE,   /* 정상: TEC,REC < 96 */
    CAN_STATE_ERROR_WARNING,  /* 경고: TEC/REC 96~127 */
    CAN_STATE_ERROR_PASSIVE,  /* 수동: TEC/REC 128~255 */
    CAN_STATE_BUS_OFF,        /* 격리: TEC > 255 */
    CAN_STATE_STOPPED,        /* 인터페이스 중지 */
    CAN_STATE_SLEEPING,       /* 절전 모드 */
    CAN_STATE_MAX
};

에러 프레임 처리

/* 에러 프레임 수신 활성화 */
can_err_mask_t err_mask = CAN_ERR_MASK;  /* 모든 에러 */
setsockopt(s, SOL_CAN_RAW, CAN_RAW_ERR_FILTER, &err_mask, sizeof(err_mask));

/* 에러 프레임 수신 및 분석 */
struct can_frame frame;
read(s, &frame, sizeof(frame));

if (frame.can_id & CAN_ERR_FLAG) {
    printf("Error frame: class=0x%08X\\n", frame.can_id & CAN_ERR_MASK);

    /* 에러 클래스 분석 */
    if (frame.can_id & CAN_ERR_TX_TIMEOUT)
        printf("  TX timeout\\n");
    if (frame.can_id & CAN_ERR_LOSTARB)
        printf("  Lost arbitration at bit %d\\n", frame.data[0]);
    if (frame.can_id & CAN_ERR_CRTL) {
        if (frame.data[1] & CAN_ERR_CRTL_RX_WARNING)
            printf("  RX error warning\\n");
        if (frame.data[1] & CAN_ERR_CRTL_TX_PASSIVE)
            printf("  TX error passive\\n");
    }
    if (frame.can_id & CAN_ERR_PROT) {
        printf("  Protocol error: type=0x%02X loc=0x%02X\\n",
               frame.data[2], frame.data[3]);
    }
    if (frame.can_id & CAN_ERR_BUSOFF)
        printf("  Bus-off!\\n");
    if (frame.can_id & CAN_ERR_BUSERROR)
        printf("  Bus error (bit/form/stuff/CRC)\\n");

    /* 에러 카운터 (data[6]=TEC, data[7]=REC) */
    printf("  TEC=%d REC=%d\\n", frame.data[6], frame.data[7]);
}

에러 클래스 비트맵

상수설명data[] 상세
CAN_ERR_TX_TIMEOUT0x00000001TX 타임아웃-
CAN_ERR_LOSTARB0x00000002중재 패배data[0]: 비트 위치
CAN_ERR_CRTL0x00000004컨트롤러 상태 변경data[1]: RX/TX warning/passive
CAN_ERR_PROT0x00000008프로토콜 위반data[2]: 타입, data[3]: 위치
CAN_ERR_TRX0x00000010트랜시버 상태data[4]: CAN H/L 상태
CAN_ERR_ACK0x00000020ACK 슬롯 에러-
CAN_ERR_BUSOFF0x00000040Bus-off 진입-
CAN_ERR_BUSERROR0x00000080버스 에러 발생-
CAN_ERR_RESTARTED0x00000100컨트롤러 재시작됨-
Bus-off 복구: Bus-off 상태에서는 노드가 버스에서 완전히 격리됩니다. 복구하려면 128회의 11비트 연속 recessive 비트를 감지해야 합니다. Linux에서는 restart-ms로 자동 복구를 설정하거나, ip link set can0 type can restart로 수동 복구합니다.

CAN FD 실전 활용

/* CAN FD 송수신 완전 예제 */
#include <linux/can.h>
#include <linux/can/raw.h>

int can_fd_example(const char *ifname)
{
    int s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
    struct ifreq ifr;
    struct sockaddr_can addr;

    /* CAN FD 활성화 (필수!) */
    int enable_fd = 1;
    setsockopt(s, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &enable_fd, sizeof(enable_fd));

    /* 바인딩 */
    strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);
    ioctl(s, SIOCGIFINDEX, &ifr);
    addr.can_family = AF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;
    bind(s, (struct sockaddr *)&addr, sizeof(addr));

    /* CAN FD 프레임 송신 (64바이트, BRS 활성) */
    struct canfd_frame fdframe = {
        .can_id = 0x123,
        .len    = 64,
        .flags  = CANFD_BRS,   /* 데이터 영역 고속 전송 */
    };
    memset(fdframe.data, 0xAA, 64);
    write(s, &fdframe, CANFD_MTU);

    /* 수신: CAN 2.0과 CAN FD 모두 처리 */
    struct canfd_frame rx;
    ssize_t nbytes = read(s, &rx, CANFD_MTU);

    if (nbytes == CAN_MTU) {
        printf("Classic CAN: ID=0x%03X len=%d\\n",
               rx.can_id & CAN_SFF_MASK, rx.len);
    } else if (nbytes == CANFD_MTU) {
        printf("CAN FD: ID=0x%03X len=%d flags=0x%02X\\n",
               rx.can_id & CAN_SFF_MASK, rx.len, rx.flags);
        if (rx.flags & CANFD_BRS)
            printf("  BRS (Bit Rate Switch) active\\n");
        if (rx.flags & CANFD_ESI)
            printf("  ESI (Error State Indicator)\\n");
    }

    close(s);
    return 0;
}
# CAN FD 인터페이스 설정
$ sudo ip link set can0 type can bitrate 500000 dbitrate 4000000 fd on
$ sudo ip link set up can0

# CAN FD MTU 확인 (72 = CANFD_MTU)
$ ip -details link show can0 | grep mtu
    ... mtu 72 ...

# CAN FD 프레임 송신 (can-utils)
$ cansend can0 123##1.DEADBEEFCAFEBABE1122334455667788
#                  ^ flags(BRS=1)  64바이트 페이로드

# CAN FD candump (길이 자동 식별)
$ candump can0
  can0  123  [64]  DE AD BE EF CA FE BA BE 11 22 33 44 55 66 77 88 ...

상위 프로토콜

ISO-TP (ISO 15765-2)

ISO-TP는 CAN의 8/64바이트 한계를 극복하여 최대 4095바이트(확장 주소 시 더 많은)의 데이터를 여러 CAN 프레임으로 분할/재조합하는 전송 프로토콜입니다. 자동차 진단(UDS/OBD-II)의 기반이며, Linux 커널 5.10부터 메인라인에 포함되었습니다.

프레임 유형N_PCI용도형식
Single Frame (SF)0x0N7바이트 이하 단일 전송[SF_DL | Data...]
First Frame (FF)0x1N NN멀티프레임 시작 (전체 길이 포함)[FF_DL_hi | FF_DL_lo | Data...]
Consecutive Frame (CF)0x2N이어지는 데이터 블록[SN | Data...]
Flow Control (FC)0x3N수신측 흐름 제어[FS | BS | STmin]
#include <linux/can/isotp.h>

/* ISO-TP 소켓 생성 */
int s = socket(PF_CAN, SOCK_DGRAM, CAN_ISOTP);

/* ISO-TP 옵션 설정 */
struct can_isotp_options opts = {
    .flags    = CAN_ISOTP_TX_PADDING | CAN_ISOTP_RX_PADDING,
    .txpad_content = 0xCC,     /* TX 패딩 바이트 */
    .rxpad_content = 0xCC,     /* RX 패딩 바이트 */
    .ext_address   = 0x00,     /* Extended Address (0이면 미사용) */
};
setsockopt(s, SOL_CAN_ISOTP, CAN_ISOTP_OPTS, &opts, sizeof(opts));

/* 흐름 제어 파라미터 설정 */
struct can_isotp_fc_options fc = {
    .bs    = 0,     /* Block Size: 0 = 제한 없음 */
    .stmin = 5,     /* Separation Time min: 5ms */
    .wftmax = 0,    /* Wait Frame 최대 수 */
};
setsockopt(s, SOL_CAN_ISOTP, CAN_ISOTP_RECV_FC, &fc, sizeof(fc));

/* 주소 바인딩 (TX CAN ID = 0x7E0, RX CAN ID = 0x7E8) */
struct sockaddr_can addr = {
    .can_family = AF_CAN,
    .can_ifindex = ifr.ifr_ifindex,
    .can_addr.tp.tx_id = 0x7E0,
    .can_addr.tp.rx_id = 0x7E8,
};
bind(s, (struct sockaddr *)&addr, sizeof(addr));

/* UDS 진단 요청 전송 (예: Read DTC Information) */
uint8_t uds_req[] = {0x19, 0x02, 0xFF};  /* SID=0x19, SubFunc=0x02 */
write(s, uds_req, sizeof(uds_req));

/* 응답 수신 (자동 분할/재조합) */
uint8_t resp[4096];
ssize_t len = read(s, resp, sizeof(resp));
printf("ISO-TP response: %zd bytes\\n", len);
# ISO-TP 명령행 도구 (can-utils)
# isotpsend: ISO-TP 데이터 송신
$ echo "19 02 FF" | isotpsend -s 7E0 -d 7E8 can0

# isotprecv: ISO-TP 데이터 수신 (별도 터미널)
$ isotprecv -s 7E8 -d 7E0 can0

# isotpdump: ISO-TP 프레임 추적
$ isotpdump -s 7E0 -d 7E8 can0

J1939 (SAE J1939)

J1939는 상용차(트럭, 버스, 농기계, 건설 장비)에서 사용되는 CAN 기반 상위 프로토콜입니다. 29-bit Extended CAN ID를 우선순위(3bit) + PGN(Parameter Group Number, 18bit) + Source Address(8bit)로 분해합니다.

J1939 CAN ID 구조

비트 위치필드크기설명
28~26Priority3-bit메시지 우선순위 (0=최고, 7=최저)
25Reserved1-bit예약 (0)
24Data Page1-bitPGN 확장 페이지
23~16PDU Format (PF)8-bit<240: PDU1 (점대점), >=240: PDU2 (브로드캐스트)
15~8PDU Specific (PS)8-bitPDU1: Destination Address, PDU2: Group Extension
7~0Source Address (SA)8-bit송신 노드 주소
#include <linux/can/j1939.h>

/* J1939 소켓 생성 */
int s = socket(PF_CAN, SOCK_DGRAM, CAN_J1939);

/* J1939 주소 바인딩 */
struct sockaddr_can addr = {
    .can_family = AF_CAN,
    .can_ifindex = ifr.ifr_ifindex,
    .can_addr.j1939 = {
        .name = J1939_NO_NAME,       /* NAME 미사용 (주소 직접 지정) */
        .addr = 0x80,                /* Source Address */
        .pgn  = J1939_NO_PGN,        /* 모든 PGN 수신 */
    },
};
bind(s, (struct sockaddr *)&addr, sizeof(addr));

/* 우선순위 설정 (setsockopt) */
int prio = 3;
setsockopt(s, SOL_CAN_J1939, SO_J1939_SEND_PRIO, &prio, sizeof(prio));

/* J1939 메시지 송신 (PGN 0xFEF1 = Cruise Control/Vehicle Speed) */
struct sockaddr_can dst = {
    .can_family = AF_CAN,
    .can_addr.j1939 = {
        .name = J1939_NO_NAME,
        .addr = J1939_NO_ADDR,       /* 브로드캐스트 */
        .pgn  = 0xFEF1,              /* PGN: CCVS */
    },
};
uint8_t data[8] = {0xFF, 0xFF, 0x00, 0x64, 0xFF, 0xFF, 0xFF, 0xFF};
sendto(s, data, 8, 0, (struct sockaddr *)&dst, sizeof(dst));

/* J1939 Transport Protocol (TP) — 8바이트 초과 자동 분할 */
uint8_t big_data[1785];
memset(big_data, 0x55, sizeof(big_data));
sendto(s, big_data, sizeof(big_data), 0,
       (struct sockaddr *)&dst, sizeof(dst));
/* 커널이 BAM(Broadcast Announce Message) 또는 CMDT 프로토콜로 자동 분할 */
J1939 주요 PGN:
  • 0xFEF1 (CCVS) — 크루즈 컨트롤/차량 속도
  • 0xF004 (EEC1) — 엔진 RPM, 토크
  • 0xFEEE (ET1) — 엔진 온도
  • 0xFEFC (DASH) — 대시보드 디스플레이
  • 0xFECA (DM1) — Active DTC (진단 코드)
  • 0xFECB (DM2) — Previously Active DTC

CAN BCM (Broadcast Manager)

CAN BCM은 주기적 CAN 메시지 전송과 메시지 변경 감지를 커널 공간에서 처리하는 고급 프로토콜입니다. 사용자 공간에서 타이머를 돌리는 것보다 정확하고 효율적입니다.

#include <linux/can/bcm.h>

/* BCM 소켓 생성 (반드시 SOCK_DGRAM) */
int s = socket(PF_CAN, SOCK_DGRAM, CAN_BCM);

/* connect (BCM은 bind 대신 connect 사용) */
struct sockaddr_can addr = {
    .can_family = AF_CAN,
    .can_ifindex = ifr.ifr_ifindex,
};
connect(s, (struct sockaddr *)&addr, sizeof(addr));

/* BCM 메시지 구조 (TX_SETUP: 주기적 전송) */
struct {
    struct bcm_msg_head msg_head;
    struct can_frame frames[1];
} bcm_msg;

memset(&bcm_msg, 0, sizeof(bcm_msg));
bcm_msg.msg_head.opcode  = TX_SETUP;
bcm_msg.msg_head.can_id  = 0x100;
bcm_msg.msg_head.flags   = SETTIMER | STARTTIMER;
bcm_msg.msg_head.nframes = 1;
bcm_msg.msg_head.ival2.tv_sec  = 0;
bcm_msg.msg_head.ival2.tv_usec = 100000;  /* 100ms 주기 */

bcm_msg.frames[0].can_id  = 0x100;
bcm_msg.frames[0].len     = 4;
bcm_msg.frames[0].data[0] = 0x01;
bcm_msg.frames[0].data[1] = 0x02;
bcm_msg.frames[0].data[2] = 0x03;
bcm_msg.frames[0].data[3] = 0x04;

write(s, &bcm_msg, sizeof(bcm_msg));
/* 커널이 100ms마다 0x100#01020304를 자동 송신 */

/* RX_SETUP: 변경 감지 (content filter) */
struct {
    struct bcm_msg_head msg_head;
    struct can_frame frames[1];
} bcm_rx;

memset(&bcm_rx, 0, sizeof(bcm_rx));
bcm_rx.msg_head.opcode  = RX_SETUP;
bcm_rx.msg_head.can_id  = 0x200;
bcm_rx.msg_head.flags   = RX_FILTER_ID;  /* ID만 필터 (데이터 무시) */
bcm_rx.msg_head.nframes = 0;

write(s, &bcm_rx, sizeof(bcm_rx.msg_head));
/* 0x200 프레임이 수신되면 read()로 알림 수신 */

BCM 명령어 (Opcode)

Opcode방향설명
TX_SETUPUser → Kernel주기적 전송 설정
TX_DELETEUser → Kernel주기적 전송 중지
TX_READUser → Kernel현재 TX 설정 읽기
TX_SENDUser → Kernel단발 전송 (주기 아님)
TX_STATUSKernel → UserTX_READ 응답
TX_EXPIREDKernel → UserTX count 만료 알림
RX_SETUPUser → Kernel수신 모니터링 설정
RX_DELETEUser → Kernel수신 모니터링 중지
RX_READUser → Kernel현재 RX 설정 읽기
RX_STATUSKernel → UserRX_READ 응답
RX_TIMEOUTKernel → User주기적 메시지 타임아웃
RX_CHANGEDKernel → User메시지 데이터 변경 감지

CAN Gateway (cangw)

CAN Gateway는 CAN 인터페이스 간 프레임을 라우팅하고, 선택적으로 CAN ID나 데이터를 변환하는 커널 기능입니다. Netlink 소켓으로 설정합니다.

# CAN Gateway 모듈 로드
$ sudo modprobe can-gw

# can0 → can1 프레임 포워딩 (모든 프레임)
$ sudo cangw -A -s can0 -d can1 -e

# can0 → can1 프레임 포워딩 (특정 ID만, ID 변환 포함)
$ sudo cangw -A -s can0 -d can1 -f 123:7FF -m SET:I:456
#   -f 123:7FF  : ID 0x123만 필터
#   -m SET:I:456: 대상 CAN ID를 0x456으로 변경

# 데이터 바이트 수정 (XOR 마스크)
$ sudo cangw -A -s can0 -d can1 -f 100:7FF -m XOR:D:FF00000000000000
#   data[0]에 0xFF를 XOR

# 양방향 게이트웨이 (can0 ↔ can1)
$ sudo cangw -A -s can0 -d can1 -e
$ sudo cangw -A -s can1 -d can0 -e

# 현재 게이트웨이 규칙 목록
$ cangw -L

# 게이트웨이 규칙 삭제
$ sudo cangw -D -s can0 -d can1 -e

# 모든 규칙 초기화
$ sudo cangw -F
CAN Gateway 활용: 두 개의 CAN 네트워크 세그먼트를 연결하여 프록시 역할을 하거나, 특정 메시지만 선택적으로 라우팅하여 보안 게이트웨이를 구성할 수 있습니다. 자동차에서는 파워트레인 CAN과 바디 CAN 사이의 게이트웨이 ECU에 해당합니다.

Virtual CAN (vcan/vxcan)

가상 CAN 인터페이스를 사용하면 실제 하드웨어 없이 CAN 통신을 테스트할 수 있습니다.

# === vcan: 단일 가상 인터페이스 (루프백) ===
$ sudo modprobe vcan
$ sudo ip link add dev vcan0 type vcan
$ sudo ip link set up vcan0

# vcan0 확인
$ ip link show vcan0
4: vcan0: <NOARP,UP,LOWER_UP> mtu 72 qdisc noqueue state UNKNOWN

# CAN FD 지원 vcan (MTU=72)
$ sudo ip link add dev vcan0 type vcan
$ ip link show vcan0 | grep mtu
    ... mtu 72 ...   # CAN FD 지원 (72 = CANFD_MTU)

# === vxcan: 네임스페이스 간 가상 CAN 터널 ===
# (veth처럼 동작하는 CAN 인터페이스 페어)
$ sudo modprobe vxcan
$ sudo ip link add dev vxcan0 type vxcan
# vxcan0과 vxcan1이 한 쌍으로 생성됨
$ sudo ip link set up vxcan0
$ sudo ip link set up vxcan1

# 네임스페이스 분리 테스트
$ sudo ip netns add can_ns
$ sudo ip link set vxcan1 netns can_ns
$ sudo ip netns exec can_ns ip link set up vxcan1

# 터미널 1: vxcan0에서 수신
$ candump vxcan0

# 터미널 2: 네임스페이스 내 vxcan1에서 송신
$ sudo ip netns exec can_ns cansend vxcan1 123#DEADBEEF

# 인터페이스 삭제
$ sudo ip link delete vcan0
$ sudo ip link delete vxcan0

can-utils 진단 도구

# can-utils 설치
$ sudo apt install can-utils   # Debian/Ubuntu
$ sudo dnf install can-utils   # Fedora/RHEL

# === cansend: 단일 프레임 송신 ===
$ cansend can0 123#DEADBEEF               # Standard ID
$ cansend can0 12345678#1122334455667788  # Extended ID (자동 감지)
$ cansend can0 123#R                       # RTR 프레임
$ cansend can0 123##1.AABBCCDD             # CAN FD (flags.data)

# === candump: 프레임 수신/덤프 ===
$ candump can0                            # 모든 프레임
$ candump can0 -t a                       # 절대 타임스탬프
$ candump can0 -t d                       # 이전 프레임과의 시간 차이
$ candump can0 -x                         # 확장 포맷 (FD 플래그 표시)
$ candump can0 -e                         # 에러 프레임 포함
$ candump can0,123:7FF                    # ID 0x123만 필터
$ candump can0,100:700,200:7FF            # 복수 필터 (OR)
$ candump can0 -l                         # 로그 파일 저장
$ candump can0 -L                         # 콤팩트 로그 형식

# === cangen: 트래픽 생성기 ===
$ cangen can0 -g 10                       # 10ms 간격 랜덤 프레임
$ cangen can0 -I 100 -L 8 -D i -g 1      # ID=0x100, 8byte, 증가 데이터
$ cangen can0 -f -L 64 -g 5               # CAN FD 64byte

# === cansniffer: 실시간 메시지 변경 감시 ===
$ cansniffer can0 -c                      # 색상 하이라이트
# 변경된 바이트만 색상으로 표시 → 리버스 엔지니어링에 유용

# === canplayer: 로그 파일 재생 ===
$ canplayer -I candump.log                # 타이밍 무시 재생
$ canplayer -I candump.log -t             # 원래 타이밍 유지

# === canbusload: 버스 부하율 측정 ===
$ canbusload can0@500000 -r 1 -b -c
# 500Kbps 기준으로 1초 간격 부하율 표시
cansniffer 활용: cansniffer는 CAN 버스에서 주기적으로 전송되는 메시지의 데이터 변경을 실시간으로 추적합니다. 차량 리버스 엔지니어링 시 특정 동작(예: 핸들 조작, 브레이크 밟기)과 연관된 CAN 메시지를 찾는 데 매우 유용합니다.

CAN 하드웨어와 드라이버

slcan (Serial Line CAN)

# slcan: 시리얼 포트를 CAN 인터페이스로 변환
$ sudo modprobe slcan

# USB-CAN 어댑터 설정 (예: Lawicel CANUSB)
$ sudo slcand -o -s6 -t hw -S 3000000 /dev/ttyUSB0 slcan0
$ sudo ip link set up slcan0

# slcan 속도 코드
#   s0 = 10 Kbps     s1 = 20 Kbps
#   s2 = 50 Kbps     s3 = 100 Kbps
#   s4 = 125 Kbps    s5 = 250 Kbps
#   s6 = 500 Kbps    s7 = 800 Kbps
#   s8 = 1 Mbps

MCP2515 (SPI CAN 컨트롤러)

/* Device Tree Overlay (Raspberry Pi + MCP2515) */
/* /boot/config.txt에 추가: */
/* dtoverlay=mcp2515-can0,oscillator=16000000,interrupt=25,spimaxfrequency=2000000 */

&spi0 {
    status = "okay";
    mcp2515: can@0 {
        compatible = "microchip,mcp2515";
        reg = <0>;
        spi-max-frequency = <2000000>;
        clocks = <&mcp2515_osc>;
        interrupt-parent = <&gpio>;
        interrupts = <25 0x2>;    /* GPIO25, 하강 에지 */
    };
};

mcp2515_osc: mcp2515-osc {
    compatible = "fixed-clock";
    #clock-cells = <0>;
    clock-frequency = <16000000>;  /* 16MHz 크리스탈 */
};
# 재부팅 후 can0 인터페이스 확인
$ ip link show can0
$ sudo ip link set can0 type can bitrate 500000
$ sudo ip link set up can0
$ dmesg | grep mcp251x
[   12.345678] mcp251x spi0.0 can0: MCP2515 successfully initialized.

주요 CAN 드라이버 목록

드라이버컨트롤러버스CAN FD커널 소스
vcan가상가상지원drivers/net/can/vcan.c
vxcan가상 페어가상지원drivers/net/can/vxcan.c
slcanSerial LineUART미지원drivers/net/can/slcan/
mcp251xMCP2515/MCP25625SPI미지원drivers/net/can/spi/mcp251x.c
mcp251xfdMCP2517FD/MCP2518FDSPI지원drivers/net/can/spi/mcp251xfd/
m_canBosch M_CAN IPPlatform지원drivers/net/can/m_can/
c_canBosch C_CAN/D_CANPlatform미지원drivers/net/can/c_can/
peak_usbPEAK PCAN-USBUSB지원drivers/net/can/usb/peak_usb/
gs_usbGeschwister SchneiderUSB지원drivers/net/can/usb/gs_usb.c
kvaser_usbKvaserUSB지원drivers/net/can/usb/kvaser_usb/
flexcanNXP FlexCANPlatform지원drivers/net/can/flexcan/
rcar_canfdRenesas R-CarPlatform지원drivers/net/can/rcar/

CAN 디바이스 드라이버 작성

CAN 드라이버는 alloc_candev()net_device를 할당하고, can_priv 구조체의 비트 타이밍/모드 정보를 설정한 후, register_candev()로 등록합니다.

#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/can.h>
#include <linux/can/dev.h>

struct my_can_priv {
    struct can_priv can;     /* 반드시 첫 번째 멤버 */
    void __iomem *base;
    struct clk *clk;
    int irq;
};

/* 비트 타이밍 상수 (하드웨어 사양) */
static const struct can_bittiming_const my_bittiming_const = {
    .name      = "my_can",
    .tseg1_min = 1,   .tseg1_max = 16,
    .tseg2_min = 1,   .tseg2_max = 8,
    .sjw_max   = 4,
    .brp_min   = 1,   .brp_max   = 64,  .brp_inc = 1,
};

/* 인터페이스 열기 */
static int my_can_open(struct net_device *dev)
{
    struct my_can_priv *priv = netdev_priv(dev);
    int err;

    /* CAN 공통 열기 (비트 타이밍 적용, 에러 카운터 초기화) */
    err = open_candev(dev);
    if (err)
        return err;

    /* 하드웨어 초기화: 레지스터 설정, IRQ 등록 등 */
    /* ... */

    netif_start_queue(dev);
    return 0;
}

/* 프레임 송신 */
static netdev_tx_t my_can_start_xmit(struct sk_buff *skb,
                                       struct net_device *dev)
{
    struct my_can_priv *priv = netdev_priv(dev);
    struct can_frame *cf = (struct can_frame *)skb->data;

    if (can_dropped_invalid_skb(dev, skb))
        return NETDEV_TX_OK;

    netif_stop_queue(dev);

    /* Echo SKB 저장 (루프백용) */
    can_put_echo_skb(skb, dev, 0, 0);

    /* 하드웨어 TX 버퍼에 프레임 기록 */
    /* ... writel(cf->can_id, priv->base + TX_ID_REG) ... */

    return NETDEV_TX_OK;
}

/* TX 완료 인터럽트 핸들러에서 호출 */
static void my_can_tx_done(struct net_device *dev)
{
    dev->stats.tx_bytes += can_get_echo_skb(dev, 0, NULL);
    dev->stats.tx_packets++;
    netif_wake_queue(dev);
}

static const struct net_device_ops my_can_netdev_ops = {
    .ndo_open       = my_can_open,
    .ndo_stop       = my_can_close,
    .ndo_start_xmit = my_can_start_xmit,
    .ndo_change_mtu = can_change_mtu,
};

static int my_can_probe(struct platform_device *pdev)
{
    struct net_device *dev;
    struct my_can_priv *priv;
    int err;

    /* CAN net_device 할당 (echo_skb 1개) */
    dev = alloc_candev(sizeof(struct my_can_priv), 1);
    if (!dev)
        return -ENOMEM;

    priv = netdev_priv(dev);

    /* CAN 속성 설정 */
    priv->can.bittiming_const = &my_bittiming_const;
    priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK |
                                    CAN_CTRLMODE_LISTENONLY |
                                    CAN_CTRLMODE_FD;
    priv->can.clock.freq = clk_get_rate(priv->clk);

    dev->netdev_ops = &my_can_netdev_ops;
    dev->flags |= IFF_ECHO;  /* 로컬 에코 지원 */

    platform_set_drvdata(pdev, dev);

    err = register_candev(dev);
    if (err) {
        free_candev(dev);
        return err;
    }

    netdev_info(dev, "my_can registered (clock=%dHz)\\n",
                priv->can.clock.freq);
    return 0;
}
Echo SKB: CAN 드라이버는 송신한 프레임을 다른 SocketCAN 소켓에 루프백해야 합니다. can_put_echo_skb()로 SKB를 저장하고, TX 완료 인터럽트에서 can_get_echo_skb()로 방출합니다. IFF_ECHO 플래그가 없으면 SocketCAN 코어가 소프트웨어 루프백을 수행합니다.

CAN 인터페이스 설정

# === 기본 설정 ===
$ sudo ip link set can0 type can bitrate 500000
$ sudo ip link set up can0

# === CAN FD 설정 ===
$ sudo ip link set can0 type can bitrate 500000 dbitrate 4000000 fd on

# === 자동 재시작 (Bus-off 복구) ===
$ sudo ip link set can0 type can restart-ms 100

# === 수동 재시작 ===
$ sudo ip link set can0 type can restart

# === 제어 모드 ===
$ sudo ip link set can0 type can loopback on     # 내부 루프백 테스트
$ sudo ip link set can0 type can listen-only on   # 수신만 (ACK 안 함)
$ sudo ip link set can0 type can one-shot on      # 재전송 비활성화
$ sudo ip link set can0 type can berr-reporting on # 버스 에러 보고 활성화

# === 상태 및 통계 확인 ===
$ ip -details -statistics link show can0
3: can0: <NOARP,UP,LOWER_UP,ECHO> mtu 16 qdisc pfifo_fast state UP
    link/can
    can state ERROR-ACTIVE (berr-counter tx 0 rx 0) restart-ms 100
          bitrate 500000 sample-point 0.875
          tq 125 prop-seg 6 phase-seg1 7 phase-seg2 2 sjw 1
          m_can: tseg1 2..256 tseg2 1..128 sjw 1..128 brp 1..512 brp-inc 1
          clock 80000000
          re-started bus-errors arbit-lost error-warn error-pass bus-off
          0          0          0          0          0          0
    RX: bytes  packets  errors  dropped  missed  mcast
    1234       150      0       0        0       0
    TX: bytes  packets  errors  dropped  carrier  collsns
    5678       200      0       0        0        0

CAN 제어 모드

모드상수설명
loopbackCAN_CTRLMODE_LOOPBACK하드웨어 내부 루프백 (자체 테스트)
listen-onlyCAN_CTRLMODE_LISTENONLY수신만, ACK/에러 프레임 미전송
triple-samplingCAN_CTRLMODE_3_SAMPLES비트당 3회 샘플링 (노이즈 방지)
one-shotCAN_CTRLMODE_ONE_SHOT자동 재전송 비활성화
berr-reportingCAN_CTRLMODE_BERR_REPORTING버스 에러 프레임 생성
fdCAN_CTRLMODE_FDCAN FD 모드 활성화
fd-non-isoCAN_CTRLMODE_FD_NON_ISO비표준 CAN FD (Bosch 원본)
presume-ackCAN_CTRLMODE_PRESUME_ACKACK 없이도 전송 성공으로 처리
cc-len8-dlcCAN_CTRLMODE_CC_LEN8_DLCClassic CAN에서 DLC 9~15 보존

Timestamp와 지연 분석

/* 하드웨어 타임스탬프 활성화 */
struct hwtstamp_config cfg = {
    .tx_type    = HWTSTAMP_TX_ON,
    .rx_filter  = HWTSTAMP_FILTER_ALL,
};
struct ifreq ifr;
strncpy(ifr.ifr_name, "can0", IFNAMSIZ - 1);
ifr.ifr_data = (void *)&cfg;
ioctl(s, SIOCSHWTSTAMP, &ifr);

/* cmsg로 타임스탬프 수신 */
int enable = 1;
setsockopt(s, SOL_SOCKET, SO_TIMESTAMPNS, &enable, sizeof(enable));

struct msghdr msg = { 0 };
struct iovec iov;
char cmsg_buf[256];
struct canfd_frame frame;

iov.iov_base = &frame;
iov.iov_len  = sizeof(frame);
msg.msg_iov     = &iov;
msg.msg_iovlen  = 1;
msg.msg_control    = cmsg_buf;
msg.msg_controllen = sizeof(cmsg_buf);

recvmsg(s, &msg, 0);

/* cmsg에서 타임스탬프 추출 */
struct cmsghdr *cmsg;
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg;
     cmsg = CMSG_NXTHDR(&msg, cmsg)) {
    if (cmsg->cmsg_level == SOL_SOCKET &&
        cmsg->cmsg_type == SO_TIMESTAMPNS) {
        struct timespec *ts = (struct timespec *)CMSG_DATA(cmsg);
        printf("Timestamp: %ld.%09ld\\n", ts->tv_sec, ts->tv_nsec);
    }
}

커널 설정

# CAN bus 서브시스템
CONFIG_CAN=y                      # CAN 프로토콜 패밀리
CONFIG_CAN_RAW=y                  # CAN RAW 프로토콜
CONFIG_CAN_BCM=y                  # CAN Broadcast Manager
CONFIG_CAN_GW=y                   # CAN Gateway (프레임 라우팅)
CONFIG_CAN_ISOTP=m                # ISO-TP (ISO 15765-2)
CONFIG_CAN_J1939=m                # SAE J1939 (상용차)

# 가상 CAN
CONFIG_CAN_VCAN=m                 # Virtual CAN (루프백)
CONFIG_CAN_VXCAN=m                # Virtual CAN 터널 (페어)
CONFIG_CAN_SLCAN=m                # Serial Line CAN

# CAN Device Drivers
CONFIG_CAN_DEV=y                  # CAN 디바이스 공통 프레임워크
CONFIG_CAN_CALC_BITTIMING=y       # 커널 내 비트 타이밍 자동 계산

# SPI CAN 컨트롤러
CONFIG_CAN_MCP251X=m              # Microchip MCP2515/MCP25625
CONFIG_CAN_MCP251XFD=m            # Microchip MCP2517FD/MCP2518FD (FD)

# Platform CAN 컨트롤러
CONFIG_CAN_M_CAN=m                # Bosch M_CAN (FD 지원)
CONFIG_CAN_M_CAN_PLATFORM=m       # M_CAN Platform (SoC 내장)
CONFIG_CAN_M_CAN_TCAN4X5X=m      # TI TCAN4550 SPI CAN FD
CONFIG_CAN_C_CAN=m                # Bosch C_CAN/D_CAN
CONFIG_CAN_FLEXCAN=m              # NXP FlexCAN (i.MX, LS)
CONFIG_CAN_RCAR_CANFD=m           # Renesas R-Car CAN FD

# USB CAN 어댑터
CONFIG_CAN_GS_USB=m               # Geschwister Schneider USB/CAN
CONFIG_CAN_PEAK_USB=m             # PEAK-System PCAN-USB
CONFIG_CAN_KVASER_USB=m           # Kvaser USB
CONFIG_CAN_EMS_USB=m              # EMS CPC-USB/ARM7
CONFIG_CAN_8DEV_USB=m             # 8 Devices USB2CAN

디버깅과 문제 해결

# === 상태 확인 ===
$ ip -details -statistics link show can0

# === 에러 카운터 모니터링 ===
$ watch -n1 "ip -details link show can0 | grep 'berr-counter\\|state'"

# === 에러 프레임 실시간 관찰 ===
$ candump can0,0:0,#FFFFFFFF -e
# 에러 프레임만 필터링하여 표시

# === 버스 부하율 측정 ===
$ canbusload can0@500000 -r 1 -b -c
# can0 @500Kbps, 1초 간격, 바 그래프, 색상

# === CAN 통계 (procfs) ===
$ cat /proc/net/can/stats
        4 transmitted frames (TFF)
       16 received frames (RFF)
        0 matched frames (MFF)
       20 frames/s max (MAXFPS)
        5 current frames/s (CRFPS)

# === 프로토콜 통계 ===
$ cat /proc/net/can/rcvlist_all
$ cat /proc/net/can/rcvlist_fil

# === dmesg CAN 로그 ===
$ dmesg | grep -i can
[  12.345] m_can_platform 40600000.can can0: m_can device registered (irq=52)
[  15.678] can0: bus-off

# === 네트워크 네임스페이스 격리 테스트 ===
$ sudo ip netns add ns_can
$ sudo ip link add vxcan0 type vxcan
$ sudo ip link set vxcan1 netns ns_can
$ sudo ip link set up vxcan0
$ sudo ip netns exec ns_can ip link set up vxcan1
$ sudo ip netns exec ns_can candump vxcan1 &
$ cansend vxcan0 123#DEADBEEF
일반적인 문제와 해결:
  • "write: No buffer space available" — TX 큐 가득 참. ip link set can0 txqueuelen 1000으로 큐 크기 증가
  • Bus-off 반복 — 비트레이트 불일치, 종단 저항 누락, 노이즈. listen-only 모드에서 버스 상태 관찰
  • CAN FD 프레임 수신 안 됨 — 소켓에 CAN_RAW_FD_FRAMES 옵션 미설정. 인터페이스 MTU가 72인지 확인
  • Lost Arbitration 빈번 — 버스 부하 과다. canbusload로 부하율 확인 (80% 이상이면 문제)
  • ISO-TP 타임아웃 — FC(Flow Control)의 STmin/BS 설정 확인. 상대방 응답 시간 고려

CAN FD/XL 프레임 구조 심화

CAN FD와 CAN XL은 클래식 CAN의 대역폭 한계를 극복하기 위해 설계된 차세대 프로토콜입니다. CAN FD는 기존 물리 계층을 유지하면서 데이터 영역만 고속 전환(BRS)하여 호환성을 확보했고, CAN XL은 완전히 새로운 프레임 포맷으로 이더넷급 페이로드를 지원합니다.

CAN FD 비트 레벨 구조

CAN FD 프레임은 중재(Arbitration) 영역과 데이터(Data) 영역에서 서로 다른 비트레이트를 사용할 수 있습니다. BRS(Bit Rate Switch) 비트가 recessive이면 데이터 영역이 고속으로 전환됩니다.

CAN FD 프레임 비트 레벨 구조 (BRS 활성) Arbitration Phase (Nominal Bitrate) SOF | ID[10:0] | RRS | IDE | FDF | res | BRS BRS Switch Data Phase (Data Bitrate, max 8 Mbps) ESI | DLC[3:0] | DATA[0..64] | SBC | CRC[16/17/21] Switch Back Nominal Bitrate CRC Del | ACK | EOF | IFS CAN FD CRC 다항식 DLC 0~16 bytes : CRC-17 (0x3685B) DLC 20~64 bytes: CRC-21 (0x102899) 핵심 제어 비트 FDF (r1) = 1: CAN FD 프레임 식별 (CAN 2.0은 항상 0) BRS = 1: 데이터 영역 고속 전환 / ESI: 에러 상태 표시 CAN FD 비트 스터핑 (Stuff Bit Counter) CAN FD는 CRC 영역에 고정 스터핑(Fixed Stuff Bits)을 사용하여 에러 검출 능력을 강화합니다. SBC(Stuff Bit Count)가 CRC 앞에 삽입됩니다.
특성CAN 2.0CAN FDCAN XL
CRC 다항식CRC-15CRC-17/CRC-21CRC-32
비트 스터핑동적 (5연속 동일 비트 후)동적 + 고정 (CRC 영역)없음 (XLCC 인코딩)
에러 검출 한도HD=6 (최대 112비트)HD=6 (최대 2048비트)HD=6+
프레임 종류Data/Remote/Error/OverloadData/Error (Remote 없음)Data/Error
비트레이트 전환없음BRS 비트로 전환XLSS 비트로 전환
최소 TQ/비트85 (데이터 영역)10

CAN XL 상세 구조

CAN XL은 기존 CAN FD와 물리적으로 호환되면서 최대 2048바이트 페이로드와 10 Mbps 데이터 전송을 지원합니다. 가장 큰 차이점은 SDT(SDU Type) 필드로 상위 프로토콜을 식별하고, AF(Acceptance Field) 32비트로 확장된 수신 필터링을 제공하는 것입니다.

/* CAN XL 프레임 상세 필드 (커널 6.3+) */

/* canxl_frame.prio 필드 구조:
 * bits 10-0 : 11-bit 우선순위 (CAN ID와 유사)
 * bit  11   : VCID (Virtual CAN Network ID) 활성화
 * bits 23-12: VCID 값 (가상 네트워크 식별)
 * bit  31   : CANXL_XLF (XL Frame 식별 필수) */

/* SDT(Service Data Unit Type) 사전 정의 값 */
#define CANXL_SDT_CONTENT_BASED  0x01  /* 콘텐츠 기반 라우팅 */
#define CANXL_SDT_TUNNELING      0x03  /* CAN 2.0/FD 터널링 */
#define CANXL_SDT_ETHERNET       0x04  /* 이더넷 프레임 캡슐화 */
#define CANXL_SDT_IEEE802_1AS    0x05  /* 시간 동기화 (gPTP) */

/* CAN XL 소켓 사용 예제 */
int s = socket(PF_CAN, SOCK_RAW, CAN_RAW);

/* XL 프레임 활성화 (CAN FD도 함께 활성화해야 함) */
int enable_fd = 1, enable_xl = 1;
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &enable_fd, sizeof(enable_fd));
setsockopt(s, SOL_CAN_RAW, CAN_RAW_XL_FRAMES, &enable_xl, sizeof(enable_xl));

/* XL 프레임 송신 */
struct canxl_frame xl = {
    .prio  = 0x42 | CANXL_XLF,  /* 우선순위 0x42 + XL 플래그 */
    .flags = 0,
    .sdt   = 0x03,              /* CAN FD 터널링 */
    .len   = 256,               /* 256바이트 데이터 */
    .af    = 0x12345678,        /* Acceptance Field */
};
memset(xl.data, 0xAA, 256);
write(s, &xl, CANXL_HEAD_SZ + xl.len);

/* 수신: MTU로 프레임 유형 구분 */
union {
    struct can_frame   cc;
    struct canfd_frame fd;
    struct canxl_frame xl;
} rxbuf;

ssize_t nbytes = read(s, &rxbuf, sizeof(rxbuf));
if (nbytes == CAN_MTU)
    printf("Classic CAN\\n");
else if (nbytes == CANFD_MTU)
    printf("CAN FD\\n");
else if (nbytes >= CANXL_HEAD_SZ && (rxbuf.xl.prio & CANXL_XLF))
    printf("CAN XL: len=%u sdt=0x%02X af=0x%08X\\n",
           rxbuf.xl.len, rxbuf.xl.sdt, rxbuf.xl.af);
CAN XL과 이더넷 터널링: CAN XL의 SDT 0x04 모드를 사용하면 CAN XL 프레임 내부에 이더넷 프레임을 캡슐화할 수 있습니다. 이를 통해 기존 CAN 배선으로 이더넷 트래픽을 전달하여, 차량 아키텍처 전환 시 과도기적 브리지 역할을 수행합니다.

AF_CAN 프로토콜 패밀리 상세

SocketCAN의 핵심은 AF_CAN(= PF_CAN) 프로토콜 패밀리입니다. net/can/af_can.c에서 구현되며, CAN 프레임의 수신 분배(dispatch), 프로토콜 모듈 등록, 수신 필터 관리를 담당합니다.

AF_CAN 수신 분배 (Receive Dispatch) 메커니즘 net_device (can0) CAN HW Driver can_rx_offload NAPI / Softirq af_can.c (CAN 코어) can_receive() -> can_rcv_filter() rcv_lists: dev_rcv_lists, rx_all, rx_fil, rx_eff 수신 리스트 (Receiver Lists) rx_all 모든 프레임 수신 rx_fil SFF 필터 매칭 rx_eff EFF 필터 매칭 rx_sff[0x800] SFF ID 해시 테이블 프로토콜 모듈 (can_rx_register 콜백) CAN_RAW raw_rcv() CAN_BCM bcm_rx_handler() CAN_ISOTP isotp_rcv() CAN_J1939 j1939_can_recv() CAN_GW cgw_rcv() User Space Sockets (sk_buff clone per receiver) 각 수신자에게 sk_buff 클론이 전달되어 독립적으로 처리됩니다
/* net/can/af_can.c — 핵심 API */

/* 프로토콜 모듈 등록 */
int can_proto_register(const struct can_proto *cp);
void can_proto_unregister(const struct can_proto *cp);

/* 수신 핸들러 등록/해제 */
int can_rx_register(struct net_device *dev, canid_t can_id,
                    canid_t mask, void (*func)(struct sk_buff *, void *),
                    void *data, char *ident, struct sock *sk);
void can_rx_unregister(struct net_device *dev, canid_t can_id,
                      canid_t mask, void (*func)(struct sk_buff *, void *),
                      void *data);

/* CAN 프레임 송신 */
int can_send(struct sk_buff *skb, int loop);

/* can_proto 구조체 — 프로토콜 모듈 정의 */
struct can_proto {
    int type;                              /* SOCK_RAW or SOCK_DGRAM */
    int protocol;                           /* CAN_RAW, CAN_BCM 등 */
    const struct proto_ops *ops;          /* 소켓 연산 */
    struct proto *prot;                    /* 프로토콜 핸들러 */
};

/* 수신 필터 동작 원리:
 * 1. 드라이버가 netif_rx() / can_rx_offload_receive_skb()로 프레임 전달
 * 2. af_can.c의 can_receive()가 호출됨
 * 3. can_rcv_filter()가 등록된 모든 수신 리스트를 순회
 * 4. (received_id & mask) == (registered_id & mask)인 핸들러에 sk_buff 클론 전달
 * 5. 매칭된 각 소켓의 수신 큐에 프레임 삽입 */

sockaddr_can 구조체 상세

필드프로토콜용도설명
can_family공통프로토콜 패밀리항상 AF_CAN
can_ifindex공통인터페이스 인덱스0이면 모든 인터페이스
can_addr.tp.tx_idISO-TP송신 CAN ID요청 방향 ID
can_addr.tp.rx_idISO-TP수신 CAN ID응답 방향 ID
can_addr.j1939.nameJ193964-bit NAMEJ1939_NO_NAME 시 미사용
can_addr.j1939.addrJ1939소스 주소8-bit SA (0x00~0xFD, 0xFE=미지정)
can_addr.j1939.pgnJ1939PGNJ1939_NO_PGN 시 모든 PGN 수신
수신 필터 성능: SFF(Standard Frame Format) ID는 2048개뿐이므로 rx_sff[0x800] 해시 테이블로 O(1) 매칭이 가능합니다. 하지만 EFF(Extended Frame Format)는 2^29개의 ID 공간이므로 rx_eff 리스트를 순회하여 매칭합니다. EFF 필터가 많으면 성능에 영향을 줄 수 있습니다.

에러 핸들링 심화

CAN 프로토콜의 에러 관리는 5가지 에러 검출 메커니즘과 Fault Confinement 상태 머신으로 구성됩니다. 이 절에서는 각 에러 유형의 발생 조건, TEC/REC 카운터 증감 규칙, Bus-Off 복구 전략을 심층 분석합니다.

5가지 에러 검출 메커니즘

에러 유형검출 주체발생 조건에러 프레임 발생
Bit Error송신측송신 비트와 버스 모니터링 비트 불일치 (중재 필드 제외)즉시
Stuff Error수신측5개 연속 동일 비트 후 반대 비트(Stuff Bit) 미검출즉시
CRC Error수신측수신된 CRC와 계산된 CRC 불일치ACK 슬롯 후
Form Error수신측고정 형식 필드(CRC Del, ACK Del, EOF)에 잘못된 비트즉시
ACK Error송신측ACK 슬롯에서 dominant 비트 미검출 (수신 노드 없음)즉시

TEC/REC 카운터 증감 규칙

상황카운터변화량설명
송신 에러 발생TEC+8기본 송신 에러 페널티
수신 에러 발생REC+1기본 수신 에러 페널티
수신 에러 (첫 번째 에러 플래그 비트 이후)REC+8에러 플래그 비트에서 추가 에러
성공적 송신TEC-1정상 전송 시 감소
성공적 수신REC-1 (최소 0)정상 수신 시 감소
REC > 127일 때 성공 수신REC= 120~127 사이 값빠른 복구 촉진
Bus-Off 복구 (128 x 11 recessive bits)TEC, REC= 0모든 카운터 초기화
/* Bus-Off 자동 복구 설정 */

/* 방법 1: iproute2로 설정 */
/* restart-ms: Bus-Off 후 자동 재시작까지 대기 시간 (밀리초)
 * 0: 자동 재시작 비활성화 (수동 재시작 필요)
 * 100: 100ms 후 자동 재시작 */

/* 방법 2: 커널 드라이버에서 */
static void my_can_bus_off(struct net_device *dev)
{
    struct my_can_priv *priv = netdev_priv(dev);

    netif_stop_queue(dev);
    can_bus_off(dev);  /* CAN 코어에 Bus-Off 보고 */

    /* can_bus_off() 내부 동작:
     * 1. priv->can.state = CAN_STATE_BUS_OFF
     * 2. priv->can.can_stats.bus_off++
     * 3. restart-ms > 0이면 지연 워크큐 스케줄링
     * 4. restart-ms == 0이면 수동 재시작 대기 */
}

/* 커널 내 에러 상태 변경 보고 */
static void my_can_error_handler(struct net_device *dev,
                                  u8 tec, u8 rec)
{
    struct can_frame *cf;
    struct sk_buff *skb;
    enum can_state new_state;

    /* 에러 카운터로 새 상태 결정 */
    if (tec >= 256)
        new_state = CAN_STATE_BUS_OFF;
    else if (tec >= 128 || rec >= 128)
        new_state = CAN_STATE_ERROR_PASSIVE;
    else if (tec >= 96 || rec >= 96)
        new_state = CAN_STATE_ERROR_WARNING;
    else
        new_state = CAN_STATE_ERROR_ACTIVE;

    /* 에러 프레임 생성 및 전송 */
    skb = alloc_can_err_skb(dev, &cf);
    if (skb) {
        cf->can_id |= CAN_ERR_CRTL;
        cf->data[6] = tec;
        cf->data[7] = rec;
        can_change_state(dev, cf, new_state, new_state);
        netif_rx(skb);
    }
}
# 에러 카운터 실시간 모니터링 스크립트
$ while true; do
    ip -details link show can0 | \
      grep -oP '(state \S+|berr-counter tx \d+ rx \d+)' | \
      tr '\n' ' '
    echo "$(date +%T)"
    sleep 0.5
  done

# 출력 예:
# state ERROR-ACTIVE berr-counter tx 0 rx 0 14:23:01
# state ERROR-WARNING berr-counter tx 96 rx 32 14:23:01
# state ERROR-PASSIVE berr-counter tx 128 rx 64 14:23:02
# state BUS-OFF berr-counter tx 256 rx 128 14:23:02

# Bus-Off 수동 재시작
$ sudo ip link set can0 type can restart

# Bus-Off 자동 재시작 설정 (100ms 후)
$ sudo ip link set can0 type can restart-ms 100
Bus-Off 반복 루프 주의: restart-ms를 너무 짧게 설정하면 Bus-Off가 반복 발생하는 무한 루프에 빠질 수 있습니다. 근본 원인(비트레이트 불일치, 종단 저항 누락, 물리적 배선 문제)을 먼저 해결해야 합니다. 프로덕션 환경에서는 restart-ms를 최소 500ms 이상으로 설정하고, 재시작 횟수를 모니터링하는 것이 권장됩니다.

비트 타이밍 계산 심화

커널의 비트 타이밍 자동 계산 알고리즘은 drivers/net/can/dev/bittiming.ccan_calc_bittiming() 함수에서 구현됩니다. 목표 비트레이트와 샘플 포인트에 가장 근접한 BRP/TSEG1/TSEG2 조합을 탐색합니다.

can_calc_bittiming() 알고리즘 흐름 입력 파라미터 bitrate, sample_point, f_clk BRP 탐색 루프 brp_min ~ brp_max 순회 TQ/비트 수 계산 bt = f_clk / (bitrate * brp) TSEG1/TSEG2 분배 tseg1 = bt * sp / 1000 tseg2 = bt - tseg1 - 1 범위 클램핑 tseg1: [tseg1_min, tseg1_max] tseg2: [tseg2_min, tseg2_max] 오차 계산 bitrate_err = |actual - target| sp_err = |actual_sp - target_sp| 최적 조합 선택 비트레이트 오차 최소 -> 샘플 포인트 오차 최소 순으로 선택 결과: brp, prop_seg, phase_seg1, phase_seg2, sjw, tq

CAN FD 듀얼 비트 타이밍 계산

CAN FD는 Nominal(중재 영역)과 Data(데이터 영역) 두 개의 비트 타이밍을 독립적으로 설정합니다. 두 비트 타이밍은 동일한 CAN 클럭(f_clk)을 공유하되 BRP가 다를 수 있습니다.

파라미터Nominal (중재)Data (데이터)제약 조건
비트레이트250K~1M1M~8MData >= Nominal
권장 샘플 포인트80.0%75.0%CiA 601 권장
SJWPHASE_SEG2의 1/4 이상PHASE_SEG2의 1/4 이상컨트롤러 의존
TDCV (전파 지연 보상)해당 없음0~127 (컨트롤러 의존)Data > 2.5Mbps 시 필수
최소 TQ/비트85컨트롤러 사양에 따라 다름
# CAN FD 듀얼 비트 타이밍 계산 예제
# f_clk = 80 MHz, Nominal 500K, Data 4M

# Nominal: 500 Kbps, SP = 80%
# BRP = 10, tq = 125 ns, bt = 16 tq
# SYNC=1, PROP+PHASE1=12, PHASE2=3
# SP = (1+12)/16 = 81.25%

# Data: 4 Mbps, SP = 75%
# BRP = 2, tq = 25 ns, bt = 10 tq
# SYNC=1, PROP+PHASE1=6, PHASE2=3
# SP = (1+6)/10 = 70%

$ sudo ip link set can0 type can \
    bitrate 500000 sample-point 0.800 \
    dbitrate 4000000 dsample-point 0.750 \
    fd on

# 결과 확인
$ ip -details link show can0
    can state ERROR-ACTIVE
          bitrate 500000 sample-point 0.812
          tq 125 prop-seg 6 phase-seg1 6 phase-seg2 3 sjw 1
          dbitrate 4000000 dsample-point 0.700
          dtq 25 dprop-seg 3 dphase-seg1 3 dphase-seg2 3 dsjw 1

# TDC (Transmitter Delay Compensation) 설정
# 고속 데이터 비트레이트에서 루프 지연 보상 필요
$ sudo ip link set can0 type can \
    bitrate 500000 dbitrate 5000000 fd on \
    tdcv 0 tdco 7 tdcf 0
# tdcv: TDC 값 (0=자동), tdco: TDC 오프셋, tdcf: TDC 필터
샘플 포인트 선택 가이드: 샘플 포인트가 너무 높으면 위상 오차에 민감해지고, 너무 낮으면 비트 검출 신뢰도가 떨어집니다. CiA(CAN in Automation) 권장값을 기준으로 버스 길이와 노드 수에 따라 조정하세요. 짧은 버스(<5m)에서는 높은 샘플 포인트(87.5%)가 안전하고, 긴 버스(>40m)에서는 전파 지연 보상을 위해 낮은 샘플 포인트(75%)가 필요할 수 있습니다.

J1939 프로토콜 스택 심화

J1939 커널 구현(net/can/j1939/)은 주소 클레임(Address Claim), 전송 프로토콜(TP: BAM/CMDT), PGN 기반 라우팅, 64-bit NAME 관리를 커널 공간에서 처리합니다.

J1939 주소 클레임 (Address Claim) 프로토콜 Node A (SA=0x80) Node B (SA=0x80) CAN Bus 1. Address Claimed (PGN 0xEE00, SA=0x80, NAME_A) 2. Address Claimed (PGN 0xEE00, SA=0x80, NAME_B) 주소 충돌 감지! NAME 값 비교 NAME_A < NAME_B (숫자적으로 작은 NAME이 우선) Node A: SA=0x80 유지 (승리) 3. Address Claimed (PGN 0xEE00, SA=0x81, NAME_B) Node B: SA=0x81로 변경 (패배) 또는 Node B가 다른 주소를 사용할 수 없는 경우: Cannot Claim (SA=0xFE, NAME_B) - 통신 불가

J1939 64-bit NAME 구조

비트필드크기설명
63Arbitrary Address Capable1-bit1이면 주소 변경 가능
62~59Industry Group4-bit0=Global, 1=Highway, 2=Agriculture...
58~56Vehicle System Instance3-bit동일 시스템 내 인스턴스 번호
55~49Vehicle System7-bit차량 시스템 유형
48Reserved1-bit예약
47~40Function8-bit기능 코드 (예: Engine=0, Brake=30)
39~35Function Instance5-bit기능 인스턴스 번호
34~32ECU Instance3-bitECU 인스턴스 번호
31~21Manufacturer Code11-bit제조사 코드 (SAE 할당)
20~0Identity Number21-bit고유 식별 번호 (시리얼 넘버)
/* J1939 소켓 상세 사용법 */
#include <linux/can/j1939.h>

/* J1939 NAME 구성 */
__u64 my_name = J1939_NO_NAME;
/* NAME 필드 조합:
 * AAC=1 | IG=1(Highway) | VSI=0 | VS=0 | Func=0(Engine) |
 * FI=0 | EI=0 | Mfr=0x123 | ID=0x456789 */
my_name = (1ULL << 63) |  /* Arbitrary Address Capable */
          (1ULL << 59) |  /* Industry Group = Highway */
          (0x123ULL << 21) |  /* Manufacturer Code */
          0x456789ULL;       /* Identity Number */

/* NAME으로 바인딩 (주소 클레임 자동 수행) */
struct sockaddr_can addr = {
    .can_family = AF_CAN,
    .can_ifindex = ifr.ifr_ifindex,
    .can_addr.j1939 = {
        .name = my_name,
        .addr = 0x80,         /* 원하는 소스 주소 */
        .pgn  = J1939_NO_PGN, /* 모든 PGN 수신 */
    },
};
bind(s, (struct sockaddr *)&addr, sizeof(addr));

/* J1939 소켓 옵션 */
int prio = 3;
setsockopt(s, SOL_CAN_J1939, SO_J1939_SEND_PRIO, &prio, sizeof(prio));

/* J1939 필터 (특정 PGN만 수신) */
struct j1939_filter filt = {
    .name      = J1939_NO_NAME,
    .name_mask = 0,
    .addr      = J1939_NO_ADDR,
    .addr_mask = 0,
    .pgn       = 0xFEF1,       /* CCVS PGN */
    .pgn_mask  = 0x3FFFF,      /* 정확한 PGN 매칭 */
};
setsockopt(s, SOL_CAN_J1939, SO_J1939_FILTER,
          &filt, sizeof(filt));

/* J1939 Transport Protocol (TP) — 큰 데이터 전송
 * 8바이트 초과 시 커널이 자동으로 TP 프로토콜 수행:
 * - 브로드캐스트: BAM(Broadcast Announce Message) + DT 패킷
 * - 점대점: RTS/CTS/EndOfMsgAck 핸드셰이크 + DT 패킷
 * 최대 1785바이트 (255 패킷 x 7바이트) */
uint8_t big_msg[1785];
memset(big_msg, 0xAA, sizeof(big_msg));

struct sockaddr_can dst = {
    .can_family = AF_CAN,
    .can_addr.j1939 = {
        .name = J1939_NO_NAME,
        .addr = 0x00,           /* 특정 대상 주소 */
        .pgn  = 0xEF00,          /* Proprietary A PGN */
    },
};
/* 커널이 RTS/CTS/DT/EndOfMsgAck 자동 수행 */
sendto(s, big_msg, sizeof(big_msg), 0,
       (struct sockaddr *)&dst, sizeof(dst));

J1939 전송 프로토콜 유형

프로토콜PGN방향최대 크기흐름 제어
BAM (Broadcast Announce)0xFECA (TP.CM_BAM)브로드캐스트1785 bytes없음 (단방향)
CMDT (Connection Mode)0xEC00 (TP.CM_RTS/CTS)점대점1785 bytesRTS/CTS 핸드셰이크
ETP (Extended Transport)0xC800 (ETP.CM)점대점117,440,505 bytesRTS/CTS + DPO
J1939 커널 모듈 의존성: can-j1939 모듈은 can(af_can.c) 모듈에 의존합니다. modprobe can-j1939를 실행하면 can 모듈이 자동 로드됩니다. 커널 설정에서 CONFIG_CAN_J1939=m이 필요합니다.

ISO-TP (ISO 15765-2) 전송 프로토콜 심화

ISO-TP는 자동차 진단(UDS/OBD-II)의 핵심 전송 계층입니다. Linux 커널 5.10부터 net/can/isotp.c에 메인라인으로 포함되어, 사용자 공간에서 SOCK_DGRAM 소켓으로 투명하게 분할/재조합을 수행합니다.

ISO-TP 멀티프레임 전송 시퀀스 (25바이트 전송 예제) Tester (TX: 0x7E0) ECU (TX: 0x7E8) FF: [10 19 | D0 D1 D2 D3 D4 D5] (len=25, 6 data bytes) FC: [30 00 05 | CC CC CC CC CC] (FS=CTS, BS=0, STmin=5ms) CF: [21 | D6 D7 D8 D9 DA DB DC] (SN=1, 7 data bytes) 5ms CF: [22 | DD DE DF E0 E1 E2 E3] (SN=2, 7 data bytes) 5ms CF: [23 | E4 E5 E6 E7 E8 CC CC] (SN=3, 5 data + 2 pad) 총 25바이트 = FF(6) + CF1(7) + CF2(7) + CF3(5) CAN 프레임 5개 사용 (FF + FC + CF x 3) Flow Status (FS) 값 0 = CTS (Continue To Send) - 전송 계속 1 = Wait - 잠시 대기 (수신 버퍼 부족) 2 = Overflow - 버퍼 초과, 전송 중단 BS (Block Size) = 0: 무제한 연속 전송 BS > 0: BS개 CF마다 FC 재수신 필요 STmin: CF 간 최소 간격 (0~127=ms, 0xF1~F9=100~900us)

ISO-TP 소켓 옵션 상세

옵션구조체설명
CAN_ISOTP_OPTScan_isotp_options일반 옵션 (패딩, 확장 주소, 프레임 타입)
CAN_ISOTP_RECV_FCcan_isotp_fc_options수신 FC 파라미터 (BS, STmin, WFTmax)
CAN_ISOTP_TX_STMIN__u32TX 측 STmin 강제 (ns)
CAN_ISOTP_RX_STMIN__u32RX 측 STmin 강제 (ns)
CAN_ISOTP_LL_OPTScan_isotp_ll_options링크 레이어 옵션 (MTU, TX DLC, TX 플래그)
/* ISO-TP CAN FD 모드 사용 */
struct can_isotp_ll_options ll_opts = {
    .mtu     = CANFD_MTU,   /* CAN FD 사용 */
    .tx_dl   = 64,           /* TX DLC = 64바이트 */
    .tx_flags = CANFD_BRS,  /* BRS 활성화 */
};
setsockopt(s, SOL_CAN_ISOTP, CAN_ISOTP_LL_OPTS,
          &ll_opts, sizeof(ll_opts));

/* CAN FD + ISO-TP 조합의 이점:
 * - 클래식 CAN: SF 최대 7바이트, CF 최대 7바이트/프레임
 * - CAN FD: SF 최대 62바이트, CF 최대 63바이트/프레임
 * - 4095바이트 전송 시: CAN 2.0은 ~586프레임, CAN FD는 ~66프레임 */

/* 확장 주소 모드 (ISO-TP Extended Addressing) */
struct can_isotp_options opts = {
    .flags = CAN_ISOTP_EXTEND_ADDR | CAN_ISOTP_TX_PADDING,
    .ext_address    = 0x01,  /* TX 확장 주소 */
    .rx_ext_address = 0x02,  /* RX 확장 주소 */
    .txpad_content  = 0xCC,
};
setsockopt(s, SOL_CAN_ISOTP, CAN_ISOTP_OPTS, &opts, sizeof(opts));

/* UDS 진단 예제: DTC 읽기
 * SID 0x19: Read DTC Information
 * SubFunc 0x02: Report DTC By Status Mask
 * Status Mask 0xFF: 모든 DTC */
uint8_t req[] = {0x19, 0x02, 0xFF};
write(s, req, sizeof(req));

uint8_t resp[4096];
ssize_t len = read(s, resp, sizeof(resp));
/* resp[0] == 0x59: 긍정 응답 (SID + 0x40)
 * resp[0] == 0x7F: 부정 응답 (NRC) */
if (resp[0] == 0x59) {
    printf("DTC 응답: %zd 바이트\\n", len);
    /* DTC 파싱: 3바이트 DTC + 1바이트 Status 반복 */
    for (int i = 3; i + 3 < len; i += 4) {
        printf("  DTC: %02X%02X%02X Status: %02X\\n",
               resp[i], resp[i+1], resp[i+2], resp[i+3]);
    }
}
ISO-TP 타이밍 주의: ISO-TP의 STmin 값이 ECU의 처리 속도보다 빠르면 프레임 손실이 발생합니다. 특히 오래된 ECU는 STmin=0(가능한 빨리)을 처리하지 못할 수 있으므로, STmin=5~10ms를 권장합니다. WFTmax(Wait Frame 최대 수)도 적절히 설정하여 무한 대기를 방지하세요.

CAN 네트워크 브릿지 (can-gw) 심화

CAN Gateway(net/can/gw.c)는 Netlink 인터페이스(NETLINK_ROUTE)를 통해 커널 내에서 CAN 프레임을 라우팅, 변환, 필터링하는 고성능 프레임 프로세싱 엔진입니다.

CAN Gateway 프레임 처리 파이프라인 can0 (Source) Powertrain CAN CAN ID Filter -f can_id:mask Modification SET/AND/OR/XOR ID, Data, DLC 변환 CRC/Checksum 업데이트 can1 (Dest) Body CAN 변환 명령어 (Modification Operations) SET (설정) I: CAN ID 변경 D: Data 바이트 설정 AND (마스킹) 특정 비트 클리어 data[n] &= mask OR (설정) 특정 비트 설정 data[n] |= value XOR (토글) 특정 비트 반전 data[n] ^= mask 체크섬 자동 업데이트 (CGW_CS_XOR8 / CGW_CS_CRC8) 데이터 변환 후 지정된 바이트 위치에 XOR8 또는 CRC8 체크섬을 자동 재계산합니다
# === CAN Gateway 고급 설정 예제 ===

# 1. 조건부 포워딩: can0의 ID 0x100~0x1FF만 can1으로
$ sudo cangw -A -s can0 -d can1 -f 100:700 -e

# 2. ID 변환: can0의 0x123 → can1의 0x456으로 변환
$ sudo cangw -A -s can0 -d can1 -f 123:7FF -m SET:I:456

# 3. 데이터 변환: data[0]을 0xFF로 고정, 나머지 유지
$ sudo cangw -A -s can0 -d can1 -f 200:7FF \
    -m AND:D:00FFFFFFFFFFFFFF \
    -m OR:D:FF00000000000000

# 4. DLC 변경: 4바이트로 자르기
$ sudo cangw -A -s can0 -d can1 -f 300:7FF -m SET:L:4

# 5. XOR8 체크섬 자동 업데이트 (data[7]에 체크섬)
$ sudo cangw -A -s can0 -d can1 -f 400:7FF \
    -m SET:D:AA00000000000000 \
    -x 0:6:7
#   -x from_idx:to_idx:result_idx (XOR8 체크섬)

# 6. CRC8 체크섬 업데이트
$ sudo cangw -A -s can0 -d can1 -f 500:7FF \
    -c 0:6:7:1D:FF:00
#   -c from:to:result:init:poly:xor_out (CRC8)

# 7. 프레임 삭제 (블랙리스트)
# src == dst 로 설정하면 해당 프레임 드롭 효과
$ sudo cangw -A -s can0 -d can0 -f 666:7FF -m SET:D:0000000000000000

# 8. 통계 확인
$ cangw -L
  can0 -> can1: filter=0x100:0x700 mod=none
  can0 -> can1: filter=0x123:0x7FF mod=SET:I:0x456
# ... 전체 규칙 + 전달된 프레임 수 표시

# 9. 규칙 플러시 (전체 삭제)
$ sudo cangw -F
CAN Gateway 활용 시나리오:
  • 보안 게이트웨이: 인포테인먼트 CAN과 파워트레인 CAN 사이에서 허용된 메시지만 통과시킴
  • 프로토콜 변환: 서로 다른 CAN ID 체계를 사용하는 시스템 간 브리지
  • 데이터 마스킹: 민감한 데이터를 제거하고 안전한 값으로 대체
  • 테스트 환경: 실제 ECU 응답을 시뮬레이션하여 통합 테스트 수행

가상 CAN (vcan/vxcan) 심화

가상 CAN 인터페이스는 실제 하드웨어 없이 CAN 애플리케이션을 개발하고 테스트하는 데 필수적입니다. vcan은 로컬 루프백, vxcan은 양방향 터널(veth와 유사)을 제공합니다.

vcan 내부 구현

/* drivers/net/can/vcan.c — 핵심 동작 */

/* vcan의 xmit 함수: 실제 하드웨어 접근 없음 */
static netdev_tx_t vcan_tx(struct sk_buff *skb,
                            struct net_device *dev)
{
    struct canfd_frame *cfd = (struct canfd_frame *)skb->data;
    struct net_device_stats *stats = &dev->stats;
    int loop;

    if (can_dropped_invalid_skb(dev, skb))
        return NETDEV_TX_OK;

    stats->tx_packets++;
    stats->tx_bytes += cfd->len;

    /* 에코/루프백: VCAN_RX_ALL이면 netif_rx_ni()로 수신 큐에 삽입 */
    loop = skb->pkt_type == PACKET_LOOPBACK;
    if (loop) {
        skb->pkt_type = PACKET_BROADCAST;
        netif_rx_ni(skb);  /* 루프백으로 다시 수신 */
        return NETDEV_TX_OK;
    }

    kfree_skb(skb);
    return NETDEV_TX_OK;
}

/* vcan은 CAN/CAN FD/CAN XL 모두 지원
 * MTU 설정:
 * - CAN_MTU (16): Classic CAN만
 * - CANFD_MTU (72): CAN FD 포함
 * - CANXL_MAX_MTU (2080): CAN XL 포함 (6.3+) */

vxcan과 네트워크 네임스페이스 활용

# === 컨테이너/네임스페이스 격리 테스트 환경 ===

# 1. 네임스페이스 생성
$ sudo ip netns add ecu_sim
$ sudo ip netns add tester

# 2. vxcan 페어 생성
$ sudo ip link add vxcan_ecu type vxcan
# vxcan_ecu와 vxcan_ecupeer가 생성됨

# 3. 각 네임스페이스에 할당
$ sudo ip link set vxcan_ecu netns ecu_sim
$ sudo ip link set vxcan_ecupeer netns tester

# 4. 인터페이스 활성화
$ sudo ip netns exec ecu_sim ip link set up vxcan_ecu
$ sudo ip netns exec tester ip link set up vxcan_ecupeer

# 5. ECU 시뮬레이터 (네임스페이스 ecu_sim에서)
$ sudo ip netns exec ecu_sim \
    cangen vxcan_ecu -I 7E8 -L 8 -D i -g 100 &

# 6. 테스터 (네임스페이스 tester에서)
$ sudo ip netns exec tester candump vxcan_ecupeer

# 7. CAN Gateway를 이용한 네임스페이스 간 브리지
# (호스트에서 설정, 두 개의 vxcan 페어를 연결)
$ sudo ip link add vxcan_a type vxcan
$ sudo ip link add vxcan_b type vxcan
$ sudo ip link set up vxcan_a
$ sudo ip link set up vxcan_b
$ sudo ip link set up vxcan_apeer
$ sudo ip link set up vxcan_bpeer
$ sudo cangw -A -s vxcan_apeer -d vxcan_bpeer -e
$ sudo cangw -A -s vxcan_bpeer -d vxcan_apeer -e

# 정리
$ sudo ip netns del ecu_sim
$ sudo ip netns del tester
Docker/Podman에서 CAN 사용: 컨테이너에서 CAN을 사용하려면 호스트의 CAN 인터페이스를 --net=host로 공유하거나, vxcan 페어의 한 쪽을 컨테이너의 네트워크 네임스페이스로 이동시킵니다. ip link set vxcan1 netns $(docker inspect -f '{{.State.Pid}}' container_name)으로 할당합니다.

CAN 드라이버 작성 가이드 심화

CAN 디바이스 드라이버 개발에서 자주 마주치는 패턴들을 정리합니다. RX 처리, 에러 인터럽트 핸들링, NAPI 폴링, 전원 관리(PM), Devres 패턴을 다룹니다.

RX 프레임 처리와 NAPI

/* CAN RX 처리 — 2가지 방식 */

/* 방식 1: 인터럽트에서 직접 수신 (저속/단순 컨트롤러) */
static void my_can_rx_irq(struct net_device *dev)
{
    struct my_can_priv *priv = netdev_priv(dev);
    struct can_frame *cf;
    struct sk_buff *skb;

    skb = alloc_can_skb(dev, &cf);
    if (!skb) {
        dev->stats.rx_dropped++;
        return;
    }

    /* 하드웨어 레지스터에서 프레임 읽기 */
    cf->can_id = readl(priv->base + RX_ID_REG);
    cf->len = can_cc_dlc2len(readl(priv->base + RX_DLC_REG) & 0xF);
    for (int i = 0; i < cf->len; i++)
        cf->data[i] = readb(priv->base + RX_DATA_REG + i);

    dev->stats.rx_packets++;
    dev->stats.rx_bytes += cf->len;

    netif_rx(skb);  /* Softirq 컨텍스트에서 프레임 전달 */
}

/* 방식 2: can_rx_offload (NAPI 기반, 고속 컨트롤러) */
#include <linux/can/rx-offload.h>

static int my_can_probe(struct platform_device *pdev)
{
    struct net_device *dev;
    struct my_can_priv *priv;

    dev = alloc_candev(sizeof(*priv), 1);
    priv = netdev_priv(dev);

    /* NAPI 기반 RX offload 초기화 */
    priv->can.rx_offload.mailbox_read = my_can_mailbox_read;
    can_rx_offload_add_timestamp(dev, &priv->can.rx_offload);
    /* 또는: can_rx_offload_add_fifo(dev, &priv->can.rx_offload, weight); */

    return register_candev(dev);
}

/* mailbox_read 콜백: NAPI 폴링에서 호출 */
static struct sk_buff *my_can_mailbox_read(
    struct can_rx_offload *offload, unsigned int n,
    u32 *timestamp, bool drop)
{
    struct net_device *dev = offload->dev;
    struct can_frame *cf;
    struct sk_buff *skb;

    if (drop) {
        /* 프레임 드롭 요청: 하드웨어 FIFO에서 읽되 버림 */
        readl(priv->base + RX_RELEASE_REG);
        return ERR_PTR(-ENOBUFS);
    }

    skb = alloc_can_skb(dev, &cf);
    if (!skb)
        return ERR_PTR(-ENOMEM);

    /* 하드웨어에서 프레임 + 타임스탬프 읽기 */
    *timestamp = readl(priv->base + RX_TS_REG);
    /* ... 프레임 데이터 복사 ... */

    return skb;
}

전원 관리 (Suspend/Resume)

/* CAN 드라이버 PM 콜백 */
static int my_can_suspend(struct device *dev)
{
    struct net_device *ndev = dev_get_drvdata(dev);
    struct my_can_priv *priv = netdev_priv(ndev);

    if (netif_running(ndev)) {
        netif_stop_queue(ndev);
        netif_device_detach(ndev);

        /* 하드웨어를 절전 모드로 */
        my_can_set_sleep_mode(priv);
        priv->can.state = CAN_STATE_SLEEPING;
    }

    clk_disable_unprepare(priv->clk);
    return 0;
}

static int my_can_resume(struct device *dev)
{
    struct net_device *ndev = dev_get_drvdata(dev);
    struct my_can_priv *priv = netdev_priv(ndev);

    clk_prepare_enable(priv->clk);

    if (netif_running(ndev)) {
        my_can_set_normal_mode(priv);
        priv->can.state = CAN_STATE_ERROR_ACTIVE;

        netif_device_attach(ndev);
        netif_start_queue(ndev);
    }

    return 0;
}

static DEFINE_SIMPLE_DEV_PM_OPS(my_can_pm_ops,
                               my_can_suspend, my_can_resume);

static struct platform_driver my_can_driver = {
    .probe  = my_can_probe,
    .remove = my_can_remove,
    .driver = {
        .name = "my_can",
        .pm   = pm_sleep_ptr(&my_can_pm_ops),
        .of_match_table = my_can_of_match,
    },
};

CAN 드라이버 개발 체크리스트

항목함수/매크로설명필수 여부
디바이스 할당alloc_candev()CAN net_device + echo_skb 할당필수
비트 타이밍 상수can_bittiming_constHW 지원 범위 정의필수
지원 모드ctrlmode_supportedLOOPBACK, FD 등필수
클럭 주파수can.clock.freqCAN 컨트롤러 클럭필수
에코 SKBcan_put_echo_skb()TX 루프백 저장필수
에코 방출can_get_echo_skb()TX 완료 시 에코 방출필수
에러 보고alloc_can_err_skb()에러 프레임 생성권장
상태 변경can_change_state()에러 상태 전이 보고권장
Bus-Offcan_bus_off()Bus-Off 보고 + 자동 복구필수
RX offloadcan_rx_offload_*()NAPI 기반 RX 처리고속 시 권장
MTU 변경can_change_mtu()CAN FD MTU 전환FD 시 필수
디바이스 해제free_candev()net_device 해제필수

보안 고려사항

CAN 버스는 원래 폐쇄 네트워크를 가정하고 설계되었기 때문에, 인증/암호화/접근 제어 메커니즘이 프로토콜 자체에 내장되어 있지 않습니다. 커넥티드 카 시대에 이는 심각한 보안 위협이 됩니다.

CAN 버스 보안 위협과 대응 CAN Bus (인증/암호화 없는 브로드캐스트 매체) Engine ECU 정상 노드 Brake ECU 정상 노드 Gateway 보안 필터 Attacker OBD-II / 물리 접근 주요 공격 벡터 Spoofing 위조 CAN ID로 가짜 제어 메시지 주입 Replay 정상 프레임 캡처 후 재전송 DoS (Bus Flood) 최고 우선순위 ID로 대량 전송하여 버스 점유 Fuzzing 무작위 프레임으로 ECU 크래시 유발 대응 방안 SecOC(AUTOSAR) | CAN XL 보안 확장 | IDS(침입 탐지) | 네트워크 세그먼트 격리 | 게이트웨이 필터링 Linux: CAN Gateway 규칙 | SocketCAN 필터 | 네임스페이스 격리 | MAC(Mandatory Access Control)

Linux CAN 보안 조치

# === 1. CAN Gateway로 보안 필터링 ===
# 허용된 CAN ID만 통과 (화이트리스트 방식)
# can0 (외부) → can1 (내부): 특정 ID만 허용
$ sudo cangw -A -s can0 -d can1 -f 100:7FF -e  # 0x100만 허용
$ sudo cangw -A -s can0 -d can1 -f 200:7FF -e  # 0x200만 허용
# 나머지 CAN ID는 자동으로 차단됨

# === 2. SocketCAN 필터로 수신 제한 ===
# 애플리케이션별 필요한 CAN ID만 수신

# === 3. 네트워크 네임스페이스 격리 ===
# vxcan으로 CAN 인터페이스를 네임스페이스별 격리
$ sudo ip netns add secure_zone
$ sudo ip link add vxcan_sec type vxcan
$ sudo ip link set vxcan_secpeer netns secure_zone
# secure_zone 네임스페이스 내 프로세스만 vxcan_secpeer 접근 가능

# === 4. SELinux/AppArmor CAN 소켓 제한 ===
# AppArmor 프로파일 예제:
# /etc/apparmor.d/usr.sbin.can-app
# profile can-app /usr/sbin/can-app {
#   network can raw,
#   network can dgram,
#   # deny network inet,  # 인터넷 접근 차단
# }

# === 5. CAN 버스 이상 탐지 (IDS) ===
# candump 로그를 분석하여 비정상 패턴 탐지
$ candump can0 -l -t d > /var/log/can/bus.log &
# 분석: 비정상적인 CAN ID, 주기 이상, 대량 트래픽 등

AUTOSAR SecOC와 CAN XL 보안

기술표준보호 범위Linux 지원
SecOCAUTOSAR 4.x메시지 인증(MAC), 재전송 방지(Freshness)사용자 공간 구현 필요
CAN XL SecOCCiA 610CANXL_SEC 플래그로 프레임 레벨 보안커널 6.3+ (프레임 구조만)
MACsec over CAN실험적CAN 프레임 데이터 암호화사용자 공간 구현
HSM 기반SHE/Evita하드웨어 보안 모듈로 키 관리드라이버 의존
CAN 보안의 한계: CAN 프로토콜 자체에 인증이 없으므로, 물리적으로 버스에 접근할 수 있는 공격자는 모든 노드를 가장할 수 있습니다. OBD-II 포트는 차량 외부에서 직접 접근 가능한 공격 벡터입니다. 프로덕션 시스템에서는 반드시 CAN Gateway에서 화이트리스트 필터링을 적용하고, 중요 ECU(브레이크, 조향)는 별도의 격리된 CAN 세그먼트에 배치해야 합니다.

성능 최적화

CAN 버스 시스템의 성능 최적화는 버스 부하율 관리, 메시지 우선순위 설계, 지연시간 최소화, 커널 튜닝에 중점을 둡니다.

버스 부하율 계산

비트레이트프레임 유형DLC프레임 비트 수 (최대)최대 프레임율50% 부하 시
500 KbpsCAN 2.0 SFF8~130 bits~3846 fps~1923 fps
500 KbpsCAN 2.0 EFF8~150 bits~3333 fps~1667 fps
1 MbpsCAN FD (64B)64~700 bits~1428 fps~714 fps
500K/4MCAN FD BRS (64B)64~200+130 bits~3000 fps~1500 fps
# 버스 부하율 측정
$ canbusload can0@500000 -r 1 -b -c
  can0@500000   988  50113  49420 [|||||||||||         ] 49%

# TX 큐 크기 조정 (고부하 환경)
$ sudo ip link set can0 txqueuelen 1000

# 소켓 수신 버퍼 크기 조정
$ sudo sysctl -w net.core.rmem_max=8388608
$ sudo sysctl -w net.core.rmem_default=1048576

# CAN 프레임 타임스탬프 정밀도 확인
$ candump can0 -t d
  (000.001023) can0 123 [8] DE AD BE EF CA FE BA BE
  (000.001015) can0 124 [8] 11 22 33 44 55 66 77 88
# 프레임 간 ~1ms 간격 확인
버스 부하 가이드라인: CAN 버스 부하율은 일반적으로 30~50%를 초과하지 않는 것이 권장됩니다. 70% 이상이면 중재 지연이 급격히 증가하고, 90% 이상이면 낮은 우선순위 메시지의 전송 실패(Lost Arbitration)가 빈번해집니다. CAN FD와 BRS를 활용하면 동일 비트레이트에서 실효 처리량을 4~8배 향상시킬 수 있습니다.

참고자료

다음 학습: