Skip to content

Commit b1baa7a

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 an option which is disabled by default to avoid any potential adverse impact on existing usage. This is using the same param 'use_system_timestamp' as established by neovi_bus.py. I've also modified logger.py to provide an additional '-H' flag in the same way that candump does in order to use this functionality.
1 parent 5be89ec commit b1baa7a

File tree

3 files changed

+51
-10
lines changed

3 files changed

+51
-10
lines changed

can/interfaces/socketcan/constants.py

Lines changed: 4 additions & 0 deletions
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_SOFTWARE = (1<<4)
9+
SOF_TIMESTAMPING_RX_SOFTWARE = (1<<3)
10+
SOF_TIMESTAMPING_RAW_HARDWARE = (1<<6)
711

812
CAN_ERR_FLAG = 0x20000000
913
CAN_RTR_FLAG = 0x40000000

can/interfaces/socketcan/socketcan.py

Lines changed: 38 additions & 10 deletions
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

@@ -557,10 +557,24 @@ def capture_message(
557557
assert len(ancillary_data) == 1, "only requested a single extra field"
558558
cmsg_level, cmsg_type, cmsg_data = ancillary_data[0]
559559
assert (
560-
cmsg_level == socket.SOL_SOCKET and cmsg_type == constants.SO_TIMESTAMPNS
560+
cmsg_level == socket.SOL_SOCKET and (
561+
(cmsg_type == constants.SO_TIMESTAMPNS) or
562+
(cmsg_type == constants.SO_TIMESTAMPING))
561563
), "received control message type that was not requested"
562564
# see https://man7.org/linux/man-pages/man3/timespec.3.html -> struct timespec for details
563-
seconds, nanoseconds = RECEIVED_TIMESTAMP_STRUCT.unpack_from(cmsg_data)
565+
566+
if cmsg_type == constants.SO_TIMESTAMPNS:
567+
seconds, nanoseconds = RECEIVED_TIMESPEC_STRUCT.unpack_from(cmsg_data)
568+
569+
if cmsg_type == constants.SO_TIMESTAMPING:
570+
# stamp[0] is the software timestamp
571+
# stamp[1] is deprecated
572+
# stamp[2] is the raw hardware timestamp
573+
# See chapter 2.1.2 Receive timestamps in
574+
# linux/Documentation/networking/timestamping.txt
575+
offset = struct.calcsize(RECEIVED_TIMESPEC_STRUCT.format) * 2
576+
seconds, nanoseconds = RECEIVED_TIMESPEC_STRUCT.unpack_from(cmsg_data, offset=offset)
577+
564578
if nanoseconds >= 1e9:
565579
raise can.CanOperationError(
566580
f"Timestamp nanoseconds field was out of range: {nanoseconds} not less than 1e9"
@@ -619,6 +633,7 @@ def __init__(
619633
self,
620634
channel: str = "",
621635
receive_own_messages: bool = False,
636+
use_system_timestamp: bool = True,
622637
local_loopback: bool = True,
623638
fd: bool = False,
624639
can_filters: Optional[CanFilters] = None,
@@ -642,6 +657,9 @@ def __init__(
642657
channel using :attr:`can.Message.channel`.
643658
:param receive_own_messages:
644659
If transmitted messages should also be received by this bus.
660+
:param bool use_system_timestamp:
661+
Use system timestamp for can messages instead of the hardware time
662+
stamp
645663
:param local_loopback:
646664
If local loopback should be enabled on this bus.
647665
Please note that local loopback does not mean that messages sent
@@ -659,6 +677,7 @@ def __init__(
659677
self.socket = create_socket()
660678
self.channel = channel
661679
self.channel_info = f"socketcan channel '{channel}'"
680+
self.use_system_timestamp = use_system_timestamp
662681
self._bcm_sockets: Dict[str, socket.socket] = {}
663682
self._is_filtered = False
664683
self._task_id = 0
@@ -703,12 +722,21 @@ def __init__(
703722
except OSError as error:
704723
log.error("Could not enable error frames (%s)", error)
705724

706-
# enable nanosecond resolution timestamping
707-
# we can always do this since
708-
# 1) it is guaranteed to be at least as precise as without
709-
# 2) it is available since Linux 2.6.22, and CAN support was only added afterward
710-
# so this is always supported by the kernel
711-
self.socket.setsockopt(socket.SOL_SOCKET, constants.SO_TIMESTAMPNS, 1)
725+
if self.use_system_timestamp:
726+
# Utilise SOF_TIMESTAMPNS interface :
727+
# we can always do this since
728+
# 1) it is guaranteed to be at least as precise as without
729+
# 2) it is available since Linux 2.6.22, and CAN support was only added afterward
730+
# so this is always supported by the kernel
731+
self.socket.setsockopt(socket.SOL_SOCKET, constants.SO_TIMESTAMPNS, 1)
732+
else:
733+
# Utilise SOF_TIMESTAMPNS interface :
734+
# Allows us to use hardware timestamps where available
735+
timestamping_flags = (constants.SOF_TIMESTAMPING_SOFTWARE |
736+
constants.SOF_TIMESTAMPING_RX_SOFTWARE |
737+
constants.SOF_TIMESTAMPING_RAW_HARDWARE)
738+
739+
self.socket.setsockopt(socket.SOL_SOCKET, constants.SO_TIMESTAMPING, timestamping_flags)
712740

713741
try:
714742
bind_socket(self.socket, channel)

can/logger.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ def _create_base_argument_parser(parser: argparse.ArgumentParser) -> None:
4646
choices=sorted(can.VALID_INTERFACES),
4747
)
4848

49+
parser.add_argument(
50+
"-H",
51+
"--hardwarets",
52+
help="Read hardware timestamps instead of system timestamps.",
53+
action="store_true"
54+
)
55+
4956
parser.add_argument(
5057
"-b", "--bitrate", type=int, help="Bitrate to use for the CAN bus."
5158
)
@@ -109,6 +116,8 @@ def _create_bus(parsed_args: argparse.Namespace, **kwargs: Any) -> can.BusABC:
109116
config["data_bitrate"] = parsed_args.data_bitrate
110117
if getattr(parsed_args, "can_filters", None):
111118
config["can_filters"] = parsed_args.can_filters
119+
if parsed_args.hardwarets:
120+
config["use_system_timestamp"] = False
112121

113122
return Bus(parsed_args.channel, **config)
114123

0 commit comments

Comments
 (0)