diff --git a/MODULE.bazel b/MODULE.bazel index ff91eeb5..1525b1a0 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -78,6 +78,11 @@ bazel_dep(name = "googletest", version = "1.17.0.bcr.2", dev_dependency = True) bazel_dep(name = "google_benchmark", version = "1.9.5", dev_dependency = True) bazel_dep(name = "score_baselibs", version = "0.2.4", dev_dependency = True) bazel_dep(name = "score_lifecycle_health", version = "0.1.0", dev_dependency = True) +git_override( + module_name = "score_lifecycle_health", + remote = "https://github.com/eclipse-score/lifecycle.git", + commit = "50ca5ca3d635ed69b11f9f348cc1d9ed6dbefa2a", +) # TODO: remove once inherited properly from `score_logging`. bazel_dep(name = "trlc", version = "0.0.0", dev_dependency = True) diff --git a/score/TimeDaemon/code/application/svt/BUILD b/score/TimeDaemon/code/application/svt/BUILD index a7695658..30043a75 100644 --- a/score/TimeDaemon/code/application/svt/BUILD +++ b/score/TimeDaemon/code/application/svt/BUILD @@ -42,7 +42,7 @@ load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER "svt_handler", False, [ - "//score/TimeDaemon/code/ptp_machine:stub_ptp_machine", + "//score/TimeDaemon/code/ptp_machine:shm_ptp_machine", ], ), ( diff --git a/score/TimeDaemon/code/application/svt/svt_handler.cpp b/score/TimeDaemon/code/application/svt/svt_handler.cpp index 000b59d9..545107fc 100644 --- a/score/TimeDaemon/code/application/svt/svt_handler.cpp +++ b/score/TimeDaemon/code/application/svt/svt_handler.cpp @@ -16,7 +16,7 @@ #include "score/TimeDaemon/code/ipc/svt/publisher/factory.h" #include "score/TimeDaemon/code/msg_broker/subscription.h" #include "score/TimeDaemon/code/msg_broker/topic.h" -#include "score/TimeDaemon/code/ptp_machine/stub/factory.h" +#include "score/TimeDaemon/code/ptp_machine/shm/factory.h" #include "score/TimeDaemon/code/verification_machine/svt/factory.h" #include "score/concurrency/interruptible_wait.h" #include "score/mw/log/logging.h" @@ -39,7 +39,7 @@ SvtHandler::SvtHandler() noexcept handler_status_{TimebaseHandler::Status::kIdle} { msg_broker_ = std::make_shared>(); - gptp_machine_ = CreateGPTPStubMachine("ptp_worker"); + gptp_machine_ = CreateGPTPShmMachine("ptp_worker"); verification_machine_ = CreateSvtVerificationMachine("time_verification_worker"); ipc_publisher_ = CreateSvtPublisher("svt_ipc_publisher"); ctrl_flow_divider_ = CreatePtpControlFlowDivider("ptp_control_flow_divider", std::chrono::milliseconds{250}); diff --git a/score/TimeDaemon/code/application/svt/svt_handler.h b/score/TimeDaemon/code/application/svt/svt_handler.h index d7e2581f..5247b5ec 100644 --- a/score/TimeDaemon/code/application/svt/svt_handler.h +++ b/score/TimeDaemon/code/application/svt/svt_handler.h @@ -18,7 +18,7 @@ #include "score/TimeDaemon/code/control_flow_divider/ptp/ptp_control_flow_divider.h" #include "score/TimeDaemon/code/ipc/svt/publisher/svt_publisher.h" #include "score/TimeDaemon/code/msg_broker/msg_broker.h" -#include "score/TimeDaemon/code/ptp_machine/stub/gptp_stub_machine.h" +#include "score/TimeDaemon/code/ptp_machine/shm/gptp_shm_machine.h" #include "score/TimeDaemon/code/verification_machine/svt/svt_verification_machine.h" #include @@ -72,7 +72,7 @@ class SvtHandler : public TimebaseHandler private: std::unique_ptr job_runner_; ///< Manages periodic jobs and tasks std::shared_ptr> msg_broker_; ///< Handles message communication - std::shared_ptr gptp_machine_; ///< Manages GPTP synchronization + std::shared_ptr gptp_machine_; ///< Manages GPTP synchronization std::shared_ptr verification_machine_; ///< Handles SVT verification std::shared_ptr ipc_publisher_; ///< Publishes SVT data via IPC std::shared_ptr ctrl_flow_divider_; ///< Divides PTP control flow diff --git a/score/TimeDaemon/code/ptp_machine/BUILD b/score/TimeDaemon/code/ptp_machine/BUILD index 6111572e..864bd10c 100644 --- a/score/TimeDaemon/code/ptp_machine/BUILD +++ b/score/TimeDaemon/code/ptp_machine/BUILD @@ -19,6 +19,12 @@ alias( visibility = ["//score/TimeDaemon:__subpackages__"], ) +alias( + name = "shm_ptp_machine", + actual = "//score/TimeDaemon/code/ptp_machine/shm:gptp_shm_machine", + visibility = ["//score/TimeDaemon:__subpackages__"], +) + cc_unit_test_suites_for_host_and_qnx( name = "unit_test_suite", test_suites_from_sub_packages = [ diff --git a/score/TimeSlave/code/gptp/details/BUILD b/score/TimeSlave/code/gptp/details/BUILD index 5c1f331b..b55730f2 100644 --- a/score/TimeSlave/code/gptp/details/BUILD +++ b/score/TimeSlave/code/gptp/details/BUILD @@ -67,6 +67,8 @@ cc_library( ":i_os_syscalls", ":i_raw_socket", ":ptp_types", + "//score/TimeSlave/code/common:logging_contexts", + "@score_baselibs//score/mw/log:frontend", ], ) diff --git a/score/TimeSlave/code/gptp/gptp_engine.cpp b/score/TimeSlave/code/gptp/gptp_engine.cpp index 782725c5..90a3c8a5 100644 --- a/score/TimeSlave/code/gptp/gptp_engine.cpp +++ b/score/TimeSlave/code/gptp/gptp_engine.cpp @@ -31,7 +31,7 @@ namespace details namespace { -constexpr int kRxTimeoutMs = 100; // poll timeout; keeps RxLoop responsive to shutdown +constexpr int kRxTimeoutMs = 500; // poll timeout; keeps RxLoop responsive to shutdown constexpr int kRxBufferSize = 2048; } // namespace @@ -69,13 +69,17 @@ GptpEngine::~GptpEngine() noexcept bool GptpEngine::Initialize() { + mw::log::LogInfo(kTimeSlaveAppContext) << "GptpEngine: Initializing on " << opts_.iface_name; if (running_.load(std::memory_order_acquire)) + { + mw::log::LogWarn(kTimeSlaveAppContext) << "GptpEngine::Initialize: Engine is already running"; return true; + } if (!identity_->Resolve(opts_.iface_name)) { score::mw::log::LogError(kTimeSlaveAppContext) - << "GptpEngine: failed to resolve ClockIdentity for " << opts_.iface_name; + << "GptpEngine::Initialize: Failed to resolve ClockIdentity"; return false; } @@ -84,14 +88,14 @@ bool GptpEngine::Initialize() if (!socket_->Open(opts_.iface_name)) { score::mw::log::LogError(kTimeSlaveAppContext) - << "GptpEngine: failed to open raw socket on " << opts_.iface_name; + << "GptpEngine::Initialize: Failed to open raw socket"; return false; } if (!socket_->EnableHwTimestamping()) { score::mw::log::LogWarn(kTimeSlaveAppContext) - << "GptpEngine: HW timestamping not available on " << opts_.iface_name << ", falling back to SW timestamps"; + << "GptpEngine::Initialize: HW timestamping not available, falling back to SW timestamps"; } running_.store(true, std::memory_order_release); @@ -104,7 +108,8 @@ bool GptpEngine::Initialize() } catch (const std::system_error& e) { - score::mw::log::LogError(kTimeSlaveAppContext) << "GptpEngine: failed to create RxThread: " << std::string_view{e.what()}; + score::mw::log::LogError(kTimeSlaveAppContext) + << "GptpEngine::Initialize: Failed to create RxThread: " << std::string_view{e.what()}; running_.store(false, std::memory_order_release); socket_->Close(); return false; @@ -116,12 +121,13 @@ bool GptpEngine::Initialize() } catch (const std::system_error& e) { - score::mw::log::LogError(kTimeSlaveAppContext) << "GptpEngine: failed to create PdelayThread: " << std::string_view{e.what()}; + score::mw::log::LogError(kTimeSlaveAppContext) + << "GptpEngine::Initialize: Failed to create PdelayThread: " << std::string_view{e.what()}; Deinitialize(); return false; } - score::mw::log::LogInfo(kTimeSlaveAppContext) << "GptpEngine initialized on " << opts_.iface_name; + score::mw::log::LogInfo(kTimeSlaveAppContext) << "GptpEngine initialized"; return true; } @@ -252,12 +258,14 @@ void GptpEngine::HandlePacket(const std::uint8_t* frame, int len, const ::timesp switch (msg.msgtype) { case kPtpMsgtypeSync: + mw::log::LogDebug(kGPtpMachineContext) << "Sync message received, hw_ts=" << hw_ts.ns << " ns"; msg.recvHardwareTS = hw_ts; msg.recvMonoNs = MonoNs(); sync_sm_.OnSync(msg); break; case kPtpMsgtypeFollowUp: + mw::log::LogDebug(kGPtpMachineContext) << "FollowUp message received, hw_ts=" << hw_ts.ns << " ns"; msg.parseMessageTs = TimestampToTmv(msg.follow_up.preciseOriginTimestamp); { auto result = sync_sm_.OnFollowUp(msg); @@ -280,6 +288,7 @@ void GptpEngine::HandlePacket(const std::uint8_t* frame, int len, const ::timesp break; case kPtpMsgtypePdelayResp: + mw::log::LogDebug(kGPtpMachineContext) << "PdelayResp message received, hw_ts=" << hw_ts.ns << " ns"; msg.recvHardwareTS = hw_ts; msg.parseMessageTs = TimestampToTmv(msg.pdelay_resp.requestReceiptTimestamp); if (pdelay_) @@ -287,12 +296,14 @@ void GptpEngine::HandlePacket(const std::uint8_t* frame, int len, const ::timesp break; case kPtpMsgtypePdelayRespFollowUp: + mw::log::LogDebug(kGPtpMachineContext) << "PdelayRespFollowUp message received, hw_ts=" << hw_ts.ns << " ns"; msg.parseMessageTs = TimestampToTmv(msg.pdelay_resp_fup.responseOriginReceiptTimestamp); if (pdelay_) pdelay_->OnResponseFollowUp(msg); break; default: + mw::log::LogDebug(kGPtpMachineContext) << "Unhandled message received: " << static_cast(msg.msgtype) << ", hw_ts=" << hw_ts.ns << " ns"; break; } } diff --git a/score/TimeSlave/code/gptp/platform/linux/raw_socket.cpp b/score/TimeSlave/code/gptp/platform/linux/raw_socket.cpp index 5a71e73d..9bad943e 100644 --- a/score/TimeSlave/code/gptp/platform/linux/raw_socket.cpp +++ b/score/TimeSlave/code/gptp/platform/linux/raw_socket.cpp @@ -12,7 +12,11 @@ ********************************************************************************/ #include "score/TimeSlave/code/gptp/details/raw_socket.h" +#include "score/TimeSlave/code/common/logging_contexts.h" +#include "score/mw/log/logging.h" + #include +#include #include #include #include @@ -67,9 +71,12 @@ bool RawSocket::Open(const std::string& iface) { Close(); - const int fd = sys_->socket_call(AF_PACKET, SOCK_RAW, htons(ETH_P_1588)); - if (fd < 0) + const int fd = sys_->socket_call(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); + if (fd < 0) { + score::mw::log::LogError(kTimeSlaveAppContext) + << "RawSocket::Open: Failed to create raw socket endpoint (errno=" << errno << ")"; return false; + } ::ifreq ifr{}; std::strncpy(ifr.ifr_name, iface.c_str(), IFNAMSIZ - 1); @@ -77,16 +84,20 @@ bool RawSocket::Open(const std::string& iface) if (sys_->ioctl_call(fd, SIOCGIFINDEX, &ifr) < 0) { sys_->close_call(fd); + score::mw::log::LogError(kTimeSlaveAppContext) + << "RawSocket::Open: Failed to manipulate raw socket endpoint (errno=" << errno << ")"; return false; } ::sockaddr_ll sa{}; sa.sll_family = AF_PACKET; - sa.sll_protocol = htons(ETH_P_1588); + sa.sll_protocol = htons(ETH_P_ALL); sa.sll_ifindex = ifr.ifr_ifindex; if (sys_->bind_call(fd, reinterpret_cast<::sockaddr*>(&sa), sizeof(sa)) < 0) { sys_->close_call(fd); + score::mw::log::LogError(kTimeSlaveAppContext) + << "RawSocket::Open: Failed to bind raw socket endpoint to interface " << iface << " (errno=" << errno << ")"; return false; } @@ -94,6 +105,46 @@ bool RawSocket::Open(const std::string& iface) (void)sys_->setsockopt_call( fd, SOL_SOCKET, SO_BINDTODEVICE, iface.c_str(), static_cast(iface.size())); + // Enable promiscuous mode so the NIC passes all frames (including PTP multicast) to the kernel. + ::packet_mreq mr{}; + mr.mr_ifindex = ifr.ifr_ifindex; + mr.mr_type = PACKET_MR_PROMISC; + if (sys_->setsockopt_call(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mr, sizeof(mr)) < 0) + { + score::mw::log::LogWarn(kTimeSlaveAppContext) + << "RawSocket::Open: Failed to set promiscuous mode (errno=" << errno << ")"; + } + + // BPF filter: pass only frames with EtherType 0x88F7 (PTP/gPTP). + // ETH_P_ALL is used for socket/bind because ETH_P_1588 misses VLAN-tagged PTP frames + // (outer EtherType is 0x8100, not 0x88F7). The BPF filter runs in-kernel before delivery + // to userspace, so non-PTP frames are dropped with zero overhead in the application. + static const ::sock_filter kPtpBpfCode[] = { + BPF_STMT(BPF_LD | BPF_H | BPF_ABS, 12), // load EtherType + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ETH_P_1588, 1, 0), // == 0x88F7 → PASS + BPF_STMT(BPF_RET | BPF_K, 0U), // FAIL: drop + BPF_STMT(BPF_RET | BPF_K, static_cast(-1)), // PASS: accept + }; + ::sock_fprog prog{}; + prog.len = static_cast(sizeof(kPtpBpfCode) / sizeof(kPtpBpfCode[0])); + prog.filter = const_cast<::sock_filter*>(kPtpBpfCode); + if (sys_->setsockopt_call(fd, SOL_SOCKET, SO_ATTACH_FILTER, &prog, sizeof(prog)) < 0) + { + score::mw::log::LogWarn(kTimeSlaveAppContext) + << "RawSocket::Open: Failed to set BPF filter (errno=" << errno << ")"; + } + + // Enable kernel layer software timestamps as a baseline, so we get at least some timestamp + // even if hw timestamping isn't available or fails to configure. The HW timestamp will come + // in the same control message but different field, so the caller can use it if present and + // fall back to SW timestamp if not. + const int enable = 1; + if (sys_->setsockopt_call(fd, SOL_SOCKET, SO_TIMESTAMPNS, &enable, sizeof(enable)) < 0) + { + score::mw::log::LogWarn(kTimeSlaveAppContext) + << "RawSocket::Open: Failed to enable SO_TIMESTAMPNS (errno=" << errno << ")"; + } + fd_.store(fd, std::memory_order_release); iface_ = iface; return true; @@ -173,7 +224,14 @@ int RawSocket::Recv(std::uint8_t* buf, std::size_t buf_len, ::timespec& hwts, in continue; const auto* ts = reinterpret_cast(CMSG_DATA(cm)); if (ts[2].tv_sec != 0 || ts[2].tv_nsec != 0) + { hwts = ts[2]; + break; // Prefer raw HW timestamp if available + } + } + else if (cm->cmsg_type == SO_TIMESTAMPNS) + { + memcpy(&hwts, CMSG_DATA(cm), sizeof(hwts)); } } return len; diff --git a/score/libTSClient/gptp_ipc_publisher.cpp b/score/libTSClient/gptp_ipc_publisher.cpp index 3d153ce8..5cecfb50 100644 --- a/score/libTSClient/gptp_ipc_publisher.cpp +++ b/score/libTSClient/gptp_ipc_publisher.cpp @@ -42,7 +42,7 @@ bool GptpIpcPublisher::Init(const std::string& ipc_name) (void)::shm_unlink(ipc_name_.c_str()); - shm_fd_ = ::shm_open(ipc_name_.c_str(), O_CREAT | O_RDWR, 0600); + shm_fd_ = ::shm_open(ipc_name_.c_str(), O_CREAT | O_RDWR, 0644); if (shm_fd_ < 0) return false;