Skip to content
Merged
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
136 changes: 136 additions & 0 deletions docs/TimeSlave/_assets/gptp_engine/gptp_engine_class.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
@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::details" {

class GptpEngine #LightSalmon {
- opts_ : GptpEngineOptions
- rx_thread_ : std::thread
- pdelay_thread_ : std::thread
- socket_ : unique_ptr<IRawSocket>
- identity_ : unique_ptr<INetworkIdentity>
- codec_ : FrameCodec
- parser_ : GptpMessageParser
- sync_sm_ : SyncStateMachine
- pdelay_ : PeerDelayMeasurer
- phc_adjuster_ : PhcAdjuster
- snapshot_mutex_ : std::mutex
- pending_snapshot_ : GptpIpcData
- current_snapshot_ : GptpIpcData
+ Initialize() : bool
+ Deinitialize() : bool
+ FinalizeSnapshot() : void
+ ReadPTPSnapshot(data : GptpIpcData&) : bool
}

interface IRawSocket #LightSkyBlue {
+ Open(iface) : bool
+ EnableHwTimestamping() : bool
+ Recv(buf, timeout_ms) : RecvResult
+ Send(buf, len) : 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
+ GetMac() : MacAddress
}

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

class FrameCodec #Wheat {
+ ParseEthernetHeader(buf) : EthernetHeader
+ AddEthernetHeader(buf, dst_mac, src_mac) : void
}

class GptpMessageParser #Wheat {
+ Parse(payload, len, msg_out) : bool
}

class SyncStateMachine #Wheat {
- timeout_ : atomic<bool>
+ OnSync(msg) : void
+ OnFollowUp(msg) : 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
}

class PhcAdjuster #Lavender {
- cfg_ : PhcConfig
- phc_fd_ : int
+ IsEnabled() : bool
+ AdjustOffset(offset_ns) : void
+ AdjustFrequency(rate_ratio) : void
}

struct PhcConfig #Lavender {
+ enabled : bool = false
+ device : string
+ step_threshold_ns : int64_t = 100000000
}

IRawSocket <|.. LinuxSocket
IRawSocket <|.. QnxSocket
INetworkIdentity <|.. NetworkIdentity
PhcAdjuster *-- PhcConfig

GptpEngine *-- IRawSocket
GptpEngine *-- INetworkIdentity
GptpEngine *-- FrameCodec
GptpEngine *-- GptpMessageParser
GptpEngine *-- SyncStateMachine
GptpEngine *-- PeerDelayMeasurer
GptpEngine *-- PhcAdjuster
}

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 *-- ProbeManager

@enduml
51 changes: 51 additions & 0 deletions docs/TimeSlave/_assets/gptp_engine/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
70 changes: 70 additions & 0 deletions docs/TimeSlave/_assets/libtsclient/ipc_channel.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
@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_fd_ : int
+ Init(name) : bool
+ Publish(data : GptpIpcData) : void
+ Destroy() : void
}
}

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

package "TimeDaemon Process" {
class GptpIpcReceiver #LightPink {
- region_ : const GptpIpcRegion*
- shm_fd_ : int
+ Init(name) : bool
+ Receive() : std::optional<GptpIpcData>
+ 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
52 changes: 52 additions & 0 deletions docs/TimeSlave/_assets/libtsclient/ipc_sequence.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
@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
Loading
Loading