Skip to content

Commit deb6c79

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 deb6c79

File tree

3 files changed

+57
-11
lines changed

3 files changed

+57
-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_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

+44-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

@@ -556,11 +556,26 @@ def capture_message(
556556
# Fetching the timestamp
557557
assert len(ancillary_data) == 1, "only requested a single extra field"
558558
cmsg_level, cmsg_type, cmsg_data = ancillary_data[0]
559-
assert (
560-
cmsg_level == socket.SOL_SOCKET and cmsg_type == constants.SO_TIMESTAMPNS
559+
assert cmsg_level == socket.SOL_SOCKET and (
560+
(cmsg_type == constants.SO_TIMESTAMPNS)
561+
or (cmsg_type == constants.SO_TIMESTAMPING)
561562
), "received control message type that was not requested"
562563
# 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)
564+
565+
if cmsg_type == constants.SO_TIMESTAMPING:
566+
# stamp[0] is the software timestamp
567+
# stamp[1] is deprecated
568+
# stamp[2] is the raw hardware timestamp
569+
# See chapter 2.1.2 Receive timestamps in
570+
# linux/Documentation/networking/timestamping.txt
571+
offset = struct.calcsize(RECEIVED_TIMESPEC_STRUCT.format) * 2
572+
seconds, nanoseconds = RECEIVED_TIMESPEC_STRUCT.unpack_from(
573+
cmsg_data, offset=offset
574+
)
575+
else:
576+
# cmsg_type == constants.SO_TIMESTAMPNS
577+
seconds, nanoseconds = RECEIVED_TIMESPEC_STRUCT.unpack_from(cmsg_data)
578+
564579
if nanoseconds >= 1e9:
565580
raise can.CanOperationError(
566581
f"Timestamp nanoseconds field was out of range: {nanoseconds} not less than 1e9"
@@ -619,6 +634,7 @@ def __init__(
619634
self,
620635
channel: str = "",
621636
receive_own_messages: bool = False,
637+
use_system_timestamp: bool = True,
622638
local_loopback: bool = True,
623639
fd: bool = False,
624640
can_filters: Optional[CanFilters] = None,
@@ -642,6 +658,9 @@ def __init__(
642658
channel using :attr:`can.Message.channel`.
643659
:param receive_own_messages:
644660
If transmitted messages should also be received by this bus.
661+
:param bool use_system_timestamp:
662+
Use system timestamp for can messages instead of the hardware time
663+
stamp
645664
:param local_loopback:
646665
If local loopback should be enabled on this bus.
647666
Please note that local loopback does not mean that messages sent
@@ -659,6 +678,7 @@ def __init__(
659678
self.socket = create_socket()
660679
self.channel = channel
661680
self.channel_info = f"socketcan channel '{channel}'"
681+
self.use_system_timestamp = use_system_timestamp
662682
self._bcm_sockets: Dict[str, socket.socket] = {}
663683
self._is_filtered = False
664684
self._task_id = 0
@@ -703,12 +723,25 @@ def __init__(
703723
except OSError as error:
704724
log.error("Could not enable error frames (%s)", error)
705725

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)
726+
if self.use_system_timestamp:
727+
# Utilise SOF_TIMESTAMPNS interface :
728+
# we can always do this since
729+
# 1) it is guaranteed to be at least as precise as without
730+
# 2) it is available since Linux 2.6.22, and CAN support was only added afterward
731+
# so this is always supported by the kernel
732+
self.socket.setsockopt(socket.SOL_SOCKET, constants.SO_TIMESTAMPNS, 1)
733+
else:
734+
# Utilise SOF_TIMESTAMPNS interface :
735+
# Allows us to use hardware timestamps where available
736+
timestamping_flags = (
737+
constants.SOF_TIMESTAMPING_SOFTWARE
738+
| constants.SOF_TIMESTAMPING_RX_SOFTWARE
739+
| constants.SOF_TIMESTAMPING_RAW_HARDWARE
740+
)
741+
742+
self.socket.setsockopt(
743+
socket.SOL_SOCKET, constants.SO_TIMESTAMPING, timestamping_flags
744+
)
712745

713746
try:
714747
bind_socket(self.socket, channel)

can/logger.py

+9
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)