From 94cb315be11c47eab7505420436c572ea1137ba2 Mon Sep 17 00:00:00 2001 From: Ryan Steel Date: Thu, 20 Aug 2026 09:22:18 +0100 Subject: [PATCH] migrate time_slave detailed design --- docs/features/index.rst | 1 - .../shm_ptp_engine/shm_ptp_engine_class.puml | 105 --- .../shm_ptp_engine_init_seq.puml | 82 -- .../shm_ptp_engine_read_seq.puml | 73 -- .../_assets/ts_client/ipc_channel.puml | 71 -- .../_assets/ts_client/ipc_sequence.puml | 52 -- docs/features/time_slave/index.rst | 827 ------------------ .../gptp_engine/gptp_engine_class.puml | 13 + .../_assets/gptp_engine/gptp_threading.puml | 13 + .../_assets/timeslave_class.puml | 13 + .../_assets/timeslave_data_flow.puml | 13 + .../_assets/timeslave_deployment.puml | 13 + .../docs/detailed_design/detailed_design.rst | 96 -- .../time_slave/docs/detailed_design/index.rst | 668 +++++++++++++- 14 files changed, 721 insertions(+), 1319 deletions(-) delete mode 100644 docs/features/time_slave/_assets/shm_ptp_engine/shm_ptp_engine_class.puml delete mode 100644 docs/features/time_slave/_assets/shm_ptp_engine/shm_ptp_engine_init_seq.puml delete mode 100644 docs/features/time_slave/_assets/shm_ptp_engine/shm_ptp_engine_read_seq.puml delete mode 100644 docs/features/time_slave/_assets/ts_client/ipc_channel.puml delete mode 100644 docs/features/time_slave/_assets/ts_client/ipc_sequence.puml delete mode 100644 docs/features/time_slave/index.rst rename {docs/features/time_slave => score/time_slave/docs/detailed_design}/_assets/gptp_engine/gptp_engine_class.puml (86%) rename {docs/features/time_slave => score/time_slave/docs/detailed_design}/_assets/gptp_engine/gptp_threading.puml (67%) rename {docs/features/time_slave => score/time_slave/docs/detailed_design}/_assets/timeslave_class.puml (86%) rename {docs/features/time_slave => score/time_slave/docs/detailed_design}/_assets/timeslave_data_flow.puml (77%) rename {docs/features/time_slave => score/time_slave/docs/detailed_design}/_assets/timeslave_deployment.puml (72%) delete mode 100644 score/time_slave/docs/detailed_design/detailed_design.rst diff --git a/docs/features/index.rst b/docs/features/index.rst index db95ddb0..cc4346d2 100644 --- a/docs/features/index.rst +++ b/docs/features/index.rst @@ -21,5 +21,4 @@ Features time/index time_daemon/index - time_slave/index test_cases diff --git a/docs/features/time_slave/_assets/shm_ptp_engine/shm_ptp_engine_class.puml b/docs/features/time_slave/_assets/shm_ptp_engine/shm_ptp_engine_class.puml deleted file mode 100644 index 1b5486c6..00000000 --- a/docs/features/time_slave/_assets/shm_ptp_engine/shm_ptp_engine_class.puml +++ /dev/null @@ -1,105 +0,0 @@ -@startuml shm_ptp_engine_class_diagram -!theme plain - -title ShmPTPEngine: Class Diagram - -legend top left - |= Color |= Description | - | <#Beige> | Base classes / data types | - | <#Wheat> | PTPMachine / ShmPTPEngine | - | <#LightPink> | libTSClient IPC | -endlegend - -package "score::td" { - class "GPTPShmMachine" as real_machine #Wheat { - type alias for PTPMachine - -- - Constructed via CreateGPTPShmMachine() - } -} - -package "score::td (base classes)" { - abstract class "BaseMachine" as base_machine #Beige { - + GetName() : string - + Init() : bool - } - - abstract class "ProactiveMachine" as proactive_machine #Beige { - + Start() : void - + Stop() : void - } - - abstract class "PeriodicMachine" as periodic_machine #Beige { - # PeriodicTask() : void - } - - abstract class "Producer" as producer #Beige { - + SetPublishCallback(cb) : void - # Publish(data : T) : void - } - - class "PTPMachine" as ptp_machine #Wheat { - - engine_ : PTPEngine - + Init() : bool - + SetPublishCallback(cb) : void - # PeriodicTask() : void - } - - base_machine <|-- proactive_machine - proactive_machine <|-- periodic_machine - periodic_machine <|-- ptp_machine - producer <|.. ptp_machine -} - -package "score::td::details" { - class ShmPTPEngine #Wheat { - - ipc_name_ : string - - receiver_ : GptpIpcReceiver - - initialized_ : bool - + ShmPTPEngine(ipc_name : string) - + Initialize() : bool - + Deinitialize() : bool - + ReadPTPSnapshot(info : PtpTimeInfo&) : bool - } -} - -package "score::ts::details" { - class GptpIpcReceiver #LightPink { - + Init(name : string) : bool - + Receive() : optional - + Close() : void - } -} - -package "Data Types" { - class GptpIpcData #Beige { - + ptp_assumed_time : chrono::nanoseconds - + local_time : chrono::nanoseconds - + rate_deviation : double - + status : GptpIpcStatus - + sync_fup_data : GptpIpcSyncFupData - + pdelay_data : GptpIpcPDelayData - } - - class PtpTimeInfo #Beige { - + ptp_assumed_time - + local_time - + rate_deviation - + status - + sync_fup_data - + pdelay_data - } -} - -ptp_machine *-- ShmPTPEngine : PTPEngine = ShmPTPEngine -ShmPTPEngine *-- GptpIpcReceiver -ShmPTPEngine ..> GptpIpcData : reads -ShmPTPEngine ..> PtpTimeInfo : produces -real_machine --|> ptp_machine : alias - -note right of ShmPTPEngine - Maps GptpIpcData → PtpTimeInfo - on every ReadPTPSnapshot() call. -end note - -@enduml diff --git a/docs/features/time_slave/_assets/shm_ptp_engine/shm_ptp_engine_init_seq.puml b/docs/features/time_slave/_assets/shm_ptp_engine/shm_ptp_engine_init_seq.puml deleted file mode 100644 index 1a3d2f62..00000000 --- a/docs/features/time_slave/_assets/shm_ptp_engine/shm_ptp_engine_init_seq.puml +++ /dev/null @@ -1,82 +0,0 @@ -@startuml shm_ptp_engine_init_seq -!theme plain - -title ShmPTPEngine: Initialization Sequence - -hide footbox -autonumber "[00]" - -legend top left - |= Color |= Description | - | <#LightCyan> | TimeDaemon | - | <#Wheat> | GPTPShmMachine | - | <#LightPink> | libTSClient IPC | -endlegend - -participant "TimeBaseHandler" as tb #LightCyan -participant "GPTPShmMachine\n(PTPMachine)" as machine #Wheat -participant "ShmPTPEngine" as engine #Wheat -participant "GptpIpcReceiver" as receiver #LightPink -participant "MessageBroker" as broker #LightCyan - -== Construction == - -tb -> machine ** : CreateGPTPShmMachine("shm", "/gptp_ptp_info") -activate tb -activate machine -machine -> engine ** : ShmPTPEngine("/gptp_ptp_info") -engine -> receiver ** : GptpIpcReceiver() -machine --> tb -deactivate machine -deactivate tb - -== Initialization == - -tb -> machine : Init() -activate tb -activate machine -machine -> engine : Initialize() -activate engine -engine -> receiver : Init("/gptp_ptp_info") -activate receiver -note right of receiver - shm_open(O_RDONLY) - mmap(PROT_READ) - verify magic == 0x47505450 -end note -receiver --> engine : true / false -deactivate receiver -engine --> machine -deactivate engine -machine --> tb -deactivate machine -deactivate tb - -== Setup Producer == - -tb -> broker : subscribe machine to "raw_ptp_data" topic -activate tb -activate broker -broker -> machine : SetPublishCallback(broker::OnNewData) -activate machine -machine --> broker -deactivate machine -broker --> tb -deactivate broker -deactivate tb - -== Start Periodic Operation == - -tb -> machine : Start() -activate tb -activate machine -machine -> machine : start periodic thread -note right - Begin periodic IPC reads - from shared memory -end note -machine --> tb -deactivate machine -deactivate tb - -@enduml diff --git a/docs/features/time_slave/_assets/shm_ptp_engine/shm_ptp_engine_read_seq.puml b/docs/features/time_slave/_assets/shm_ptp_engine/shm_ptp_engine_read_seq.puml deleted file mode 100644 index 0a6a9142..00000000 --- a/docs/features/time_slave/_assets/shm_ptp_engine/shm_ptp_engine_read_seq.puml +++ /dev/null @@ -1,73 +0,0 @@ -@startuml shm_ptp_engine_read_seq -!theme plain - -title ShmPTPEngine: Periodic Read and Publish Workflow - -hide footbox -autonumber "[00]" - -legend top left - |= Color |= Description | - | <#Wheat> | GPTPShmMachine | - | <#LightPink> | libTSClient IPC | - | <#LightCyan> | Shared Memory | - | <#PaleTurquoise> | MessageBroker | - | <#LightBlue> | ControlFlowDivider | -endlegend - -participant "PTPMachine\n(PeriodicTask)" as machine #Wheat -participant "ShmPTPEngine" as engine #Wheat -participant "GptpIpcReceiver" as receiver #LightPink -participant "SharedMemory\n(GptpIpcRegion)" as shm #LightCyan -participant "MessageBroker" as broker #PaleTurquoise -participant "ControlFlowDivider" as cfd #LightBlue - -loop periodic (e.g., every 50 ms) - activate machine - machine -> machine : PeriodicTask() - - machine -> engine : ReadPTPSnapshot(info) - activate engine - engine -> receiver : Receive() - activate receiver - receiver -> shm : seqlock read (up to 20 retries) - activate shm - note right of shm - 1. read seq1 (acquire, must be even) - 2. memcpy GptpIpcData - 3. fence, read seq_confirm + seq - 4. verify seq1 == seq2 == seq3 - end note - shm --> receiver : GptpIpcData or contention - deactivate shm - receiver --> engine : optional - deactivate receiver - - alt data available - engine -> engine : map GptpIpcData → PtpTimeInfo - note right - status, ptp_assumed_time, - local_time, rate_deviation, - sync_fup_data, pdelay_data - end note - engine --> machine : true (PtpTimeInfo filled) - else no data (nullopt) - engine --> machine : false - end - deactivate engine - - alt ReadPTPSnapshot returned true - machine -> machine : Publish(PtpTimeInfo) - machine -> broker : publish_callback_(PtpTimeInfo) - activate broker - broker -> cfd : subscription.callback_(PtpTimeInfo) - activate cfd - cfd --> broker - deactivate cfd - broker --> machine - deactivate broker - end - deactivate machine -end - -@enduml diff --git a/docs/features/time_slave/_assets/ts_client/ipc_channel.puml b/docs/features/time_slave/_assets/ts_client/ipc_channel.puml deleted file mode 100644 index ba0ce3c7..00000000 --- a/docs/features/time_slave/_assets/ts_client/ipc_channel.puml +++ /dev/null @@ -1,71 +0,0 @@ -@startuml -!theme plain - -title libTSClient Shared Memory IPC - -legend top left - |= Color |= Description | - | <#LightPink> | IPC components | - | <#LightCyan> | Shared memory region | - | <#LightSalmon> | TimeDaemon adapter | -endlegend - -package "TimeSlave Process" { - class GptpIpcPublisher #LightPink { - - region_ : GptpIpcRegion* - - shm_resource_ : shared_ptr - - ipc_name_ : string - + Init(name) : bool - + Publish(data : GptpIpcData) : void - + Destroy() : void - } -} - -package "Shared Memory" { - class GptpIpcRegion <> #LightCyan { - + magic : atomic = 0x47505450 - + seq : atomic - + data : GptpIpcData - + seq_confirm : atomic - -- - 64-byte aligned for\ncache line efficiency - } -} - -package "TimeDaemon Process" { - class GptpIpcReceiver #LightPink { - - region_ : const GptpIpcRegion* - - shm_resource_ : shared_ptr - + Init(name) : bool - + Receive() : std::optional - + Close() : void - } - - class ShmPTPEngine #LightSalmon { - - receiver_ : GptpIpcReceiver - - ipc_name_ : string - + Initialize() : bool - + Deinitialize() : bool - + ReadPTPSnapshot(info : PtpTimeInfo&) : bool - } -} - -GptpIpcPublisher --> GptpIpcRegion : "shm_open(O_CREAT)\nmmap(PROT_WRITE)" -GptpIpcReceiver --> GptpIpcRegion : "shm_open(O_RDONLY)\nmmap(PROT_READ)" -ShmPTPEngine *-- GptpIpcReceiver -ShmPTPEngine ..> "PtpTimeInfo" : converts to - -note right of GptpIpcRegion - **Seqlock Protocol:** - Writer: seq++ (odd) → fence → memcpy → seq_confirm++, seq++ (even) - Reader: read seq1 (even) → memcpy → fence → read seq2, seq3 - retry if seq1 != seq2 or seq1 != seq3 - Retry up to 20 times on torn read -end note - -note bottom of ShmPTPEngine - Maps GptpIpcData fields to PtpTimeInfo. - Instantiated as GPTPShmMachine via CreateGPTPShmMachine(). -end note - -@enduml diff --git a/docs/features/time_slave/_assets/ts_client/ipc_sequence.puml b/docs/features/time_slave/_assets/ts_client/ipc_sequence.puml deleted file mode 100644 index 4139f95b..00000000 --- a/docs/features/time_slave/_assets/ts_client/ipc_sequence.puml +++ /dev/null @@ -1,52 +0,0 @@ -@startuml -!theme plain - -title libTSClient Seqlock IPC Protocol - -participant "TimeSlave\n(GptpIpcPublisher)" as PUB #LightPink -participant "SharedMemory\n(GptpIpcRegion)" as SHM #LightCyan -participant "TimeDaemon\n(GptpIpcReceiver)" as RCV #LightPink - -== Initialization == - -PUB -> SHM : shm_open("/gptp_ptp_info", O_CREAT | O_RDWR) -PUB -> SHM : ftruncate(sizeof(GptpIpcRegion)) -PUB -> SHM : mmap(PROT_READ | PROT_WRITE) -PUB -> SHM : write magic = 0x47505450 ('GPTP') - -... - -RCV -> SHM : shm_open("/gptp_ptp_info", O_RDONLY) -RCV -> SHM : mmap(PROT_READ) -RCV -> SHM : verify magic == 0x47505450 - -== Publish (Writer Side) == - -PUB -> SHM : seq.fetch_add(1, relaxed) // seq becomes odd (write in progress) -PUB -> SHM : atomic_thread_fence(release) -PUB -> SHM : memcpy(&data, &src, sizeof(GptpIpcData)) -PUB -> SHM : seq_confirm.store(seq+1, release) // seq_confirm becomes even -PUB -> SHM : seq.store(seq+1, release) // seq becomes even (write done) - -== Receive (Reader Side) == - -loop up to 20 retries - RCV -> SHM : seq1 = seq.load(acquire) - alt seq1 is odd (write in progress) - RCV -> RCV : retry - else seq1 is even - RCV -> SHM : memcpy(&local, &data, sizeof(GptpIpcData)) - RCV -> SHM : atomic_thread_fence(acq_rel) - RCV -> SHM : seq2 = seq_confirm.load(acquire) - RCV -> SHM : seq3 = seq.load(acquire) - alt seq1 == seq2 && seq1 == seq3 - RCV --> RCV : return GptpIpcData (consistent) - else torn read (new write started) - RCV -> RCV : retry - end - end -end - -RCV --> RCV : return std::nullopt (exhausted retries) - -@enduml diff --git a/docs/features/time_slave/index.rst b/docs/features/time_slave/index.rst deleted file mode 100644 index 31d2585f..00000000 --- a/docs/features/time_slave/index.rst +++ /dev/null @@ -1,827 +0,0 @@ -Concept for TimeSlave -====================== - -.. contents:: Table of Contents - :depth: 3 - :local: - -TimeSlave concept ------------------- - -Use Cases -~~~~~~~~~ - -TimeSlave is a standalone gPTP (IEEE 802.1AS) slave endpoint process that implements the low-level time synchronization protocol for the Eclipse SCORE time system. It is deployed as a separate process from the TimeDaemon to isolate real-time network I/O from the higher-level time validation and distribution logic. - -More precisely we can specify the following use cases for the TimeSlave: - -1. Receiving gPTP Sync/FollowUp messages from a Time Master on the Ethernet network -2. Measuring peer delay via the IEEE 802.1AS PDelayReq/PDelayResp exchange -3. Optionally adjusting the PTP Hardware Clock (PHC) on the NIC -4. Publishing the resulting ``GptpIpcData`` to shared memory for consumption by the TimeDaemon - -The raw architectural diagram is represented below. - -.. raw:: html - -
- -.. uml:: _assets/timeslave_deployment.puml - :alt: Raw architectural diagram - -.. raw:: html - -
- -Components decomposition -~~~~~~~~~~~~~~~~~~~~~~~~~ - -The design consists of several sw components: - -1. `TimeSlave Application <#timeslave-application-sw-component>`_ -2. `GptpEngine <#gptpengine-sw-component>`_ -3. `FrameCodec <#framecodec-sw-component>`_ -4. `MessageParser <#messageparser-sw-component>`_ -5. `SyncStateMachine <#syncstatemachine-sw-component>`_ -6. `PeerDelayMeasurer <#peerdelaymeasurer-sw-component>`_ -7. `PhcAdjuster <#phcadjuster-sw-component>`_ -8. `libTSClient <#libtsclient-sw-component>`_ -9. `ShmPTPEngine <#shmptpengine-sw-component>`_ - -Class view -~~~~~~~~~~ - -Main classes and components are presented on this diagram: - -.. raw:: html - -
- -.. uml:: _assets/timeslave_class.puml - :alt: Class View - :width: 100% - :align: center - -.. raw:: html - -
- -Data and control flow -~~~~~~~~~~~~~~~~~~~~~ - -The Data and Control flow are presented in the following diagram: - -.. raw:: html - -
- -.. uml:: _assets/timeslave_data_flow.puml - :alt: Data and Control flow View - -.. raw:: html - -
- -On this view you could see several "workers" scopes: - -1. RxThread scope -2. PdelayThread scope -3. Main thread (periodic publish) scope - -Each control flow is implemented with the dedicated thread and is independent from another ones. - -Control flows -^^^^^^^^^^^^^ - -RxThread scope -'''''''''''''' - -This control flow is responsible for the: - -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 - -PdelayThread scope -''''''''''''''''''' - -This control flow is responsible for the: - -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 -'''''''''''''''''''''''''''''''''''''' - -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)`` - -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 -'''''''''''' - -``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. - -SW Components decomposition -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -TimeSlave Application SW component -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -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. - -Component 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 SW component -^^^^^^^^^^^^^^^^^^^^^^^^ - -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. - -Component requirements -'''''''''''''''''''''' - -The ``GptpEngine`` has the following requirements: - -- 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) - -Class view -'''''''''' - -The Class Diagram is presented below: - -.. raw:: html - -
- -.. uml:: _assets/gptp_engine/gptp_engine_class.puml - :alt: Class Diagram - -.. raw:: html - -
- -Threading model -''''''''''''''' - -The GptpEngine operates with two background threads. The threading model is represented below: - -.. raw:: html - -
- -.. uml:: _assets/gptp_engine/gptp_threading.puml - :alt: Threading Model - -.. raw:: html - -
- -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 - -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: - -.. code-block:: none - - GptpEngine: HW timestamping not available on , falling back to SW timestamps - -The engine continues to run normally. The difference between the two modes: - -.. list-table:: - :header-rows: 1 - :widths: 30 35 35 - - * - Field - - HW timestamping available - - SW timestamping fallback - * - ``recvHardwareTS`` (Sync receive time) - - NIC hardware timestamp (nanosecond precision, captured at wire level) - - Software timestamp (captured at socket receive, higher jitter) - * - ``sync_fup_data.reference_local_timestamp`` - - Derived from NIC hardware timestamp - - Derived from software timestamp - * - ``GptpIpcData.local_time`` - - Always ``CLOCK_MONOTONIC`` (unaffected) - - Always ``CLOCK_MONOTONIC`` (unaffected) - * - Clock offset accuracy - - 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 SW component -^^^^^^^^^^^^^^^^^^^^^^^^^ - -The ``FrameCodec`` component handles raw Ethernet frame encoding and decoding for gPTP communication. - -Component 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 SW component -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The ``MessageParser`` component parses the PTP wire format (IEEE 1588-v2) from raw payload bytes. - -Component 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 SW component -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -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. - -Component requirements -'''''''''''''''''''''' - -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 SW component -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -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. - -Timestamp definitions -''''''''''''''''''''' - -.. list-table:: Peer Delay Timestamps (IEEE 802.1AS) - :header-rows: 1 - :widths: 10 20 30 40 - - * - Symbol - - Message - - Captured by - - Meaning - * - ``t1`` - - PDelayReq (TX) - - Slave (PdelayThread) - - HW transmit timestamp of the PDelayReq frame leaving the slave NIC - * - ``t2`` - - PDelayResp (RX) - - Master → carried in PDelayResp body - - HW receive timestamp of the PDelayReq frame arriving at the master NIC - * - ``t3c`` - - PDelayRespFollowUp - - 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) - - Slave (RxThread) - - HW receive timestamp of the PDelayResp frame arriving at the slave NIC - -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 -- The average of the two gives the one-way link delay - -Component 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 SW component -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -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. - -Component requirements -'''''''''''''''''''''' - -The ``PhcAdjuster`` has the following requirements: - -- 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) - -Fallback behavior when PHC is unavailable -'''''''''''''''''''''''''''''''''''''''''' - -The ``PhcAdjuster`` degrades gracefully in two scenarios: - -1. **PHC disabled** (``PhcConfig.enabled = false``, the default): ``AdjustOffset()`` and ``AdjustFrequency()`` are no-ops. The gPTP protocol pipeline (Sync/FollowUp reception, peer-delay measurement, ``GptpIpcData`` publishing) is completely unaffected. The hardware clock is not touched. - -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. - - - **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. - -libTSClient SW component -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The ``libTSClient`` component is the shared memory IPC library that connects the TimeSlave process to the TimeDaemon process. It provides a lock-free, single-writer/multi-reader communication channel using a seqlock protocol over POSIX shared memory. - -The component provides two sub components: publisher and receiver to be deployed on the TimeSlave and TimeDaemon sides accordingly. - -Component requirements -'''''''''''''''''''''' - -The ``libTSClient`` has the following requirements: - -- The ``libTSClient`` shall define a shared memory layout (``GptpIpcRegion``) with a magic number (``0x47505450`` = 'GPTP') for validation, an atomic seqlock counter (``seq``), a confirmation counter (``seq_confirm``), and a ``GptpIpcData`` data payload -- The ``libTSClient`` shall align the shared memory region to 64 bytes (cache line size) to prevent false sharing -- The ``libTSClient`` shall provide a ``GptpIpcPublisher`` component (in ``score::ts::details``) that creates and manages the POSIX shared memory segment and writes ``GptpIpcData`` using the seqlock protocol -- The ``libTSClient`` shall provide a ``GptpIpcReceiver`` component (in ``score::ts::details``) that opens the shared memory segment read-only and reads ``GptpIpcData`` with up to 20 seqlock retries -- The ``libTSClient`` shall use the POSIX shared memory name ``/gptp_ptp_info`` by default - -Class view -'''''''''' - -The Class Diagram is presented below: - -.. raw:: html - -
- -.. uml:: _assets/ts_client/ipc_channel.puml - :alt: Class Diagram - -.. raw:: html - -
- -Publish new data -'''''''''''''''' - -When ``TimeSlave Application`` has a new ``GptpIpcData`` snapshot, it publishes to the shared memory via the seqlock protocol: - -1. Increment ``seq`` (becomes odd — signals write in progress); a release fence is applied -2. ``memcpy`` the ``GptpIpcData`` -3. Store ``seq_confirm = seq + 1`` and increment ``seq`` (both become even — signals write complete) - -Receive data -'''''''''''' - -From TimeDaemon side, the receiver reads from the shared memory using the seqlock protocol with bounded retry: - -1. Read ``seq1`` with acquire ordering (must be even, otherwise retry — write in progress) -2. ``memcpy`` the ``GptpIpcData`` -3. Apply an acquire-release fence; read ``seq_confirm`` as ``seq2`` and re-read ``seq`` as ``seq3`` -4. If ``seq1 == seq2 == seq3``, the read is consistent; otherwise retry — torn read detected -5. Return ``std::optional`` (empty if all 20 retries exhausted) - -The seqlock protocol workflow is presented in the following sequence diagram: - -.. raw:: html - -
- -.. uml:: _assets/ts_client/ipc_sequence.puml - :alt: Seqlock Protocol - -.. raw:: html - -
- -Platform support -~~~~~~~~~~~~~~~~~ - -TimeSlave supports two target platforms with platform-specific implementations selected at compile time via Bazel ``select()``: - -.. list-table:: Platform Implementations - :header-rows: 1 - :widths: 25 37 38 - - * - 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/``. - -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. - -Supported probe points (``ProbePoint`` enum): - -.. 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 - -Logging configuration -~~~~~~~~~~~~~~~~~~~~~ - -The TimeSlave and its TimeDaemon-side adapter use the following logging contexts: - -.. list-table:: Logging Contexts - :header-rows: 1 - :widths: 35 20 45 - - * - Component - - Context ID - - Comments - * - TimeSlave Application - - TSAP - - **T**\ ime\ **S**\ lave **App**\ lication lifecycle (Initialize / Run) - * - gPTP Engine (RxThread / PdelayThread) - - GTPS - - **GPTP** **SLAVE** engine — low-level protocol processing - * - ShmPTPEngine (TimeDaemon side) - - GPTP - - TimeDaemon **GPTP** machine adapter (Initialize / ReadPTPSnapshot) - -Variability -~~~~~~~~~~~ - -Configuration -^^^^^^^^^^^^^ - -The ``GptpEngineOptions`` struct provides all configurable parameters for the gPTP engine: - -.. list-table:: GptpEngine Configuration - :header-rows: 1 - :widths: 30 15 55 - - * - Parameter - - Type - - Description - * - ``iface_name`` - - string - - Network interface for gPTP frames (e.g., ``emac0``); default: ``"emac0"`` - * - ``pdelay_interval_ms`` - - int - - Interval between PDelayReq transmissions (ms); default: ``1000`` - * - ``pdelay_warmup_ms`` - - int - - Delay before the first PDelayReq is sent (ms); default: ``2000`` - * - ``sync_timeout_ms`` - - int - - Timeout for Sync message reception before declaring timeout state (ms); default: ``3300`` - * - ``jump_future_threshold_ns`` - - int64_t - - Threshold above which a positive clock offset is flagged as a forward time jump (ns); default: ``500 000 000`` - * - ``phc_config`` - - PhcConfig - - PHC hardware clock adjustment settings (see ``PhcConfig`` table below); disabled by default - -The ``PhcConfig`` struct (embedded in ``GptpEngineOptions``) contains: - -.. list-table:: PhcAdjuster Configuration - :header-rows: 1 - :widths: 30 15 55 - - * - Parameter - - Type - - Description - * - ``enabled`` - - bool - - Enable or disable PHC adjustment; default: ``false`` - * - ``device`` - - string - - PHC device identifier: ``/dev/ptp0`` on Linux, ``emac0`` on QNX - * - ``step_threshold_ns`` - - int64_t - - Offset threshold above which a step correction is applied instead of frequency slew (ns); default: ``100 000 000`` - -Scalability -^^^^^^^^^^^ - -The TimeSlave architecture supports the following extensibility points: - -Platform extensibility -'''''''''''''''''''''' - -1. New target platforms can be supported by implementing the ``RawSocket`` and ``NetworkIdentity`` interfaces under a new ``platform//`` directory and selecting the implementation via ``Bazel select()`` -2. The ``PhcAdjuster`` platform implementations (``clock_adjtime`` on Linux, EMAC ioctls on QNX) can be extended for additional hardware without changing protocol logic - -Protocol extensibility -'''''''''''''''''''''' - -1. The ``GptpEngine`` accepts injected ``RawSocket`` and ``NetworkIdentity`` dependencies, making it straightforward to test or replace individual platform abstractions -2. The shared memory IPC channel name is configurable (``GptpIpcPublisher::Init(name)`` / ``GptpIpcReceiver::Init(name)``), allowing multiple gPTP instances per ECU if needed - -TimeDaemon integration extensibility -'''''''''''''''''''''''''''''''''''''' - -1. The ``ShmPTPEngine`` implements the same ``PTPEngine`` concept as other ``PTPMachine`` backends, making it transparently exchangeable with any other engine implementation -2. Alternative IPC mechanisms (e.g., socket-based) can be introduced by implementing a new engine class without modifying the ``PTPMachine`` template or downstream components - -ShmPTPEngine SW component -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The ``ShmPTPEngine`` component (in ``score::td::details``) is the TimeDaemon-side adapter that reads ``GptpIpcData`` from the shared memory channel written by TimeSlave and converts it into the ``PtpTimeInfo`` structure expected by the TimeDaemon pipeline. - -It is instantiated as ``GPTPShmMachine`` — a type alias for ``PTPMachine`` — which connects ``ShmPTPEngine`` to the TimeDaemon's internal ``MessageBroker``. - -Component requirements -'''''''''''''''''''''' - -The ``ShmPTPEngine`` has the following requirements: - -- The ``ShmPTPEngine`` shall call ``GptpIpcReceiver::Init(ipc_name)`` during ``Initialize()`` to open the shared memory channel -- The ``ShmPTPEngine`` shall call ``GptpIpcReceiver::Receive()`` in ``ReadPTPSnapshot()`` to fetch the latest ``GptpIpcData`` -- The ``ShmPTPEngine`` shall map all fields of ``GptpIpcData`` to the corresponding fields of ``PtpTimeInfo`` (status flags, Sync/FollowUp data, peer-delay data, time references) -- The ``ShmPTPEngine`` shall call ``GptpIpcReceiver::Close()`` during ``Deinitialize()`` -- The ``ShmPTPEngine`` shall be instantiatable with a configurable IPC channel name (default: ``/gptp_ptp_info``) - -Class view -'''''''''' - -The Class Diagram is presented below: - -.. raw:: html - -
- -.. uml:: _assets/shm_ptp_engine/shm_ptp_engine_class.puml - :alt: Class Diagram - -.. raw:: html - -
- -Component initialization -'''''''''''''''''''''''' - -During initialization the ``ShmPTPEngine`` shall open the shared memory channel to be able to read from it. - -The initialization workflow is represented in the following sequence diagram: - -.. raw:: html - -
- -.. uml:: _assets/shm_ptp_engine/shm_ptp_engine_init_seq.puml - :alt: Initialization workflow - -.. raw:: html - -
- -Read PTP snapshot -''''''''''''''''' - -After ``ShmPTPEngine`` reads the latest ``GptpIpcData`` from shared memory, it maps it to ``PtpTimeInfo`` and publishes via the ``MessageBroker``. - -The periodic read and publish workflow is described below: - -.. raw:: html - -
- -.. uml:: _assets/shm_ptp_engine/shm_ptp_engine_read_seq.puml - :alt: Periodic read and publish workflow - -.. raw:: html - -
- -Data mapping -'''''''''''' - -``ShmPTPEngine::ReadPTPSnapshot()`` performs a field-by-field mapping from ``GptpIpcData`` to ``PtpTimeInfo``: - -.. list-table:: GptpIpcData → PtpTimeInfo Mapping - :header-rows: 1 - :widths: 50 50 - - * - ``GptpIpcData`` field - - ``PtpTimeInfo`` field - * - ``ptp_assumed_time`` - - ``ptp_assumed_time`` - * - ``local_time`` - - ``local_time`` (wrapped in ``ReferenceClock::time_point``) - * - ``rate_deviation`` - - ``rate_deviation`` - * - ``status.is_synchronized`` - - ``status.is_synchronized`` - * - ``status.is_timeout`` - - ``status.is_timeout`` - * - ``status.is_time_jump_future`` - - ``status.is_time_jump_future`` - * - ``status.is_time_jump_past`` - - ``status.is_time_jump_past`` - * - ``status.is_correct`` - - ``status.is_correct`` - * - ``sync_fup_data.*`` (9 fields) - - ``sync_fup_data.*`` (direct copy) - * - ``pdelay_data.*`` (12 fields) - - ``pdelay_data.*`` (direct copy) - -Factory -''''''' - -``CreateGPTPShmMachine(name, ipc_name)`` is a convenience factory function in ``score::td`` that creates a configured ``GPTPShmMachine`` (``shared_ptr``) backed by ``ShmPTPEngine``: - -.. code-block:: cpp - - auto machine = CreateGPTPShmMachine("shm", "/gptp_ptp_info"); - -Using in test environment -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Using in ITF -^^^^^^^^^^^^ - -Normal behavior is expected. TimeSlave runs as a standalone process, communicates over real Ethernet, and writes to ``/gptp_ptp_info`` shared memory as in production. - -Using in Component Tests on the host -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Overview -'''''''' - -The ``TimeSlave`` and its constituent components can be tested on an x86 Linux host without PTP hardware or a real network. The key platform-dependent abstractions all have test-injectable counterparts: - -.. list-table:: Testable Abstractions - :header-rows: 1 - :widths: 30 35 35 - - * - Abstraction - - Production implementation - - Test replacement - * - ``RawSocket`` - - ``RawSocket`` (AF_PACKET) - - ``FakeSocket`` (push-based frame queue) - * - ``NetworkIdentity`` - - ``NetworkIdentity`` (ioctl) - - ``FakeIdentity`` (fixed clock identity) - * - ``HighPrecisionLocalSteadyClock`` - - Platform clock (Linux / QNX) - - ``FakeClock`` (returns fixed timestamp) - -The ``GptpEngine`` provides a dedicated test constructor that accepts injected implementations: - -.. code-block:: cpp - - GptpEngine engine(opts, - std::make_unique(), - std::make_unique()); - -This allows complete white-box testing of the Sync/FollowUp correlation, peer-delay measurement, timeout detection, and time-jump flagging logic by pushing crafted PTP frames directly into the ``FakeSocket`` queue. - -The ``GptpIpcPublisher`` and ``GptpIpcReceiver`` rely on POSIX shared memory (``shm_open``), which works on any Linux host, so ``ShmPTPEngine`` component tests can run end-to-end using real IPC without modification. diff --git a/docs/features/time_slave/_assets/gptp_engine/gptp_engine_class.puml b/score/time_slave/docs/detailed_design/_assets/gptp_engine/gptp_engine_class.puml similarity index 86% rename from docs/features/time_slave/_assets/gptp_engine/gptp_engine_class.puml rename to score/time_slave/docs/detailed_design/_assets/gptp_engine/gptp_engine_class.puml index 39209e23..ddb58bb0 100644 --- a/docs/features/time_slave/_assets/gptp_engine/gptp_engine_class.puml +++ b/score/time_slave/docs/detailed_design/_assets/gptp_engine/gptp_engine_class.puml @@ -1,3 +1,16 @@ +' ******************************************************************************* +' Copyright (c) 2026 Contributors to the Eclipse Foundation +' +' See the NOTICE file(s) distributed with this work for additional +' information regarding copyright ownership. +' +' This program and the accompanying materials are made available under the +' terms of the Apache License Version 2.0 which is available at +' https://www.apache.org/licenses/LICENSE-2.0 +' +' SPDX-License-Identifier: Apache-2.0 +' ******************************************************************************* + @startuml !theme plain diff --git a/docs/features/time_slave/_assets/gptp_engine/gptp_threading.puml b/score/time_slave/docs/detailed_design/_assets/gptp_engine/gptp_threading.puml similarity index 67% rename from docs/features/time_slave/_assets/gptp_engine/gptp_threading.puml rename to score/time_slave/docs/detailed_design/_assets/gptp_engine/gptp_threading.puml index 79ee3b2a..dd9e4d55 100644 --- a/docs/features/time_slave/_assets/gptp_engine/gptp_threading.puml +++ b/score/time_slave/docs/detailed_design/_assets/gptp_engine/gptp_threading.puml @@ -1,3 +1,16 @@ +' ******************************************************************************* +' Copyright (c) 2026 Contributors to the Eclipse Foundation +' +' See the NOTICE file(s) distributed with this work for additional +' information regarding copyright ownership. +' +' This program and the accompanying materials are made available under the +' terms of the Apache License Version 2.0 which is available at +' https://www.apache.org/licenses/LICENSE-2.0 +' +' SPDX-License-Identifier: Apache-2.0 +' ******************************************************************************* + @startuml gptp_threading_model title gPTP Engine Threading Model diff --git a/docs/features/time_slave/_assets/timeslave_class.puml b/score/time_slave/docs/detailed_design/_assets/timeslave_class.puml similarity index 86% rename from docs/features/time_slave/_assets/timeslave_class.puml rename to score/time_slave/docs/detailed_design/_assets/timeslave_class.puml index 6e2560ab..bc43227f 100644 --- a/docs/features/time_slave/_assets/timeslave_class.puml +++ b/score/time_slave/docs/detailed_design/_assets/timeslave_class.puml @@ -1,3 +1,16 @@ +' ******************************************************************************* +' Copyright (c) 2026 Contributors to the Eclipse Foundation +' +' See the NOTICE file(s) distributed with this work for additional +' information regarding copyright ownership. +' +' This program and the accompanying materials are made available under the +' terms of the Apache License Version 2.0 which is available at +' https://www.apache.org/licenses/LICENSE-2.0 +' +' SPDX-License-Identifier: Apache-2.0 +' ******************************************************************************* + @startuml !theme plain diff --git a/docs/features/time_slave/_assets/timeslave_data_flow.puml b/score/time_slave/docs/detailed_design/_assets/timeslave_data_flow.puml similarity index 77% rename from docs/features/time_slave/_assets/timeslave_data_flow.puml rename to score/time_slave/docs/detailed_design/_assets/timeslave_data_flow.puml index 3981db47..dd3ebbf2 100644 --- a/docs/features/time_slave/_assets/timeslave_data_flow.puml +++ b/score/time_slave/docs/detailed_design/_assets/timeslave_data_flow.puml @@ -1,3 +1,16 @@ +' ******************************************************************************* +' Copyright (c) 2026 Contributors to the Eclipse Foundation +' +' See the NOTICE file(s) distributed with this work for additional +' information regarding copyright ownership. +' +' This program and the accompanying materials are made available under the +' terms of the Apache License Version 2.0 which is available at +' https://www.apache.org/licenses/LICENSE-2.0 +' +' SPDX-License-Identifier: Apache-2.0 +' ******************************************************************************* + @startuml !theme plain diff --git a/docs/features/time_slave/_assets/timeslave_deployment.puml b/score/time_slave/docs/detailed_design/_assets/timeslave_deployment.puml similarity index 72% rename from docs/features/time_slave/_assets/timeslave_deployment.puml rename to score/time_slave/docs/detailed_design/_assets/timeslave_deployment.puml index 2e922ca9..2cd6c955 100644 --- a/docs/features/time_slave/_assets/timeslave_deployment.puml +++ b/score/time_slave/docs/detailed_design/_assets/timeslave_deployment.puml @@ -1,3 +1,16 @@ +' ******************************************************************************* +' Copyright (c) 2026 Contributors to the Eclipse Foundation +' +' See the NOTICE file(s) distributed with this work for additional +' information regarding copyright ownership. +' +' This program and the accompanying materials are made available under the +' terms of the Apache License Version 2.0 which is available at +' https://www.apache.org/licenses/LICENSE-2.0 +' +' SPDX-License-Identifier: Apache-2.0 +' ******************************************************************************* + @startuml !theme plain skinparam nodesep 40 diff --git a/score/time_slave/docs/detailed_design/detailed_design.rst b/score/time_slave/docs/detailed_design/detailed_design.rst deleted file mode 100644 index 0c8cfb1f..00000000 --- a/score/time_slave/docs/detailed_design/detailed_design.rst +++ /dev/null @@ -1,96 +0,0 @@ -.. - # ******************************************************************************* - # Copyright (c) 2026 Contributors to the Eclipse Foundation - # - # See the NOTICE file(s) distributed with this work for additional - # information regarding copyright ownership. - # - # This program and the accompanying materials are made available under the - # terms of the Apache License Version 2.0 which is available at - # https://www.apache.org/licenses/LICENSE-2.0 - # - # SPDX-License-Identifier: Apache-2.0 - # ******************************************************************************* - -.. _time_slave_detailed_design: - -Time Slave Detailed Design -=========================== - -.. document:: Time Slave Detailed Design - :id: doc__time_slave_detailed_design - :status: draft - :version: 1 - :safety: ASIL_B - :security: NO - :realizes: wp__sw_implementation - :tags: time_slave - -.. note:: - Work in progress: structure, titles, and needs IDs only. Content and req/comp/feat traceability links to follow in later PRs. - -.. attention:: - The above directive must be updated according to your Component. - - - Adjust ``status`` to be ``valid`` - - Adjust ``safety`` and ``tags`` according to your needs - -Detailed Design for Time Slave -=============================== - -Description ------------ - -| Design Decisions - For the documentation of the decision the :need:`gd_temp__change_decision_record` can be used. -| Design Constraints - -Example: - - - component is split into two units unit1 and unit2 based on single responsibility principle. - - unit2 is injected to unit1 one via dependency injection for testability. - -Rationale Behind Decomposition into Units -****************************************** -| mandatory: a motivation for the decomposition into one or more units. - -.. note:: Reason for split into multiple units could be- - - Based on design principles like SOLID,DRY etc - - Based on design pattern's etc. - -Static Diagrams for Unit Interactions -------------------------------------- - -A static view provides an overview of the units and their relationships using -UML 2.0 notations (e.g. class diagrams, component diagrams). Use ``.. uml::`` -or ``.. image::`` directives to include the diagram. - -.. .. uml:: dd_example_ex_sta.puml - -Dynamic Diagrams for Unit Interactions (optional) --------------------------------------------------- - -A dynamic view illustrates how the units within a component interact over their -interfaces to fulfill a specific use case or functionality. It is optional when the -component's behaviour is straightforward and can be understood from the static view -and interface documentation alone. - -Use standard UML behavioural diagrams (sequence diagrams, state machine diagrams) -with ``.. uml::`` or ``.. image::`` directives. - -.. .. uml:: dd_example_ex_dyn.puml - -Units within the Component --------------------------- - -The relationship between a unit and its parent component is established implicitly -through the file path. Each component has its own directory, and units residing -within that directory belong to it. The unit's attributes and behaviour are documented -in the source code itself. A separate static diagram per unit is not required. - -Interface documentation of a software unit is part of the source code (e.g. public -API headers, trait definitions, or documented function signatures). - -Example: - -- unit1: implements the main logic (see source code for details) -- unit2: injected into unit1 via dependency injection for testability diff --git a/score/time_slave/docs/detailed_design/index.rst b/score/time_slave/docs/detailed_design/index.rst index 71f2534d..fce3cb17 100644 --- a/score/time_slave/docs/detailed_design/index.rst +++ b/score/time_slave/docs/detailed_design/index.rst @@ -14,26 +14,670 @@ .. _time_slave_detailed_design_index: -Detailed Design -############### +Time Slave Detailed Design +========================== -.. note:: - Work in progress: page structure only, content to follow in later PRs. +.. document:: Time Slave Detailed Design + :id: doc__time_slave_detailed_design + :status: draft + :version: 1 + :safety: ASIL_B + :security: NO + :realizes: wp__sw_implementation + :tags: time_slave -.. attention:: +Description +----------- - The detailed design document is optional and should be created if the design of the component is complex and cannot be easily understood from the architecture documentation and interface documentation alone. - But the inspection checklist for the implementation is mandatory. +Use Cases +~~~~~~~~~ +TimeSlave is a standalone gPTP (IEEE 802.1AS) slave endpoint process that implements the low-level time synchronization protocol for the Eclipse SCORE time system. It is deployed as a separate process from the TimeDaemon to isolate real-time network I/O from the higher-level time validation and distribution logic. -Detail design example ---------------------- +More precisely we can specify the following use cases for the TimeSlave: -An example of documenting detailed design can be found in: +1. Receiving gPTP Sync/FollowUp messages from a Time Master on the Ethernet network +2. Measuring peer delay via the IEEE 802.1AS PDelayReq/PDelayResp exchange +3. Optionally adjusting the PTP Hardware Clock (PHC) on the NIC +4. Publishing the resulting ``GptpIpcData`` to shared memory for consumption by the TimeDaemon - .. toctree:: +The raw architectural diagram is represented below. - detailed_design +.. raw:: html + +
+ +.. uml:: _assets/timeslave_deployment.puml + :alt: Raw architectural diagram + +.. raw:: html + +
+ +Rationale Behind Decomposition into Units +----------------------------------------- + +TimeSlave is decomposed into seven implementation units following SOLID principles +(Single Responsibility, Open/Closed) and design patterns (Strategy, Facade): + +1. **TimeSlave Application** — Orchestrates the overall process lifecycle and periodic publish loop +2. **GptpEngine** — Core gPTP protocol engine managing the RxThread and PdelayThread for network I/O +3. **FrameCodec** — Encodes and decodes raw Ethernet frames for gPTP communication +4. **MessageParser** — Parses the PTP wire format (IEEE 1588-v2) from raw payload bytes +5. **SyncStateMachine** — Correlates Sync/FollowUp messages and computes clock offset and rate ratio +6. **PeerDelayMeasurer** — Implements the IEEE 802.1AS peer delay measurement protocol +7. **PhcAdjuster** — Synchronizes the PTP Hardware Clock on the NIC + +This separation enables independent testing, exchangeability of platform-specific implementations +(raw sockets, PHC drivers), and clear responsibility boundaries critical for ASIL_B safety qualification. + +TimeSlave publishes its ``GptpIpcData`` snapshot to shared memory using the ``GptpIpcPublisher`` +from the :ref:`ts_client ` component; the TimeDaemon-side +consumer (``ShmPTPEngine``) is documented in the :ref:`Time Daemon detailed design +`. + +Static Diagrams for Unit Interactions +------------------------------------- + +Class View +~~~~~~~~~~ + +Main classes and unit relationships are presented on this diagram: + +.. raw:: html + +
+ +.. uml:: _assets/timeslave_class.puml + :alt: Class View + :width: 100% + :align: center + +.. raw:: html + +
+ +Dynamic Diagrams for Unit Interactions +-------------------------------------- + +Data and Control Flow +~~~~~~~~~~~~~~~~~~~~~ + +The data and control flow between units is presented in the following diagram: + +.. raw:: html + +
+ +.. uml:: _assets/timeslave_data_flow.puml + :alt: Data and Control flow View + +.. raw:: html + +
+ +On this view you could see several "workers" scopes: + +1. RxThread scope +2. PdelayThread scope +3. Main thread (periodic publish) scope + +Each control flow is implemented with the dedicated thread and is independent from another ones. + +Control Flows +^^^^^^^^^^^^^ + +RxThread Scope +'''''''''''''' + +This control flow is responsible for the: + +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 + +PdelayThread Scope +'''''''''''''''''' + +This control flow is responsible for the: + +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 +'''''''''''''''''''''''''''''''''''' + +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)`` + +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 +''''''''''' + +``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. + +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 +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``GptpEngine`` has the following requirements: + +- 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) + +Class View +^^^^^^^^^^ + +The Class Diagram is presented below: + +.. raw:: html + +
+ +.. uml:: _assets/gptp_engine/gptp_engine_class.puml + :alt: Class Diagram + +.. raw:: html + +
+ +Threading Model +^^^^^^^^^^^^^^^ + +The GptpEngine operates with two background threads. The threading model is represented below: + +.. raw:: html + +
+ +.. uml:: _assets/gptp_engine/gptp_threading.puml + :alt: Threading Model + +.. raw:: html + +
+ +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 + +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: + +.. code-block:: none + + GptpEngine: HW timestamping not available on , falling back to SW timestamps + +The engine continues to run normally. The difference between the two modes: + +.. list-table:: + :header-rows: 1 + :widths: 30 35 35 + + * - Field + - HW timestamping available + - SW timestamping fallback + * - ``recvHardwareTS`` (Sync receive time) + - NIC hardware timestamp (nanosecond precision, captured at wire level) + - Software timestamp (captured at socket receive, higher jitter) + * - ``sync_fup_data.reference_local_timestamp`` + - Derived from NIC hardware timestamp + - Derived from software timestamp + * - ``GptpIpcData.local_time`` + - Always ``CLOCK_MONOTONIC`` (unaffected) + - Always ``CLOCK_MONOTONIC`` (unaffected) + * - Clock offset accuracy + - 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. + +Implementation Requirements +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +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. + +Timestamp Definitions +^^^^^^^^^^^^^^^^^^^^^ + +.. list-table:: Peer Delay Timestamps (IEEE 802.1AS) + :header-rows: 1 + :widths: 10 20 30 40 + + * - Symbol + - Message + - Captured by + - Meaning + * - ``t1`` + - PDelayReq (TX) + - Slave (PdelayThread) + - HW transmit timestamp of the PDelayReq frame leaving the slave NIC + * - ``t2`` + - PDelayResp (RX) + - Master → carried in PDelayResp body + - HW receive timestamp of the PDelayReq frame arriving at the master NIC + * - ``t3c`` + - PDelayRespFollowUp + - 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) + - Slave (RxThread) + - HW receive timestamp of the PDelayResp frame arriving at the slave NIC + +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 +- 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: + +- 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) + +Fallback Behavior When PHC Is Unavailable +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``PhcAdjuster`` degrades gracefully in two scenarios: + +1. **PHC disabled** (``PhcConfig.enabled = false``, the default): ``AdjustOffset()`` and ``AdjustFrequency()`` are no-ops. The gPTP protocol pipeline (Sync/FollowUp reception, peer-delay measurement, ``GptpIpcData`` publishing) is completely unaffected. The hardware clock is not touched. + +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. + + - **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. + +Platform Support +~~~~~~~~~~~~~~~~ + +TimeSlave supports two target platforms with platform-specific implementations selected at compile time via Bazel ``select()``: + +.. list-table:: Platform Implementations + :header-rows: 1 + :widths: 25 37 38 + + * - 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/``. + +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. + +Supported probe points (``ProbePoint`` enum): + +.. 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 + +Logging configuration +~~~~~~~~~~~~~~~~~~~~~ + +TimeSlave uses the following logging contexts: + +.. list-table:: Logging Contexts + :header-rows: 1 + :widths: 35 20 45 + + * - Component + - Context ID + - Comments + * - TimeSlave Application + - TSAP + - **T**\ ime\ **S**\ lave **App**\ lication lifecycle (Initialize / Run) + * - gPTP Engine (RxThread / PdelayThread) + - GTPS + - **GPTP** **SLAVE** engine — low-level protocol processing + +Variability +~~~~~~~~~~~ + +Configuration +^^^^^^^^^^^^^ + +The ``GptpEngineOptions`` struct provides all configurable parameters for the gPTP engine: + +.. list-table:: GptpEngine Configuration + :header-rows: 1 + :widths: 30 15 55 + + * - Parameter + - Type + - Description + * - ``iface_name`` + - string + - Network interface for gPTP frames (e.g., ``emac0``); default: ``"emac0"`` + * - ``pdelay_interval_ms`` + - int + - Interval between PDelayReq transmissions (ms); default: ``1000`` + * - ``pdelay_warmup_ms`` + - int + - Delay before the first PDelayReq is sent (ms); default: ``2000`` + * - ``sync_timeout_ms`` + - int + - Timeout for Sync message reception before declaring timeout state (ms); default: ``3300`` + * - ``jump_future_threshold_ns`` + - int64_t + - Threshold above which a positive clock offset is flagged as a forward time jump (ns); default: ``500 000 000`` + * - ``phc_config`` + - PhcConfig + - PHC hardware clock adjustment settings (see ``PhcConfig`` table below); disabled by default + +The ``PhcConfig`` struct (embedded in ``GptpEngineOptions``) contains: + +.. list-table:: PhcAdjuster Configuration + :header-rows: 1 + :widths: 30 15 55 + + * - Parameter + - Type + - Description + * - ``enabled`` + - bool + - Enable or disable PHC adjustment; default: ``false`` + * - ``device`` + - string + - PHC device identifier: ``/dev/ptp0`` on Linux, ``emac0`` on QNX + * - ``step_threshold_ns`` + - int64_t + - Offset threshold above which a step correction is applied instead of frequency slew (ns); default: ``100 000 000`` + +Scalability +^^^^^^^^^^^ + +The TimeSlave architecture supports the following extensibility points: + +Platform extensibility +'''''''''''''''''''''' + +1. New target platforms can be supported by implementing the ``RawSocket`` and ``NetworkIdentity`` interfaces under a new ``platform//`` directory and selecting the implementation via ``Bazel select()`` +2. The ``PhcAdjuster`` platform implementations (``clock_adjtime`` on Linux, EMAC ioctls on QNX) can be extended for additional hardware without changing protocol logic + +Protocol extensibility +'''''''''''''''''''''' + +1. The ``GptpEngine`` accepts injected ``RawSocket`` and ``NetworkIdentity`` dependencies, making it straightforward to test or replace individual platform abstractions +2. The shared memory IPC channel name is configurable (``GptpIpcPublisher::Init(name)``), allowing multiple gPTP instances per ECU if needed + +The ``GptpIpcPublisher`` used here, and the corresponding ``GptpIpcReceiver``/``ShmPTPEngine`` on the +consuming side, are documented in the :ref:`ts_client ` and +:ref:`Time Daemon ` detailed designs respectively. + +Using in Test Environment +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Using in ITF +^^^^^^^^^^^^ + +Normal behavior is expected. TimeSlave runs as a standalone process, communicates over real Ethernet, and writes to ``/gptp_ptp_info`` shared memory as in production. + +Using in Component Tests on Host +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Overview +'''''''' + +The ``TimeSlave`` and its constituent components can be tested on an x86 Linux host without PTP hardware or a real network. The key platform-dependent abstractions all have test-injectable counterparts: + +.. list-table:: Testable Abstractions + :header-rows: 1 + :widths: 30 35 35 + + * - Abstraction + - Production implementation + - Test replacement + * - ``RawSocket`` + - ``RawSocket`` (AF_PACKET) + - ``FakeSocket`` (push-based frame queue) + * - ``NetworkIdentity`` + - ``NetworkIdentity`` (ioctl) + - ``FakeIdentity`` (fixed clock identity) + * - ``HighPrecisionLocalSteadyClock`` + - Platform clock (Linux / QNX) + - ``FakeClock`` (returns fixed timestamp) + +The ``GptpEngine`` provides a dedicated test constructor that accepts injected implementations: + +.. code-block:: cpp + + GptpEngine engine(opts, + std::make_unique(), + std::make_unique()); + +This allows complete white-box testing of the Sync/FollowUp correlation, peer-delay measurement, timeout detection, and time-jump flagging logic by pushing crafted PTP frames directly into the ``FakeSocket`` queue. + +The ``GptpIpcPublisher`` and ``GptpIpcReceiver`` rely on POSIX shared memory (``shm_open``), which works on any Linux host, so ``ShmPTPEngine`` component tests can run end-to-end using real IPC without modification. Inspection Checklist --------------------