Skip to content

Commit 3f148c2

Browse files
author
David Peverley
committed
socketcan: support use of SO_TIMESTAMPING for hardware timestamps
The current implemenation of socketcan utilises SO_TIMESTAMPNS which only offers system timestamps. I've looked at how can-utils candump.c configures hardware timestamping and implemented this in socketcan as a new option 'can_hardware_timestamps' which is disabled by default to avoid any potential adverse impact on existing usage. Additionally modify logger.py to provide an additional '-H' flag in the same way that candump does in order to use this functionality.
1 parent 5d62394 commit 3f148c2

File tree

2 files changed

+54
-11
lines changed

2 files changed

+54
-11
lines changed

can/interfaces/socketcan/constants.py

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
# Generic socket constants
66
SO_TIMESTAMPNS = 35
7+
SO_TIMESTAMPING = 37
8+
SOF_TIMESTAMPING_RX_SOFTWARE = 1 << 3
9+
SOF_TIMESTAMPING_SOFTWARE = 1 << 4
10+
SOF_TIMESTAMPING_RAW_HARDWARE = 1 << 6
711

812
CAN_ERR_FLAG = 0x20000000
913
CAN_RTR_FLAG = 0x40000000

can/interfaces/socketcan/socketcan.py

+50-11
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@
4242

4343

4444
# Constants needed for precise handling of timestamps
45-
RECEIVED_TIMESTAMP_STRUCT = struct.Struct("@ll")
45+
RECEIVED_TIMESPEC_STRUCT = struct.Struct("@ll")
4646
RECEIVED_ANCILLARY_BUFFER_SIZE = (
47-
CMSG_SPACE(RECEIVED_TIMESTAMP_STRUCT.size) if CMSG_SPACE_available else 0
47+
CMSG_SPACE(RECEIVED_TIMESPEC_STRUCT.size * 3) if CMSG_SPACE_available else 0
4848
)
4949

5050

@@ -636,11 +636,24 @@ def capture_message(
636636
# Fetching the timestamp
637637
assert len(ancillary_data) == 1, "only requested a single extra field"
638638
cmsg_level, cmsg_type, cmsg_data = ancillary_data[0]
639-
assert (
640-
cmsg_level == socket.SOL_SOCKET and cmsg_type == constants.SO_TIMESTAMPNS
639+
assert cmsg_level == socket.SOL_SOCKET and cmsg_type in (
640+
constants.SO_TIMESTAMPNS,
641+
constants.SO_TIMESTAMPING,
641642
), "received control message type that was not requested"
642643
# see https://man7.org/linux/man-pages/man3/timespec.3.html -> struct timespec for details
643-
seconds, nanoseconds = RECEIVED_TIMESTAMP_STRUCT.unpack_from(cmsg_data)
644+
if cmsg_type == constants.SO_TIMESTAMPNS:
645+
seconds, nanoseconds = RECEIVED_TIMESPEC_STRUCT.unpack_from(cmsg_data)
646+
else:
647+
# cmsg_type == constants.SO_TIMESTAMPING
648+
#
649+
# stamp[0] is the software timestamp
650+
# stamp[1] is deprecated
651+
# stamp[2] is the raw hardware timestamp
652+
offset = struct.calcsize(RECEIVED_TIMESPEC_STRUCT.format) * 2
653+
seconds, nanoseconds = RECEIVED_TIMESPEC_STRUCT.unpack_from(
654+
cmsg_data, offset=offset
655+
)
656+
644657
if nanoseconds >= 1e9:
645658
raise can.CanOperationError(
646659
f"Timestamp nanoseconds field was out of range: {nanoseconds} not less than 1e9"
@@ -699,6 +712,7 @@ def __init__(
699712
self,
700713
channel: str = "",
701714
receive_own_messages: bool = False,
715+
can_hardware_timestamps: bool = False,
702716
local_loopback: bool = True,
703717
fd: bool = False,
704718
can_filters: Optional[CanFilters] = None,
@@ -722,6 +736,17 @@ def __init__(
722736
channel using :attr:`can.Message.channel`.
723737
:param receive_own_messages:
724738
If transmitted messages should also be received by this bus.
739+
:param bool can_hardware_timestamps:
740+
Use raw hardware timestamp for can messages if available instead
741+
of the system timestamp. By default we use the SO_TIMESTAMPNS
742+
interface which provides ns resolution but low accuracy. If your
743+
can hardware supports it you can use this parameter to
744+
alternatively use the SO_TIMESTAMPING interface and request raw
745+
hardware timestamps. These are much higher precision but will
746+
almost certainly not be referenced to the time of day. There
747+
may be other pitfalls to such as loopback packets reporting with
748+
no timestamp at all.
749+
See https://www.kernel.org/doc/html/latest/networking/timestamping.html
725750
:param local_loopback:
726751
If local loopback should be enabled on this bus.
727752
Please note that local loopback does not mean that messages sent
@@ -739,6 +764,7 @@ def __init__(
739764
self.socket = create_socket()
740765
self.channel = channel
741766
self.channel_info = f"socketcan channel '{channel}'"
767+
self._can_hardware_timestamps = can_hardware_timestamps
742768
self._bcm_sockets: Dict[str, socket.socket] = {}
743769
self._is_filtered = False
744770
self._task_id = 0
@@ -783,12 +809,25 @@ def __init__(
783809
except OSError as error:
784810
log.error("Could not enable error frames (%s)", error)
785811

786-
# enable nanosecond resolution timestamping
787-
# we can always do this since
788-
# 1) it is guaranteed to be at least as precise as without
789-
# 2) it is available since Linux 2.6.22, and CAN support was only added afterward
790-
# so this is always supported by the kernel
791-
self.socket.setsockopt(socket.SOL_SOCKET, constants.SO_TIMESTAMPNS, 1)
812+
if not self._can_hardware_timestamps:
813+
# Utilise SO_TIMESTAMPNS interface :
814+
# we can always do this since
815+
# 1) it is guaranteed to be at least as precise as without
816+
# 2) it is available since Linux 2.6.22, and CAN support was only added afterward
817+
# so this is always supported by the kernel
818+
self.socket.setsockopt(socket.SOL_SOCKET, constants.SO_TIMESTAMPNS, 1)
819+
else:
820+
# Utilise SO_TIMESTAMPING interface :
821+
# Allows us to use raw hardware timestamps where available
822+
timestamping_flags = (
823+
constants.SOF_TIMESTAMPING_SOFTWARE
824+
| constants.SOF_TIMESTAMPING_RX_SOFTWARE
825+
| constants.SOF_TIMESTAMPING_RAW_HARDWARE
826+
)
827+
828+
self.socket.setsockopt(
829+
socket.SOL_SOCKET, constants.SO_TIMESTAMPING, timestamping_flags
830+
)
792831

793832
try:
794833
bind_socket(self.socket, channel)

0 commit comments

Comments
 (0)