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 ``IRawSocket::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/libtsclient/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/libtsclient/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 ``IRawSocket`` and ``INetworkIdentity`` interfaces provide the abstraction boundary. Platform-specific source files are organized under ``score/TimeSlave/code/gptp/platform/linux/`` and ``score/TimeSlave/code/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 ``IRawSocket`` and ``INetworkIdentity`` 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 ``IRawSocket`` and ``INetworkIdentity`` 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 * - ``IRawSocket`` - ``RawSocket`` (AF_PACKET) - ``FakeSocket`` (push-based frame queue) * - ``INetworkIdentity`` - ``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(), 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.