Skip to content
Draft
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
5 changes: 5 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion score/TimeDaemon/code/application/svt/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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",
],
),
(
Expand Down
4 changes: 2 additions & 2 deletions score/TimeDaemon/code/application/svt/svt_handler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -39,7 +39,7 @@ SvtHandler::SvtHandler() noexcept
handler_status_{TimebaseHandler::Status::kIdle}
{
msg_broker_ = std::make_shared<MessageBroker<PtpTimeInfo>>();
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});
Expand Down
4 changes: 2 additions & 2 deletions score/TimeDaemon/code/application/svt/svt_handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <memory>
Expand Down Expand Up @@ -72,7 +72,7 @@ class SvtHandler : public TimebaseHandler
private:
std::unique_ptr<JobRunner> job_runner_; ///< Manages periodic jobs and tasks
std::shared_ptr<MessageBroker<PtpTimeInfo>> msg_broker_; ///< Handles message communication
std::shared_ptr<GPTPStubMachine> gptp_machine_; ///< Manages GPTP synchronization
std::shared_ptr<GPTPShmMachine> gptp_machine_; ///< Manages GPTP synchronization
std::shared_ptr<SvtVerificationMachine> verification_machine_; ///< Handles SVT verification
std::shared_ptr<SvtPublisher> ipc_publisher_; ///< Publishes SVT data via IPC
std::shared_ptr<PtpControlFlowDivider> ctrl_flow_divider_; ///< Divides PTP control flow
Expand Down
6 changes: 6 additions & 0 deletions score/TimeDaemon/code/ptp_machine/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
2 changes: 2 additions & 0 deletions score/TimeSlave/code/gptp/details/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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",
],
)

Expand Down
25 changes: 18 additions & 7 deletions score/TimeSlave/code/gptp/gptp_engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}

Expand All @@ -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);
Expand All @@ -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;
Expand All @@ -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;
}

Expand Down Expand Up @@ -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);
Expand All @@ -280,19 +288,22 @@ 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_)
pdelay_->OnResponse(msg);
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<int>(msg.msgtype) << ", hw_ts=" << hw_ts.ns << " ns";
break;
}
}
Expand Down
64 changes: 61 additions & 3 deletions score/TimeSlave/code/gptp/platform/linux/raw_socket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <arpa/inet.h>
#include <linux/filter.h>
#include <linux/if_ether.h>
#include <linux/net_tstamp.h>
#include <linux/sockios.h>
Expand Down Expand Up @@ -67,33 +71,80 @@ 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);
ifr.ifr_name[IFNAMSIZ - 1] = '\0';
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;
}

// SO_BINDTODEVICE: best-effort, don't fail if it doesn't work
(void)sys_->setsockopt_call(
fd, SOL_SOCKET, SO_BINDTODEVICE, iface.c_str(), static_cast<socklen_t>(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<unsigned int>(-1)), // PASS: accept
};
::sock_fprog prog{};
prog.len = static_cast<unsigned short>(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;
Expand Down Expand Up @@ -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<const ::timespec*>(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;
Expand Down
2 changes: 1 addition & 1 deletion score/libTSClient/gptp_ipc_publisher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Loading