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
56 changes: 8 additions & 48 deletions score/ts_client/docs/detailed_design/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,56 +67,16 @@ Main classes and their relationships:

</div>

Units within Time Sync Client
------------------------------

GptpIpcPublisher Unit
~~~~~~~~~~~~~~~~~~~~~

The ``GptpIpcPublisher`` component creates and manages the POSIX shared memory segment and writes ``GptpIpcData`` using the seqlock protocol.

Implementation Requirements
'''''''''''''''''''''''''''

The ``GptpIpcPublisher`` has the following requirements:

- The ``GptpIpcPublisher`` shall create a POSIX shared memory segment via ``shm_open()`` with ``O_CREAT`` flag
- The ``GptpIpcPublisher`` shall map the shared memory region as ``GptpIpcRegion`` aligned to 64 bytes
- The ``GptpIpcPublisher`` shall initialize the magic number field to ``0x47505450`` ('GPTP')
- The ``GptpIpcPublisher`` shall write ``GptpIpcData`` using the seqlock protocol:

1. Increment ``seq`` (becomes odd — signals write in progress)
2. Apply a release memory fence
3. ``memcpy`` the ``GptpIpcData`` payload
4. Store ``seq_confirm = seq + 1``
5. Increment ``seq`` (both ``seq`` and ``seq_confirm`` become even — signals write complete)

- The ``GptpIpcPublisher`` shall use the default shared memory name ``/gptp_ptp_info`` unless overridden
- The ``GptpIpcPublisher`` shall support ``Destroy()`` to unmap and unlink the shared memory segment

GptpIpcReceiver Unit
~~~~~~~~~~~~~~~~~~~~

The ``GptpIpcReceiver`` component opens the shared memory segment read-only and reads ``GptpIpcData`` with bounded retry on torn reads.

Implementation Requirements
'''''''''''''''''''''''''''

The ``GptpIpcReceiver`` has the following requirements:

- The ``GptpIpcReceiver`` shall open the POSIX shared memory segment via ``shm_open()`` with ``O_RDONLY`` flag
- The ``GptpIpcReceiver`` shall map the shared memory region as read-only (``PROT_READ``)
- The ``GptpIpcReceiver`` shall validate the magic number (``0x47505450``) on ``Init()``
- The ``GptpIpcReceiver`` shall read ``GptpIpcData`` using the seqlock protocol with up to 20 retries:
Units within the Component
--------------------------

1. Read ``seq1`` with acquire ordering (must be even, otherwise retry)
2. ``memcpy`` the ``GptpIpcData`` payload
3. Apply an acquire-release fence
4. Read ``seq_confirm`` as ``seq2`` and re-read ``seq`` as ``seq3``
5. If ``seq1 == seq2 == seq3``, the read is consistent; otherwise retry
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.

- The ``GptpIpcReceiver`` shall return ``std::optional<GptpIpcData>`` (empty if all retries exhausted)
- The ``GptpIpcReceiver`` shall support ``Close()`` to unmap the shared memory region
- **GptpIpcPublisher**: Creates and writes to shared memory using seqlock protocol (see ``src/gptp_ipc_publisher.h``)
- **GptpIpcReceiver**: Reads from shared memory with bounded retry on torn reads (see ``src/gptp_ipc_receiver.h``)

Seqlock Protocol Workflow
~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
3 changes: 3 additions & 0 deletions score/ts_client/src/gptp_ipc.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
#ifndef SCORE_TS_CLIENT_SRC_GPTP_IPC_H
#define SCORE_TS_CLIENT_SRC_GPTP_IPC_H

/// @file
/// @brief Umbrella include for gPTP IPC channel, publisher, and receiver types.

#include "score/ts_client/src/gptp_ipc_channel.h"
#include "score/ts_client/src/gptp_ipc_publisher.h"
#include "score/ts_client/src/gptp_ipc_receiver.h"
Expand Down
16 changes: 7 additions & 9 deletions score/ts_client/src/gptp_ipc_data.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@ namespace score
namespace ts
{

/**
* @brief IPC-layer status flags transmitted from TimeSlave to TimeDaemon.
*/
/// @brief IPC-layer status flags transmitted from TimeSlave to TimeDaemon.
struct GptpIpcStatus
{
bool is_synchronized;
Expand All @@ -33,6 +31,7 @@ struct GptpIpcStatus
bool is_correct;
};

/// @brief Sync/FollowUp snapshot from latest received gPTP sync cycle.
struct GptpIpcSyncFupData
{
std::uint64_t precise_origin_timestamp;
Expand All @@ -46,6 +45,7 @@ struct GptpIpcSyncFupData
std::uint64_t clock_identity;
};

/// @brief Peer-delay measurement snapshot from latest completed pDelay cycle.
struct GptpIpcPDelayData
{
std::uint64_t request_origin_timestamp;
Expand All @@ -62,12 +62,10 @@ struct GptpIpcPDelayData
std::uint64_t resp_clock_identity;
};

/**
* @brief IPC data snapshot written by TimeSlave and read by TimeDaemon.
*
* This type is internal to ts_client and intentionally decoupled from
* score::td::PtpTimeInfo. Callers are responsible for mapping between the two.
*/
/// @brief IPC data snapshot written by TimeSlave and read by TimeDaemon.
///
/// This type is internal to ts_client and intentionally decoupled from
/// score::td::PtpTimeInfo. Callers are responsible for mapping between the two.
struct GptpIpcData
{
std::chrono::nanoseconds ptp_assumed_time;
Expand Down
39 changes: 33 additions & 6 deletions score/ts_client/src/gptp_ipc_publisher.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,50 @@ namespace ts
namespace details
{

/**
* @brief Single-writer publisher for the gPTP IPC channel.
*
* Creates the POSIX shared memory segment and writes PtpTimeInfo using
* the seqlock protocol. Used by TimeSlave.
*/
/// @brief Single-writer publisher for the gPTP IPC shared memory channel.
///
/// Creates the POSIX shared memory segment and writes @c GptpIpcData using
/// the seqlock protocol. Used by TimeSlave to publish time synchronisation
/// snapshots to applications (consumed by @c ShmPTPEngine / @c GptpIpcReceiver
/// in TimeDaemon).
///
/// @see @c GptpIpcReceiver Multi-reader counterpart.
class GptpIpcPublisher final
{
public:
/// @brief Constructs unopened publisher instance.
GptpIpcPublisher() = default;

/// @brief Ensures shared memory cleanup via @c Close().
~GptpIpcPublisher();

GptpIpcPublisher(const GptpIpcPublisher&) = delete;
GptpIpcPublisher& operator=(const GptpIpcPublisher&) = delete;

/// @brief Creates and maps the POSIX shared memory segment.
///
/// Opens the segment via @c shm_open() with @c O_CREAT, maps it as a
/// @c GptpIpcRegion, and initialises the magic number to @c 0x47505450.
///
/// @param ipc_name Shared memory name. Default: @c kGptpIpcName (@c /gptp_ptp_info).
/// @return true if successful.
bool Open(const std::string& ipc_name = kGptpIpcName);

/// @brief Writes @c GptpIpcData using the 5-step seqlock write protocol.
///
/// 1. @c seq++ (becomes odd — signals write in progress to readers).
/// 2. Release memory fence.
/// 3. @c memcpy of payload.
/// 4. @c seq_confirm = seq + 1.
/// 5. @c seq++ (both counters even — write complete).
///
/// Dual seq counters detect torn reads: reader checks seq1 == seq2 == seq3
/// after memcpy proves no concurrent write.
///
/// @param data The @c GptpIpcData snapshot to publish.
void Publish(const score::ts::GptpIpcData& data);

/// @brief Unmaps and unlinks the shared memory segment.
void Close();

private:
Expand Down
37 changes: 31 additions & 6 deletions score/ts_client/src/gptp_ipc_receiver.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,48 @@ namespace ts
namespace details
{

/**
* @brief Multi-reader receiver for the gPTP IPC channel.
*
* Opens an existing POSIX shared memory segment (read-only) and reads
* PtpTimeInfo using the seqlock protocol. Used by ShmPTPEngine.
*/
/// @brief Multi-reader receiver for the gPTP IPC shared memory channel.
///
/// Opens an existing POSIX shared memory segment read-only and reads
/// @c GptpIpcData using the seqlock protocol with bounded retry on torn reads.
/// Used by @c ShmPTPEngine in TimeDaemon.
///
/// @see GptpIpcPublisher Single-writer counterpart.
class GptpIpcReceiver final
{
public:
/// @brief Constructs unopened receiver instance.
GptpIpcReceiver() = default;

/// @brief Ensures mapping teardown via @c Close().
~GptpIpcReceiver();

GptpIpcReceiver(const GptpIpcReceiver&) = delete;
GptpIpcReceiver& operator=(const GptpIpcReceiver&) = delete;

/// @brief Opens the existing POSIX shared memory segment read-only.
///
/// Opens via @c shm_open() with @c O_RDONLY, maps as read-only, and
/// validates the magic number (@c 0x47505450).
///
/// @param ipc_name Shared memory name. Default: @c kGptpIpcName (@c /gptp_ptp_info).
/// @return true if successful.
bool Open(const std::string& ipc_name = kGptpIpcName);

/// @brief Reads @c GptpIpcData using the seqlock read protocol (up to 20 retries).
///
/// Read sequence per attempt:
/// 1. Read @c seq1 (acquire — must be even; odd means write in progress, skip).
/// 2. @c memcpy of payload.
/// 3. Acquire-release fence.
/// 4. Read @c seq_confirm as @c seq2, re-read @c seq as @c seq3.
/// 5. Accept only if @c seq1 == @c seq2 == @c seq3; otherwise retry.
///
/// @return @c GptpIpcData if a consistent read was achieved within the retry
/// budget, @c std::nullopt if all retries detected a torn read.
std::optional<score::ts::GptpIpcData> Receive();

/// @brief Unmaps the shared memory region.
void Close();

private:
Expand Down
17 changes: 14 additions & 3 deletions score/ts_client/src/gptp_ipc_test_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,29 @@ namespace ts
namespace details
{

/// @brief Creates process-local unique shared-memory name for tests.
///
/// Name format: @c /gptp_ipc_ut_<pid>_<counter>.
inline std::string UniqueShmName()
{
static std::atomic<int> counter{0};
return "/gptp_ipc_ut_" + std::to_string(::getpid()) + "_" +
std::to_string(counter.fetch_add(1, std::memory_order_relaxed));
}

/// RAII helper: creates SHM via SharedMemoryFactory (same layout as GptpIpcPublisher)
/// so that GptpIpcReceiver can open it. Gives direct access to the region for
/// edge-case tests that need to corrupt seq/magic.
/// @brief RAII helper for manual shared-memory setup in tests.
///
/// Creates SHM via @c SharedMemoryFactory with @c GptpIpcRegion layout so
/// @c GptpIpcReceiver can open same region. Exposes raw region pointer for
/// edge-case tests that intentionally corrupt seqlock state or magic.
struct ManualShm
{
std::shared_ptr<score::memory::shared::ISharedMemoryResource> resource_;
GptpIpcRegion* region_{nullptr};
std::string name_;

/// @brief Creates shared memory region with provided name.
/// @param n POSIX shared-memory object name.
explicit ManualShm(const std::string& n) : name_{n}
{
score::memory::shared::SharedMemoryFactory::Remove(n);
Expand All @@ -57,16 +64,20 @@ struct ManualShm
score::memory::shared::permission::WorldWritable{});
}

/// @brief Releases shared memory resource and removes object.
~ManualShm()
{
resource_.reset();
score::memory::shared::SharedMemoryFactory::Remove(name_);
}

/// @brief Returns true when region and resource are initialized.
bool Valid() const
{
return resource_ != nullptr && region_ != nullptr;
}

/// @brief Returns mutable pointer to mapped IPC region.
GptpIpcRegion* Region()
{
return region_;
Expand Down
Loading