From 46ec7c11d40fe461af6ff6a0f727658ea4fb6e15 Mon Sep 17 00:00:00 2001 From: Ryan Steel Date: Thu, 27 Aug 2026 16:22:00 +0100 Subject: [PATCH 1/6] migrate time_slave detailed design to doxygen --- .../time_slave/docs/detailed_design/index.rst | 332 ++++-------------- score/time_slave/src/application/time_slave.h | 6 + .../time_slave/src/gptp/details/clock_util.h | 13 + .../src/gptp/details/network_identity_impl.h | 4 +- .../src/gptp/details/pdelay_measurer.h | 45 ++- score/time_slave/src/gptp/details/ptp_types.h | 35 +- .../time_slave/src/gptp/details/raw_socket.h | 31 +- .../src/gptp/details/raw_socket_impl.h | 11 +- .../src/gptp/details/sync_state_machine.h | 61 ++-- score/time_slave/src/gptp/gptp_engine.h | 73 ++-- score/time_slave/src/gptp/instrument/probe.h | 50 ++- score/time_slave/src/gptp/phc/phc_adjuster.h | 57 ++- score/time_slave/src/gptp/record/recorder.h | 53 ++- 13 files changed, 373 insertions(+), 398 deletions(-) diff --git a/score/time_slave/docs/detailed_design/index.rst b/score/time_slave/docs/detailed_design/index.rst index fce3cb17..48c1251d 100644 --- a/score/time_slave/docs/detailed_design/index.rst +++ b/score/time_slave/docs/detailed_design/index.rst @@ -118,104 +118,63 @@ The data and control flow between units is presented in the following diagram: On this view you could see several "workers" scopes: -1. RxThread scope -2. PdelayThread scope -3. Main thread (periodic publish) scope +1. RxThread scope — receive raw gPTP Ethernet frames, decode PTP messages, correlate Sync/FollowUp pairs +2. PdelayThread scope — transmit PDelayReq frames, compute peer delay via IEEE 802.1AS formula +3. Main thread scope — periodically publish aggregated snapshot to shared memory -Each control flow is implemented with the dedicated thread and is independent from another ones. +See ``gptp_engine.h`` for detailed threading model, control flow responsibilities, and concurrency aspects. Control Flows ^^^^^^^^^^^^^ -RxThread Scope -'''''''''''''' +Each control flow has dedicated thread and runs independently. -This control flow is responsible for the: +- **RxThread scope** -1. receive raw gPTP Ethernet frames with hardware timestamps from the NIC via raw sockets -2. decode and parse the PTP messages (Sync, FollowUp, PdelayResp, PdelayRespFollowUp) -3. correlate Sync/FollowUp pairs and compute clock offset and neighborRateRatio -4. update the shared ``PtpTimeInfo`` snapshot under mutex protection + 1. receive raw gPTP Ethernet frames with hardware timestamps from NIC via raw sockets + 2. decode and parse PTP messages (Sync, FollowUp, PdelayResp, PdelayRespFollowUp) + 3. correlate Sync/FollowUp pairs and compute clock offset and neighborRateRatio + 4. update shared snapshot under mutex protection -PdelayThread Scope -'''''''''''''''''' +- **PdelayThread scope** -This control flow is responsible for the: + 1. periodically transmit PDelayReq frames and capture hardware transmit timestamps + 2. coordinate with RxThread to receive PDelayResp and PDelayRespFollowUp + 3. compute peer delay using IEEE 802.1AS formula: ``path_delay = ((t2 - t1) + (t4 - t3c)) / 2`` -1. periodically transmit PDelayReq frames and capture hardware transmit timestamps -2. coordinate with the RxThread to receive PDelayResp and PDelayRespFollowUp messages -3. compute the peer delay using the IEEE 802.1AS formula: ``path_delay = ((t2 - t1) + (t4 - t3c)) / 2`` +- **Main thread (periodic publish) scope** -Main Thread (Periodic Publish) Scope -'''''''''''''''''''''''''''''''''''' - -This control flow is responsible for the: - -1. periodically call ``GptpEngine::FinalizeSnapshot()`` to check timeout and commit the pending snapshot -2. call ``GptpEngine::ReadPTPSnapshot(data)`` to copy the latest ``GptpIpcData`` into a local variable -3. publish to shared memory via ``GptpIpcPublisher::Publish(data)`` + 1. call ``GptpEngine::FinalizeSnapshot()`` to check timeout and commit pending snapshot + 2. call ``GptpEngine::ReadPTPSnapshot(data)`` to copy latest ``GptpIpcData`` to local variable + 3. publish snapshot via ``GptpIpcPublisher::Publish(data)`` Data Types or Events ^^^^^^^^^^^^^^^^^^^^ -There are several data types, which components are communicating to each other: - -PTPMessage -'''''''''' - -``PTPMessage`` is a union-based container for decoded gPTP messages including the hardware receive timestamp. It is produced by ``MessageParser`` and consumed by ``SyncStateMachine`` and ``PeerDelayMeasurer``. - -SyncResult -'''''''''' - -``SyncResult`` is produced by ``SyncStateMachine::OnFollowUp()`` and contains the computed master timestamp, clock offset, Sync/FollowUp data, and time jump flags (forward/backward). - -PDelayResult -'''''''''''' - -``PDelayResult`` is produced by ``PeerDelayMeasurer`` and contains the computed path delay in nanoseconds and a validity flag. - -PtpTimeInfo -''''''''''' +Main data exchanged between units: -``PtpTimeInfo`` is the TimeDaemon-internal aggregated snapshot. It is **not** the shared memory type; it is produced by ``ShmPTPEngine::ReadPTPSnapshot()`` by field-mapping from ``GptpIpcData`` into the format expected by the TimeDaemon pipeline. +- **PTPMessage** — union-based container for decoded gPTP messages plus hardware receive timestamp; produced by ``MessageParser`` and consumed by ``SyncStateMachine`` and ``PeerDelayMeasurer`` +- **SyncResult** — produced by ``SyncStateMachine::OnFollowUp()``; includes computed master timestamp, clock offset, Sync/FollowUp data, and time-jump flags +- **PDelayResult** — produced by ``PeerDelayMeasurer``; includes computed path delay in nanoseconds and validity flag +- **PtpTimeInfo** — TimeDaemon-internal aggregated snapshot, not shared-memory type; produced by ``ShmPTPEngine::ReadPTPSnapshot()`` by mapping from ``GptpIpcData`` Units Within Time Slave ----------------------- The following units comprise TimeSlave's internal implementation: -TimeSlave Application Unit -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The ``TimeSlave Application`` component is the main entry point for the TimeSlave process. It extends ``score::mw::lifecycle::Application`` and is responsible for orchestrating the overall lifecycle of the GptpEngine and the IPC publisher. - -Implementation Requirements -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The ``TimeSlave Application`` has the following requirements: - -- The ``TimeSlave Application`` shall implement the ``Initialize()`` method to create the ``GptpEngine`` with configured options, initialize the ``GptpIpcPublisher`` (creates the shared memory segment), and create the ``HighPrecisionLocalSteadyClock`` for the engine -- The ``TimeSlave Application`` shall implement the ``Run()`` method to enter a periodic publish loop (50 ms interval) and monitor the ``stop_token`` for graceful shutdown -- On each loop iteration, ``TimeSlave Application`` shall call ``GptpEngine::FinalizeSnapshot()``, then ``GptpEngine::ReadPTPSnapshot(data)``, and publish the resulting ``GptpIpcData`` via ``GptpIpcPublisher::Publish(data)`` -- The ``TimeSlave Application`` shall call ``GptpEngine::Deinitialize()`` and ``GptpIpcPublisher::Destroy()`` after the ``stop_token`` is set - -GptpEngine Unit -~~~~~~~~~~~~~~~ - -The ``GptpEngine`` component is the core gPTP protocol engine. It manages two background threads (RxThread and PdelayThread) for network I/O and peer delay measurement, and exposes a thread-safe ``ReadPTPSnapshot()`` method for the main thread to read the latest time measurement. - -Implementation Requirements -^^^^^^^^^^^^^^^^^^^^^^^^^^^ +1. **TimeSlave Application** -- process entry point; orchestrates GptpEngine lifecycle and periodic shared-memory publish loop +2. **GptpEngine** -- core gPTP engine with RxThread/PdelayThread and snapshot API +3. **FrameCodec** -- raw Ethernet frame encode/decode for gPTP +4. **MessageParser** -- IEEE 1588-v2 payload parsing +5. **SyncStateMachine** -- Sync/FollowUp correlation, clock offset, neighbor rate ratio, time-jump detection +6. **PeerDelayMeasurer** -- IEEE 802.1AS peer-delay measurement +7. **PhcAdjuster** -- PHC step/slew synchronization backend -The ``GptpEngine`` has the following requirements: +GptpEngine +~~~~~~~~~~ -- The ``GptpEngine`` shall manage an RxThread for receiving and parsing gPTP frames from raw Ethernet sockets -- The ``GptpEngine`` shall manage a PdelayThread for periodic peer delay measurement -- The ``GptpEngine`` shall provide a ``FinalizeSnapshot()`` method that checks for sync timeout, applies status flags, and commits the pending snapshot to the current snapshot; this must be called before ``ReadPTPSnapshot()`` -- The ``GptpEngine`` shall provide a ``ReadPTPSnapshot(GptpIpcData&)`` method that copies the latest committed snapshot into the caller's buffer and returns false only if the engine is not initialized -- The ``GptpEngine`` shall support configurable parameters via ``GptpEngineOptions`` (interface name, PDelay interval, PDelay warmup, sync timeout, time-jump threshold, PHC configuration) -- The ``GptpEngine`` shall support exchangeability of the raw socket implementation for different platforms (Linux, QNX) +The ``GptpEngine`` runs RxThread and PdelayThread, and provides ``FinalizeSnapshot()`` + ``ReadPTPSnapshot()`` for periodic publish logic. Class View ^^^^^^^^^^ @@ -252,13 +211,11 @@ The GptpEngine operates with two background threads. The threading model is repr Concurrency Aspects ^^^^^^^^^^^^^^^^^^^ -The ``GptpEngine`` uses the following synchronization mechanisms: - -- A ``std::mutex`` protects the ``pending_snapshot_`` and ``current_snapshot_`` fields (both ``GptpIpcData``): the RxThread writes ``pending_snapshot_``; the main thread calls ``FinalizeSnapshot()`` (commits pending to current) and ``ReadPTPSnapshot()`` (reads current) -- The ``PeerDelayMeasurer`` uses its own ``std::mutex`` to synchronize between the PdelayThread (``SendRequest()``) and the RxThread (``OnResponse()``, ``OnResponseFollowUp()``) -- The ``SyncStateMachine`` uses ``std::atomic`` for the timeout flag, which is read from the main thread and written from the RxThread +- ``std::mutex`` protects ``pending_snapshot_`` and ``current_snapshot_`` (both ``GptpIpcData``): RxThread writes pending; main thread finalizes and reads current +- ``PeerDelayMeasurer`` uses internal ``std::mutex`` to synchronize ``SendRequest()`` (PdelayThread) with ``OnResponse()`` / ``OnResponseFollowUp()`` (RxThread) +- ``SyncStateMachine`` uses ``std::atomic`` timeout flag written by RxThread and read by main thread -Hardware timestamping fallback +Hardware Timestamping Fallback ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ During ``Initialize()``, ``GptpEngine`` calls ``RawSocket::EnableHwTimestamping()`` to request NIC-level receive timestamps (``SO_TIMESTAMPING`` on Linux). If the NIC does not support hardware timestamping, the call returns ``false`` and a warning is logged: @@ -289,55 +246,12 @@ The engine continues to run normally. The difference between the two modes: - High (sub-microsecond typical) - Reduced (jitter depends on OS scheduling latency) -The fallback does not affect protocol correctness — Sync/FollowUp correlation and peer delay measurement continue to work — but the computed clock offset will be less accurate due to higher receive timestamp jitter. - -FrameCodec Unit -~~~~~~~~~~~~~~~ - -The ``FrameCodec`` component handles raw Ethernet frame encoding and decoding for gPTP communication. - -Implementation Requirements -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The ``FrameCodec`` has the following requirements: - -- The ``FrameCodec`` shall parse incoming Ethernet frames, extracting source/destination MAC addresses, handling 802.1Q VLAN tags, and validating the EtherType (``0x88F7``) -- The ``FrameCodec`` shall construct outgoing Ethernet headers for PDelayReq frames using the standard PTP multicast destination MAC (``01:80:C2:00:00:0E``) - -MessageParser Unit -~~~~~~~~~~~~~~~~~~ - -The ``MessageParser`` component parses the PTP wire format (IEEE 1588-v2) from raw payload bytes. - -Implementation Requirements -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The ``MessageParser`` has the following requirements: - -- The ``MessageParser`` shall validate the PTP header (version, domain, message length) -- The ``MessageParser`` shall decode all relevant message types: Sync, FollowUp, PdelayReq, PdelayResp, PdelayRespFollowUp -- The ``MessageParser`` shall use packed wire structures (``__attribute__((packed))``) for direct memory mapping of PTP messages - -SyncStateMachine Unit -~~~~~~~~~~~~~~~~~~~~~ - -The ``SyncStateMachine`` component implements the two-step Sync/FollowUp correlation logic. It correlates incoming Sync and FollowUp messages by sequence ID, computes the clock offset and neighbor rate ratio, and detects time jumps. +The fallback does not affect protocol correctness -- Sync/FollowUp correlation and peer delay measurement continue to work -- but the computed clock offset will be less accurate due to higher receive timestamp jitter. -Implementation Requirements -^^^^^^^^^^^^^^^^^^^^^^^^^^^ +PeerDelayMeasurer +~~~~~~~~~~~~~~~~~~~~~~~ -The ``SyncStateMachine`` has the following requirements: - -- The ``SyncStateMachine`` shall store Sync messages and correlate them with subsequent FollowUp messages by sequence ID -- The ``SyncStateMachine`` shall compute the clock offset: ``offset_ns = master_time - slave_receive_time - path_delay`` -- The ``SyncStateMachine`` shall compute the ``neighborRateRatio`` from successive Sync intervals (master vs. slave clock progression) -- The ``SyncStateMachine`` shall detect forward and backward time jumps against configurable thresholds -- The ``SyncStateMachine`` shall provide thread-safe timeout detection via ``std::atomic``, set when no Sync is received within the configured timeout - -PeerDelayMeasurer Unit -~~~~~~~~~~~~~~~~~~~~~~ - -The ``PeerDelayMeasurer`` component implements the IEEE 802.1AS two-step peer delay measurement protocol. It manages the four timestamps (``t1``, ``t2``, ``t3c``, ``t4``) across two threads. +The ``PeerDelayMeasurer`` unit implements the IEEE 802.1AS two-step peer delay measurement protocol. It manages the four timestamps (``t1``, ``t2``, ``t3c``, ``t4``) across two threads. Timestamp Definitions ^^^^^^^^^^^^^^^^^^^^^ @@ -356,11 +270,11 @@ Timestamp Definitions - HW transmit timestamp of the PDelayReq frame leaving the slave NIC * - ``t2`` - PDelayResp (RX) - - Master → carried in PDelayResp body + - Master -> carried in PDelayResp body - HW receive timestamp of the PDelayReq frame arriving at the master NIC * - ``t3c`` - PDelayRespFollowUp - - Master → carried in PDelayRespFollowUp body + - Master -> carried in PDelayRespFollowUp body - HW transmit timestamp of the PDelayResp frame leaving the master NIC ("corrected" because it includes the master's turnaround correction) * - ``t4`` - PDelayResp (RX) @@ -369,36 +283,14 @@ Timestamp Definitions The peer delay formula is: ``path_delay = ((t2 - t1) + (t4 - t3c)) / 2`` -- ``(t2 - t1)`` = propagation time from slave → master -- ``(t4 - t3c)`` = propagation time from master → slave +- ``(t2 - t1)`` = propagation time from slave -> master +- ``(t4 - t3c)`` = propagation time from master -> slave - The average of the two gives the one-way link delay -Implementation Requirements -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The ``PeerDelayMeasurer`` has the following requirements: - -- The ``PeerDelayMeasurer`` shall transmit PDelayReq frames and capture the hardware transmit timestamp (``t1``) -- The ``PeerDelayMeasurer`` shall receive PDelayResp (providing ``t2``, ``t4``) and PDelayRespFollowUp (providing ``t3c``) messages -- The ``PeerDelayMeasurer`` shall compute the peer delay using the IEEE 802.1AS formula: ``path_delay = ((t2 - t1) + (t4 - t3c)) / 2`` -- The ``PeerDelayMeasurer`` shall discard PDelayResp and PDelayRespFollowUp messages whose sequence ID does not match the most recently transmitted PDelayReq -- The ``PeerDelayMeasurer`` shall suppress the path-delay result when more than one PDelayResp is received for a single PDelayReq (detection of non-time-aware bridges per IEEE 802.1AS) -- The ``PeerDelayMeasurer`` shall provide thread-safe access to the ``PDelayResult`` via a mutex, as ``SendRequest()`` runs on the PdelayThread while response handlers are called from the RxThread - -PhcAdjuster Unit -~~~~~~~~~~~~~~~~ - -The ``PhcAdjuster`` component synchronizes the PTP Hardware Clock (PHC) on the NIC. It applies step corrections for large offsets and frequency slew for smooth convergence of small offsets. - -Implementation Requirements -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The ``PhcAdjuster`` has the following requirements: +PhcAdjuster +~~~~~~~~~~~ -- The ``PhcAdjuster`` shall apply an immediate time step correction for offsets exceeding ``step_threshold_ns`` -- The ``PhcAdjuster`` shall apply frequency slew (in ppb) for offsets below the step threshold -- The ``PhcAdjuster`` shall support platform-specific implementations: ``clock_adjtime()`` on Linux, EMAC PTP ioctls on QNX -- The ``PhcAdjuster`` shall be configurable via ``PhcConfig`` (device path, step threshold, enable/disable flag) +The ``PhcAdjuster`` unit synchronizes the PTP Hardware Clock (PHC) on the NIC. It applies step corrections for large offsets and frequency slew for smooth convergence of small offsets. Fallback Behavior When PHC Is Unavailable ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -409,137 +301,31 @@ The ``PhcAdjuster`` degrades gracefully in two scenarios: 2. **PHC enabled but device inaccessible** (e.g., ``/dev/ptp0`` does not exist on Linux, or the EMAC interface name is wrong on QNX): - - **Linux**: the constructor calls ``open(device, O_RDWR)``; on failure ``phc_fd_`` stays at ``-1``. Both ``AdjustOffset()`` and ``AdjustFrequency()`` guard against ``phc_fd_ < 0`` and return immediately — a true silent skip with no system call. + - **Linux**: the constructor calls ``open(device, O_RDWR)``; on failure ``phc_fd_`` stays at ``-1``. Both ``AdjustOffset()`` and ``AdjustFrequency()`` guard against ``phc_fd_ < 0`` and return immediately -- a true silent skip with no system call. - - **QNX**: ``qnx_phc_open()`` always returns ``0`` and never fails — it only stores the device name in a thread-local context. There is no ``phc_fd_ < 0`` guard. The adjustment methods always call ``qnx_phc_adjtime_step()`` / ``qnx_phc_adjfreq_ppb()``, which internally create a UDP socket and issue ``SIOCGDRVSPEC`` / ``SIOCSDRVSPEC`` ioctls. If the socket or ioctl fails (e.g., wrong interface name, unsupported hardware), the function returns ``-1``, but the caller discards it with a ``(void)`` cast. There is no explicit skip — the call is always attempted and errors are silently absorbed. + - **QNX**: ``qnx_phc_open()`` always returns ``0`` and never fails -- it only stores the device name in a thread-local context. There is no ``phc_fd_ < 0`` guard. The adjustment methods always call ``qnx_phc_adjtime_step()`` / ``qnx_phc_adjfreq_ppb()``, which internally create a UDP socket and issue ``SIOCGDRVSPEC`` / ``SIOCSDRVSPEC`` ioctls. If the socket or ioctl fails (e.g., wrong interface name, unsupported hardware), the function returns ``-1``, but the caller discards it with a ``(void)`` cast. There is no explicit skip -- the call is always attempted and errors are silently absorbed. -In both scenarios TimeSlave continues to track the master clock and publish accurate ``GptpIpcData`` snapshots (including offset and status flags) to shared memory. The downstream TimeDaemon and any applications consuming time are unaffected — only the NIC hardware clock itself will drift relative to PTP time. +In both scenarios TimeSlave continues to track the master clock and publish accurate ``GptpIpcData`` snapshots (including offset and status flags) to shared memory. The downstream TimeDaemon and any applications consuming time are unaffected -- only the NIC hardware clock itself will drift relative to PTP time. Platform Support ~~~~~~~~~~~~~~~~ -TimeSlave supports two target platforms with platform-specific implementations selected at compile time via Bazel ``select()``: +TimeSlave supports two target platforms with platform-specific implementations selected at compile time via Bazel ``select()``. The ``RawSocket`` and ``NetworkIdentity`` interfaces provide the abstraction boundary. -.. list-table:: Platform Implementations - :header-rows: 1 - :widths: 25 37 38 +See ``gptp_engine.h`` and ``raw_socket.h`` for hardware timestamping mechanisms (``AF_PACKET``/BPF), ``phc_adjuster.h`` for PHC adjustment APIs (``clock_adjtime`` vs QNX ioctls), and ``network_identity.h`` for MAC address retrieval methods. - * - Component - - Linux - - QNX - * - Raw Socket - - ``AF_PACKET`` + ``SO_TIMESTAMPING``; HW RX timestamp via ``recvmsg`` ``SCM_TIMESTAMPING`` - - BPF (``/dev/bpf``); HW RX timestamp via ``bpf_xhdr.bh_tstamp`` (``BIOCSTSTAMP BPF_T_BINTIME|BPF_T_PTP``); TX PHC timestamp via dedicated TX loopback fd (``BIOCSSEESENT``), filtered to Pdelay_Req frames only (BPF message-type 0x02); single static context (not thread-local) - * - Network Identity - - ``ioctl(SIOCGIFHWADDR)`` → EUI-48 → EUI-64 - - ``getifaddrs()`` + ``AF_LINK`` / ``sockaddr_dl`` (``LLADDR``) → EUI-48/64 - * - PHC Adjuster - - ``clock_adjtime()`` (``SYS_clock_adjtime`` syscall); step via ``ADJ_SETOFFSET|ADJ_NANO``; slew via ``ADJ_FREQUENCY`` (scaled-ppm) - - ``SIOCGDRVSPEC`` / ``SIOCSDRVSPEC`` on UDP socket; step via ``PTP_GET_TIME`` (0x102) + ``PTP_SET_TIME`` (0x103); slew via ``EMAC_PTP_ADJ_FREQ_PPM`` (0x200) in ppm - * - HighPrecisionLocalSteadyClock - - ``CLOCK_MONOTONIC`` via ``clock_gettime()`` - - QNX ``ClockCycles()`` CPU instruction (reads hardware performance counter directly, equivalent to ``RDTSC`` on x86 / ``CNTVCT`` on ARM64), converted to nanoseconds via cycles-per-second calibration. Used instead of ``clock_gettime()`` because QNX ``CLOCK_MONOTONIC`` resolution is limited to microsecond level, whereas ``ClockCycles()`` provides nanosecond-level precision with no syscall overhead. - -The ``RawSocket`` and ``NetworkIdentity`` interfaces provide the abstraction boundary. Platform-specific source files are organized under ``score/time_slave/src/gptp/platform/linux/`` and ``score/time_slave/src/gptp/platform/qnx/``. +Platform-specific source files are organized under ``score/time_slave/src/gptp/platform/linux/`` and ``score/time_slave/src/gptp/platform/qnx/``. Instrumentation ~~~~~~~~~~~~~~~ -ProbeManager -^^^^^^^^^^^^ - -The ``ProbeManager`` is a singleton that traces probe events at key processing points in the gPTP engine. It emits a ``LogDebug`` entry on every ``Trace()`` call and forwards the event to a linked ``Recorder`` (if set and enabled). Probing is controlled at runtime via ``SetEnabled()``; the ``GPTP_PROBE()`` macro provides zero overhead when disabled. +TimeSlave provides two runtime instrumentation mechanisms for development and debugging: -Supported probe points (``ProbePoint`` enum): +- **ProbeManager** — singleton that traces probe events at key processing points (packet RX, Sync/FollowUp processing, peer delay completion, PHC adjustments) +- **Recorder** — thread-safe CSV file writer that appends timestamped event rows to disk -.. list-table:: ProbePoint Events - :header-rows: 1 - :widths: 10 30 60 - - * - Value - - Enumerator - - Trigger - * - 0 - - ``kRxPacketReceived`` - - Raw Ethernet frame received from socket (RxThread) - * - 1 - - ``kSyncFrameParsed`` - - Sync message successfully decoded by ``GptpMessageParser`` - * - 2 - - ``kFollowUpProcessed`` - - FollowUp received; ``SyncStateMachine::OnFollowUp()`` returned a ``SyncResult`` - * - 3 - - ``kOffsetComputed`` - - Final clock offset value available after Sync/FollowUp correlation - * - 4 - - ``kPdelayReqSent`` - - PDelayReq frame transmitted by ``PeerDelayMeasurer`` - * - 5 - - ``kPdelayCompleted`` - - Peer delay computation finished (all four timestamps collected) - * - 6 - - ``kPhcAdjusted`` - - ``PhcAdjuster`` applied a step or frequency correction - -When a probe event is forwarded to the ``Recorder``, it is written with ``RecordEvent::kProbe`` and the ``ProbePoint`` value stored in the ``status_flags`` field of the CSV row. - -Recorder -^^^^^^^^ - -Thread-safe CSV file writer. When enabled, appends one row per event to the configured file. The file is opened in append mode (``ios::app``); a CSV header is written only if the file is newly created (size == 0). - -**Status model:** the ``Recorder`` starts in the state determined by ``Config.enabled``. If a write error occurs (``file_.good()`` fails after a flush), ``enabled_`` is atomically set to ``false`` and all subsequent ``Record()`` calls become no-ops. The file is never re-opened after an error. - -Configuration (``Recorder::Config``): - -.. list-table:: Recorder Configuration - :header-rows: 1 - :widths: 30 15 55 - - * - Parameter - - Type - - Description - * - ``enabled`` - - bool - - Enable or disable recording; default: ``false`` - * - ``file_path`` - - string - - Output CSV file path; default: ``/var/log/gptp_record.csv`` - * - ``offset_threshold_ns`` - - int64_t - - Reserved for ``kOffsetThreshold`` events (threshold above which offsets are logged); default: ``1 000 000`` (1 ms) - * - ``flush_interval`` - - uint32_t - - Number of rows between explicit ``file_.flush()`` calls; default: ``8`` - -CSV output format:: - - mono_ns,event,offset_ns,pdelay_ns,seq_id,status_flags - -Supported ``RecordEvent`` values written to the ``event`` column: - -.. list-table:: RecordEvent Values - :header-rows: 1 - :widths: 10 30 60 - - * - Value - - Enumerator - - Description - * - 0 - - ``kSyncReceived`` - - A Sync message was received and processed - * - 1 - - ``kPdelayCompleted`` - - A full peer delay measurement cycle completed - * - 2 - - ``kClockJump`` - - A forward or backward time jump was detected - * - 3 - - ``kOffsetThreshold`` - - Clock offset exceeded ``offset_threshold_ns`` - * - 4 - - ``kProbe`` - - Forwarded from ``ProbeManager::Trace()``; ``status_flags`` column carries the ``ProbePoint`` value +See ``probe.h`` for ProbePoint enumeration and zero-overhead ``GPTP_PROBE()`` macro. +See ``recorder.h`` for CSV format, RecordEvent types, Recorder::Config parameters, and error-handling behavior. Logging configuration ~~~~~~~~~~~~~~~~~~~~~ diff --git a/score/time_slave/src/application/time_slave.h b/score/time_slave/src/application/time_slave.h index 6c0d3b41..f9f8e10d 100644 --- a/score/time_slave/src/application/time_slave.h +++ b/score/time_slave/src/application/time_slave.h @@ -44,7 +44,13 @@ class TimeSlave final : public score::mw::lifecycle::Application TimeSlave& operator=(TimeSlave&&) & = delete; TimeSlave& operator=(const TimeSlave&) & = delete; + /// @brief Initializes the gPTP engine and shared memory publisher. + /// @return 0 on success, non-zero on failure. std::int32_t Initialize(const score::mw::lifecycle::ApplicationContext& context) override; + + /// @brief Runs the main loop: finalizes gPTP snapshots and publishes to shared memory. + /// @param token Stop token for graceful shutdown. + /// @return 0 on success, non-zero on failure. std::int32_t Run(const score::cpp::stop_token& token) override; private: diff --git a/score/time_slave/src/gptp/details/clock_util.h b/score/time_slave/src/gptp/details/clock_util.h index 5d9aaab4..8f22ce07 100644 --- a/score/time_slave/src/gptp/details/clock_util.h +++ b/score/time_slave/src/gptp/details/clock_util.h @@ -25,6 +25,19 @@ namespace ts namespace details { +/// @brief Returns current @c CLOCK_MONOTONIC time in nanoseconds. +/// +/// @details +/// `CLOCK_MONOTONIC` is a non-decreasing system uptime-style clock and is not +/// tied to wall-clock time. +/// - Linux: starts at an unspecified point (typically boot) and is not affected +/// by manual/NTP wall-clock adjustments; it may still be slightly slewed. +/// - QNX/POSIX systems: monotonic clock with an unspecified epoch, intended for +/// interval measurement and not affected by wall-clock set operations. +/// +/// Use this value only for elapsed-time calculations, not for calendar time. +/// +/// @return Nanoseconds since an unspecified epoch, or 0 on failure. inline std::int64_t MonoNs() noexcept { ::timespec ts{}; diff --git a/score/time_slave/src/gptp/details/network_identity_impl.h b/score/time_slave/src/gptp/details/network_identity_impl.h index f7311104..a9e8d6b7 100644 --- a/score/time_slave/src/gptp/details/network_identity_impl.h +++ b/score/time_slave/src/gptp/details/network_identity_impl.h @@ -35,11 +35,11 @@ namespace details class NetworkIdentityImpl : public NetworkIdentity { public: - /// Resolve the ClockIdentity for @p iface_name. + /// @brief Resolve the ClockIdentity for @p iface_name. /// @return true on success. bool Resolve(const std::string& iface_name) override; - /// Return the resolved identity. Valid only after a successful Resolve(). + /// @brief Return the resolved identity. Valid only after a successful Resolve(). ClockIdentity GetClockIdentity() const override { return identity_; diff --git a/score/time_slave/src/gptp/details/pdelay_measurer.h b/score/time_slave/src/gptp/details/pdelay_measurer.h index f76f8762..ba194397 100644 --- a/score/time_slave/src/gptp/details/pdelay_measurer.h +++ b/score/time_slave/src/gptp/details/pdelay_measurer.h @@ -35,33 +35,48 @@ struct PDelayResult bool valid{false}; }; -/** - * @brief Measures one-way peer delay using the IEEE 802.1AS Pdelay mechanism. - * - * Implements the IEEE 802.1AS two-step peer-delay measurement: - * path_delay = ((t2 − t1) + (t4 − t3c)) / 2 - * - * Thread-safety: @c SendRequest() is called from the PdelayThread. - * @c OnResponse() / @c OnResponseFollowUp() / @c GetResult() - * are called from the RxThread. An internal mutex makes the - * class safe for this two-thread usage pattern. - */ +/// @brief Measures the one-way peer delay using the IEEE 802.1AS Pdelay mechanism. +/// +/// Implements the IEEE 802.1AS two-step peer-delay measurement. The formula +/// applied by @c OnResponseFollowUp() when a complete cycle is received is: +/// +/// path_delay = ((t2 − t1) + (t4 − t3c)) / 2 +/// +/// Thread-safety: @c SendRequest() is called from the PdelayThread. +/// @c OnResponse(), @c OnResponseFollowUp(), and @c GetResult() +/// are called from the RxThread. Mutex protects: @c seqnum_, +/// @c resp_count_, @c req_, @c resp_, @c resp_fup_, @c result_. class PeerDelayMeasurer final { public: + /// @brief Construct a new PeerDelayMeasurer with the local ClockIdentity and gPTP domain. + /// + /// @param local_identity The local ClockIdentity (used in Pdelay_Req). + /// @param domain The gPTP domain number (0–127). explicit PeerDelayMeasurer(const ClockIdentity& local_identity, std::uint8_t domain = 0U) noexcept; - /// Build and transmit a Pdelay_Req frame. @p socket must be open. + /// @brief Build and transmit a Pdelay_Req frame. @p socket must be open. + /// + /// Increments the sequence number, encodes the frame, transmits it via @c socket, and records @c t1 (transmit + /// hardware timestamp; used in path_delay formula when FollowUp arrives). + /// /// @return 0 on success, negative on error. int SendRequest(RawSocket& socket); - /// Process an incoming Pdelay_Resp message. + /// @brief Process an incoming Pdelay_Resp message. + /// + /// Called from the RxThread. Records @c t2 (the responder's receive + /// hardware timestamp) and the responder's identity for the current sequence. void OnResponse(const PTPMessage& msg); - /// Process an incoming Pdelay_Resp_Follow_Up message; triggers computation. + /// @brief Process an incoming Pdelay_Resp_Follow_Up message; triggers path delay computation. + /// + /// Called from the RxThread. Records @c t3c (the responder's transmit + /// correction) and computes the path delay using the IEEE 802.1AS formula + /// if @c t1, @c t2, and @c t4 are all available. void OnResponseFollowUp(const PTPMessage& msg); - /// Return the latest computed measurement (or invalid if none yet). + /// @brief Returns the latest computed measurement (or invalid if none yet available). PDelayResult GetResult() const; private: diff --git a/score/time_slave/src/gptp/details/ptp_types.h b/score/time_slave/src/gptp/details/ptp_types.h index ca3b809a..0195a235 100644 --- a/score/time_slave/src/gptp/details/ptp_types.h +++ b/score/time_slave/src/gptp/details/ptp_types.h @@ -40,27 +40,40 @@ namespace details { // ─── EtherType constants ──────────────────────────────────────────────────── +/// IEEE 1588 PTP EtherType. constexpr std::uint16_t kEthP1588 = 0x88F7U; +/// IEEE 802.1Q VLAN tag EtherType. constexpr std::uint16_t kEthP8021Q = 0x8100U; // ─── MAC / buffer sizes ───────────────────────────────────────────────────── +/// Ethernet MAC address length in bytes. constexpr std::size_t kMacAddrLen = 6U; +/// 802.1Q VLAN tag length in bytes. constexpr std::size_t kVlanTagLen = 4U; // ─── PTP message-type codes ───────────────────────────────────────────────── +/// PTP message type: Sync. constexpr std::uint8_t kPtpMsgtypeSync = 0x0; +/// PTP message type: Pdelay_Req. constexpr std::uint8_t kPtpMsgtypePdelayReq = 0x2; +/// PTP message type: Pdelay_Resp. constexpr std::uint8_t kPtpMsgtypePdelayResp = 0x3; +/// PTP message type: Follow_Up. constexpr std::uint8_t kPtpMsgtypeFollowUp = 0x8; +/// PTP message type: Pdelay_Resp_Follow_Up. constexpr std::uint8_t kPtpMsgtypePdelayRespFollowUp = 0xA; // ─── PTP header constants ──────────────────────────────────────────────────── +/// IEEE 802.1AS transport-specific value (upper nibble of first header byte). constexpr std::uint8_t kPtpTransportSpecific = (1U << 4U); +/// PTP protocol version (2 for IEEE 1588-2008 / 802.1AS). constexpr std::uint8_t kPtpVersion = 2U; +/// Nanoseconds per second. constexpr std::int64_t kNsPerSec = 1'000'000'000LL; // ─── Control field ─────────────────────────────────────────────────────────── +/// PTP control field values (IEEE 1588-2008 §13.3.2.10). enum class ControlField : std::uint8_t { kSync = 0, @@ -72,6 +85,7 @@ enum class ControlField : std::uint8_t }; // ─── State machine states ──────────────────────────────────────────────────── +/// Internal states for the Sync/Follow_Up correlation state machine. enum class SyncState : std::uint8_t { kEmpty, @@ -80,23 +94,27 @@ enum class SyncState : std::uint8_t }; // ─── Time value type ───────────────────────────────────────────────────────── +/// Time value in nanoseconds (internal representation). struct TmvT { std::int64_t ns{0}; }; // ─── PTP wire structures (all SCORE_TS_PACKED) ─────────────────────────────── +/// IEEE 1588 ClockIdentity (8-byte EUI-64). struct SCORE_TS_PACKED ClockIdentity { std::uint8_t id[8]{}; }; +/// IEEE 1588 PortIdentity (ClockIdentity + port number). struct SCORE_TS_PACKED PortIdentity { ClockIdentity clockIdentity; std::uint16_t portNumber{0}; }; +/// IEEE 1588 Timestamp (48-bit seconds + 32-bit nanoseconds). struct SCORE_TS_PACKED Timestamp { std::uint16_t seconds_msb{0}; @@ -104,6 +122,7 @@ struct SCORE_TS_PACKED Timestamp std::uint32_t nanoseconds{0}; }; +/// IEEE 1588 PTP message header (common to all message types). struct SCORE_TS_PACKED PTPHeader { std::uint8_t tsmt{0}; @@ -120,18 +139,21 @@ struct SCORE_TS_PACKED PTPHeader std::int8_t logMessageInterval{0}; }; +/// Sync message body. struct SCORE_TS_PACKED SyncBody { PTPHeader ptpHdr{}; Timestamp originTimestamp{}; }; +/// Follow_Up message body. struct SCORE_TS_PACKED FollowUpBody { PTPHeader ptpHdr{}; Timestamp preciseOriginTimestamp{}; }; +/// Pdelay_Req message body. struct SCORE_TS_PACKED PdelayReqBody { PTPHeader ptpHdr{}; @@ -139,13 +161,15 @@ struct SCORE_TS_PACKED PdelayReqBody PortIdentity reserved{}; }; +/// Pdelay_Resp message body. struct SCORE_TS_PACKED PdelayRespBody { PTPHeader ptpHdr{}; - Timestamp requestReceiptTimestamp{}; ///< IEEE 802.1AS: t₂ — time the remote peer received our PdelayReq + Timestamp requestReceiptTimestamp{}; ///< IEEE 802.1AS: t₂ — time the remote peer received our PdelayReq. PortIdentity requestingPortIdentity{}; }; +/// Pdelay_Resp_Follow_Up message body. struct SCORE_TS_PACKED PdelayRespFollowUpBody { PTPHeader ptpHdr{}; @@ -153,13 +177,16 @@ struct SCORE_TS_PACKED PdelayRespFollowUpBody PortIdentity requestingPortIdentity{}; }; +/// Raw message buffer (maximum Ethernet payload). struct SCORE_TS_PACKED RawMessageData { std::uint8_t buffer[1500]{}; }; +/// Parsed PTP message with metadata. struct PTPMessage { + /// Union of all PTP message types. union SCORE_TS_PACKED { PTPHeader ptpHdr; @@ -181,6 +208,8 @@ struct PTPMessage static_assert(sizeof(PTPMessage) <= 1600, "PTPMessage too large"); // ─── Timestamp conversion helpers ──────────────────────────────────────────── +/// @brief Convert PTP wire Timestamp to internal TmvT (nanoseconds since epoch). +/// @return TmvT with zero ns on overflow. inline TmvT TimestampToTmv(const Timestamp& ts) noexcept { const std::uint64_t sec = @@ -195,6 +224,8 @@ inline TmvT TimestampToTmv(const Timestamp& ts) noexcept return TmvT{static_cast(total_ns)}; } +/// @brief Convert internal TmvT to PTP wire Timestamp. +/// @return Timestamp with all zeros if @p x is negative. inline Timestamp TmvToTimestamp(const TmvT& x) noexcept { if (x.ns < 0) @@ -208,11 +239,13 @@ inline Timestamp TmvToTimestamp(const TmvT& x) noexcept return t; } +/// @brief Convert PTP correctionField (scaled nanoseconds, 16.48 fixed-point) to TmvT. inline TmvT CorrectionToTmv(std::int64_t corr) noexcept { return TmvT{corr / 65536LL}; } +/// @brief Convert ClockIdentity to uint64_t (host byte order). inline std::uint64_t ClockIdentityToU64(const ClockIdentity& ci) noexcept { std::uint64_t v{0}; diff --git a/score/time_slave/src/gptp/details/raw_socket.h b/score/time_slave/src/gptp/details/raw_socket.h index ef8fc9f8..fea5b29b 100644 --- a/score/time_slave/src/gptp/details/raw_socket.h +++ b/score/time_slave/src/gptp/details/raw_socket.h @@ -25,30 +25,47 @@ namespace ts namespace details { -/// Interface for a platform raw socket used by GptpEngine and PeerDelayMeasurer. +/// @brief Platform-agnostic raw socket interface used by @c GptpEngine and +/// @c PeerDelayMeasurer for gPTP frame transmission and reception. +/// +/// Provides hardware timestamping support with automatic fallback to software +/// timestamps when the NIC does not support hardware timestamping. class RawSocket { public: virtual ~RawSocket() noexcept = default; - /// Open the socket bound to @p iface. Returns false on failure. + /// @brief Open the socket bound to @p iface. + /// + /// @return false on failure. virtual bool Open(const std::string& iface) = 0; - /// Configure hardware TX/RX timestamping. Returns false on failure. + /// @brief Configures hardware TX/RX timestamping on the NIC if supported. + /// + /// @return false on failure or if hardware timestamping is unsupported. virtual bool EnableHwTimestamping() = 0; - /// Close the socket and release the file descriptor. + /// @brief Close the socket and release the file descriptor. virtual void Close() = 0; - /// Receive one frame. + /// @brief Receive one Ethernet frame with its hardware (or software fallback) timestamp. + /// + /// @param buf Buffer for the received frame. + /// @param buf_len Buffer length in bytes. + /// @param hwts Output: hardware timestamp if available, otherwise software timestamp. + /// @param timeout_ms Receive timeout in milliseconds (0 = non-blocking). /// @return Number of bytes received, 0 on timeout, -1 on error. virtual int Recv(std::uint8_t* buf, std::size_t buf_len, ::timespec& hwts, int timeout_ms) = 0; - /// Send one frame. + /// @brief Send one Ethernet frame and capture its hardware transmit timestamp. + /// + /// @param buf Frame buffer. + /// @param len Frame length in bytes. + /// @param hwts Output: hardware transmit timestamp if available, otherwise software timestamp. /// @return Number of bytes sent, or -1 on error. virtual int Send(const void* buf, int len, ::timespec& hwts) = 0; - /// Return the underlying file descriptor. + /// @brief Return the underlying file descriptor (for use with @c select / @c poll). virtual int GetFd() const = 0; }; diff --git a/score/time_slave/src/gptp/details/raw_socket_impl.h b/score/time_slave/src/gptp/details/raw_socket_impl.h index 75feb3b8..d9856a25 100644 --- a/score/time_slave/src/gptp/details/raw_socket_impl.h +++ b/score/time_slave/src/gptp/details/raw_socket_impl.h @@ -33,8 +33,9 @@ namespace details /** * @brief Platform raw socket for Ethernet I/O with hardware timestamping. * - * On Linux uses AF_PACKET / SO_TIMESTAMPING. - * On QNX uses the QNX raw-socket shim. + * Implements platform-specific raw socket operations: + * - Linux: AF_PACKET with SO_TIMESTAMPING for NIC-level timestamps + * - QNX: io-pkt raw-socket shim */ class RawSocketImpl : public RawSocket { @@ -52,6 +53,12 @@ class RawSocketImpl : public RawSocket bool Open(const std::string& iface) override; /// Configure hardware TX/RX timestamping on the already-opened socket. + /// + /// On Linux, requests SO_TIMESTAMPING (NIC-level timestamps). If the NIC + /// does not support hardware timestamping, returns false and logs a warning. + /// The socket continues to work, populating hwts with software timestamps + /// (higher jitter but protocol correctness is unaffected). + /// /// Returns false on failure. A no-op on platforms that don't support it. bool EnableHwTimestamping() override; diff --git a/score/time_slave/src/gptp/details/sync_state_machine.h b/score/time_slave/src/gptp/details/sync_state_machine.h index 570770e4..86db57cd 100644 --- a/score/time_slave/src/gptp/details/sync_state_machine.h +++ b/score/time_slave/src/gptp/details/sync_state_machine.h @@ -38,38 +38,56 @@ struct SyncResult bool is_time_jump_past{false}; }; -/** - * @brief Two-step Sync / Follow_Up correlation state machine - * (IEEE 802.1AS slave port). - * - * Detects forward time jumps (> @p jump_future_threshold_ns) and backward - * jumps. Computes neighborRateRatio from successive Sync intervals. - * Does NOT adjust any hardware clock; offset computation is purely - * informational for the upstream consumer. - * - * Thread-safety: NOT thread-safe. All calls must come from the same thread - * (the RxLoop thread in GptpEngine), except IsTimeout() which is atomic. - */ +/// @brief Two-step Sync / Follow_Up correlation state machine (IEEE 802.1AS slave port). +/// +/// Correlates Sync and Follow_Up message pairs to compute the clock offset and +/// detect time jumps. Does @b not adjust any hardware clock; offset computation +/// is purely informational for the upstream consumer. +/// +/// Internal states: +/// - @c kEmpty No Sync has been received yet. +/// - @c kSyncReceived Sync received, waiting for a matching Follow_Up. +/// - @c kPaired Sync+Follow_Up paired successfully. +/// +/// Computes @c neighborRateRatio from successive Sync intervals per IEEE 802.1AS: +/// neighborRateRatio = (Sync[n].rx_ns − Sync[n−1].rx_ns) / (Sync[n].origin_ns − Sync[n−1].origin_ns) +/// Initialised to 1.0 until the first pair. +/// +/// Thread-safety: NOT thread-safe. All calls must come from the same thread +/// (the RxLoop thread in GptpEngine), except @c IsTimeout() which uses +/// @c std::atomic and may be called from any thread. class SyncStateMachine final { public: - /// @param jump_future_threshold_ns Offset delta above which the state is - /// flagged as a future time jump. Set to 0 to disable detection. + /// @brief Constructs a SyncStateMachine. + /// + /// @param jump_future_threshold_ns Offset delta above which state is flagged as forward time jump (ns). explicit SyncStateMachine(std::int64_t jump_future_threshold_ns = 500'000'000LL) noexcept; - /// Called when a Sync message is received (with its HW receive timestamp - /// already stored in @p msg.recvHardwareTS). + /// @brief Called when a Sync message is received. + /// + /// Stores the Sync message (with its HW receive timestamp already in + /// @c msg.recvHardwareTS) and transitions state @c kEmpty \u2192 @c kSyncReceived. void OnSync(const PTPMessage& msg); - /// Called when a FollowUp message is received. - /// @return A SyncResult on a successful Sync+FUP pairing, std::nullopt otherwise. + /// @brief Called when a FollowUp message is received. + /// + /// If the sequence ID matches the pending Sync, computes the clock offset + /// (local hw_ts minus master_ns), detects time jumps (forward: + /// abs(offset_ns minus prev_offset_ns) greater than threshold; backward: master_ns less than prev_master_ns), + /// updates @c neighborRateRatio, and transitions to @c kPaired. + /// + /// @return A @c SyncResult on successful Sync+FUP pairing; @c std::nullopt on sequence mismatch. std::optional OnFollowUp(const PTPMessage& msg); - /// @return true if no valid Sync+FUP has been received for longer than - /// @p timeout_ns nanoseconds (monotonic). + /// @brief Returns @c true if no valid Sync+FUP has been received for longer + /// than @p timeout_ns nanoseconds (monotonic clock). + /// + /// Thread-safe: may be called from the main thread while the RxThread + /// processes messages. bool IsTimeout(std::int64_t mono_now_ns, std::int64_t timeout_ns) const; - /// @return The latest computed neighborRateRatio (1.0 until first pair). + /// @brief Returns the latest computed neighborRateRatio (1.0 until the first pair). double GetNeighborRateRatio() const { return neighbor_rate_ratio_; @@ -92,7 +110,6 @@ class SyncStateMachine final std::atomic last_sync_mono_ns_{0}; std::atomic created_mono_ns_; }; - } // namespace details } // namespace ts } // namespace score diff --git a/score/time_slave/src/gptp/gptp_engine.h b/score/time_slave/src/gptp/gptp_engine.h index f6f5c276..25ba1afc 100644 --- a/score/time_slave/src/gptp/gptp_engine.h +++ b/score/time_slave/src/gptp/gptp_engine.h @@ -38,7 +38,9 @@ namespace ts namespace details { -/// Configuration for GptpEngine. +/// @brief All configurable parameters for the gPTP engine. +/// +/// Platform-specific defaults apply for QNX (@c emac0) and Linux (@c /dev/ptp0). struct GptpEngineOptions { std::string iface_name = "emac0"; ///< Network interface for gPTP @@ -50,20 +52,34 @@ struct GptpEngineOptions std::uint8_t domain_number = 0U; ///< gPTP domain number (0–127) }; -/** - * @brief gPTP engine for the TimeSlave process. - * - * Runs two POSIX threads: RxThread (receive/parse PTP frames) and - * PdelayThread (periodic Pdelay_Req transmission). - * - * Dual-snapshot design: - * - pending_snapshot_: filled by the RxThread on every Sync+FollowUp - * - current_snapshot_: a committed, fully-flagged snapshot - * - * Callers should: - * 1. Call FinalizeSnapshot() to check timeout and commit pending to current. - * 2. Call ReadPTPSnapshot() (const) to retrieve the current snapshot. - */ +/// @brief Core gPTP protocol engine for the TimeSlave process. +/// +/// Manages two background threads for network I/O and peer delay measurement, +/// exposing thread-safe @c ReadPTPSnapshot() for the main thread. +/// +/// @b RxThread responsibilities: +/// 1. Receive raw gPTP Ethernet frames with hardware timestamps from the NIC via raw sockets. +/// 2. Decode and parse PTP messages (Sync, FollowUp, PdelayResp, PdelayRespFollowUp). +/// 3. Correlate Sync/FollowUp pairs and compute the clock offset and neighborRateRatio. +/// 4. Update @c pending_snapshot_ under @c snapshot_mutex_ protection. +/// +/// @b PdelayThread responsibilities: +/// 1. Periodically transmit PDelayReq frames and capture hardware transmit timestamps. +/// 2. Coordinate with RxThread to receive PDelayResp and PDelayRespFollowUp messages. +/// 3. Compute peer delay using the IEEE 802.1AS formula: +/// @c path_delay = ((t2 \u2212 t1) + (t4 \u2212 t3c)) / 2 +/// +/// @b Dual-snapshot design: +/// - @c pending_snapshot_: filled by the RxThread on every Sync+FollowUp. +/// - @c current_snapshot_: committed snapshot, advanced by @c FinalizeSnapshot(). +/// +/// @c FinalizeSnapshot() must be called regularly (typically main loop); missing calls delay commits. +/// +/// @c PeerDelayMeasurer uses its own @c std::mutex for PdelayThread / RxThread +/// synchronisation. @c SyncStateMachine uses @c std::atomic for its timeout flag. +/// +/// A test constructor accepts injected @c RawSocket and @c NetworkIdentity +/// dependencies for white-box unit testing without PTP hardware. class GptpEngine final { public: @@ -81,21 +97,34 @@ class GptpEngine final GptpEngine(GptpEngine&&) = delete; GptpEngine& operator=(GptpEngine&&) = delete; - /// Open the raw socket, enable HW timestamping, resolve the ClockIdentity, - /// and start the Rx and Pdelay background threads. + /// @brief Opens the raw socket, enables hardware timestamping, resolves the + /// @c ClockIdentity, and starts the Rx and PDelay background threads. + /// + /// Calls @c RawSocket::EnableHwTimestamping() to request NIC-level receive + /// timestamps (@c SO_TIMESTAMPING on Linux). If the NIC does not support + /// hardware timestamping, the call returns @c false and a warning is logged; + /// the engine continues normally with software timestamps (higher jitter but + /// protocol correctness is unaffected). + /// /// @return true on success. bool Initialize(); - /// Stop background threads and close the socket. + /// @brief Stops background threads and closes the socket. + /// /// @return true (always succeeds). bool Deinitialize(); - /// Check for sync timeout, apply status flags, and commit pending_snapshot_ - /// to current_snapshot_. Must be called periodically before ReadPTPSnapshot(). + /// @brief Checks the sync timeout, applies status flags, and commits + /// @c pending_snapshot_ to @c current_snapshot_ under @c snapshot_mutex_. + /// + /// Must be called periodically from the main thread before @c ReadPTPSnapshot(). + /// Atomic copy under @c snapshot_mutex_. void FinalizeSnapshot() noexcept; - /// Copy the latest committed snapshot into @p data. - /// Non-blocking; returns false only if the engine is not initialized. + /// @brief Copies the latest committed snapshot into @p data under @c snapshot_mutex_. + /// + /// Non-blocking and thread-safe. Returns @c false only if the engine is not + /// initialised. bool ReadPTPSnapshot(score::ts::GptpIpcData& data) const noexcept; private: diff --git a/score/time_slave/src/gptp/instrument/probe.h b/score/time_slave/src/gptp/instrument/probe.h index 273ad2dc..362a9238 100644 --- a/score/time_slave/src/gptp/instrument/probe.h +++ b/score/time_slave/src/gptp/instrument/probe.h @@ -25,19 +25,19 @@ namespace ts namespace details { -/// Measurement probe points within the gPTP pipeline. +/// @brief Measurement probe points within the gPTP pipeline. enum class ProbePoint : std::uint8_t { - kRxPacketReceived = 0, - kSyncFrameParsed = 1, - kFollowUpProcessed = 2, - kOffsetComputed = 3, - kPdelayReqSent = 4, - kPdelayCompleted = 5, - kPhcAdjusted = 6, + kRxPacketReceived = 0, ///< Raw Ethernet frame received from socket (RxThread). + kSyncFrameParsed = 1, ///< Sync message successfully decoded by GptpMessageParser. + kFollowUpProcessed = 2,///< FollowUp received; SyncStateMachine::OnFollowUp() returned a SyncResult. + kOffsetComputed = 3, ///< Final clock offset value available after Sync/FollowUp correlation. + kPdelayReqSent = 4, ///< PDelayReq frame transmitted by PeerDelayMeasurer. + kPdelayCompleted = 5, ///< Peer delay computation finished (all four timestamps collected). + kPhcAdjusted = 6, ///< PhcAdjuster applied a step or frequency correction. }; -/// Data payload for a single probe event. +/// @brief Data payload for a single probe event. struct ProbeData { std::int64_t ts_mono_ns{0}; @@ -45,12 +45,21 @@ struct ProbeData std::uint32_t seq_id{0}; }; -/** - * @brief Singleton manager for runtime measurement probes. - * - * When enabled, traces probe events to the logger and optionally to a Recorder. - * Controlled at runtime via SetEnabled(). - */ +/// @brief Singleton manager for runtime measurement probes in the gPTP pipeline. +/// +/// When enabled, records probe events at key processing points (packet RX, +/// Sync/FollowUp processing, peer delay completion, PHC adjustments) to the +/// logger and optionally to a @c Recorder for CSV output. +/// +/// Provides zero overhead when disabled: @c IsEnabled() is an atomic load that +/// causes an early exit in the @c GPTP_PROBE() macro before any argument +/// evaluation. +/// +/// Thread-safe: @c enabled_ and @c recorder_ use @c std::atomic; @c Trace() +/// may be called concurrently from RxThread, PdelayThread, and main thread. +/// +/// @see ProbePoint Enumeration of instrumentation points. +/// @see GPTP_PROBE Convenience macro for zero-overhead instrumented calls. class ProbeManager final { public: @@ -65,13 +74,18 @@ class ProbeManager final return enabled_.load(std::memory_order_acquire); } - /// Optional: link to a Recorder for persistent probe output. + /// @brief Optional: link to a @c Recorder for persistent CSV probe output. + /// Pass @c nullptr to unlink. void SetRecorder(Recorder* recorder) { recorder_.store(recorder, std::memory_order_release); } - /// Record a probe event. Thread-safe. + /// @brief Records a probe event. Thread-safe. + /// + /// No-op if @c IsEnabled() returns @c false (fast atomic-load check). + /// When enabled, logs the event via the middleware logger and, if a + /// @c Recorder is linked, forwards the event for CSV file output. void Trace(ProbePoint point, const ProbeData& data); private: @@ -80,7 +94,7 @@ class ProbeManager final std::atomic recorder_{nullptr}; }; -/// Returns the current monotonic timestamp in nanoseconds. +/// @brief Returns the current monotonic timestamp in nanoseconds (@c CLOCK_MONOTONIC). std::int64_t ProbeMonoNs() noexcept; } // namespace details diff --git a/score/time_slave/src/gptp/phc/phc_adjuster.h b/score/time_slave/src/gptp/phc/phc_adjuster.h index 05a57de8..5475b55f 100644 --- a/score/time_slave/src/gptp/phc/phc_adjuster.h +++ b/score/time_slave/src/gptp/phc/phc_adjuster.h @@ -23,22 +23,39 @@ namespace ts namespace details { -/// Configuration for PHC hardware clock synchronization. +/// @brief Configuration for PHC hardware clock synchronisation. +/// +/// The @c device field is platform-specific: +/// - Linux: path to the PHC character device, e.g. @c /dev/ptp0 +/// - QNX: network interface name, e.g. @c emac0 struct PhcConfig { - bool enabled = false; - std::string device = ""; ///< QNX: "emac0", Linux: "/dev/ptp0" - std::int64_t step_threshold_ns = 100'000'000LL; ///< >100ms = step, else slew + bool enabled = false; ///< Enable or disable PHC adjustment. Default: @c false + std::string device = ""; ///< PHC device identifier. QNX: @c "emac0"; Linux: @c "/dev/ptp0" + std::int64_t step_threshold_ns = 100'000'000LL; ///< Offset above which a step correction is applied instead of + ///< frequency slew (ns). Default: 100\,000\,000 (100 ms) }; -/** - * @brief Adjusts the PTP Hardware Clock (PHC) based on gPTP offset and rate. - * - * When enabled, applies step corrections for large offsets and frequency - * slew for continuous tracking. When disabled, all methods are no-ops. - * - * Platform-specific: Linux uses clock_adjtime(), QNX uses EMAC PTP ioctls. - */ +/// @brief Adjusts the PTP Hardware Clock (PHC) on the NIC based on gPTP offset and rate. +/// +/// When @c PhcConfig::enabled is @c true, applies step corrections for large +/// offsets and frequency slew for continuous tracking. When disabled, all +/// methods are no-ops. +/// +/// Platform-specific implementations: +/// - @b Linux: @c clock_adjtime() (@c SYS_clock_adjtime syscall); +/// step via @c ADJ_SETOFFSET|ADJ_NANO; slew via @c ADJ_FREQUENCY (scaled-ppm). +/// - @b QNX: @c SIOCGDRVSPEC / @c SIOCSDRVSPEC on a UDP socket; +/// step via @c PTP_GET_TIME (0x102) + @c PTP_SET_TIME (0x103); +/// slew via @c EMAC_PTP_ADJ_FREQ_PPM (0x200) in ppm. +/// +/// @b Fallback when PHC is unavailable: +/// On Linux the constructor calls @c open(device, O_RDWR); on failure +/// @c phc_fd_ stays at @c -1 and both @c AdjustOffset() and +/// @c AdjustFrequency() guard against @c phc_fd_ < 0 and return immediately. +/// The gPTP protocol pipeline (Sync/FollowUp reception, peer-delay, snapshot +/// publishing) is completely unaffected — only the NIC hardware clock itself +/// will drift relative to PTP time. class PhcAdjuster final { public: @@ -48,19 +65,23 @@ class PhcAdjuster final PhcAdjuster(const PhcAdjuster&) = delete; PhcAdjuster& operator=(const PhcAdjuster&) = delete; - /// @return true if hardware clock adjustment is enabled. + /// @brief Returns @c true if hardware clock adjustment is enabled. bool IsEnabled() const { return cfg_.enabled; } - /// Apply a time step or slew based on offset magnitude. - /// If |offset_ns| > step_threshold_ns, a step correction is applied; - /// otherwise the offset is ignored (frequency slew handles drift). + /// @brief Applies a time step or frequency slew based on offset magnitude. + /// + /// If |@p offset_ns| > @c step_threshold_ns a step correction is applied; + /// otherwise the offset is ignored and frequency slew handles residual drift. + /// No-op when PHC is disabled or @c phc_fd_ < 0. void AdjustOffset(std::int64_t offset_ns); - /// Adjust the PHC frequency to track the master clock rate. - /// @param rate_ratio neighborRateRatio (1.0 = no drift). + /// @brief Adjusts the PHC frequency to track the master clock rate. + /// + /// @param rate_ratio @c neighborRateRatio from @c SyncStateMachine (1.0 = no drift). + /// No-op when PHC is disabled or @c phc_fd_ < 0. void AdjustFrequency(double rate_ratio); private: diff --git a/score/time_slave/src/gptp/record/recorder.h b/score/time_slave/src/gptp/record/recorder.h index 7d15c70e..36861953 100644 --- a/score/time_slave/src/gptp/record/recorder.h +++ b/score/time_slave/src/gptp/record/recorder.h @@ -26,17 +26,17 @@ namespace ts namespace details { -/// Event types that can be recorded. +/// @brief Event types that can be recorded by the @c Recorder. enum class RecordEvent : std::uint8_t { - kSyncReceived = 0, - kPdelayCompleted = 1, - kClockJump = 2, - kOffsetThreshold = 3, - kProbe = 4, + kSyncReceived = 0, ///< A Sync message was received and processed. + kPdelayCompleted = 1, ///< A full peer delay measurement cycle completed. + kClockJump = 2, ///< A forward or backward time jump was detected. + kOffsetThreshold = 3, ///< Clock offset exceeded offset_threshold_ns. + kProbe = 4, ///< Forwarded from ProbeManager::Trace(); status_flags column carries the ``ProbePoint`` value. }; -/// A single record entry written to the log file. +/// @brief A single record entry written to the CSV log file. struct RecordEntry { std::int64_t mono_ns{0}; @@ -47,21 +47,32 @@ struct RecordEntry std::uint8_t status_flags{0}; }; -/** - * @brief Thread-safe CSV file recorder for gPTP events. - * - * When enabled, appends CSV lines to the configured file path. - * Format: mono_ns,event,offset_ns,pdelay_ns,seq_id,status_flags - */ +/// @brief Thread-safe CSV file recorder for gPTP events and diagnostics. +/// +/// When enabled, appends one CSV row per event to the configured file path. +/// The file is opened in append mode; a header row is written only if the file +/// is newly created. CSV format: +/// @code +/// mono_ns,event,offset_ns,pdelay_ns,seq_id,status_flags +/// 1234567890,0,1500,250000,42,3 +/// @endcode +/// +/// On write or flush failure, @c enabled_ is set to @c false atomically. +/// Failures are non-recoverable; subsequent @c Record() calls are no-ops. +/// The file is never re-opened after an error. +/// +/// @see RecordEvent Enumeration of recordable event types. +/// @see RecordEntry Single CSV row data structure. class Recorder final { public: + /// @brief Configuration parameters for the @c Recorder. struct Config { - bool enabled = false; - std::string file_path = "/var/log/gptp_record.csv"; - std::int64_t offset_threshold_ns = 1'000'000LL; ///< 1 ms - std::uint32_t flush_interval = 8U; + bool enabled = false; ///< Enable or disable recording. + std::string file_path = "/var/log/gptp_record.csv"; ///< Output CSV file path. + std::int64_t offset_threshold_ns = 1'000'000LL; ///< Reserved for ``kOffsetThreshold`` events (threshold above which offsets are logged); 1ms. + std::uint32_t flush_interval = 8U; ///< Number of rows between explicit ``file_.flush()`` calls. }; explicit Recorder(Config cfg); @@ -75,7 +86,13 @@ class Recorder final return enabled_.load(std::memory_order_relaxed) && file_.is_open(); } - /// Record an entry. Thread-safe. + /// @brief Records an entry to the CSV file. Thread-safe. + /// + /// Serialises writes via @c mutex_. Appends one CSV line in the format + /// @c mono_ns,event,offset_ns,pdelay_ns,seq_id,status_flags. + /// Every @c Config::flush_interval rows, calls @c std::ofstream::flush(). + /// On write or flush failure sets @c enabled_ to @c false; later calls + /// become no-ops. void Record(const RecordEntry& entry); private: From cb18cd5df375d6dd6cc6ce6560d051bef4f2d343 Mon Sep 17 00:00:00 2001 From: Ryan Steel Date: Fri, 28 Aug 2026 14:11:46 +0100 Subject: [PATCH 2/6] fix formatting --- score/time_slave/src/gptp/instrument/probe.h | 14 +++++++------- score/time_slave/src/gptp/record/recorder.h | 15 ++++++++------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/score/time_slave/src/gptp/instrument/probe.h b/score/time_slave/src/gptp/instrument/probe.h index 362a9238..647c7ddb 100644 --- a/score/time_slave/src/gptp/instrument/probe.h +++ b/score/time_slave/src/gptp/instrument/probe.h @@ -28,13 +28,13 @@ namespace details /// @brief Measurement probe points within the gPTP pipeline. enum class ProbePoint : std::uint8_t { - kRxPacketReceived = 0, ///< Raw Ethernet frame received from socket (RxThread). - kSyncFrameParsed = 1, ///< Sync message successfully decoded by GptpMessageParser. - kFollowUpProcessed = 2,///< FollowUp received; SyncStateMachine::OnFollowUp() returned a SyncResult. - kOffsetComputed = 3, ///< Final clock offset value available after Sync/FollowUp correlation. - kPdelayReqSent = 4, ///< PDelayReq frame transmitted by PeerDelayMeasurer. - kPdelayCompleted = 5, ///< Peer delay computation finished (all four timestamps collected). - kPhcAdjusted = 6, ///< PhcAdjuster applied a step or frequency correction. + kRxPacketReceived = 0, ///< Raw Ethernet frame received from socket (RxThread). + kSyncFrameParsed = 1, ///< Sync message successfully decoded by GptpMessageParser. + kFollowUpProcessed = 2, ///< FollowUp received; SyncStateMachine::OnFollowUp() returned a SyncResult. + kOffsetComputed = 3, ///< Final clock offset value available after Sync/FollowUp correlation. + kPdelayReqSent = 4, ///< PDelayReq frame transmitted by PeerDelayMeasurer. + kPdelayCompleted = 5, ///< Peer delay computation finished (all four timestamps collected). + kPhcAdjusted = 6, ///< PhcAdjuster applied a step or frequency correction. }; /// @brief Data payload for a single probe event. diff --git a/score/time_slave/src/gptp/record/recorder.h b/score/time_slave/src/gptp/record/recorder.h index 36861953..e6ddc2c7 100644 --- a/score/time_slave/src/gptp/record/recorder.h +++ b/score/time_slave/src/gptp/record/recorder.h @@ -29,11 +29,11 @@ namespace details /// @brief Event types that can be recorded by the @c Recorder. enum class RecordEvent : std::uint8_t { - kSyncReceived = 0, ///< A Sync message was received and processed. - kPdelayCompleted = 1, ///< A full peer delay measurement cycle completed. - kClockJump = 2, ///< A forward or backward time jump was detected. - kOffsetThreshold = 3, ///< Clock offset exceeded offset_threshold_ns. - kProbe = 4, ///< Forwarded from ProbeManager::Trace(); status_flags column carries the ``ProbePoint`` value. + kSyncReceived = 0, ///< A Sync message was received and processed. + kPdelayCompleted = 1, ///< A full peer delay measurement cycle completed. + kClockJump = 2, ///< A forward or backward time jump was detected. + kOffsetThreshold = 3, ///< Clock offset exceeded offset_threshold_ns. + kProbe = 4, ///< Forwarded from ProbeManager::Trace(); status_flags column carries the ``ProbePoint`` value. }; /// @brief A single record entry written to the CSV log file. @@ -71,8 +71,9 @@ class Recorder final { bool enabled = false; ///< Enable or disable recording. std::string file_path = "/var/log/gptp_record.csv"; ///< Output CSV file path. - std::int64_t offset_threshold_ns = 1'000'000LL; ///< Reserved for ``kOffsetThreshold`` events (threshold above which offsets are logged); 1ms. - std::uint32_t flush_interval = 8U; ///< Number of rows between explicit ``file_.flush()`` calls. + std::int64_t offset_threshold_ns = + 1'000'000LL; ///< Reserved for ``kOffsetThreshold`` events (threshold above which offsets are logged); 1ms. + std::uint32_t flush_interval = 8U; ///< Number of rows between explicit ``file_.flush()`` calls. }; explicit Recorder(Config cfg); From 126a59722296bde49b0a1c2ca116f34e948e8190 Mon Sep 17 00:00:00 2001 From: Ryan Steel Date: Tue, 8 Sep 2026 13:51:29 +0000 Subject: [PATCH 3/6] address comments --- score/time_slave/src/application/time_slave.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/score/time_slave/src/application/time_slave.h b/score/time_slave/src/application/time_slave.h index f9f8e10d..2cc5430c 100644 --- a/score/time_slave/src/application/time_slave.h +++ b/score/time_slave/src/application/time_slave.h @@ -30,7 +30,7 @@ namespace ts * and publishes time data to shared memory. * * TimeSlave is the gPTP protocol endpoint. It runs GptpEngine internally - * (with RxThread + PdelayThread) and periodically writes PtpTimeInfo + * (with RxThread + PdelayThread) and periodically writes GptpIpcData * to shared memory for consumption by TimeDaemon via ShmPTPEngine. */ class TimeSlave final : public score::mw::lifecycle::Application From 069ce4bd1f32295738f1184d6a6a362c07a5f883 Mon Sep 17 00:00:00 2001 From: Ryan Steel Date: Wed, 9 Sep 2026 16:17:32 +0000 Subject: [PATCH 4/6] address review comments --- .../time_slave/docs/detailed_design/index.rst | 35 +++++++------------ score/time_slave/src/gptp/gptp_engine.h | 13 ++++--- 2 files changed, 19 insertions(+), 29 deletions(-) diff --git a/score/time_slave/docs/detailed_design/index.rst b/score/time_slave/docs/detailed_design/index.rst index 48c1251d..ec296f1f 100644 --- a/score/time_slave/docs/detailed_design/index.rst +++ b/score/time_slave/docs/detailed_design/index.rst @@ -118,8 +118,8 @@ The data and control flow between units is presented in the following diagram: On this view you could see several "workers" scopes: -1. RxThread scope — receive raw gPTP Ethernet frames, decode PTP messages, correlate Sync/FollowUp pairs -2. PdelayThread scope — transmit PDelayReq frames, compute peer delay via IEEE 802.1AS formula +1. RxThread scope — receive raw gPTP Ethernet frames, decode PTP messages, correlate Sync/FollowUp and handle Pdelay messages +2. PdelayThread scope — periodically transmit PDelayReq frames 3. Main thread scope — periodically publish aggregated snapshot to shared memory See ``gptp_engine.h`` for detailed threading model, control flow responsibilities, and concurrency aspects. @@ -131,22 +131,23 @@ Each control flow has dedicated thread and runs independently. - **RxThread scope** - 1. receive raw gPTP Ethernet frames with hardware timestamps from NIC via raw sockets - 2. decode and parse PTP messages (Sync, FollowUp, PdelayResp, PdelayRespFollowUp) - 3. correlate Sync/FollowUp pairs and compute clock offset and neighborRateRatio - 4. update shared snapshot under mutex protection + #. receive raw gPTP Ethernet frames with hardware timestamps from NIC via raw sockets + #. decode and parse PTP messages (Sync, FollowUp, PdelayResp, PdelayRespFollowUp, PdelayReq) + #. depending on message type: + #. correlate Sync/FollowUp pairs and compute clock offset and neighborRateRatio. Update shared snapshot under mutex protection + #. correlate PdelayResp/PdelayRespFollowUp pairs with sent PdelayReq using the PeerDelayMeasurer unit and compute the propagation delay as defined in the IEEE 802.1AS standard + #. react on incoming PdelayReq by sending PdelayResp and PdelayRespFollowUp - **PdelayThread scope** - 1. periodically transmit PDelayReq frames and capture hardware transmit timestamps - 2. coordinate with RxThread to receive PDelayResp and PDelayRespFollowUp - 3. compute peer delay using IEEE 802.1AS formula: ``path_delay = ((t2 - t1) + (t4 - t3c)) / 2`` + #. delay sending the first PdelayReq by the configured pdelay_warmup timespan + #. periodically trigger the PeerDelayMeasurer unit to send PdelayReq frames and capture hardware transmit timestamps - **Main thread (periodic publish) scope** - 1. call ``GptpEngine::FinalizeSnapshot()`` to check timeout and commit pending snapshot - 2. call ``GptpEngine::ReadPTPSnapshot(data)`` to copy latest ``GptpIpcData`` to local variable - 3. publish snapshot via ``GptpIpcPublisher::Publish(data)`` + #. call ``GptpEngine::FinalizeSnapshot()`` to check timeout and commit pending snapshot + #. call ``GptpEngine::ReadPTPSnapshot(data)`` to copy latest ``GptpIpcData`` to local variable + #. publish snapshot via ``GptpIpcPublisher::Publish(data)`` Data Types or Events ^^^^^^^^^^^^^^^^^^^^ @@ -161,16 +162,6 @@ Main data exchanged between units: Units Within Time Slave ----------------------- -The following units comprise TimeSlave's internal implementation: - -1. **TimeSlave Application** -- process entry point; orchestrates GptpEngine lifecycle and periodic shared-memory publish loop -2. **GptpEngine** -- core gPTP engine with RxThread/PdelayThread and snapshot API -3. **FrameCodec** -- raw Ethernet frame encode/decode for gPTP -4. **MessageParser** -- IEEE 1588-v2 payload parsing -5. **SyncStateMachine** -- Sync/FollowUp correlation, clock offset, neighbor rate ratio, time-jump detection -6. **PeerDelayMeasurer** -- IEEE 802.1AS peer-delay measurement -7. **PhcAdjuster** -- PHC step/slew synchronization backend - GptpEngine ~~~~~~~~~~ diff --git a/score/time_slave/src/gptp/gptp_engine.h b/score/time_slave/src/gptp/gptp_engine.h index 4d4face2..18a8fb36 100644 --- a/score/time_slave/src/gptp/gptp_engine.h +++ b/score/time_slave/src/gptp/gptp_engine.h @@ -59,15 +59,14 @@ struct GptpEngineOptions /// /// @b RxThread responsibilities: /// 1. Receive raw gPTP Ethernet frames with hardware timestamps from the NIC via raw sockets. -/// 2. Decode and parse PTP messages (Sync, FollowUp, PdelayResp, PdelayRespFollowUp). -/// 3. Correlate Sync/FollowUp pairs and compute the clock offset and neighborRateRatio. -/// 4. Update @c pending_snapshot_ under @c snapshot_mutex_ protection. +/// 2. Decode and parse PTP messages (Sync, FollowUp, PdelayResp, PdelayRespFollowUp, PdelayReq). +/// 3a. Correlate Sync/FollowUp pairs and compute the clock offset and neighborRateRatio. Update @c pending_snapshot_ under @c snapshot_mutex_ protection. +/// 3b. Correlate PdelayResp/PdelayRespFollowUp pairs and sent PdelayReq using the PeerDelayMeasurer unit and compute the propagation delay as defined in the IEEE 802.1AS standard. +/// 3c. React on incoming PdelayReq by sending PdelayResp and PdelayRespFollowUp /// /// @b PdelayThread responsibilities: -/// 1. Periodically transmit PDelayReq frames and capture hardware transmit timestamps. -/// 2. Coordinate with RxThread to receive PDelayResp and PDelayRespFollowUp messages. -/// 3. Compute peer delay using the IEEE 802.1AS formula: -/// @c path_delay = ((t2 \u2212 t1) + (t4 \u2212 t3c)) / 2 +/// 1. Delay sending the first PdelayReq by the configured pdelay_warmup timespan. +/// 2. Periodically trigger the PeerDelayMeasurer unit to send PdelayReq frames and capture hardware transmit timestamps. /// /// @b Dual-snapshot design: /// - @c pending_snapshot_: filled by the RxThread on every Sync+FollowUp. From 364a9fa315884cca92eef910cc71e2b69e3f7929 Mon Sep 17 00:00:00 2001 From: Ryan Steel Date: Mon, 14 Sep 2026 13:26:14 +0100 Subject: [PATCH 5/6] format fix --- score/time_slave/docs/detailed_design/index.rst | 7 ++++--- score/time_slave/src/gptp/gptp_engine.h | 9 ++++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/score/time_slave/docs/detailed_design/index.rst b/score/time_slave/docs/detailed_design/index.rst index ec296f1f..fb9657b6 100644 --- a/score/time_slave/docs/detailed_design/index.rst +++ b/score/time_slave/docs/detailed_design/index.rst @@ -134,9 +134,10 @@ Each control flow has dedicated thread and runs independently. #. receive raw gPTP Ethernet frames with hardware timestamps from NIC via raw sockets #. decode and parse PTP messages (Sync, FollowUp, PdelayResp, PdelayRespFollowUp, PdelayReq) #. depending on message type: - #. correlate Sync/FollowUp pairs and compute clock offset and neighborRateRatio. Update shared snapshot under mutex protection - #. correlate PdelayResp/PdelayRespFollowUp pairs with sent PdelayReq using the PeerDelayMeasurer unit and compute the propagation delay as defined in the IEEE 802.1AS standard - #. react on incoming PdelayReq by sending PdelayResp and PdelayRespFollowUp + + #. correlate Sync/FollowUp pairs and compute clock offset and neighborRateRatio. Update shared snapshot under mutex protection + #. correlate PdelayResp/PdelayRespFollowUp pairs with sent PdelayReq using the PeerDelayMeasurer unit and compute the propagation delay as defined in the IEEE 802.1AS standard + #. react on incoming PdelayReq by sending PdelayResp and PdelayRespFollowUp - **PdelayThread scope** diff --git a/score/time_slave/src/gptp/gptp_engine.h b/score/time_slave/src/gptp/gptp_engine.h index 18a8fb36..43449de5 100644 --- a/score/time_slave/src/gptp/gptp_engine.h +++ b/score/time_slave/src/gptp/gptp_engine.h @@ -60,13 +60,16 @@ struct GptpEngineOptions /// @b RxThread responsibilities: /// 1. Receive raw gPTP Ethernet frames with hardware timestamps from the NIC via raw sockets. /// 2. Decode and parse PTP messages (Sync, FollowUp, PdelayResp, PdelayRespFollowUp, PdelayReq). -/// 3a. Correlate Sync/FollowUp pairs and compute the clock offset and neighborRateRatio. Update @c pending_snapshot_ under @c snapshot_mutex_ protection. -/// 3b. Correlate PdelayResp/PdelayRespFollowUp pairs and sent PdelayReq using the PeerDelayMeasurer unit and compute the propagation delay as defined in the IEEE 802.1AS standard. +/// 3a. Correlate Sync/FollowUp pairs and compute the clock offset and neighborRateRatio. Update @c pending_snapshot_ +/// under @c snapshot_mutex_ protection. +/// 3b. Correlate PdelayResp/PdelayRespFollowUp pairs and sent PdelayReq using +/// the PeerDelayMeasurer unit and compute the propagation delay as defined in the IEEE 802.1AS standard. /// 3c. React on incoming PdelayReq by sending PdelayResp and PdelayRespFollowUp /// /// @b PdelayThread responsibilities: /// 1. Delay sending the first PdelayReq by the configured pdelay_warmup timespan. -/// 2. Periodically trigger the PeerDelayMeasurer unit to send PdelayReq frames and capture hardware transmit timestamps. +/// 2. Periodically trigger the PeerDelayMeasurer unit to send PdelayReq frames and capture hardware transmit +/// timestamps. /// /// @b Dual-snapshot design: /// - @c pending_snapshot_: filled by the RxThread on every Sync+FollowUp. From 45266f700e00d535e5102148264c97152334e8cb Mon Sep 17 00:00:00 2001 From: Ryan Steel Date: Fri, 18 Sep 2026 09:21:03 +0100 Subject: [PATCH 6/6] address review comments --- score/time_slave/src/gptp/gptp_engine.h | 5 ++--- score/time_slave/src/gptp/phc/phc_adjuster.h | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/score/time_slave/src/gptp/gptp_engine.h b/score/time_slave/src/gptp/gptp_engine.h index 43449de5..a84635bc 100644 --- a/score/time_slave/src/gptp/gptp_engine.h +++ b/score/time_slave/src/gptp/gptp_engine.h @@ -104,9 +104,8 @@ class GptpEngine final /// /// Calls @c RawSocket::EnableHwTimestamping() to request NIC-level receive /// timestamps (@c SO_TIMESTAMPING on Linux). If the NIC does not support - /// hardware timestamping, the call returns @c false and a warning is logged; - /// the engine continues normally with software timestamps (higher jitter but - /// protocol correctness is unaffected). + /// hardware timestamping, a warning is logged; the engine continues normally + /// with software timestamps (higher jitter but protocol correctness is unaffected). /// /// @return true on success. bool Initialize(); diff --git a/score/time_slave/src/gptp/phc/phc_adjuster.h b/score/time_slave/src/gptp/phc/phc_adjuster.h index 5475b55f..535c6b25 100644 --- a/score/time_slave/src/gptp/phc/phc_adjuster.h +++ b/score/time_slave/src/gptp/phc/phc_adjuster.h @@ -46,8 +46,8 @@ struct PhcConfig /// - @b Linux: @c clock_adjtime() (@c SYS_clock_adjtime syscall); /// step via @c ADJ_SETOFFSET|ADJ_NANO; slew via @c ADJ_FREQUENCY (scaled-ppm). /// - @b QNX: @c SIOCGDRVSPEC / @c SIOCSDRVSPEC on a UDP socket; -/// step via @c PTP_GET_TIME (0x102) + @c PTP_SET_TIME (0x103); -/// slew via @c EMAC_PTP_ADJ_FREQ_PPM (0x200) in ppm. +/// step via @c PTP_GET_TIME + @c PTP_SET_TIME; +/// slew via @c EMAC_PTP_ADJ_FREQ_PPM in ppm. /// /// @b Fallback when PHC is unavailable: /// On Linux the constructor calls @c open(device, O_RDWR); on failure