Skip to content

Commit

Permalink
Fix socketcan timestamp on newer 32 bit kernel (#341)
Browse files Browse the repository at this point in the history
Kernel now defines old and new SO_TIMESTAMP and can send both of them in
ancillary data.

---------

Co-authored-by: Pavel Kirienko <[email protected]>
  • Loading branch information
tomasjakubik and pavel-kirienko committed Jun 19, 2024
1 parent 4df7d14 commit d232fc1
Show file tree
Hide file tree
Showing 4 changed files with 24 additions and 9 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/test-and-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ jobs:
pycyphal-test:
name: Test PyCyphal
# Run on push OR on 3rd-party PR.
if: (github.event_name == 'push') || (github.event.pull_request.author_association == 'NONE')
# https://docs.github.com/en/webhooks/webhook-events-and-payloads?actionType=edited#pull_request
if: (github.event_name == 'push') || github.event.pull_request.head.repo.fork
strategy:
fail-fast: false
matrix:
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ v1.19
-----
- Implement configure_acceptance_filters for socketcan.

- **v1.19.1:**
Fix socketcan timestamp on newer 32 bit kernel.

v1.18
-----
- Add FileClient2 which reports errors by raising exceptions.
Expand Down
2 changes: 1 addition & 1 deletion pycyphal/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.19.0"
__version__ = "1.19.1"
25 changes: 18 additions & 7 deletions pycyphal/transport/can/media/socketcan/_socketcan.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ def __init__(self, iface_name: str, mtu: int, loop: typing.Optional[asyncio.Abst
self._maybe_thread: typing.Optional[threading.Thread] = None
self._loopback_enabled = False

self._ancillary_data_buffer_size = socket.CMSG_SPACE(_TIMEVAL_STRUCT.size) # type: ignore
# We could receive both old and new timestamps, so we need to allocate space for both.
self._ancillary_data_buffer_size = socket.CMSG_SPACE(_TIMEVAL_STRUCT_OLD.size) + socket.CMSG_SPACE(_TIMEVAL_STRUCT_NEW.size) # type: ignore

super().__init__()

Expand Down Expand Up @@ -232,9 +233,15 @@ def _read_frame(self, ts_mono_ns: int) -> typing.Tuple[Timestamp, Envelope]:
loopback = bool(msg_flags & socket.MSG_CONFIRM) # type: ignore
ts_system_ns = 0
for cmsg_level, cmsg_type, cmsg_data in ancdata:
if cmsg_level == socket.SOL_SOCKET and cmsg_type == _SO_TIMESTAMP:
sec, usec = _TIMEVAL_STRUCT.unpack(cmsg_data)
if cmsg_level == socket.SOL_SOCKET and cmsg_type == _SO_TIMESTAMP_OLD:
# This structure provides time in platform native size
sec, usec = _TIMEVAL_STRUCT_OLD.unpack(cmsg_data)
ts_system_ns = (sec * 1_000_000 + usec) * 1000
elif cmsg_level == socket.SOL_SOCKET and cmsg_type == _SO_TIMESTAMP_NEW:
# This structure is present only when there is a 64 bit time on a 32 bit platform
sec, usec = _TIMEVAL_STRUCT_NEW.unpack(cmsg_data)
ts_system_ns = (sec * 1_000_000 + usec) * 1000
break # The new timestamp is preferred
else:
assert False, f"Unexpected ancillary data: {cmsg_level}, {cmsg_type}, {cmsg_data!r}"

Expand Down Expand Up @@ -309,10 +316,14 @@ class _NativeFrameDataCapacity(enum.IntEnum):
# __u8 data[CANFD_MAX_DLEN] __attribute__((aligned(8)));
# };
_FRAME_HEADER_STRUCT = struct.Struct("=IBB2x") # Using standard size because the native definition relies on stdint.h
_TIMEVAL_STRUCT = struct.Struct("@Ll") # Using native size because the native definition uses plain integers

# From the Linux kernel; not exposed via the Python's socket module
_SO_TIMESTAMP = 29
# structs __kernel_old_timeval and __kernel_sock_timeval in include/uapi/linux/time_types.h
_TIMEVAL_STRUCT_OLD = struct.Struct("@ll") # Using native size because the native definition uses plain integers
_TIMEVAL_STRUCT_NEW = struct.Struct("@qq") # New structure uses s64 for seconds and microseconds

# From the Linux kernel (include/uapi/asm-generic/socket.h); not exposed via the Python's socket module
_SO_TIMESTAMP_OLD = 29
_SO_TIMESTAMP_NEW = 63
_SO_SNDBUF = 7

_CANFD_BRS = 1
Expand Down Expand Up @@ -342,7 +353,7 @@ def _make_socket(iface_name: str, can_fd: bool, native_frame_size: int) -> socke
s = socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW) # type: ignore
try:
s.bind((iface_name,))
s.setsockopt(socket.SOL_SOCKET, _SO_TIMESTAMP, 1) # timestamping
s.setsockopt(socket.SOL_SOCKET, _SO_TIMESTAMP_OLD, 1) # timestamping
default_sndbuf_size = s.getsockopt(socket.SOL_SOCKET, _SO_SNDBUF)
blocking_sndbuf_size = (native_frame_size + _SKB_OVERHEAD) * _get_tx_queue_len(iface_name)

Expand Down

0 comments on commit d232fc1

Please sign in to comment.