NetCAN
NetCAN is the PF_CAN protocol family for FreeBSD -
kernel modules, a generic interface layer, a hardware driver, and a
full userland toolkit. Modeled after Linux SocketCAN, written native
to FreeBSD: NETISR_CAN dispatch, ifnet
integration.
Frame formats
A single CAN_RAW socket opts into whichever generations it
needs via setsockopt(SOL_CAN_RAW, …). Default is
Classical CAN for binary compatibility; FD and XL are independent
opt-ins.
- Classical CAN 2.0B -
struct can_frame, MTU 16 - 11/29-bit identifiers, RTR frames, error frames.
- CAN FD -
struct canfd_frame, MTU 72 - Up to 64-byte payloads, Bit Rate Switching, Error State Indicator.
- CAN XL -
struct canxl_frame, MTU 2060 - Up to 2048-byte payloads, VCID handling via
CAN_RAW_XL_VCID_OPTS.
Kernel modules
| Module | Role |
|---|---|
can.ko | Core domain. Registers PF_CAN via DOMAIN_SET(can), provides the NETISR_CAN handler, the generic can_if interface layer, the ioctl handler, and the bit-timing calculator. |
can_raw.ko | CAN_RAW for SOCK_RAW sockets. Up to 512 filters per socket, loopback control, recv_own_msgs semantics, CAN FD / CAN XL support, epoch-based PCB synchronization. |
can_nmea2000.ko | NMEA 2000 / SAE J1939 for SOCK_DGRAM sockets. Fast Packet reassembly, Address Claim (PGN 60928), Product Information (PGN 126996). |
vcan.ko | Virtual CAN driver. Cloned loopback interfaces (vcanX) for testing without hardware. CAN FD and CAN XL MTUs. |
ahc0512.ko | Hardware driver for the AHC0512 (RDC A9610LITOA) Flexcan-compatible controller found on Advantech industrial platforms. |
Userland
| Command | Role |
|---|---|
canconfig(8) | Configure CAN interfaces - bitrate, data-bitrate (FD), control modes (FD, XL, loopback, listen-only, one-shot, …), filters. |
candump(1) | Capture and display CAN frames. Ported from Linux can-utils; kqueue-driven. |
cansend(1) | Send a CAN frame from the command line. |
slcand(1) | Serial Line CAN daemon. Bridges a serial SLCAN device to a CAN ifnet. |
n2kdump(1) | Decode NMEA 2000 messages from a CAN bus. Configurable PGN catalog, JSON output. |
API
Standard socket(2) family. If you’ve written SocketCAN
code on Linux, this is what you already know.
/* Open a raw CAN socket on vcan0. */ int s = socket(PF_CAN, SOCK_RAW, CAN_RAW); struct sockaddr_can addr = { .len = sizeof addr, /* bind() validates this */ .can_family = AF_CAN, .can_ifindex = if_nametoindex("vcan0"), }; bind(s, (struct sockaddr *)&addr, sizeof addr); /* Opt into CAN FD (72-byte MTU). */ int on = 1; setsockopt(s, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &on, sizeof on); /* Filter two arbitration IDs. */ struct can_filter f[2] = { { .can_id = 0x123, .can_mask = CAN_SFF_MASK }, { .can_id = 0x18EEFF01 | CAN_EFF_FLAG, .can_mask = CAN_EFF_MASK }, }; setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, f, sizeof f); /* Send. */ struct canfd_frame fr = { .can_id = 0x123, .len = 8, .data = { 0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba, 0xbe }, }; write(s, &fr, CANFD_MTU);
Socket options
CAN_RAW_FILTER- Up to 512
struct can_filterentries per socket. CAN_RAW_JOIN_FILTERS- Switch the filter aggregator from OR (default) to AND.
CAN_RAW_FD_FRAMES/CAN_RAW_XL_FRAMES- Opt into CAN FD or CAN XL frames respectively. Independent switches.
CAN_RAW_LOOPBACK- Software loopback. On by default.
CAN_RAW_RECV_OWN_MSGS- Receive your own transmissions. Off by default.
CAN_RAW_ERR_FILTER- Receive error frames matching a mask.
CAN_RAW_XL_VCID_OPTS- CAN XL Virtual CAN Identifier handling.
Inside the kernel
- Two-phase netisr dispatch
NETISR_CANsnapshots the registered protocol list under a briefNET_EPOCHhold, then delivers each protocol’s input function outside epoch. Prevents grace-period starvation under sustained frame rates.- Per-interface backpressure
can_if_transmit()maintains an atomicnetisr_inflightcounter per interface. Above the backlog limit, transmits are dropped rather than overrunning the netisr. The limit isdev.can.netisr_backlog_maxglobally, anddev.<driver>.<unit>.netisr_backlog_maxper device.- ISR + taskqueue, no shortcuts
- Hardware ISRs never touch mbufs or the stack. They snapshot pending hardware state under a spin lock and schedule a taskqueue. Allocation and delivery happen in the taskqueue.
- Bus-off recovery + TX watchdog
- Automatic restart (
sysctl restart_ms), TX watchdog for stuck transmissions, synthetic error frames (CAN_ERR_FLAG) delivered to sockets on state transitions.
Locking hierarchy
The documented acquisition order. Not every pair below is exercised:
the netisr walks the protocol list under NET_EPOCH
without taking can_proto_list_mtx, and registration takes
the mutex without entering epoch.
CAN_LOCK (per-interface mtx, MTX_DEF) +- can_proto_list_mtx (global, MTX_DEF) +- NET_EPOCH (read-only, brief) +- CAN_RAW_LOCK (global, MTX_DEF) +- socket buffer locks (sb_mtx) AHC0512_INTR_LOCK (per-device, MTX_SPIN) -- ISR-only, never nested
Writing a driver
Embed a struct can_softc, populate
hw_transmit_locked / set_bittiming /
set_ctrlmode / set_powermode_locked /
get_bus_state, call can_if_attach(). The
ahc0512 driver under sys/dev/can/ahc0512/ is
a complete reference implementation - ACPI probe, ISR + taskqueue,
full state machine. It attaches to the ACPI bus only; PCI and
platform-bus attachment are not implemented.
Try it
# virtual CAN - no hardware needed kldload vcan ifconfig vcan create ifconfig vcan0 up # the interface is ifconfig's job candump vcan0 & cansend vcan0 123#deadbeef
# on an AHC0512 kldload ahc0512 canconfig can0 bitrate 250000 canconfig can0 listenonly on ifconfig can0 up n2kdump can0 -j # interface first, then options
A full CAN edge
NetCAN is the kernel floor. A real telemetry edge stacks a decoder, a message bus, persistence, and dashboards on top of it. The MarlinOS racing platform is that worked example - NMEA 2000 capture over native CAN, NATS routing, TimescaleDB, and onboard dashboards, in a five-jail platform that runs offline for days. Swap the decoder and the same shape is any industrial or automotive CAN edge - a factory floor, a test bench, a vehicle gateway.