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
104 changes: 104 additions & 0 deletions docs/TimeSlave/_assets/gptp_engine_class.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
@startuml
!theme plain

title gPTP Engine Internal Class Diagram

legend top left
|= Color |= Description |
| <#LightSalmon> | gPTP Engine core |
| <#Wheat> | Protocol processing |
| <#Lavender> | PHC adjustment |
| <#LightSkyBlue> | Platform abstraction |
| <#Beige> | Instrumentation |
endlegend

package "score::ts::gptp" {

class GptpEngine #LightSalmon {
- options_ : GptpEngineOptions
- rx_thread_ : std::thread
- pdelay_thread_ : std::thread
- socket_ : std::unique_ptr<IRawSocket>
- codec_ : FrameCodec
- parser_ : MessageParser
- sync_sm_ : SyncStateMachine
- pdelay_ : PeerDelayMeasurer
- phc_ : PhcAdjuster
- probe_mgr_ : ProbeManager
- recorder_ : Recorder
- snapshot_mutex_ : std::mutex
- latest_snapshot_ : PtpTimeInfo
+ Initialize() : bool
+ Deinitialize() : void
+ ReadPTPSnapshot() : PtpTimeInfo
}

interface IRawSocket #LightSkyBlue {
+ Open(iface) : bool
+ EnableHwTimestamping() : bool
+ Recv(buf, timeout_ms) : RecvResult
+ Send(buf, hw_ts) : bool
+ GetFd() : int
+ Close() : void
}

class "RawSocket\n<<Linux>>" as LinuxSocket #LightSkyBlue {
AF_PACKET + SO_TIMESTAMPING
}

class "RawSocket\n<<QNX>>" as QnxSocket #LightSkyBlue {
QNX raw-socket shim
}

interface INetworkIdentity #LightSkyBlue {
+ Resolve(iface) : bool
+ GetClockIdentity() : ClockIdentity
}

class NetworkIdentity #LightSkyBlue {
Derives EUI-64 from MAC\n(inserts 0xFF 0xFE)
}

IRawSocket <|.. LinuxSocket
IRawSocket <|.. QnxSocket
INetworkIdentity <|.. NetworkIdentity

GptpEngine *-- IRawSocket
GptpEngine *-- INetworkIdentity
}

package "score::ts::gptp::details" {
class FrameCodec #Wheat
class MessageParser #Wheat
class SyncStateMachine #Wheat
class PeerDelayMeasurer #Wheat
}

package "score::ts::gptp::phc" {
class PhcAdjuster #Lavender
}

package "score::ts::gptp::instrument" {
class ProbeManager #Beige {
+ {static} Instance() : ProbeManager&
+ Record(point, data) : void
+ SetRecorder(recorder) : void
}

class Recorder #Beige {
- file_ : std::ofstream
- mutex_ : std::mutex
+ Record(entry) : void
}

ProbeManager --> Recorder
}

GptpEngine *-- FrameCodec
GptpEngine *-- MessageParser
GptpEngine *-- SyncStateMachine
GptpEngine *-- PeerDelayMeasurer
GptpEngine *-- PhcAdjuster
GptpEngine *-- ProbeManager

@enduml
51 changes: 51 additions & 0 deletions docs/TimeSlave/_assets/gptp_threading.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
@startuml gptp_threading_model

title gPTP Engine Threading Model

legend top left
|= Color |= Description |
| <#LightSalmon> | RxThread |
| <#LightSkyBlue> | PdelayThread |
| <#LightCyan> | Main Thread (TimeSlave) |
endlegend

|#LightCyan| Main Thread
start
:Initialize GptpEngine;
:Start RxThread;
:Start PdelayThread;

fork
|#LightSalmon| RxThread
repeat
:Wait for gPTP frame;
:Recv Sync frame;
:Parse + SyncStateMachine\nstore Sync timestamp;
:Recv FollowUp frame;
:Parse + SyncStateMachine\ncompute offset & rate ratio;
:Update latest_snapshot_\n(mutex protected);
repeat while (stop_token?)
stop

fork again
|#LightSkyBlue| PdelayThread
repeat
:Sleep(pdelay_interval_ms);
:Send PDelayReq;
:Recv PDelayResp;
:Recv PDelayRespFollowUp\ncompute path delay;
:Update PDelayResult;
repeat while (stop_token?)
stop

fork again
|#LightCyan| Main Thread
repeat
:ReadPTPSnapshot();
:Publish PtpTimeInfo\nvia GptpIpcPublisher;
repeat while (stop_token?)
stop

end fork

@enduml
52 changes: 52 additions & 0 deletions docs/TimeSlave/_assets/ipc_channel.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
@startuml
!theme plain

title libTSClient Shared Memory IPC

legend top left
|= Color |= Description |
| <#LightPink> | IPC components |
| <#LightCyan> | Shared memory region |
endlegend

package "TimeSlave Process" {
class GptpIpcPublisher #LightPink {
- region_ : GptpIpcRegion*
- fd_ : int
+ Init(name) : bool
+ Publish(info) : void
+ Destroy() : void
}
}

package "Shared Memory" {
class GptpIpcRegion <<aligned(64)>> #LightCyan {
+ magic : uint32_t = 0x47505440
+ seq : std::atomic<uint32_t>
+ data : PtpTimeInfo
--
64-byte aligned for\ncache line efficiency
}
}

package "TimeDaemon Process" {
class GptpIpcReceiver #LightPink {
- region_ : const GptpIpcRegion*
- fd_ : int
+ Init(name) : bool
+ Receive() : std::optional<PtpTimeInfo>
+ Close() : void
}
}

GptpIpcPublisher --> GptpIpcRegion : "shm_open(O_CREAT)\nmmap(PROT_WRITE)"
GptpIpcReceiver --> GptpIpcRegion : "shm_open(O_RDONLY)\nmmap(PROT_READ)"

note right of GptpIpcRegion
**Seqlock Protocol:**
Writer: seq++ → memcpy → seq++
Reader: read seq (even) → memcpy → check seq
Retry up to 20 times on torn read
end note

@enduml
46 changes: 46 additions & 0 deletions docs/TimeSlave/_assets/ipc_sequence.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
@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 = 0x47505440

...

RCV -> SHM : shm_open("/gptp_ptp_info", O_RDONLY)
RCV -> SHM : mmap(PROT_READ)
RCV -> SHM : verify magic == 0x47505440

== Publish (Writer Side) ==

PUB -> SHM : seq.fetch_add(1, release) // seq becomes odd
PUB -> SHM : memcpy(data, &info, sizeof)
PUB -> SHM : seq.fetch_add(1, release) // seq becomes even

== Receive (Reader Side) ==

loop up to 20 retries
RCV -> SHM : s1 = seq.load(acquire)
alt s1 is odd (write in progress)
RCV -> RCV : retry
else s1 is even
RCV -> SHM : memcpy(&local, data, sizeof)
RCV -> SHM : s2 = seq.load(acquire)
alt s1 == s2
RCV --> RCV : return PtpTimeInfo
else s1 != s2 (torn read)
RCV -> RCV : retry
end
end
end

@enduml
125 changes: 125 additions & 0 deletions docs/TimeSlave/_assets/timeslave_class.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
@startuml
!theme plain

title TimeSlave Class Diagram

legend top left
|= Color |= Description |
| <#LightCyan> | TimeSlave application |
| <#LightSalmon> | gPTP Engine core |
| <#Wheat> | Protocol processing |
| <#Lavender> | PHC adjustment |
| <#LightPink> | IPC components |
| <#Beige> | Data structures |
endlegend

package "score::ts" {

class TimeSlave #LightCyan {
- engine_ : GptpEngine
- publisher_ : GptpIpcPublisher
- clock_ : HighPrecisionLocalSteadyClock
+ Initialize() : score::cpp::expected<void>
+ Run(stop_token) : score::cpp::expected<void>
+ Deinitialize() : score::cpp::expected<void>
}

class GptpEngine #LightSalmon {
- options_ : GptpEngineOptions
- rx_thread_ : std::thread
- pdelay_thread_ : std::thread
- sync_sm_ : SyncStateMachine
- pdelay_ : PeerDelayMeasurer
- socket_ : IRawSocket
- codec_ : FrameCodec
- parser_ : MessageParser
- phc_ : PhcAdjuster
- snapshot_mutex_ : std::mutex
- latest_snapshot_ : PtpTimeInfo
+ Initialize() : bool
+ Deinitialize() : void
+ ReadPTPSnapshot() : PtpTimeInfo
- RxThreadFunc(stop_token) : void
- PdelayThreadFunc(stop_token) : void
}

struct GptpEngineOptions #Beige {
+ interface_name : std::string
+ pdelay_interval_ms : uint32_t
+ sync_timeout_ms : uint32_t
+ time_jump_forward_ns : int64_t
+ time_jump_backward_ns : int64_t
+ phc_config : PhcConfig
}

TimeSlave *-- GptpEngine
TimeSlave *-- "1" GptpIpcPublisher
}

package "score::ts::gptp::details" {
class FrameCodec #Wheat {
+ ParseEthernetHeader(buf) : EthernetHeader
+ AddEthernetHeader(buf, dst_mac, src_mac) : void
}

class MessageParser #Wheat {
+ Parse(payload, hw_ts) : std::optional<PTPMessage>
}

class SyncStateMachine #Wheat {
- last_sync_ : PTPMessage
- last_offset_ns_ : int64_t
- neighbor_rate_ratio_ : double
- timeout_ : std::atomic<bool>
+ OnSync(msg) : void
+ OnFollowUp(msg) : std::optional<SyncResult>
+ IsTimeout() : bool
+ GetNeighborRateRatio() : double
}

class PeerDelayMeasurer #Wheat {
- mutex_ : std::mutex
- result_ : PDelayResult
+ SendRequest(socket) : void
+ OnResponse(msg) : void
+ OnResponseFollowUp(msg) : void
+ GetResult() : PDelayResult
}

struct SyncResult #Beige {
+ master_ns : int64_t
+ offset_ns : int64_t
+ sync_fup_data : SyncFupData
+ time_jump_forward : bool
+ time_jump_backward : bool
}

struct PDelayResult #Beige {
+ path_delay_ns : int64_t
+ valid : bool
}
}

package "score::ts::gptp::phc" {
class PhcAdjuster #Lavender {
- config_ : PhcConfig
- fd_ : int
+ IsEnabled() : bool
+ AdjustOffset(offset_ns) : void
+ AdjustFrequency(ppb) : void
}

struct PhcConfig #Beige {
+ enabled : bool
+ device_path : std::string
+ step_threshold_ns : int64_t
}
}

GptpEngine *-- FrameCodec
GptpEngine *-- MessageParser
GptpEngine *-- SyncStateMachine
GptpEngine *-- PeerDelayMeasurer
GptpEngine *-- PhcAdjuster

@enduml
Loading
Loading