Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
328 changes: 53 additions & 275 deletions score/time_slave/docs/detailed_design/index.rst

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion score/time_slave/src/application/time_slave.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ namespace ts
* and publishes time data to shared memory.
*
* TimeSlave is the gPTP protocol endpoint. It runs GptpEngine internally
* (with RxThread + PdelayThread) and periodically writes PtpTimeInfo
* (with RxThread + PdelayThread) and periodically writes GptpIpcData
* to shared memory for consumption by TimeDaemon via ShmPTPEngine.
*/
class TimeSlave final : public score::mw::lifecycle::Application
Expand All @@ -44,7 +44,13 @@ class TimeSlave final : public score::mw::lifecycle::Application
TimeSlave& operator=(TimeSlave&&) & = delete;
TimeSlave& operator=(const TimeSlave&) & = delete;

/// @brief Initializes the gPTP engine and shared memory publisher.
/// @return 0 on success, non-zero on failure.
std::int32_t Initialize(const score::mw::lifecycle::ApplicationContext& context) override;

/// @brief Runs the main loop: finalizes gPTP snapshots and publishes to shared memory.
/// @param token Stop token for graceful shutdown.
/// @return 0 on success, non-zero on failure.
std::int32_t Run(const score::cpp::stop_token& token) override;

private:
Expand Down
13 changes: 13 additions & 0 deletions score/time_slave/src/gptp/details/clock_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,19 @@ namespace ts
namespace details
{

/// @brief Returns current @c CLOCK_MONOTONIC time in nanoseconds.
Comment thread
ryan-steel marked this conversation as resolved.
///
/// @details
/// `CLOCK_MONOTONIC` is a non-decreasing system uptime-style clock and is not
/// tied to wall-clock time.
/// - Linux: starts at an unspecified point (typically boot) and is not affected
/// by manual/NTP wall-clock adjustments; it may still be slightly slewed.
/// - QNX/POSIX systems: monotonic clock with an unspecified epoch, intended for
/// interval measurement and not affected by wall-clock set operations.
///
/// Use this value only for elapsed-time calculations, not for calendar time.
///
/// @return Nanoseconds since an unspecified epoch, or 0 on failure.
inline std::int64_t MonoNs() noexcept
{
::timespec ts{};
Expand Down
4 changes: 2 additions & 2 deletions score/time_slave/src/gptp/details/network_identity_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ namespace details
class NetworkIdentityImpl : public NetworkIdentity
{
public:
/// Resolve the ClockIdentity for @p iface_name.
/// @brief Resolve the ClockIdentity for @p iface_name.
/// @return true on success.
bool Resolve(const std::string& iface_name) override;

/// Return the resolved identity. Valid only after a successful Resolve().
/// @brief Return the resolved identity. Valid only after a successful Resolve().
ClockIdentity GetClockIdentity() const override
{
return identity_;
Expand Down
45 changes: 30 additions & 15 deletions score/time_slave/src/gptp/details/pdelay_measurer.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,33 +35,48 @@ struct PDelayResult
bool valid{false};
};

/**
* @brief Measures one-way peer delay using the IEEE 802.1AS Pdelay mechanism.
*
* Implements the IEEE 802.1AS two-step peer-delay measurement:
* path_delay = ((t2 − t1) + (t4 − t3c)) / 2
*
* Thread-safety: @c SendRequest() is called from the PdelayThread.
* @c OnResponse() / @c OnResponseFollowUp() / @c GetResult()
* are called from the RxThread. An internal mutex makes the
* class safe for this two-thread usage pattern.
*/
/// @brief Measures the one-way peer delay using the IEEE 802.1AS Pdelay mechanism.
///
/// Implements the IEEE 802.1AS two-step peer-delay measurement. The formula
/// applied by @c OnResponseFollowUp() when a complete cycle is received is:
///
/// path_delay = ((t2 − t1) + (t4 − t3c)) / 2
///
/// Thread-safety: @c SendRequest() is called from the PdelayThread.
/// @c OnResponse(), @c OnResponseFollowUp(), and @c GetResult()
/// are called from the RxThread. Mutex protects: @c seqnum_,
/// @c resp_count_, @c req_, @c resp_, @c resp_fup_, @c result_.
class PeerDelayMeasurer final
{
public:
/// @brief Construct a new PeerDelayMeasurer with the local ClockIdentity and gPTP domain.
///
/// @param local_identity The local ClockIdentity (used in Pdelay_Req).
/// @param domain The gPTP domain number (0–127).
explicit PeerDelayMeasurer(const ClockIdentity& local_identity, std::uint8_t domain = 0U) noexcept;
Comment thread
ryan-steel marked this conversation as resolved.

/// Build and transmit a Pdelay_Req frame. @p socket must be open.
/// @brief Build and transmit a Pdelay_Req frame. @p socket must be open.
///
/// Increments the sequence number, encodes the frame, transmits it via @c socket, and records @c t1 (transmit
/// hardware timestamp; used in path_delay formula when FollowUp arrives).
///
/// @return 0 on success, negative on error.
int SendRequest(RawSocket& socket);

/// Process an incoming Pdelay_Resp message.
/// @brief Process an incoming Pdelay_Resp message.
///
/// Called from the RxThread. Records @c t2 (the responder's receive
/// hardware timestamp) and the responder's identity for the current sequence.
void OnResponse(const PTPMessage& msg);

/// Process an incoming Pdelay_Resp_Follow_Up message; triggers computation.
/// @brief Process an incoming Pdelay_Resp_Follow_Up message; triggers path delay computation.
///
/// Called from the RxThread. Records @c t3c (the responder's transmit
/// correction) and computes the path delay using the IEEE 802.1AS formula
/// if @c t1, @c t2, and @c t4 are all available.
void OnResponseFollowUp(const PTPMessage& msg);

/// Return the latest computed measurement (or invalid if none yet).
/// @brief Returns the latest computed measurement (or invalid if none yet available).
PDelayResult GetResult() const;

private:
Expand Down
35 changes: 34 additions & 1 deletion score/time_slave/src/gptp/details/ptp_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,27 +40,40 @@ namespace details
{

// ─── EtherType constants ────────────────────────────────────────────────────
/// IEEE 1588 PTP EtherType.
constexpr std::uint16_t kEthP1588 = 0x88F7U;
/// IEEE 802.1Q VLAN tag EtherType.
constexpr std::uint16_t kEthP8021Q = 0x8100U;

// ─── MAC / buffer sizes ─────────────────────────────────────────────────────
/// Ethernet MAC address length in bytes.
constexpr std::size_t kMacAddrLen = 6U;
/// 802.1Q VLAN tag length in bytes.
constexpr std::size_t kVlanTagLen = 4U;

// ─── PTP message-type codes ─────────────────────────────────────────────────
/// PTP message type: Sync.
constexpr std::uint8_t kPtpMsgtypeSync = 0x0;
/// PTP message type: Pdelay_Req.
constexpr std::uint8_t kPtpMsgtypePdelayReq = 0x2;
/// PTP message type: Pdelay_Resp.
constexpr std::uint8_t kPtpMsgtypePdelayResp = 0x3;
/// PTP message type: Follow_Up.
constexpr std::uint8_t kPtpMsgtypeFollowUp = 0x8;
/// PTP message type: Pdelay_Resp_Follow_Up.
constexpr std::uint8_t kPtpMsgtypePdelayRespFollowUp = 0xA;

// ─── PTP header constants ────────────────────────────────────────────────────
/// IEEE 802.1AS transport-specific value (upper nibble of first header byte).
constexpr std::uint8_t kPtpTransportSpecific = (1U << 4U);
/// PTP protocol version (2 for IEEE 1588-2008 / 802.1AS).
constexpr std::uint8_t kPtpVersion = 2U;

/// Nanoseconds per second.
constexpr std::int64_t kNsPerSec = 1'000'000'000LL;

// ─── Control field ───────────────────────────────────────────────────────────
/// PTP control field values (IEEE 1588-2008 §13.3.2.10).
enum class ControlField : std::uint8_t
{
kSync = 0,
Expand All @@ -72,6 +85,7 @@ enum class ControlField : std::uint8_t
};

// ─── State machine states ────────────────────────────────────────────────────
/// Internal states for the Sync/Follow_Up correlation state machine.
enum class SyncState : std::uint8_t
{
kEmpty,
Expand All @@ -80,30 +94,35 @@ enum class SyncState : std::uint8_t
};

// ─── Time value type ─────────────────────────────────────────────────────────
/// Time value in nanoseconds (internal representation).
struct TmvT
{
std::int64_t ns{0};
};

// ─── PTP wire structures (all SCORE_TS_PACKED) ───────────────────────────────
/// IEEE 1588 ClockIdentity (8-byte EUI-64).
struct SCORE_TS_PACKED ClockIdentity
{
std::uint8_t id[8]{};
};

/// IEEE 1588 PortIdentity (ClockIdentity + port number).
struct SCORE_TS_PACKED PortIdentity
{
ClockIdentity clockIdentity;
std::uint16_t portNumber{0};
};

/// IEEE 1588 Timestamp (48-bit seconds + 32-bit nanoseconds).
struct SCORE_TS_PACKED Timestamp
{
std::uint16_t seconds_msb{0};
std::uint32_t seconds_lsb{0};
std::uint32_t nanoseconds{0};
};

/// IEEE 1588 PTP message header (common to all message types).
struct SCORE_TS_PACKED PTPHeader
{
std::uint8_t tsmt{0};
Expand All @@ -120,46 +139,54 @@ struct SCORE_TS_PACKED PTPHeader
std::int8_t logMessageInterval{0};
};

/// Sync message body.
struct SCORE_TS_PACKED SyncBody
{
PTPHeader ptpHdr{};
Timestamp originTimestamp{};
};

/// Follow_Up message body.
struct SCORE_TS_PACKED FollowUpBody
{
PTPHeader ptpHdr{};
Timestamp preciseOriginTimestamp{};
};

/// Pdelay_Req message body.
struct SCORE_TS_PACKED PdelayReqBody
{
PTPHeader ptpHdr{};
Timestamp requestReceiptTimestamp{};
PortIdentity reserved{};
};

/// Pdelay_Resp message body.
struct SCORE_TS_PACKED PdelayRespBody
{
PTPHeader ptpHdr{};
Timestamp requestReceiptTimestamp{}; ///< IEEE 802.1AS: t₂ — time the remote peer received our PdelayReq
Timestamp requestReceiptTimestamp{}; ///< IEEE 802.1AS: t₂ — time the remote peer received our PdelayReq.
PortIdentity requestingPortIdentity{};
};

/// Pdelay_Resp_Follow_Up message body.
struct SCORE_TS_PACKED PdelayRespFollowUpBody
{
PTPHeader ptpHdr{};
Timestamp responseOriginReceiptTimestamp{};
PortIdentity requestingPortIdentity{};
};

/// Raw message buffer (maximum Ethernet payload).
struct SCORE_TS_PACKED RawMessageData
{
std::uint8_t buffer[1500]{};
};

/// Parsed PTP message with metadata.
struct PTPMessage
{
/// Union of all PTP message types.
union SCORE_TS_PACKED
{
PTPHeader ptpHdr;
Expand All @@ -181,6 +208,8 @@ struct PTPMessage
static_assert(sizeof(PTPMessage) <= 1600, "PTPMessage too large");

// ─── Timestamp conversion helpers ────────────────────────────────────────────
/// @brief Convert PTP wire Timestamp to internal TmvT (nanoseconds since epoch).
/// @return TmvT with zero ns on overflow.
inline TmvT TimestampToTmv(const Timestamp& ts) noexcept
{
const std::uint64_t sec =
Expand All @@ -195,6 +224,8 @@ inline TmvT TimestampToTmv(const Timestamp& ts) noexcept
return TmvT{static_cast<std::int64_t>(total_ns)};
}

/// @brief Convert internal TmvT to PTP wire Timestamp.
/// @return Timestamp with all zeros if @p x is negative.
inline Timestamp TmvToTimestamp(const TmvT& x) noexcept
{
if (x.ns < 0)
Expand All @@ -208,11 +239,13 @@ inline Timestamp TmvToTimestamp(const TmvT& x) noexcept
return t;
}

/// @brief Convert PTP correctionField (scaled nanoseconds, 16.48 fixed-point) to TmvT.
inline TmvT CorrectionToTmv(std::int64_t corr) noexcept
{
return TmvT{corr / 65536LL};
}

/// @brief Convert ClockIdentity to uint64_t (host byte order).
inline std::uint64_t ClockIdentityToU64(const ClockIdentity& ci) noexcept
{
std::uint64_t v{0};
Expand Down
31 changes: 24 additions & 7 deletions score/time_slave/src/gptp/details/raw_socket.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,30 +25,47 @@ namespace ts
namespace details
{

/// Interface for a platform raw socket used by GptpEngine and PeerDelayMeasurer.
/// @brief Platform-agnostic raw socket interface used by @c GptpEngine and
/// @c PeerDelayMeasurer for gPTP frame transmission and reception.
///
/// Provides hardware timestamping support with automatic fallback to software
/// timestamps when the NIC does not support hardware timestamping.
class RawSocket
{
public:
virtual ~RawSocket() noexcept = default;

/// Open the socket bound to @p iface. Returns false on failure.
/// @brief Open the socket bound to @p iface.
///
/// @return false on failure.
virtual bool Open(const std::string& iface) = 0;

/// Configure hardware TX/RX timestamping. Returns false on failure.
/// @brief Configures hardware TX/RX timestamping on the NIC if supported.
///
/// @return false on failure or if hardware timestamping is unsupported.
virtual bool EnableHwTimestamping() = 0;

/// Close the socket and release the file descriptor.
/// @brief Close the socket and release the file descriptor.
virtual void Close() = 0;

/// Receive one frame.
/// @brief Receive one Ethernet frame with its hardware (or software fallback) timestamp.
///
/// @param buf Buffer for the received frame.
/// @param buf_len Buffer length in bytes.
/// @param hwts Output: hardware timestamp if available, otherwise software timestamp.
/// @param timeout_ms Receive timeout in milliseconds (0 = non-blocking).
/// @return Number of bytes received, 0 on timeout, -1 on error.
virtual int Recv(std::uint8_t* buf, std::size_t buf_len, ::timespec& hwts, int timeout_ms) = 0;

/// Send one frame.
/// @brief Send one Ethernet frame and capture its hardware transmit timestamp.
///
/// @param buf Frame buffer.
/// @param len Frame length in bytes.
/// @param hwts Output: hardware transmit timestamp if available, otherwise software timestamp.
/// @return Number of bytes sent, or -1 on error.
virtual int Send(const void* buf, int len, ::timespec& hwts) = 0;

/// Return the underlying file descriptor.
/// @brief Return the underlying file descriptor (for use with @c select / @c poll).
virtual int GetFd() const = 0;
};

Expand Down
11 changes: 9 additions & 2 deletions score/time_slave/src/gptp/details/raw_socket_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ namespace details
/**
* @brief Platform raw socket for Ethernet I/O with hardware timestamping.
*
* On Linux uses AF_PACKET / SO_TIMESTAMPING.
* On QNX uses the QNX raw-socket shim.
* Implements platform-specific raw socket operations:
* - Linux: AF_PACKET with SO_TIMESTAMPING for NIC-level timestamps
* - QNX: io-pkt raw-socket shim
*/
class RawSocketImpl : public RawSocket
{
Expand All @@ -52,6 +53,12 @@ class RawSocketImpl : public RawSocket
bool Open(const std::string& iface) override;

/// Configure hardware TX/RX timestamping on the already-opened socket.
///
/// On Linux, requests SO_TIMESTAMPING (NIC-level timestamps). If the NIC
/// does not support hardware timestamping, returns false and logs a warning.
/// The socket continues to work, populating hwts with software timestamps
/// (higher jitter but protocol correctness is unaffected).
///
/// Returns false on failure. A no-op on platforms that don't support it.
bool EnableHwTimestamping() override;

Expand Down
Loading
Loading