Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CC-2174: modify CAN frame header structure to match updated struct ca… #1851

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions can/interfaces/socketcan/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,18 @@
SIOCGSTAMP = 0x8906
EXTFLG = 0x0004

CANFD_BRS = 0x01
CANFD_ESI = 0x02
CANFD_BRS = 0x01 # bit rate switch (second bitrate for payload data)
CANFD_ESI = 0x02 # error state indicator of the transmitting node
CANFD_FDF = 0x04 # mark CAN FD for dual use of struct canfd_frame

# CAN payload length and DLC definitions according to ISO 11898-1
CAN_MAX_DLC = 8
CAN_MAX_RAW_DLC = 15
CAN_MAX_DLEN = 8

# CAN FD payload length and DLC definitions according to ISO 11898-7
CANFD_MAX_DLC = 15
CANFD_MAX_DLEN = 64

CANFD_MTU = 72

Expand Down
98 changes: 86 additions & 12 deletions can/interfaces/socketcan/socketcan.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,23 +139,68 @@ def bcm_header_factory(
# The 32bit can id is directly followed by the 8bit data link count
# The data field is aligned on an 8 byte boundary, hence we add padding
# which aligns the data field to an 8 byte boundary.
CAN_FRAME_HEADER_STRUCT = struct.Struct("=IBB2x")
CAN_FRAME_HEADER_STRUCT = struct.Struct("=IBB1xB")


def build_can_frame(msg: Message) -> bytes:
"""CAN frame packing/unpacking (see 'struct can_frame' in <linux/can.h>)
/**
* struct can_frame - basic CAN frame structure
* @can_id: the CAN ID of the frame and CAN_*_FLAG flags, see above.
* @can_dlc: the data length field of the CAN frame
* @data: the CAN frame payload.
*/
* struct can_frame - Classical CAN frame structure (aka CAN 2.0B)
* @can_id: CAN ID of the frame and CAN_*_FLAG flags, see canid_t definition
* @len: CAN frame payload length in byte (0 .. 8)
* @can_dlc: deprecated name for CAN frame payload length in byte (0 .. 8)
* @__pad: padding
* @__res0: reserved / padding
* @len8_dlc: optional DLC value (9 .. 15) at 8 byte payload length
* len8_dlc contains values from 9 .. 15 when the payload length is
* 8 bytes but the DLC value (see ISO 11898-1) is greater then 8.
* CAN_CTRLMODE_CC_LEN8_DLC flag has to be enabled in CAN driver.
* @data: CAN frame payload (up to 8 byte)
*/
struct can_frame {
canid_t can_id; /* 32 bit CAN_ID + EFF/RTR/ERR flags */
__u8 can_dlc; /* data length code: 0 .. 8 */
__u8 data[8] __attribute__((aligned(8)));
union {
/* CAN frame payload length in byte (0 .. CAN_MAX_DLEN)
* was previously named can_dlc so we need to carry that
* name for legacy support
*/
__u8 len;
__u8 can_dlc; /* deprecated */
} __attribute__((packed)); /* disable padding added in some ABIs */
__u8 __pad; /* padding */
__u8 __res0; /* reserved / padding */
__u8 len8_dlc; /* optional DLC for 8 byte payload length (9 .. 15) */
__u8 data[CAN_MAX_DLEN] __attribute__((aligned(8)));
};

/*
* defined bits for canfd_frame.flags
*
* The use of struct canfd_frame implies the FD Frame (FDF) bit to
* be set in the CAN frame bitstream on the wire. The FDF bit switch turns
* the CAN controllers bitstream processor into the CAN FD mode which creates
* two new options within the CAN FD frame specification:
*
* Bit Rate Switch - to indicate a second bitrate is/was used for the payload
* Error State Indicator - represents the error state of the transmitting node
*
* As the CANFD_ESI bit is internally generated by the transmitting CAN
* controller only the CANFD_BRS bit is relevant for real CAN controllers when
* building a CAN FD frame for transmission. Setting the CANFD_ESI bit can make
* sense for virtual CAN interfaces to test applications with echoed frames.
*
* The struct can_frame and struct canfd_frame intentionally share the same
* layout to be able to write CAN frame content into a CAN FD frame structure.
* When this is done the former differentiation via CAN_MTU / CANFD_MTU gets
* lost. CANFD_FDF allows programmers to mark CAN FD frames in the case of
* using struct canfd_frame for mixed CAN / CAN FD content (dual use).
* Since the introduction of CAN XL the CANFD_FDF flag is set in all CAN FD
* frame structures provided by the CAN subsystem of the Linux kernel.
*/
#define CANFD_BRS 0x01 /* bit rate switch (second bitrate for payload data) */
#define CANFD_ESI 0x02 /* error state indicator of the transmitting node */
#define CANFD_FDF 0x04 /* mark CAN FD for dual use of struct canfd_frame */

/**
* struct canfd_frame - CAN flexible data rate frame structure
* @can_id: CAN ID of the frame and CAN_*_FLAG flags, see canid_t definition
Expand All @@ -175,14 +220,30 @@ def build_can_frame(msg: Message) -> bytes:
};
"""
can_id = _compose_arbitration_id(msg)

flags = 0

# The socketcan code identify the received FD frame by the packet length.
# So, padding to the data length is performed according to the message type (Classic / FD)
if msg.is_fd:
flags |= constants.CANFD_FDF
max_len = constants.CANFD_MAX_DLEN
else:
max_len = constants.CAN_MAX_DLEN

if msg.bitrate_switch:
flags |= constants.CANFD_BRS
if msg.error_state_indicator:
flags |= constants.CANFD_ESI
max_len = 64 if msg.is_fd else 8

data = bytes(msg.data).ljust(max_len, b"\x00")
return CAN_FRAME_HEADER_STRUCT.pack(can_id, msg.dlc, flags) + data

if msg.is_remote_frame:
data_len = msg.dlc
else:
data_len = min(i for i in can.util.CAN_FD_DLC if i >= len(msg.data))
header = CAN_FRAME_HEADER_STRUCT.pack(can_id, data_len, flags, msg.dlc)
return header + data


def build_bcm_header(
Expand Down Expand Up @@ -260,11 +321,24 @@ def build_bcm_update_header(can_id: int, msg_flags: int, nframes: int = 1) -> by


def dissect_can_frame(frame: bytes) -> Tuple[int, int, int, bytes]:
can_id, can_dlc, flags = CAN_FRAME_HEADER_STRUCT.unpack_from(frame)
can_id, data_len, flags, len8_dlc = CAN_FRAME_HEADER_STRUCT.unpack_from(frame)
if len(frame) != constants.CANFD_MTU:
# Flags not valid in non-FD frames
flags = 0
return can_id, can_dlc, flags, frame[8 : 8 + can_dlc]

if data_len not in can.util.CAN_FD_DLC:
lumagi marked this conversation as resolved.
Show resolved Hide resolved
data_len = min(i for i in can.util.CAN_FD_DLC if i >= data_len)

# Allow deprecated can frames with old struct
if (
data_len == constants.CAN_MAX_DLEN
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took another final look at it, and I have one last request:
This feature only concerns CAN 2.0 so could you add another condition to this if clause that check that we're dealing with a non-FD frame?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amm yes you are correct, maybe I should add tests for this case as well

and constants.CAN_MAX_DLEN < len8_dlc <= constants.CAN_MAX_RAW_DLC
):
can_dlc = len8_dlc
else:
can_dlc = data_len

return can_id, can_dlc, flags, frame[8 : 8 + data_len]
lumagi marked this conversation as resolved.
Show resolved Hide resolved


def create_bcm_socket(channel: str) -> socket.socket:
Expand Down
Loading