* [RFC v1 0/4] introduce PTP protocol library and software relay example
@ 2026-04-28 1:01 Rajesh Kumar
2026-04-27 23:42 ` Stephen Hemminger
` (11 more replies)
0 siblings, 12 replies; 60+ messages in thread
From: Rajesh Kumar @ 2026-04-28 1:01 UTC (permalink / raw)
To: dev; +Cc: bruce.richardson, aman.deep.singh, rajesh3.kumar
This series introduces a new DPDK library (lib/ptp) for IEEE 1588-2019
PTP protocol packet processing and a companion example application
(ptp_tap_relay_sw) that demonstrates its usage.
Motivation
----------
Several DPDK applications need to classify and manipulate PTP packets
(e.g. ptpclient, ptp_tap_relay, custom Transparent Clocks). Today each
application re-implements its own PTP header parsing and correctionField
handling. A shared library avoids duplication and provides a tested,
standards-compliant foundation.
Library: lib/ptp
----------------
The library provides:
- PTP header structures (IEEE 1588-2019 common header, timestamp,
port identity)
- Packet classification: rte_ptp_classify() detects PTP over L2
(EtherType 0x88F7), VLAN-tagged L2, and UDP/IPv4 (ports 319/320)
- Header access: rte_ptp_hdr_get() returns a pointer to the PTP
header inside an mbuf
- Inline helpers: correctionField manipulation (48.16 fixed-point),
message type extraction, two-step flag check, timestamp conversion
- Debug: rte_ptp_msg_type_str() for human-readable message names
The API is experimental (targeted for 26.07). Dependencies are minimal:
mbuf and net libraries only.
Example: ptp_tap_relay_sw
-------------------------
A minimal PTP Transparent Clock relay between a DPDK-bound physical NIC
and a kernel TAP interface using software timestamps only. No patched
kernel modules, custom TAP PMD, or hardware timestamp support is
required.
The relay:
1. Receives packets on the physical NIC via DPDK
2. Classifies PTP packets using rte_ptp_classify()
3. For event messages, records software timestamps
(CLOCK_MONOTONIC_RAW) at ingress and egress
4. Adds residence time to correctionField via rte_ptp_add_correction()
(IEEE 1588-2019 §10.2 Transparent Clock)
5. Forwards bidirectionally: PHY <-> TAP
A two-pass design takes the TX timestamp as close to rte_eth_tx_burst()
as possible, minimising untracked delay.
Test Results
------------
Test setup: Intel E610 10GbE NIC (vfio-pci), ptp4l master with HW
timestamps on physical port, ptp4l slave with SW timestamps (-S) on TAP
interface, logSyncInterval=-4 (16 Sync/sec), 120s duration.
Post-convergence (60s of locked data):
Average RMS offset: 929 ns
Worst max offset: 11615 ns (single transient)
Steady-state range: 489 - 1055 ns RMS
Corrections applied: 3635
linuxptp's PI servo uses relaxed gains for software timestamps
(kp=0.1, ki=0.001 vs kp=0.7, ki=0.3 for HW), expecting ~5-50 µs
jitter. Sub-microsecond RMS is achieved here because the master uses
HW timestamps and DPDK's poll-mode relay provides deterministic
low-latency forwarding.
Rajesh Kumar (4):
ptp: introduce PTP protocol library
doc: add PTP library programmer's guide
examples/ptp_tap_relay_sw: add software PTP relay example
doc: add PTP software relay sample app guide
MAINTAINERS | 7 +
doc/api/doxy-api-index.md | 1 +
doc/api/doxy-api.conf.in | 1 +
doc/guides/prog_guide/index.rst | 1 +
doc/guides/prog_guide/ptp_lib.rst | 195 ++++++++
doc/guides/sample_app_ug/index.rst | 1 +
doc/guides/sample_app_ug/ptp_tap_relay_sw.rst | 210 +++++++++
examples/meson.build | 1 +
examples/ptp_tap_relay_sw/Makefile | 41 ++
examples/ptp_tap_relay_sw/meson.build | 14 +
examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c | 424 ++++++++++++++++++
lib/meson.build | 1 +
lib/ptp/meson.build | 6 +
lib/ptp/rte_ptp.c | 195 ++++++++
lib/ptp/rte_ptp.h | 328 ++++++++++++++
15 files changed, 1426 insertions(+)
create mode 100644 doc/guides/prog_guide/ptp_lib.rst
create mode 100644 doc/guides/sample_app_ug/ptp_tap_relay_sw.rst
create mode 100644 examples/ptp_tap_relay_sw/Makefile
create mode 100644 examples/ptp_tap_relay_sw/meson.build
create mode 100644 examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c
create mode 100644 lib/ptp/meson.build
create mode 100644 lib/ptp/rte_ptp.c
create mode 100644 lib/ptp/rte_ptp.h
--
2.52.0
^ permalink raw reply [flat|nested] 60+ messages in thread* Re: [RFC v1 0/4] introduce PTP protocol library and software relay example 2026-04-28 1:01 [RFC v1 0/4] introduce PTP protocol library and software relay example Rajesh Kumar @ 2026-04-27 23:42 ` Stephen Hemminger 2026-04-28 16:52 ` Kumar, Rajesh 2026-04-28 1:01 ` [RFC v1 1/4] ptp: introduce PTP protocol library Rajesh Kumar ` (10 subsequent siblings) 11 siblings, 1 reply; 60+ messages in thread From: Stephen Hemminger @ 2026-04-27 23:42 UTC (permalink / raw) To: Rajesh Kumar; +Cc: dev, bruce.richardson, aman.deep.singh On Tue, 28 Apr 2026 06:31:02 +0530 Rajesh Kumar <rajesh3.kumar@intel.com> wrote: > This series introduces a new DPDK library (lib/ptp) for IEEE 1588-2019 > PTP protocol packet processing and a companion example application > (ptp_tap_relay_sw) that demonstrates its usage. > > Motivation > ---------- > Several DPDK applications need to classify and manipulate PTP packets > (e.g. ptpclient, ptp_tap_relay, custom Transparent Clocks). Today each > application re-implements its own PTP header parsing and correctionField > handling. A shared library avoids duplication and provides a tested, > standards-compliant foundation. > > Library: lib/ptp > ---------------- > The library provides: > - PTP header structures (IEEE 1588-2019 common header, timestamp, > port identity) > - Packet classification: rte_ptp_classify() detects PTP over L2 > (EtherType 0x88F7), VLAN-tagged L2, and UDP/IPv4 (ports 319/320) > - Header access: rte_ptp_hdr_get() returns a pointer to the PTP > header inside an mbuf > - Inline helpers: correctionField manipulation (48.16 fixed-point), > message type extraction, two-step flag check, timestamp conversion > - Debug: rte_ptp_msg_type_str() for human-readable message names The things I noticed were: Do not use older PTP terminology master/slave; we fixed this in DPDK. This would cause a NAK for me, and checkpatch would flag it. AI had lots more to say... Subject: Re: [RFC v1 0/4] introduce PTP protocol library and software relay example Some review comments on this RFC. Overall direction (shared PTP header+classification library) is good, but there are a couple of correctness bugs in patch 1/4 that need to be fixed before this can move out of RFC. Patch 1/4: ptp: introduce PTP protocol library ============================================== Error: flag bit positions wrong for the host-order representation ----------------------------------------------------------------- +#define RTE_PTP_FLAG_TWO_STEP (1 << 1) /**< Two-step flag (bit 1) */ +#define RTE_PTP_FLAG_UNICAST (1 << 2) /**< Unicast flag */ +#define RTE_PTP_FLAG_LI_61 (1 << 0) /**< Leap indicator 61 */ +#define RTE_PTP_FLAG_LI_59 (1 << 1) /**< Leap indicator 59 (byte 1) */ +static inline bool +rte_ptp_is_two_step(const struct rte_ptp_hdr *hdr) +{ + return (rte_be_to_cpu_16(hdr->flags) & RTE_PTP_FLAG_TWO_STEP) != 0; +} twoStepFlag and unicastFlag live in byte 0 of the wire flag field (IEEE 1588-2019 Table 37). leap61/leap59 live in byte 1. After rte_be_to_cpu_16(), byte 0 of the wire ends up in the upper 8 bits of the host value, so: TWO_STEP needs to be (1 << 9), not (1 << 1) UNICAST needs to be (1 << 10), not (1 << 2) LI_61 stays at (1 << 0) LI_59 stays at (1 << 1) As written, rte_ptp_is_two_step() always returns false for any real two-step Sync from the wire, which silently breaks any Transparent Clock or Boundary Clock built on this library. TWO_STEP and LI_59 also collide on (1 << 1), which by itself shows the macro layout isn't right. Worth adding a unit test that constructs a packet with twoStepFlag set and checks that the helper returns true. Error: QinQ classification path is unreachable for standard QinQ ---------------------------------------------------------------- In ptp_hdr_find(), the VLAN block is only entered when the outer EtherType is RTE_ETHER_TYPE_VLAN (0x8100): + if (ether_type == RTE_ETHER_TYPE_VLAN) { ... + /* Double-tagged VLAN (QinQ) */ + if (ether_type == RTE_ETHER_TYPE_QINQ || + ether_type == RTE_ETHER_TYPE_VLAN) { Standard 802.1ad QinQ uses outer S-tag 0x88A8 (RTE_ETHER_TYPE_QINQ) followed by a C-tag 0x8100, then the payload EtherType. Packets with the typical S-tag outer never enter the VLAN branch at all, so the "QinQ supported" claim in the cover letter and the prog_guide does not match what the code does. Either accept 0x88A8 as an entry point to the VLAN parser, or drop the QinQ claim from the documentation. Warning: VLAN-tagged PTP-over-UDP not handled --------------------------------------------- The classifier handles plain L2, VLAN-L2, IPv4-UDP, and IPv6-UDP, but there is no path for VLAN -> IPv4/IPv6 -> UDP -> PTP. This is a common deployment pattern. Not fatal, but please mention it in the limitations list if it's intentional. Warning: signed left shift in rte_ptp_add_correction() ------------------------------------------------------ + int64_t cf = (int64_t)rte_be_to_cpu_64(hdr->correction); + cf += (residence_ns << 16); residence_ns is int64_t. Left-shift of a signed value where the shifted value is not representable in the result type is undefined behaviour (C11 6.5.7p4). In the relay example residence_ns is computed from (uint64_t)tx_ts - (uint64_t)rx_ts cast to int64_t, so clock noise or a tx_ts < rx_ts edge case turns this negative. GCC and clang happen to produce arithmetic shift, but please make it well-defined: cf += (int64_t)((uint64_t)residence_ns << 16); or just multiply by (1LL << 16). Same issue, lower severity, in rte_ptp_correction_ns(): + return (int64_t)rte_be_to_cpu_64(hdr->correction) >> 16; Right-shift of a negative signed value is implementation-defined (not UB), but a divide expresses intent more clearly. Info: include order ------------------- +#include "rte_ptp.h" + +#include <eal_export.h> eal_export.h is conventionally included with the other system / EAL headers, before the local rte_ptp.h. Info: overlap with examples/ptpclient ------------------------------------- ptpclient.c defines its own struct ptp_header / ptp_message. Since this series adds a shared library, it would be worth converting ptpclient as part of the series (or in a follow-up) to avoid duplicating the same definitions in-tree. Patch 2/4: doc: add PTP library programmer's guide ================================================== Just the QinQ wording carryover - prog_guide claims "Double VLAN (QinQ) + EtherType 0x88F7" but the code as posted only handles 0x8100/0x8100 nesting. Update either the code or the doc to match. Patch 3/4: examples/ptp_tap_relay_sw ==================================== Warning: promiscuous enable failure on PHY port is not fatal ------------------------------------------------------------ + ret = rte_eth_promiscuous_enable(port); + if (ret != 0) + LOG_ERR("Failed to enable promiscuous on port %u", port); + + return 0; For the PHY port this is what lets the relay see the PTP multicast group (01:1B:19:00:00:00 / 01:80:C2:00:00:0E). If it fails, the relay starts up looking healthy but never receives anything. At least exit on failure for the PHY port, or enable allmulticast explicitly so the failure mode is visible. Info: per-burst single timestamp -------------------------------- A single rx_ts and tx_ts is taken for the whole burst. At logSyncInterval=-4 (16 packets/sec) bursts are essentially always 1 packet so this doesn't matter, but at higher rates the early packets in a burst get under-corrected and the late ones over-corrected by up to a burst's worth of poll latency. Worth calling out in the comment block; the test results already imply single-packet bursts. Info: missing release notes for new library ------------------------------------------- Targeted at 26.07 per RTE_EXPORT_EXPERIMENTAL_SYMBOL. A new library should add an entry under "New Features" in doc/guides/rel_notes/release_26_07.rst. ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [RFC v1 0/4] introduce PTP protocol library and software relay example 2026-04-27 23:42 ` Stephen Hemminger @ 2026-04-28 16:52 ` Kumar, Rajesh 0 siblings, 0 replies; 60+ messages in thread From: Kumar, Rajesh @ 2026-04-28 16:52 UTC (permalink / raw) To: Stephen Hemminger; +Cc: dev, bruce.richardson, aman.deep.singh On 28-04-2026 05:12 am, Stephen Hemminger wrote: > things I noticed were: > Do not use older PTP terminology master/slave; we fixed this in DPDK. > This would cause a NAK for me, and checkpatch would flag it Fixed. ^ permalink raw reply [flat|nested] 60+ messages in thread
* [RFC v1 1/4] ptp: introduce PTP protocol library 2026-04-28 1:01 [RFC v1 0/4] introduce PTP protocol library and software relay example Rajesh Kumar 2026-04-27 23:42 ` Stephen Hemminger @ 2026-04-28 1:01 ` Rajesh Kumar 2026-04-27 23:01 ` Stephen Hemminger 2026-04-28 1:01 ` [RFC v1 2/4] doc: add PTP library programmer's guide Rajesh Kumar ` (9 subsequent siblings) 11 siblings, 1 reply; 60+ messages in thread From: Rajesh Kumar @ 2026-04-28 1:01 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, rajesh3.kumar Add IEEE 1588-2019 Precision Time Protocol (PTP) processing library. The library provides packet classification, header structures, and helper functions for PTP packet processing in DPDK. This avoids duplicate PTP header definitions across multiple applications and drivers. Supported transports: 1. L2 PTP (EtherType 0x88F7) 2. VLAN-tagged L2 PTP (single and QinQ) 3. PTP over UDP/IPv4 (ports 319/320) 4. PTP over UDP/IPv6 (ports 319/320) Public APIs: 1. rte_ptp_classify() 2. rte_ptp_hdr_get() 3. rte_ptp_msg_type_str() Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- MAINTAINERS | 5 + lib/meson.build | 1 + lib/ptp/meson.build | 6 + lib/ptp/rte_ptp.c | 195 ++++++++++++++++++++++++++ lib/ptp/rte_ptp.h | 328 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 535 insertions(+) create mode 100644 lib/ptp/meson.build create mode 100644 lib/ptp/rte_ptp.c create mode 100644 lib/ptp/rte_ptp.h diff --git a/MAINTAINERS b/MAINTAINERS index 0f5539f851..f2b47acfc3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1665,6 +1665,11 @@ F: doc/guides/prog_guide/ipsec_lib.rst M: Vladimir Medvedkin <vladimir.medvedkin@intel.com> F: app/test-sad/ +PTP - EXPERIMENTAL +M: Rajesh Kumar <rajesh3.kumar@intel.com> +F: lib/ptp/ +F: doc/guides/prog_guide/ptp_lib.rst + PDCP - EXPERIMENTAL M: Anoob Joseph <anoobj@marvell.com> M: Volodymyr Fialko <vfialko@marvell.com> diff --git a/lib/meson.build b/lib/meson.build index 8f5cfd28a5..78d694f951 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -50,6 +50,7 @@ libraries = [ 'member', 'pcapng', 'power', + 'ptp', 'rawdev', 'regexdev', 'mldev', diff --git a/lib/ptp/meson.build b/lib/ptp/meson.build new file mode 100644 index 0000000000..05f9d87cbe --- /dev/null +++ b/lib/ptp/meson.build @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2026 Intel Corporation + +sources = files('rte_ptp.c') +headers = files('rte_ptp.h') +deps += ['mbuf', 'net'] diff --git a/lib/ptp/rte_ptp.c b/lib/ptp/rte_ptp.c new file mode 100644 index 0000000000..cc851c5080 --- /dev/null +++ b/lib/ptp/rte_ptp.c @@ -0,0 +1,195 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2026 Intel Corporation + * + * IEEE 1588 / PTP Protocol Library — Implementation + */ + +#include <rte_ether.h> +#include <rte_ip.h> +#include <rte_udp.h> + +#include "rte_ptp.h" + +#include <eal_export.h> + +/* + * Internal: find PTP header offset within a packet. + * Returns pointer to PTP header or NULL. + */ +static struct rte_ptp_hdr * +ptp_hdr_find(const struct rte_mbuf *m) +{ + const struct rte_ether_hdr *eth; + uint16_t ether_type; + const uint8_t *p; + uint32_t offset; + + if (rte_pktmbuf_data_len(m) < sizeof(struct rte_ether_hdr)) + return NULL; + + eth = rte_pktmbuf_mtod(m, const struct rte_ether_hdr *); + ether_type = rte_be_to_cpu_16(eth->ether_type); + offset = sizeof(struct rte_ether_hdr); + + /* L2 PTP: EtherType 0x88F7 */ + if (ether_type == RTE_PTP_ETHERTYPE) { + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ptp_hdr)) + return NULL; + p = (const uint8_t *)eth + offset; + return (struct rte_ptp_hdr *)(uintptr_t)p; + } + + /* VLAN-tagged L2 PTP */ + if (ether_type == RTE_ETHER_TYPE_VLAN) { + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_vlan_hdr)) + return NULL; + const struct rte_vlan_hdr *vlan = + (const struct rte_vlan_hdr *)((const uint8_t *)eth + offset); + ether_type = rte_be_to_cpu_16(vlan->eth_proto); + offset += sizeof(struct rte_vlan_hdr); + + if (ether_type == RTE_PTP_ETHERTYPE) { + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ptp_hdr)) + return NULL; + p = (const uint8_t *)eth + offset; + return (struct rte_ptp_hdr *)(uintptr_t)p; + } + + /* Double-tagged VLAN (QinQ) */ + if (ether_type == RTE_ETHER_TYPE_QINQ || + ether_type == RTE_ETHER_TYPE_VLAN) { + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_vlan_hdr)) + return NULL; + vlan = (const struct rte_vlan_hdr *) + ((const uint8_t *)eth + offset); + ether_type = rte_be_to_cpu_16(vlan->eth_proto); + offset += sizeof(struct rte_vlan_hdr); + + if (ether_type == RTE_PTP_ETHERTYPE) { + if (rte_pktmbuf_data_len(m) < + offset + sizeof(struct rte_ptp_hdr)) + return NULL; + p = (const uint8_t *)eth + offset; + return (struct rte_ptp_hdr *)(uintptr_t)p; + } + } + } + + /* PTP over UDP/IPv4 */ + if (ether_type == RTE_ETHER_TYPE_IPV4) { + const struct rte_ipv4_hdr *iph; + uint16_t ihl; + + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ipv4_hdr)) + return NULL; + + iph = (const struct rte_ipv4_hdr *)((const uint8_t *)eth + offset); + if (iph->next_proto_id != IPPROTO_UDP) + return NULL; + + ihl = (iph->version_ihl & 0x0F) * 4; + offset += ihl; + + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_udp_hdr)) + return NULL; + + const struct rte_udp_hdr *udp = + (const struct rte_udp_hdr *)((const uint8_t *)eth + offset); + uint16_t dst_port = rte_be_to_cpu_16(udp->dst_port); + + if (dst_port != RTE_PTP_EVENT_PORT && + dst_port != RTE_PTP_GENERAL_PORT) + return NULL; + + offset += sizeof(struct rte_udp_hdr); + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ptp_hdr)) + return NULL; + + p = (const uint8_t *)eth + offset; + return (struct rte_ptp_hdr *)(uintptr_t)p; + } + + /* PTP over UDP/IPv6 */ + if (ether_type == RTE_ETHER_TYPE_IPV6) { + const struct rte_ipv6_hdr *ip6h; + + if (rte_pktmbuf_data_len(m) < + offset + sizeof(struct rte_ipv6_hdr)) + return NULL; + + ip6h = (const struct rte_ipv6_hdr *) + ((const uint8_t *)eth + offset); + if (ip6h->proto != IPPROTO_UDP) + return NULL; + + offset += sizeof(struct rte_ipv6_hdr); + + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_udp_hdr)) + return NULL; + + const struct rte_udp_hdr *udp = + (const struct rte_udp_hdr *) + ((const uint8_t *)eth + offset); + uint16_t dst_port = rte_be_to_cpu_16(udp->dst_port); + + if (dst_port != RTE_PTP_EVENT_PORT && + dst_port != RTE_PTP_GENERAL_PORT) + return NULL; + + offset += sizeof(struct rte_udp_hdr); + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ptp_hdr)) + return NULL; + + p = (const uint8_t *)eth + offset; + return (struct rte_ptp_hdr *)(uintptr_t)p; + } + + return NULL; +} + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_ptp_classify, 26.07) +int +rte_ptp_classify(const struct rte_mbuf *m) +{ + struct rte_ptp_hdr *hdr = ptp_hdr_find(m); + + if (hdr == NULL) + return RTE_PTP_MSGTYPE_INVALID; + + return rte_ptp_msg_type(hdr); +} + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_ptp_hdr_get, 26.07) +struct rte_ptp_hdr * +rte_ptp_hdr_get(const struct rte_mbuf *m) +{ + return ptp_hdr_find(m); +} + +static const char * const ptp_msg_names[] = { + [RTE_PTP_MSGTYPE_SYNC] = "Sync", + [RTE_PTP_MSGTYPE_DELAY_REQ] = "Delay_Req", + [RTE_PTP_MSGTYPE_PDELAY_REQ] = "PDelay_Req", + [RTE_PTP_MSGTYPE_PDELAY_RESP] = "PDelay_Resp", + [0x4] = "Reserved_4", + [0x5] = "Reserved_5", + [0x6] = "Reserved_6", + [0x7] = "Reserved_7", + [RTE_PTP_MSGTYPE_FOLLOW_UP] = "Follow_Up", + [RTE_PTP_MSGTYPE_DELAY_RESP] = "Delay_Resp", + [RTE_PTP_MSGTYPE_PDELAY_RESP_FU] = "PDelay_Resp_Follow_Up", + [RTE_PTP_MSGTYPE_ANNOUNCE] = "Announce", + [RTE_PTP_MSGTYPE_SIGNALING] = "Signaling", + [RTE_PTP_MSGTYPE_MANAGEMENT] = "Management", + [0xE] = "Reserved_E", + [0xF] = "Reserved_F", +}; + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_ptp_msg_type_str, 26.07) +const char * +rte_ptp_msg_type_str(int msg_type) +{ + if (msg_type < 0 || msg_type > 0xF) + return "Not_PTP"; + return ptp_msg_names[msg_type]; +} diff --git a/lib/ptp/rte_ptp.h b/lib/ptp/rte_ptp.h new file mode 100644 index 0000000000..689e0d140b --- /dev/null +++ b/lib/ptp/rte_ptp.h @@ -0,0 +1,328 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2026 Intel Corporation + * + * IEEE 1588 / PTP Protocol Library + * + * Provides header structures, packet classification, and helper functions + * for Precision Time Protocol (IEEE 1588-2019) packet processing in DPDK. + * + * Supports: + * - L2 PTP (EtherType 0x88F7) + * - VLAN-tagged L2 PTP + * - PTP over UDP/IPv4 (ports 319/320) + * - PTP over UDP/IPv6 (ports 319/320) + * - Two-step and one-step message identification + * - correctionField manipulation (scaled nanoseconds, 48.16 fixed-point) + */ + +#ifndef _RTE_PTP_H_ +#define _RTE_PTP_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdint.h> +#include <stdbool.h> +#include <rte_byteorder.h> +#include <rte_mbuf.h> +#include <rte_common.h> + +/** + * @file + * IEEE 1588 PTP protocol definitions and helpers. + */ + +/* ============================================================ + * PTP Constants + * ============================================================ + */ + +/** PTP EtherType (IEEE 802.1AS / IEEE 1588) */ +#define RTE_PTP_ETHERTYPE 0x88F7 + +/** PTP over UDP event port (Sync, Delay_Req, PDelay_Req, PDelay_Resp) */ +#define RTE_PTP_EVENT_PORT 319 + +/** PTP over UDP general port (Follow_Up, Delay_Resp, Announce, etc.) */ +#define RTE_PTP_GENERAL_PORT 320 + +/** PTP multicast MAC address: 01:1B:19:00:00:00 */ +#define RTE_PTP_MULTICAST_MAC { 0x01, 0x1B, 0x19, 0x00, 0x00, 0x00 } + +/** PTP peer delay multicast MAC: 01:80:C2:00:00:0E */ +#define RTE_PTP_PDELAY_MULTICAST_MAC { 0x01, 0x80, 0xC2, 0x00, 0x00, 0x0E } + +/* ============================================================ + * PTP Message Types (IEEE 1588-2019 Table 36) + * ============================================================ + */ + +#define RTE_PTP_MSGTYPE_SYNC 0x0 /**< Sync (event) */ +#define RTE_PTP_MSGTYPE_DELAY_REQ 0x1 /**< Delay_Req (event) */ +#define RTE_PTP_MSGTYPE_PDELAY_REQ 0x2 /**< Peer_Delay_Req (event) */ +#define RTE_PTP_MSGTYPE_PDELAY_RESP 0x3 /**< Peer_Delay_Resp (event) */ +#define RTE_PTP_MSGTYPE_FOLLOW_UP 0x8 /**< Follow_Up (general) */ +#define RTE_PTP_MSGTYPE_DELAY_RESP 0x9 /**< Delay_Resp (general) */ +#define RTE_PTP_MSGTYPE_PDELAY_RESP_FU 0xA /**< Peer_Delay_Resp_Follow_Up */ +#define RTE_PTP_MSGTYPE_ANNOUNCE 0xB /**< Announce (general) */ +#define RTE_PTP_MSGTYPE_SIGNALING 0xC /**< Signaling (general) */ +#define RTE_PTP_MSGTYPE_MANAGEMENT 0xD /**< Management (general) */ + +/** Invalid / not a PTP packet */ +#define RTE_PTP_MSGTYPE_INVALID (-1) + +/* ============================================================ + * PTP Flag Field Bits (IEEE 1588-2019 Table 37) + * ============================================================ + */ + +#define RTE_PTP_FLAG_TWO_STEP (1 << 1) /**< Two-step flag (bit 1) */ +#define RTE_PTP_FLAG_UNICAST (1 << 2) /**< Unicast flag */ +#define RTE_PTP_FLAG_LI_61 (1 << 0) /**< Leap indicator 61 */ +#define RTE_PTP_FLAG_LI_59 (1 << 1) /**< Leap indicator 59 (byte 1) */ + +/* ============================================================ + * PTP Header Structures (IEEE 1588-2019) + * ============================================================ + */ + +/** + * PTP Port Identity (10 bytes). + */ +struct __rte_packed_begin rte_ptp_port_id { + uint8_t clock_id[8]; /**< clockIdentity (EUI-64) */ + uint16_t port_number; /**< portNumber */ +} __rte_packed_end; + +/** + * PTP Common Message Header (34 bytes). + * All PTP messages begin with this header. + */ +struct __rte_packed_begin rte_ptp_hdr { + uint8_t msg_type; /**< transportSpecific (4) | messageType (4) */ + uint8_t version; /**< reserved (4) | versionPTP (4) */ + uint16_t msg_length; /**< Total message length in bytes */ + uint8_t domain_number; /**< PTP domain (0-255) */ + uint8_t minor_sdo_id; /**< minorSdoId (IEEE 1588-2019) */ + uint16_t flags; /**< Flag field (see RTE_PTP_FLAG_*) */ + int64_t correction; /**< correctionField (scaled ns, 48.16 fixed) */ + uint32_t msg_type_specific; /**< messageTypeSpecific */ + struct rte_ptp_port_id source_port_id; /**< sourcePortIdentity */ + uint16_t sequence_id; /**< sequenceId */ + uint8_t control; /**< controlField (deprecated in 1588-2019) */ + int8_t log_msg_interval; /**< logMessageInterval */ +} __rte_packed_end; + +/** + * PTP Timestamp (10 bytes, used in Sync/Delay_Req/Follow_Up bodies). + */ +struct __rte_packed_begin rte_ptp_timestamp { + uint16_t seconds_hi; /**< Upper 16 bits of seconds */ + uint32_t seconds_lo; /**< Lower 32 bits of seconds */ + uint32_t nanoseconds; /**< Nanoseconds (0-999999999) */ +} __rte_packed_end; + +/* ============================================================ + * Inline Helpers + * ============================================================ + */ + +/** + * Extract PTP message type from header. + * + * @param hdr + * Pointer to PTP header. + * @return + * Message type (0x0-0xF). + */ +static inline uint8_t +rte_ptp_msg_type(const struct rte_ptp_hdr *hdr) +{ + return hdr->msg_type & 0x0F; +} + +/** + * Extract transport-specific field from header. + * + * @param hdr + * Pointer to PTP header. + * @return + * Transport-specific value (upper nibble, 0x0-0xF). + */ +static inline uint8_t +rte_ptp_transport_specific(const struct rte_ptp_hdr *hdr) +{ + return (hdr->msg_type >> 4) & 0x0F; +} + +/** + * Extract PTP version from header. + * + * @param hdr + * Pointer to PTP header. + * @return + * PTP version number (typically 2). + */ +static inline uint8_t +rte_ptp_version(const struct rte_ptp_hdr *hdr) +{ + return hdr->version & 0x0F; +} + +/** + * Get sequence ID from PTP header (host byte order). + * + * @param hdr + * Pointer to PTP header. + * @return + * Sequence ID in host byte order. + */ +static inline uint16_t +rte_ptp_seq_id(const struct rte_ptp_hdr *hdr) +{ + return rte_be_to_cpu_16(hdr->sequence_id); +} + +/** + * Get PTP domain number. + * + * @param hdr + * Pointer to PTP header. + * @return + * Domain number (0-255). + */ +static inline uint8_t +rte_ptp_domain(const struct rte_ptp_hdr *hdr) +{ + return hdr->domain_number; +} + +/** + * Check if PTP message type is an event message. + * Event messages (msg_type 0x0-0x3) require hardware timestamps. + * + * @param msg_type + * PTP message type value (0x0-0xF). + * @return + * true if event message, false otherwise. + */ +static inline bool +rte_ptp_is_event(uint8_t msg_type) +{ + return msg_type <= RTE_PTP_MSGTYPE_PDELAY_RESP; +} + +/** + * Check if the two-step flag is set in a PTP header. + * + * @param hdr + * Pointer to PTP header. + * @return + * true if two-step flag is set. + */ +static inline bool +rte_ptp_is_two_step(const struct rte_ptp_hdr *hdr) +{ + return (rte_be_to_cpu_16(hdr->flags) & RTE_PTP_FLAG_TWO_STEP) != 0; +} + +/** + * Get correctionField value in nanoseconds (from 48.16 fixed-point). + * + * @param hdr + * Pointer to PTP header. + * @return + * Correction value in nanoseconds. + */ +static inline int64_t +rte_ptp_correction_ns(const struct rte_ptp_hdr *hdr) +{ + return (int64_t)rte_be_to_cpu_64(hdr->correction) >> 16; +} + +/** + * Add a residence time (in nanoseconds) to the correctionField. + * Used by Transparent Clocks to account for relay transit delay. + * The correctionField uses IEEE 1588 scaled nanoseconds (48.16 fixed-point). + * + * @param hdr + * Pointer to PTP header (will be modified in-place). + * @param residence_ns + * Residence time in nanoseconds to add. + */ +static inline void +rte_ptp_add_correction(struct rte_ptp_hdr *hdr, int64_t residence_ns) +{ + int64_t cf = (int64_t)rte_be_to_cpu_64(hdr->correction); + cf += (residence_ns << 16); + hdr->correction = rte_cpu_to_be_64(cf); +} + +/** + * Convert a PTP timestamp structure to nanoseconds since epoch. + * + * @param ts + * Pointer to PTP timestamp. + * @return + * Time in nanoseconds since epoch. + */ +static inline uint64_t +rte_ptp_timestamp_to_ns(const struct rte_ptp_timestamp *ts) +{ + uint64_t sec = ((uint64_t)rte_be_to_cpu_16(ts->seconds_hi) << 32) | + rte_be_to_cpu_32(ts->seconds_lo); + return sec * 1000000000ULL + rte_be_to_cpu_32(ts->nanoseconds); +} + +/* ============================================================ + * Packet Classification Functions (implemented in rte_ptp.c) + * ============================================================ + */ + +/** + * Classify a packet as PTP and return the message type. + * + * Examines the mbuf to determine if it contains a PTP message. + * Supports L2 (EtherType 0x88F7), VLAN-tagged L2, and PTP over + * UDP/IPv4 (ports 319/320). + * + * @param m + * Pointer to the mbuf to classify. + * @return + * PTP message type (0x0-0xF) on success, RTE_PTP_MSGTYPE_INVALID (-1) + * if the packet is not PTP. + */ +__rte_experimental +int rte_ptp_classify(const struct rte_mbuf *m); + +/** + * Get a pointer to the PTP header inside an mbuf. + * + * Locates the PTP header within the packet, handling L2, VLAN-tagged L2, + * and PTP over UDP/IPv4 encapsulations. + * + * @param m + * Pointer to the mbuf. + * @return + * Pointer to the PTP header, or NULL if not a PTP packet. + */ +__rte_experimental +struct rte_ptp_hdr *rte_ptp_hdr_get(const struct rte_mbuf *m); + +/** + * Get a human-readable name for a PTP message type. + * + * @param msg_type + * PTP message type (0x0-0xF or RTE_PTP_MSGTYPE_INVALID). + * @return + * Static string with the message type name (e.g., "Sync", "Follow_Up"). + */ +__rte_experimental +const char *rte_ptp_msg_type_str(int msg_type); + +#ifdef __cplusplus +} +#endif + +#endif /* _RTE_PTP_H_ */ -- 2.52.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* Re: [RFC v1 1/4] ptp: introduce PTP protocol library 2026-04-28 1:01 ` [RFC v1 1/4] ptp: introduce PTP protocol library Rajesh Kumar @ 2026-04-27 23:01 ` Stephen Hemminger 2026-04-28 16:50 ` Kumar, Rajesh 0 siblings, 1 reply; 60+ messages in thread From: Stephen Hemminger @ 2026-04-27 23:01 UTC (permalink / raw) To: Rajesh Kumar; +Cc: dev, bruce.richardson, aman.deep.singh On Tue, 28 Apr 2026 06:31:03 +0530 Rajesh Kumar <rajesh3.kumar@intel.com> wrote: > + const struct rte_udp_hdr *udp = > + (const struct rte_udp_hdr *) > + ((const uint8_t *)eth + offset); Use rte_pktmbuf_mtod_offset() avoid pointer math as error prone. ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [RFC v1 1/4] ptp: introduce PTP protocol library 2026-04-27 23:01 ` Stephen Hemminger @ 2026-04-28 16:50 ` Kumar, Rajesh 0 siblings, 0 replies; 60+ messages in thread From: Kumar, Rajesh @ 2026-04-28 16:50 UTC (permalink / raw) To: Stephen Hemminger; +Cc: dev, bruce.richardson, aman.deep.singh On 28-04-2026 04:31 am, Stephen Hemminger wrote: > On Tue, 28 Apr 2026 06:31:03 +0530 > Rajesh Kumar <rajesh3.kumar@intel.com> wrote: > >> + const struct rte_udp_hdr *udp = >> + (const struct rte_udp_hdr *) >> + ((const uint8_t *)eth + offset); > Use rte_pktmbuf_mtod_offset() avoid pointer math as error prone. Fixed. ^ permalink raw reply [flat|nested] 60+ messages in thread
* [RFC v1 2/4] doc: add PTP library programmer's guide 2026-04-28 1:01 [RFC v1 0/4] introduce PTP protocol library and software relay example Rajesh Kumar 2026-04-27 23:42 ` Stephen Hemminger 2026-04-28 1:01 ` [RFC v1 1/4] ptp: introduce PTP protocol library Rajesh Kumar @ 2026-04-28 1:01 ` Rajesh Kumar 2026-04-28 1:01 ` [RFC v1 3/4] examples/ptp_tap_relay_sw: add software PTP relay example Rajesh Kumar ` (8 subsequent siblings) 11 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-04-28 1:01 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, rajesh3.kumar Add programmer's guide for the PTP protocol library covering message types, header structures, packet classification API, inline helpers, and usage examples. Add PTP header to Doxygen API index under layers section. Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- doc/api/doxy-api-index.md | 1 + doc/api/doxy-api.conf.in | 1 + doc/guides/prog_guide/index.rst | 1 + doc/guides/prog_guide/ptp_lib.rst | 195 ++++++++++++++++++++++++++++++ 4 files changed, 198 insertions(+) create mode 100644 doc/guides/prog_guide/ptp_lib.rst diff --git a/doc/api/doxy-api-index.md b/doc/api/doxy-api-index.md index 9296042119..bbc79168bb 100644 --- a/doc/api/doxy-api-index.md +++ b/doc/api/doxy-api-index.md @@ -137,6 +137,7 @@ The public API headers are grouped by topics: [eCPRI](@ref rte_ecpri.h), [PDCP hdr](@ref rte_pdcp_hdr.h), [PDCP](@ref rte_pdcp.h), + [PTP](@ref rte_ptp.h), [L2TPv2](@ref rte_l2tpv2.h), [PPP](@ref rte_ppp.h), [IB](@ref rte_ib.h) diff --git a/doc/api/doxy-api.conf.in b/doc/api/doxy-api.conf.in index bedd944681..f15d962733 100644 --- a/doc/api/doxy-api.conf.in +++ b/doc/api/doxy-api.conf.in @@ -72,6 +72,7 @@ INPUT = @TOPDIR@/doc/api/doxy-api-index.md \ @TOPDIR@/lib/pmu \ @TOPDIR@/lib/port \ @TOPDIR@/lib/power \ + @TOPDIR@/lib/ptp \ @TOPDIR@/lib/ptr_compress \ @TOPDIR@/lib/rawdev \ @TOPDIR@/lib/rcu \ diff --git a/doc/guides/prog_guide/index.rst b/doc/guides/prog_guide/index.rst index e6f24945b0..60dad4475c 100644 --- a/doc/guides/prog_guide/index.rst +++ b/doc/guides/prog_guide/index.rst @@ -97,6 +97,7 @@ Protocol Processing Libraries :maxdepth: 1 :numbered: + ptp_lib pdcp_lib ipsec_lib diff --git a/doc/guides/prog_guide/ptp_lib.rst b/doc/guides/prog_guide/ptp_lib.rst new file mode 100644 index 0000000000..46770f1bd7 --- /dev/null +++ b/doc/guides/prog_guide/ptp_lib.rst @@ -0,0 +1,195 @@ +.. SPDX-License-Identifier: BSD-3-Clause + Copyright(c) 2026 Intel Corporation. + +PTP Protocol Library +==================== + +The DPDK PTP library provides IEEE 1588 / Precision Time Protocol (PTP) +packet structures, constants, and helper functions for PTP packet processing. + +The library supports classification and header parsing of PTP messages +across multiple transport encapsulations: + +- L2 PTP (EtherType 0x88F7) +- VLAN-tagged L2 PTP (single and double/QinQ) +- PTP over UDP/IPv4 (destination ports 319 and 320) +- PTP over UDP/IPv6 (destination ports 319 and 320) + +The library conforms to +`IEEE 1588-2019 <https://standards.ieee.org/ieee/1588/6825/>`_ +(Precision Time Protocol). + +Overview +-------- + +PTP is the foundation of time synchronization in networking. +DPDK applications that relay, classify, or timestamp PTP packets +currently duplicate header definitions and parsing logic. +This library provides a shared, tested implementation. + +The library provides: + +#. Packed header structures matching the IEEE 1588-2019 wire format +#. Constants for message types, flags, ports, and multicast addresses +#. Inline helpers for common field extraction and manipulation +#. Packet classification across L2, VLAN, UDP/IPv4, and UDP/IPv6 transports +#. Correction field manipulation for Transparent Clock residence time + +PTP Message Types +----------------- + +IEEE 1588-2019 defines the following message types, all supported by the library: + +.. csv-table:: PTP Message Types + :header: "Type", "Name", "Category", "Macro" + :widths: 5, 20, 10, 30 + + "0x0", "Sync", "Event", "``RTE_PTP_MSGTYPE_SYNC``" + "0x1", "Delay_Req", "Event", "``RTE_PTP_MSGTYPE_DELAY_REQ``" + "0x2", "Peer_Delay_Req", "Event", "``RTE_PTP_MSGTYPE_PDELAY_REQ``" + "0x3", "Peer_Delay_Resp", "Event", "``RTE_PTP_MSGTYPE_PDELAY_RESP``" + "0x8", "Follow_Up", "General", "``RTE_PTP_MSGTYPE_FOLLOW_UP``" + "0x9", "Delay_Resp", "General", "``RTE_PTP_MSGTYPE_DELAY_RESP``" + "0xA", "PDelay_Resp_Follow_Up", "General", "``RTE_PTP_MSGTYPE_PDELAY_RESP_FU``" + "0xB", "Announce", "General", "``RTE_PTP_MSGTYPE_ANNOUNCE``" + "0xC", "Signaling", "General", "``RTE_PTP_MSGTYPE_SIGNALING``" + "0xD", "Management", "General", "``RTE_PTP_MSGTYPE_MANAGEMENT``" + +Event messages (types 0x0–0x3) require hardware timestamps for accurate +time transfer. + +Header Structures +----------------- + +The library defines the following packed structures that map directly to +the IEEE 1588-2019 wire format: + +``struct rte_ptp_hdr`` + Common PTP message header (34 bytes). All PTP messages begin with this header. + Contains message type, version, flags, correction field, source port identity, + sequence ID, and log message interval. + +``struct rte_ptp_timestamp`` + PTP timestamp (10 bytes). Used in Sync, Delay_Req, and Follow_Up message bodies. + Contains seconds (48-bit) and nanoseconds (32-bit). + +``struct rte_ptp_port_id`` + PTP port identity (10 bytes). Contains an EUI-64 clock identity and a + 16-bit port number. + +Packet Classification API +-------------------------- + +``rte_ptp_classify()`` +~~~~~~~~~~~~~~~~~~~~~~ + +Classify a packet and return the PTP message type. + +.. code-block:: C + + int rte_ptp_classify(const struct rte_mbuf *m); + +Examines the mbuf to determine if it contains a PTP message. +Returns the PTP message type (0x0–0xF) on success, +or ``RTE_PTP_MSGTYPE_INVALID`` (-1) if the packet is not PTP. + +Supported encapsulations: + +- EtherType 0x88F7 (L2 PTP) +- Single VLAN + EtherType 0x88F7 +- Double VLAN (QinQ) + EtherType 0x88F7 +- IPv4 + UDP destination port 319 or 320 +- IPv6 + UDP destination port 319 or 320 + +``rte_ptp_hdr_get()`` +~~~~~~~~~~~~~~~~~~~~~ + +Get a pointer to the PTP header inside a packet. + +.. code-block:: C + + struct rte_ptp_hdr *rte_ptp_hdr_get(const struct rte_mbuf *m); + +Returns a pointer to the PTP header, or NULL if the packet is not PTP. +Handles the same set of encapsulations as ``rte_ptp_classify()``. + +``rte_ptp_msg_type_str()`` +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Convert a PTP message type to a human-readable string. + +.. code-block:: C + + const char *rte_ptp_msg_type_str(int msg_type); + +Returns a string such as ``"Sync"``, ``"Delay_Req"``, ``"Follow_Up"``, etc. +Returns ``"Not_PTP"`` for invalid message types. + +Inline Helpers +-------------- + +The following inline functions operate on ``struct rte_ptp_hdr`` and require +no function call overhead: + +.. csv-table:: Inline Helper Functions + :header: "Function", "Returns", "Description" + :widths: 30, 15, 40 + + "``rte_ptp_msg_type()``", "``uint8_t``", "Extract message type (lower nibble)" + "``rte_ptp_transport_specific()``", "``uint8_t``", "Extract transport-specific field (upper nibble)" + "``rte_ptp_version()``", "``uint8_t``", "Extract PTP version number" + "``rte_ptp_seq_id()``", "``uint16_t``", "Get sequence ID in host byte order" + "``rte_ptp_domain()``", "``uint8_t``", "Get PTP domain number" + "``rte_ptp_is_event()``", "``bool``", "Check if message type is an event (0x0–0x3)" + "``rte_ptp_is_two_step()``", "``bool``", "Check if two-step flag is set" + "``rte_ptp_correction_ns()``", "``int64_t``", "Get correctionField in nanoseconds (from 48.16 fixed-point)" + "``rte_ptp_add_correction()``", "``void``", "Add residence time to correctionField (for Transparent Clocks)" + "``rte_ptp_timestamp_to_ns()``", "``uint64_t``", "Convert PTP timestamp struct to nanoseconds" + +Usage Example +------------- + +Classifying and processing PTP packets: + +.. code-block:: C + + #include <rte_ptp.h> + + void process_packets(struct rte_mbuf **pkts, uint16_t nb_pkts) + { + for (uint16_t i = 0; i < nb_pkts; i++) { + int ptp_type = rte_ptp_classify(pkts[i]); + if (ptp_type == RTE_PTP_MSGTYPE_INVALID) + continue; + + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(pkts[i]); + + printf("PTP %s seq=%u domain=%u\n", + rte_ptp_msg_type_str(ptp_type), + rte_ptp_seq_id(hdr), + rte_ptp_domain(hdr)); + + if (rte_ptp_is_event((uint8_t)ptp_type)) { + /* Event message — needs hardware timestamp */ + } + } + } + +Adding residence time for a Transparent Clock: + +.. code-block:: C + + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(pkt); + if (hdr != NULL) { + int64_t residence_ns = egress_ts - ingress_ts; + rte_ptp_add_correction(hdr, residence_ns); + } + +Limitations +----------- + +- IPv6 extension headers are not traversed. Only the base IPv6 header + with ``next_header == UDP`` is handled. +- Multi-segment mbufs are not supported. PTP event messages are + typically less than 128 bytes and fit in a single segment. +- PTP over MPLS or other tunneling protocols is not supported. -- 2.52.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [RFC v1 3/4] examples/ptp_tap_relay_sw: add software PTP relay example 2026-04-28 1:01 [RFC v1 0/4] introduce PTP protocol library and software relay example Rajesh Kumar ` (2 preceding siblings ...) 2026-04-28 1:01 ` [RFC v1 2/4] doc: add PTP library programmer's guide Rajesh Kumar @ 2026-04-28 1:01 ` Rajesh Kumar 2026-04-28 1:01 ` [RFC v1 4/4] doc: add PTP software relay sample app guide Rajesh Kumar ` (7 subsequent siblings) 11 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-04-28 1:01 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, rajesh3.kumar Add a minimal PTP Transparent Clock relay that bridges PTP packets between a DPDK-bound physical NIC and a kernel TAP interface using software timestamps only. The relay uses the rte_ptp library for packet classification and correctionField manipulation. For each PTP event message, it records software timestamps (CLOCK_MONOTONIC_RAW) at ingress and egress, then adds the residence time to the correctionField per IEEE 1588-2019 section 10.2. A two-pass design is used: first all packets are classified and PTP header pointers saved, then a single TX timestamp is taken immediately before applying corrections and calling rte_eth_tx_burst(). This minimises the gap between the measured timestamp and actual wire egress. Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- MAINTAINERS | 2 + examples/meson.build | 1 + examples/ptp_tap_relay_sw/Makefile | 41 ++ examples/ptp_tap_relay_sw/meson.build | 14 + examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c | 424 +++++++++++++++++++ 5 files changed, 482 insertions(+) create mode 100644 examples/ptp_tap_relay_sw/Makefile create mode 100644 examples/ptp_tap_relay_sw/meson.build create mode 100644 examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c diff --git a/MAINTAINERS b/MAINTAINERS index f2b47acfc3..665e08dc90 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1669,6 +1669,8 @@ PTP - EXPERIMENTAL M: Rajesh Kumar <rajesh3.kumar@intel.com> F: lib/ptp/ F: doc/guides/prog_guide/ptp_lib.rst +F: examples/ptp_tap_relay_sw/ +F: doc/guides/sample_app_ug/ptp_tap_relay_sw.rst PDCP - EXPERIMENTAL M: Anoob Joseph <anoobj@marvell.com> diff --git a/examples/meson.build b/examples/meson.build index 25d9c88457..ab82cf5fbd 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -43,6 +43,7 @@ all_examples = [ 'packet_ordering', 'pipeline', 'ptpclient', + 'ptp_tap_relay_sw', 'qos_meter', 'qos_sched', 'rxtx_callbacks', diff --git a/examples/ptp_tap_relay_sw/Makefile b/examples/ptp_tap_relay_sw/Makefile new file mode 100644 index 0000000000..fd178f46ae --- /dev/null +++ b/examples/ptp_tap_relay_sw/Makefile @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2026 Intel Corporation + +# binary name +APP = dpdk-ptp_tap_relay_sw + +# all source are stored in SRCS-y +SRCS-y := ptp_tap_relay_sw.c + +PKGCONF ?= pkg-config + +# Build using pkg-config variables if possible +ifneq ($(shell $(PKGCONF) --exists libdpdk && echo 0),0) +$(error "no installation of DPDK found") +endif + +all: shared +.PHONY: shared static +shared: build/$(APP)-shared + ln -sf $(APP)-shared build/$(APP) +static: build/$(APP)-static + ln -sf $(APP)-static build/$(APP) + +PC_FILE := $(shell $(PKGCONF) --path libdpdk 2>/dev/null) +CFLAGS += -O3 $(shell $(PKGCONF) --cflags libdpdk) +LDFLAGS_SHARED = $(shell $(PKGCONF) --libs libdpdk) +LDFLAGS_STATIC = $(shell $(PKGCONF) --static --libs libdpdk) + +build/$(APP)-shared: $(SRCS-y) Makefile $(PC_FILE) | build + $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_SHARED) + +build/$(APP)-static: $(SRCS-y) Makefile $(PC_FILE) | build + $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_STATIC) + +build: + @mkdir -p $@ + +.PHONY: clean +clean: + rm -f build/$(APP) build/$(APP)-static build/$(APP)-shared + test -d build && rmdir -p build || true diff --git a/examples/ptp_tap_relay_sw/meson.build b/examples/ptp_tap_relay_sw/meson.build new file mode 100644 index 0000000000..56abad02ed --- /dev/null +++ b/examples/ptp_tap_relay_sw/meson.build @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2026 Intel Corporation + +# meson file, for building this example as part of a main DPDK build. +# +# To build this example as a standalone application with an already-installed +# DPDK instance, use 'make' + +allow_experimental_apis = true +sources = files( + 'ptp_tap_relay_sw.c', +) +deps += ['net', 'ptp'] +cflags += no_shadow_cflag diff --git a/examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c b/examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c new file mode 100644 index 0000000000..9db85bb382 --- /dev/null +++ b/examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c @@ -0,0 +1,424 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2026 Intel Corporation + */ + +/* + * PTP Software Relay — lib/ptp usage example + * + * A minimal PTP relay between a DPDK-bound physical NIC and a kernel + * TAP interface using software timestamps only. No patched TUN driver + * or ptp_proxy kernel module is required. + * + * How it works: + * 1. Physical NIC receives PTP (and non-PTP) packets via DPDK RX. + * 2. For PTP event messages (Sync, Delay_Req, PDelay_Req, PDelay_Resp) + * the relay records an RX software timestamp (clock_gettime). + * 3. Just before TX on the other side it records a TX software timestamp. + * 4. The relay residence time (tx_ts − rx_ts) is added to the PTP + * correctionField via rte_ptp_add_correction() — standard + * Transparent Clock behaviour (IEEE 1588-2019 §10.2). + * 5. Packets are forwarded bi-directionally: + * PHY → TAP (network → ptp4l) + * TAP → PHY (ptp4l → network) + * + * ptp4l runs in software-timestamping mode on the TAP interface: + * + * ptp4l -i dtap0 -m -s -S # -S = software timestamps + * + * Topology: + * + * PTP Master (remote) ──L2── Physical NIC (DPDK) + * │ + * PTP SW Relay ← correctionField update + * │ + * TAP (kernel) ── ptp4l -S (slave) + * + * Usage: + * dpdk-ptp_tap_relay_sw -l 0-1 --vdev=net_tap0,iface=dtap0 -- \ + * -p 0 -t 1 + * + * Parameters: + * -p PORT Physical NIC port ID (default: 0) + * -t PORT TAP port ID (default: 1) + * -T SECS Stats print interval in seconds (default: 10) + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <stdbool.h> +#include <signal.h> +#include <getopt.h> +#include <time.h> + +#include <rte_eal.h> +#include <rte_ethdev.h> +#include <rte_mbuf.h> +#include <rte_cycles.h> +#include <rte_lcore.h> +#include <rte_ptp.h> + +/* Ring sizes */ +#define RX_RING_SIZE 1024 +#define TX_RING_SIZE 1024 + +/* Mempool */ +#define NUM_MBUFS 8191 +#define MBUF_CACHE 250 +#define BURST_SIZE 32 + +#define NSEC_PER_SEC 1000000000ULL + +/* Logging helpers */ +#define LOG_INFO(fmt, ...) \ + fprintf(stdout, "[PTP-SW] " fmt "\n", ##__VA_ARGS__) +#define LOG_ERR(fmt, ...) \ + fprintf(stderr, "[PTP-SW ERROR] " fmt "\n", ##__VA_ARGS__) + +static volatile bool force_quit; + +/* Port IDs */ +static uint16_t phy_port; +static uint16_t tap_port = 1; +static unsigned int stats_interval = 10; /* seconds */ + +/* Statistics */ +static struct { + uint64_t phy_rx; /* total packets from PHY */ + uint64_t phy_rx_ptp; /* PTP packets from PHY */ + uint64_t tap_tx; /* packets forwarded to TAP */ + uint64_t tap_rx; /* total packets from TAP */ + uint64_t tap_rx_ptp; /* PTP packets from TAP */ + uint64_t phy_tx; /* packets forwarded to PHY */ + uint64_t corrections; /* correctionField updates */ +} stats; + +static void +signal_handler(int signum) +{ + if (signum == SIGINT || signum == SIGTERM) { + LOG_INFO("Signal %d received, shutting down...", signum); + force_quit = true; + } +} + +/* Helpers */ + +/* Read monotonic clock in nanoseconds (for residence time). */ +static inline uint64_t +sw_timestamp_ns(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC_RAW, &ts); + return (uint64_t)ts.tv_sec * NSEC_PER_SEC + (uint64_t)ts.tv_nsec; +} + +/* Port Init */ + +static int +port_init(uint16_t port, struct rte_mempool *mp) +{ + struct rte_eth_conf port_conf; + struct rte_eth_dev_info dev_info; + uint16_t nb_rxd = RX_RING_SIZE; + uint16_t nb_txd = TX_RING_SIZE; + int ret; + + memset(&port_conf, 0, sizeof(port_conf)); + + ret = rte_eth_dev_info_get(port, &dev_info); + if (ret != 0) { + LOG_ERR("rte_eth_dev_info_get(port %u) failed: %d", port, ret); + return ret; + } + + if (dev_info.tx_offload_capa & RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE) + port_conf.txmode.offloads |= + RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE; + + ret = rte_eth_dev_configure(port, 1, 1, &port_conf); + if (ret != 0) + return ret; + + ret = rte_eth_dev_adjust_nb_rx_tx_desc(port, &nb_rxd, &nb_txd); + if (ret != 0) + return ret; + + ret = rte_eth_rx_queue_setup(port, 0, nb_rxd, + rte_eth_dev_socket_id(port), NULL, mp); + if (ret < 0) + return ret; + + ret = rte_eth_tx_queue_setup(port, 0, nb_txd, + rte_eth_dev_socket_id(port), NULL); + if (ret < 0) + return ret; + + ret = rte_eth_dev_start(port); + if (ret < 0) + return ret; + + ret = rte_eth_promiscuous_enable(port); + if (ret != 0) + LOG_ERR("Failed to enable promiscuous on port %u", port); + + return 0; +} + +/* Relay one direction */ + +/* + * Forward packets from src_port to dst_port. + * For PTP event messages, record SW timestamps around the + * relay path and add the residence time to the correctionField. + * + * This implements a Transparent Clock (IEEE 1588-2019 §10.2): + * correctionField += (t_egress − t_ingress) + */ +static void +relay_burst(uint16_t src_port, uint16_t dst_port, + uint64_t *rx_cnt, uint64_t *rx_ptp_cnt, + uint64_t *tx_cnt) +{ + struct rte_mbuf *bufs[BURST_SIZE]; + struct rte_ptp_hdr *ptp_hdrs[BURST_SIZE]; + uint64_t rx_ts; + uint16_t nb_rx, nb_tx, i; + + nb_rx = rte_eth_rx_burst(src_port, 0, bufs, BURST_SIZE); + if (nb_rx == 0) + return; + + /* Record a single RX software timestamp for the whole burst. + * All packets in one burst arrived at essentially the same instant + * from rte_eth_rx_burst()'s perspective. + */ + rx_ts = sw_timestamp_ns(); + + *rx_cnt += nb_rx; + + /* + * Pass 1: Classify packets and remember PTP event header pointers. + * This avoids taking the TX timestamp too early — we want it as + * close to the actual rte_eth_tx_burst() call as possible. + */ + memset(ptp_hdrs, 0, sizeof(ptp_hdrs[0]) * nb_rx); + for (i = 0; i < nb_rx; i++) { + int ptp_type = rte_ptp_classify(bufs[i]); + + if (ptp_type == RTE_PTP_MSGTYPE_INVALID) + continue; + + (*rx_ptp_cnt)++; + + /* Only event messages carry timestamps that need correction */ + if (!rte_ptp_is_event((uint8_t)ptp_type)) + continue; + + ptp_hdrs[i] = rte_ptp_hdr_get(bufs[i]); + } + + /* + * Pass 2: Take a single TX timestamp right before transmission. + * This minimises the gap between the measured tx_ts and the + * actual kernel write inside rte_eth_tx_burst(), giving the + * most accurate residence time we can achieve with SW timestamps. + * + * residence_time = tx_ts − rx_ts + * + * Remaining untracked delays: + * - Pre-RX: NIC DMA → rx_burst return (~1-5 µs, unavoidable) + * - Post-TX: tx_ts → kernel TAP write (~1-2 µs) + * Both are symmetric for Sync and Delay_Req so they largely + * cancel in the ptp4l offset calculation. + */ + uint64_t tx_ts = sw_timestamp_ns(); + int64_t residence_ns = (int64_t)(tx_ts - rx_ts); + + for (i = 0; i < nb_rx; i++) { + if (ptp_hdrs[i] == NULL) + continue; + rte_ptp_add_correction(ptp_hdrs[i], residence_ns); + stats.corrections++; + } + + /* Forward the burst */ + nb_tx = rte_eth_tx_burst(dst_port, 0, bufs, nb_rx); + *tx_cnt += nb_tx; + + /* Free any unsent packets */ + for (i = nb_tx; i < nb_rx; i++) + rte_pktmbuf_free(bufs[i]); +} + +/* Print statistics */ + +static void +print_stats(void) +{ + LOG_INFO("=== Statistics ==="); + LOG_INFO(" PHY RX total: %"PRIu64, stats.phy_rx); + LOG_INFO(" PHY RX PTP: %"PRIu64, stats.phy_rx_ptp); + LOG_INFO(" TAP TX: %"PRIu64, stats.tap_tx); + LOG_INFO(" TAP RX total: %"PRIu64, stats.tap_rx); + LOG_INFO(" TAP RX PTP: %"PRIu64, stats.tap_rx_ptp); + LOG_INFO(" PHY TX: %"PRIu64, stats.phy_tx); + LOG_INFO(" Corrections: %"PRIu64, stats.corrections); +} + +/* Main relay loop */ + +static int +relay_loop(__rte_unused void *arg) +{ + uint64_t last_stats = rte_rdtsc(); + uint64_t stats_tsc = rte_get_tsc_hz() * stats_interval; + + LOG_INFO("Relay loop started on lcore %u", rte_lcore_id()); + LOG_INFO(" PHY port %u <--> TAP port %u", phy_port, tap_port); + LOG_INFO(" Correction field updates: enabled for event messages"); + + while (!force_quit) { + /* PHY → TAP */ + relay_burst(phy_port, tap_port, + &stats.phy_rx, &stats.phy_rx_ptp, + &stats.tap_tx); + + /* TAP → PHY */ + relay_burst(tap_port, phy_port, + &stats.tap_rx, &stats.tap_rx_ptp, + &stats.phy_tx); + + /* Periodic stats */ + if (rte_rdtsc() - last_stats > stats_tsc) { + print_stats(); + last_stats = rte_rdtsc(); + } + } + + print_stats(); + return 0; +} + +/* Argument parsing */ + +static void +usage(const char *prog) +{ + fprintf(stderr, + "Usage: %s [EAL options] -- [options]\n" + " -p PORT Physical NIC port ID (default: 0)\n" + " -t PORT TAP port ID (default: 1)\n" + " -T SECS Stats interval in seconds (default: 10)\n" + "\n" + "Example:\n" + " %s -l 0-1 --vdev=net_tap0,iface=dtap0 -- -p 0 -t 1\n" + "\n" + "Then run ptp4l with software timestamps:\n" + " ptp4l -i dtap0 -m -s -S\n", + prog, prog); +} + +static int +parse_args(int argc, char **argv) +{ + int opt; + + while ((opt = getopt(argc, argv, "p:t:T:h")) != -1) { + switch (opt) { + case 'p': + phy_port = (uint16_t)atoi(optarg); + break; + case 't': + tap_port = (uint16_t)atoi(optarg); + break; + case 'T': + stats_interval = (unsigned int)atoi(optarg); + break; + case 'h': + default: + usage(argv[0]); + return -1; + } + } + + return 0; +} + +/* Main */ + +int +main(int argc, char **argv) +{ + struct rte_mempool *mp; + uint16_t nb_ports; + int ret; + + /* EAL init */ + ret = rte_eal_init(argc, argv); + if (ret < 0) + rte_exit(EXIT_FAILURE, "EAL init failed\n"); + argc -= ret; + argv += ret; + + /* App args */ + ret = parse_args(argc, argv); + if (ret < 0) + rte_exit(EXIT_FAILURE, "Invalid arguments\n"); + + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + + nb_ports = rte_eth_dev_count_avail(); + if (nb_ports < 2) + rte_exit(EXIT_FAILURE, + "Need at least 2 ports (PHY + TAP).\n" + "Use --vdev=net_tap0,iface=dtap0\n"); + + if (!rte_eth_dev_is_valid_port(phy_port)) + rte_exit(EXIT_FAILURE, "Invalid PHY port %u\n", phy_port); + if (!rte_eth_dev_is_valid_port(tap_port)) + rte_exit(EXIT_FAILURE, "Invalid TAP port %u\n", tap_port); + + mp = rte_pktmbuf_pool_create("MBUF_POOL", NUM_MBUFS * nb_ports, + MBUF_CACHE, 0, + RTE_MBUF_DEFAULT_BUF_SIZE, + rte_socket_id()); + if (mp == NULL) + rte_exit(EXIT_FAILURE, "Cannot create mbuf pool\n"); + + LOG_INFO("Initializing PHY port %u...", phy_port); + ret = port_init(phy_port, mp); + if (ret != 0) + rte_exit(EXIT_FAILURE, "Cannot init PHY port %u (%d)\n", + phy_port, ret); + + LOG_INFO("Initializing TAP port %u...", tap_port); + ret = port_init(tap_port, mp); + if (ret != 0) + rte_exit(EXIT_FAILURE, "Cannot init TAP port %u (%d)\n", + tap_port, ret); + + LOG_INFO("PTP Software Relay ready"); + LOG_INFO(" PHY port: %u", phy_port); + LOG_INFO(" TAP port: %u", tap_port); + LOG_INFO(" Stats every: %u seconds", stats_interval); + LOG_INFO(" Correction: Transparent Clock (SW timestamps)"); + LOG_INFO(""); + LOG_INFO("Run ptp4l: ptp4l -i dtap0 -m -s -S"); + + /* Run relay on main lcore */ + relay_loop(NULL); + + /* Cleanup */ + LOG_INFO("Stopping ports..."); + rte_eth_dev_stop(phy_port); + rte_eth_dev_stop(tap_port); + rte_eth_dev_close(phy_port); + rte_eth_dev_close(tap_port); + rte_eal_cleanup(); + + return 0; +} -- 2.52.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [RFC v1 4/4] doc: add PTP software relay sample app guide 2026-04-28 1:01 [RFC v1 0/4] introduce PTP protocol library and software relay example Rajesh Kumar ` (3 preceding siblings ...) 2026-04-28 1:01 ` [RFC v1 3/4] examples/ptp_tap_relay_sw: add software PTP relay example Rajesh Kumar @ 2026-04-28 1:01 ` Rajesh Kumar 2026-04-28 22:28 ` [RFC v2 0/6] introduce PTP protocol library and software relay Rajesh Kumar ` (6 subsequent siblings) 11 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-04-28 1:01 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, rajesh3.kumar Add a sample application user guide for the ptp_tap_relay_sw example. The guide covers the application topology, packet flow, compilation, command-line options, and a code walkthrough explaining the two-pass relay burst design and CLOCK_MONOTONIC_RAW timestamp source selection. Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- doc/guides/sample_app_ug/index.rst | 1 + doc/guides/sample_app_ug/ptp_tap_relay_sw.rst | 210 ++++++++++++++++++ 2 files changed, 211 insertions(+) create mode 100644 doc/guides/sample_app_ug/ptp_tap_relay_sw.rst diff --git a/doc/guides/sample_app_ug/index.rst b/doc/guides/sample_app_ug/index.rst index e895f692f9..f12623bb66 100644 --- a/doc/guides/sample_app_ug/index.rst +++ b/doc/guides/sample_app_ug/index.rst @@ -51,6 +51,7 @@ Sample Applications User Guides dist_app vm_power_management ptpclient + ptp_tap_relay_sw fips_validation ipsec_secgw bbdev_app diff --git a/doc/guides/sample_app_ug/ptp_tap_relay_sw.rst b/doc/guides/sample_app_ug/ptp_tap_relay_sw.rst new file mode 100644 index 0000000000..1924bafe98 --- /dev/null +++ b/doc/guides/sample_app_ug/ptp_tap_relay_sw.rst @@ -0,0 +1,210 @@ +.. SPDX-License-Identifier: BSD-3-Clause + Copyright(c) 2026 Intel Corporation. + +PTP Software Relay Sample Application +====================================== + +The PTP Software Relay sample application demonstrates how to use the +DPDK ``lib/ptp`` library to build a minimal PTP Transparent Clock relay +between a DPDK-bound physical NIC and a kernel TAP interface using +**software timestamps only**. + +No patched kernel modules or custom TAP PMD changes are required. +The application works with an unmodified Linux kernel and stock DPDK. + +For background on PTP see: +`Precision Time Protocol +<https://en.wikipedia.org/wiki/Precision_Time_Protocol>`_. + + +Limitations +----------- + +* Only L2 PTP (EtherType 0x88F7) is supported on the wire. +* Only PTP v2 messages are processed. +* Software timestamps have microsecond-class jitter; sub-microsecond + precision depends on system load and NIC-to-TAP forwarding latency. +* The PTP master must be reachable on the physical NIC's L2 network. +* Only one physical port and one TAP port are supported. + + +How the Application Works +------------------------- + +Topology +~~~~~~~~ + +:: + + PTP Master Physical NIC TAP (kernel) + (ptp4l -H) ──L2── (DPDK vfio-pci) ────── dtap0 + │ │ + ptp_tap_relay_sw ptp4l -S + (correctionField += (SW timestamps, + residence time) adjusts CLOCK_REALTIME) + +The relay sits between a DPDK-owned physical NIC and a kernel TAP +virtual interface. ``ptp4l`` runs on the TAP interface in software +timestamp mode (``-S``) as a PTP time receiver. + +Packet Flow +~~~~~~~~~~~ + +1. The physical NIC receives PTP (and non-PTP) packets via DPDK RX. +2. A software RX timestamp is recorded using + ``clock_gettime(CLOCK_MONOTONIC_RAW)``. +3. Each packet is classified with ``rte_ptp_classify()``. +4. For PTP **event** messages (Sync, Delay_Req, PDelay_Req, PDelay_Resp), + a TX software timestamp is taken just before transmission. +5. The residence time (``tx_ts − rx_ts``) is added to the PTP + ``correctionField`` via ``rte_ptp_add_correction()`` — standard + IEEE 1588-2019 Transparent Clock behaviour (§10.2). +6. Packets are forwarded bidirectionally: + + * PHY → TAP (network → ptp4l) + * TAP → PHY (ptp4l → network) + +A two-pass design is used: first all packets are classified and PTP +header pointers saved, then a single TX timestamp is taken immediately +before applying corrections and calling ``rte_eth_tx_burst()``. +This minimises the gap between the measured timestamp and the actual +wire egress. + + +Compiling the Application +------------------------- + +To compile the sample application see :doc:`compiling`. + +The application is located in the ``ptp_tap_relay_sw`` sub-directory. + +.. note:: + + The application depends on the ``ptp`` library. + Ensure that ``lib/ptp`` is built (it is built by default). + + +Running the Application +----------------------- + +Prerequisites +~~~~~~~~~~~~~ + +* A PTP-capable physical NIC bound to DPDK (e.g. via ``vfio-pci``). +* ``linuxptp`` (``ptp4l``) installed on the system. +* A PTP time transmitter reachable on the same L2 network. + +Start the relay +~~~~~~~~~~~~~~~~ + +.. code-block:: console + + ./<build_dir>/examples/dpdk-ptp_tap_relay_sw \ + -l 18-19 -a 0000:cc:00.1 --vdev=net_tap0,iface=dtap0 -- \ + -p 0 -t 1 -T 10 + +Command-line Options +~~~~~~~~~~~~~~~~~~~~ + +* ``-p PORT`` — Physical NIC port ID (default: 0). +* ``-t PORT`` — TAP port ID (default: 1). +* ``-T SECS`` — Statistics print interval in seconds (default: 10). + +Start PTP time transmitter (master) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +On a separate terminal or remote host, start ``ptp4l`` as time +transmitter with hardware timestamps on the physical NIC: + +.. code-block:: console + + ptp4l -i <iface> -m -2 -H --masterOnly=1 \ + --logSyncInterval=-4 --logMinDelayReqInterval=-4 + +Start PTP time receiver (slave) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +On the TAP interface, start ``ptp4l`` in software timestamp mode: + +.. code-block:: console + + ptp4l -i dtap0 -m -2 -s -S \ + --delay_filter=moving_median --delay_filter_length=10 + +The time receiver will enter UNCALIBRATED state for approximately 60 +seconds while the PI servo estimates the frequency offset, then step +the clock and enter SLAVE state. Steady-state RMS offset of 500–1000 ns +is typical on a lightly loaded system with a hardware-timestamped time +transmitter. + +Example Output +~~~~~~~~~~~~~~ + +Relay statistics printed every ``-T`` seconds: + +:: + + [PTP-SW] === Statistics === + [PTP-SW] PHY RX total: 5646 + [PTP-SW] PHY RX PTP: 5598 + [PTP-SW] TAP TX: 5646 + [PTP-SW] TAP RX total: 1800 + [PTP-SW] TAP RX PTP: 1788 + [PTP-SW] PHY TX: 1800 + [PTP-SW] Corrections: 3635 + +Slave ``ptp4l`` output after convergence: + +:: + + ptp4l[451534.520]: rms 630 max 1166 freq -44365 +/- 100 delay 37668 +/- 71 + ptp4l[451539.525]: rms 602 max 1177 freq -44339 +/- 119 delay 37517 +/- 43 + ptp4l[451544.530]: rms 535 max 1194 freq -44345 +/- 103 delay 37410 +/- 81 + + +Code Explanation +---------------- + +The following sections explain the main components of the application. + +Relay Burst Function +~~~~~~~~~~~~~~~~~~~~ + +The core relay logic is in ``relay_burst()``, which handles one direction +(PHY→TAP or TAP→PHY) per call: + +**Pass 1 — Classify:** + +For each received packet, ``rte_ptp_classify()`` determines if it is a +PTP message. For event messages, ``rte_ptp_hdr_get()`` retrieves a +pointer to the PTP header, which is saved for the second pass. + +**Pass 2 — Timestamp and correct:** + +A single software TX timestamp is taken via +``clock_gettime(CLOCK_MONOTONIC_RAW)``. The residence time +(``tx_ts − rx_ts``) is added to each saved PTP header's +``correctionField`` using ``rte_ptp_add_correction()``. +The burst is then transmitted with ``rte_eth_tx_burst()``. + +Main Loop +~~~~~~~~~ + +The ``relay_loop()`` function polls both directions in a tight loop: + +.. code-block:: c + + while (!force_quit) { + relay_burst(phy_port, tap_port, ...); /* PHY → TAP */ + relay_burst(tap_port, phy_port, ...); /* TAP → PHY */ + } + +Statistics are printed at the interval specified by ``-T``. + +Timestamp Source +~~~~~~~~~~~~~~~~ + +``CLOCK_MONOTONIC_RAW`` is used rather than ``CLOCK_REALTIME`` because +the PTP time receiver's servo continuously adjusts ``CLOCK_REALTIME``. +Using ``CLOCK_REALTIME`` would corrupt residence time measurements +during clock stepping or frequency slewing. -- 2.52.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [RFC v2 0/6] introduce PTP protocol library and software relay 2026-04-28 1:01 [RFC v1 0/4] introduce PTP protocol library and software relay example Rajesh Kumar ` (4 preceding siblings ...) 2026-04-28 1:01 ` [RFC v1 4/4] doc: add PTP software relay sample app guide Rajesh Kumar @ 2026-04-28 22:28 ` Rajesh Kumar 2026-04-28 22:28 ` [RFC v2 1/6] ptp: introduce PTP protocol library Rajesh Kumar ` (6 more replies) 2026-05-04 9:17 ` [RFC v3 " Rajesh Kumar ` (5 subsequent siblings) 11 siblings, 7 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-04-28 22:28 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, Rajesh Kumar [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #1: Type: text/plain; charset=yes, Size: 6974 bytes --] This series introduces a new DPDK library (lib/ptp) for IEEE 1588-2019 PTP protocol packet processing and a companion example application (ptp_tap_relay_sw) that demonstrates its usage. Motivation ---------- Several DPDK applications need to classify and manipulate PTP packets (e.g. ptpclient, ptp_tap_relay, custom Transparent Clocks). Today each application re-implements its own PTP header parsing and correctionField handling. A shared library avoids duplication and provides a tested, standards-compliant foundation. Library: lib/ptp ---------------- The library provides: - PTP header structures (IEEE 1588-2019 common header, timestamp, port identity) - Packet classification: rte_ptp_classify() detects PTP over L2 (EtherType 0x88F7), VLAN-tagged L2 (TPIDs 0x8100/0x88A8, single or double), UDP/IPv4, UDP/IPv6 (ports 319/320), and VLAN-tagged UDP variants - Header access: rte_ptp_hdr_get() returns a pointer to the PTP header inside an mbuf - Inline helpers: correctionField manipulation (48.16 fixed-point), message type extraction, two-step flag check, timestamp conversion - Debug: rte_ptp_msg_type_str() for human-readable message names The API is experimental (targeted for 26.07). Dependencies are minimal: mbuf and net libraries only. Example: ptp_tap_relay_sw ------------------------- A minimal PTP Transparent Clock relay between a DPDK-bound physical NIC and a kernel TAP interface using software timestamps only. No patched kernel modules, custom TAP PMD, or hardware timestamp support is required. The relay: 1. Receives packets on the physical NIC via DPDK 2. Classifies PTP packets using rte_ptp_classify() 3. For event messages, records software timestamps (CLOCK_MONOTONIC_RAW) at ingress and egress 4. Adds residence time to correctionField via rte_ptp_add_correction() (IEEE 1588-2019 §10.2 Transparent Clock) 5. Forwards bidirectionally: PHY <-> TAP A two-pass design takes the TX timestamp as close to rte_eth_tx_burst() as possible, minimising untracked delay. Unit Tests: app/test -------------------- A comprehensive test suite (ptp_autotest) covers all library APIs: - Packet classification across L2, VLAN, QinQ, IPv4-UDP, IPv6-UDP, and VLAN-tagged UDP encapsulations - Flag bit position validation (TWO_STEP, UNICAST, LI_61, LI_59) - correctionField manipulation and round-trip accuracy - Timestamp conversion, message type helpers, edge cases - Negative tests: truncated packets, bad IHL, non-PTP traffic 33 test cases total. ptpclient Conversion -------------------- The existing ptpclient example is converted to use lib/ptp shared definitions (rte_ptp_hdr, rte_ptp_timestamp, rte_ptp_port_id, RTE_PTP_ETHERTYPE, RTE_PTP_MULTICAST_MAC), removing ~120 lines of duplicate struct and constant definitions. Test Results ------------ Test setup: Intel E610 10GbE NIC (vfio-pci), ptp4l time transmitter with HW timestamps on physical port, ptp4l time receiver with SW timestamps (-S) on TAP interface, logSyncInterval=-4 (16 Sync/sec), 120s duration. Post-convergence (60s of locked data): Average RMS offset: 929 ns Worst max offset: 11615 ns (single transient) Steady-state range: 489 - 1055 ns RMS Corrections applied: 3635 linuxptp's PI servo uses relaxed gains for software timestamps (kp=0.1, ki=0.001 vs kp=0.7, ki=0.3 for HW), expecting ~5-50 µs jitter. Sub-microsecond RMS is achieved here because the time transmitter uses HW timestamps and DPDK's poll-mode relay provides deterministic low-latency forwarding. v2: Fixed below AI generated Review comments - Fixed flag bit positions for host-order representation after rte_be_to_cpu_16(): TWO_STEP (1<<9), UNICAST (1<<10), LI_61 (1<<0), LI_59 (1<<1). Added unit test for all flags. - Fixed QinQ classification: outer 0x88A8 (802.1ad S-tag) now enters the VLAN parser alongside 0x8100. - Fixed signed left-shift UB in rte_ptp_add_correction(): shift is now performed on uint64_t before casting back to int64_t. - Fixed promiscuous enable failure in relay example: port_init() now returns error instead of logging and continuing silently. - Replaced deprecated master/slave PTP terminology with IEEE 1588-2019 terms (time transmitter / time receiver) throughout code and documentation. - Documented per-burst single-timestamp design choice and its accuracy characteristics in the relay example. - Added IHL validation (reject IHL < 20) in IPv4 PTP-over-UDP path. - Changed rte_ptp_is_event() parameter from uint8_t to int with negative value guard for safe use with rte_ptp_classify() return. - Changed rte_ptp_hdr_get() parameter from const to non-const mbuf since it returns a non-const pointer into packet data. - All pointer accesses use rte_pktmbuf_mtod_offset() — no raw pointer arithmetic. - Added programmer's guide note about avoiding double-parse when both message type and header pointer are needed. Rajesh Kumar (6): ptp: introduce PTP protocol library doc: add PTP library programmer's guide examples/ptp_tap_relay_sw: add software PTP relay example doc: add PTP software relay sample app guide app/test: add PTP library unit tests examples/ptpclient: use shared PTP library definitions MAINTAINERS | 8 + app/test/meson.build | 1 + app/test/test_ptp.c | 1052 +++++++++++++++++ doc/api/doxy-api-index.md | 1 + doc/api/doxy-api.conf.in | 1 + doc/guides/prog_guide/index.rst | 1 + doc/guides/prog_guide/ptp_lib.rst | 205 ++++ doc/guides/rel_notes/release_26_07.rst | 13 + doc/guides/sample_app_ug/index.rst | 1 + doc/guides/sample_app_ug/ptp_tap_relay_sw.rst | 210 ++++ examples/meson.build | 1 + examples/ptp_tap_relay_sw/Makefile | 41 + examples/ptp_tap_relay_sw/meson.build | 14 + examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c | 433 +++++++ examples/ptpclient/meson.build | 1 + examples/ptpclient/ptpclient.c | 188 ++- lib/meson.build | 1 + lib/ptp/meson.build | 6 + lib/ptp/rte_ptp.c | 185 +++ lib/ptp/rte_ptp.h | 338 ++++++ 20 files changed, 2585 insertions(+), 116 deletions(-) create mode 100644 app/test/test_ptp.c create mode 100644 doc/guides/prog_guide/ptp_lib.rst create mode 100644 doc/guides/sample_app_ug/ptp_tap_relay_sw.rst create mode 100644 examples/ptp_tap_relay_sw/Makefile create mode 100644 examples/ptp_tap_relay_sw/meson.build create mode 100644 examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c create mode 100644 lib/ptp/meson.build create mode 100644 lib/ptp/rte_ptp.c create mode 100644 lib/ptp/rte_ptp.h -- 2.53.0 ^ permalink raw reply [flat|nested] 60+ messages in thread
* [RFC v2 1/6] ptp: introduce PTP protocol library 2026-04-28 22:28 ` [RFC v2 0/6] introduce PTP protocol library and software relay Rajesh Kumar @ 2026-04-28 22:28 ` Rajesh Kumar 2026-04-28 22:28 ` [RFC v2 2/6] doc: add PTP library programmer's guide Rajesh Kumar ` (5 subsequent siblings) 6 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-04-28 22:28 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, Rajesh Kumar [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #1: Type: text/plain; charset=yes, Size: 19011 bytes --] Add IEEE 1588-2019 Precision Time Protocol (PTP) processing library. The library provides packet classification, header structures, and helper functions for PTP packet processing in DPDK. This avoids duplicate PTP header definitions across multiple applications and drivers. Supported transports: 1. L2 PTP (EtherType 0x88F7) 2. VLAN-tagged L2 PTP (single and QinQ) 3. PTP over UDP/IPv4 (ports 319/320) 4. PTP over UDP/IPv6 (ports 319/320) Public APIs: 1. rte_ptp_classify() 2. rte_ptp_hdr_get() 3. rte_ptp_msg_type_str() Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- MAINTAINERS | 7 + doc/guides/rel_notes/release_26_07.rst | 7 + lib/meson.build | 1 + lib/ptp/meson.build | 6 + lib/ptp/rte_ptp.c | 185 ++++++++++++++ lib/ptp/rte_ptp.h | 338 +++++++++++++++++++++++++ 6 files changed, 544 insertions(+) create mode 100644 lib/ptp/meson.build create mode 100644 lib/ptp/rte_ptp.c create mode 100644 lib/ptp/rte_ptp.h diff --git a/MAINTAINERS b/MAINTAINERS index 0f5539f851..665e08dc90 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1665,6 +1665,13 @@ F: doc/guides/prog_guide/ipsec_lib.rst M: Vladimir Medvedkin <vladimir.medvedkin@intel.com> F: app/test-sad/ +PTP - EXPERIMENTAL +M: Rajesh Kumar <rajesh3.kumar@intel.com> +F: lib/ptp/ +F: doc/guides/prog_guide/ptp_lib.rst +F: examples/ptp_tap_relay_sw/ +F: doc/guides/sample_app_ug/ptp_tap_relay_sw.rst + PDCP - EXPERIMENTAL M: Anoob Joseph <anoobj@marvell.com> M: Volodymyr Fialko <vfialko@marvell.com> diff --git a/doc/guides/rel_notes/release_26_07.rst b/doc/guides/rel_notes/release_26_07.rst index f012d47a4b..b2208d6fb3 100644 --- a/doc/guides/rel_notes/release_26_07.rst +++ b/doc/guides/rel_notes/release_26_07.rst @@ -63,6 +63,13 @@ New Features ``rte_eal_init`` and the application is responsible for probing each device, * ``--auto-probing`` enables the initial bus probing, which is the current default behavior. +* **Added PTP protocol library.** + + Added a new library ``rte_ptp`` for IEEE 1588 Precision Time Protocol + packet processing. The library provides packet classification, header + parsing, and correctionField manipulation across L2, VLAN-tagged, + UDP/IPv4, and UDP/IPv6 transports. + Removed Items ------------- diff --git a/lib/meson.build b/lib/meson.build index 8f5cfd28a5..78d694f951 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -50,6 +50,7 @@ libraries = [ 'member', 'pcapng', 'power', + 'ptp', 'rawdev', 'regexdev', 'mldev', diff --git a/lib/ptp/meson.build b/lib/ptp/meson.build new file mode 100644 index 0000000000..05f9d87cbe --- /dev/null +++ b/lib/ptp/meson.build @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2026 Intel Corporation + +sources = files('rte_ptp.c') +headers = files('rte_ptp.h') +deps += ['mbuf', 'net'] diff --git a/lib/ptp/rte_ptp.c b/lib/ptp/rte_ptp.c new file mode 100644 index 0000000000..5e4fc666fb --- /dev/null +++ b/lib/ptp/rte_ptp.c @@ -0,0 +1,185 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2026 Intel Corporation + * + * IEEE 1588 / PTP Protocol Library — Implementation + */ + +#include <eal_export.h> +#include <rte_ether.h> +#include <rte_ip.h> +#include <rte_udp.h> + +#include "rte_ptp.h" + +/* + * Internal: find PTP header offset within a packet. + * Returns pointer to PTP header or NULL. + */ +static struct rte_ptp_hdr * +ptp_hdr_find(const struct rte_mbuf *m) +{ + const struct rte_ether_hdr *eth; + uint16_t ether_type; + uint32_t offset; + + if (rte_pktmbuf_data_len(m) < sizeof(struct rte_ether_hdr)) + return NULL; + + eth = rte_pktmbuf_mtod(m, const struct rte_ether_hdr *); + ether_type = rte_be_to_cpu_16(eth->ether_type); + offset = sizeof(struct rte_ether_hdr); + + /* Strip VLAN / QinQ tags to reach the inner EtherType */ + if (ether_type == RTE_ETHER_TYPE_VLAN || + ether_type == RTE_ETHER_TYPE_QINQ) { + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_vlan_hdr)) + return NULL; + const struct rte_vlan_hdr *vlan = + rte_pktmbuf_mtod_offset(m, + const struct rte_vlan_hdr *, offset); + ether_type = rte_be_to_cpu_16(vlan->eth_proto); + offset += sizeof(struct rte_vlan_hdr); + + /* Second tag (QinQ inner or stacked VLAN) */ + if (ether_type == RTE_ETHER_TYPE_VLAN || + ether_type == RTE_ETHER_TYPE_QINQ) { + if (rte_pktmbuf_data_len(m) < + offset + sizeof(struct rte_vlan_hdr)) + return NULL; + vlan = rte_pktmbuf_mtod_offset(m, + const struct rte_vlan_hdr *, offset); + ether_type = rte_be_to_cpu_16(vlan->eth_proto); + offset += sizeof(struct rte_vlan_hdr); + } + } + + /* L2 PTP: EtherType 0x88F7 (plain, VLAN, or QinQ) */ + if (ether_type == RTE_PTP_ETHERTYPE) { + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ptp_hdr)) + return NULL; + return rte_pktmbuf_mtod_offset(m, + struct rte_ptp_hdr *, offset); + } + + /* PTP over UDP/IPv4 (plain or VLAN-tagged) */ + if (ether_type == RTE_ETHER_TYPE_IPV4) { + const struct rte_ipv4_hdr *iph; + uint16_t ihl; + + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ipv4_hdr)) + return NULL; + + iph = rte_pktmbuf_mtod_offset(m, + const struct rte_ipv4_hdr *, offset); + if (iph->next_proto_id != IPPROTO_UDP) + return NULL; + + ihl = (iph->version_ihl & 0x0F) * 4; + if (ihl < 20) + return NULL; + offset += ihl; + + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_udp_hdr)) + return NULL; + + const struct rte_udp_hdr *udp = + rte_pktmbuf_mtod_offset(m, + const struct rte_udp_hdr *, offset); + uint16_t dst_port = rte_be_to_cpu_16(udp->dst_port); + + if (dst_port != RTE_PTP_EVENT_PORT && + dst_port != RTE_PTP_GENERAL_PORT) + return NULL; + + offset += sizeof(struct rte_udp_hdr); + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ptp_hdr)) + return NULL; + + return rte_pktmbuf_mtod_offset(m, + struct rte_ptp_hdr *, offset); + } + + /* PTP over UDP/IPv6 (plain or VLAN-tagged) */ + if (ether_type == RTE_ETHER_TYPE_IPV6) { + const struct rte_ipv6_hdr *ip6h; + + if (rte_pktmbuf_data_len(m) < + offset + sizeof(struct rte_ipv6_hdr)) + return NULL; + + ip6h = rte_pktmbuf_mtod_offset(m, + const struct rte_ipv6_hdr *, offset); + if (ip6h->proto != IPPROTO_UDP) + return NULL; + + offset += sizeof(struct rte_ipv6_hdr); + + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_udp_hdr)) + return NULL; + + const struct rte_udp_hdr *udp = + rte_pktmbuf_mtod_offset(m, + const struct rte_udp_hdr *, offset); + uint16_t dst_port = rte_be_to_cpu_16(udp->dst_port); + + if (dst_port != RTE_PTP_EVENT_PORT && + dst_port != RTE_PTP_GENERAL_PORT) + return NULL; + + offset += sizeof(struct rte_udp_hdr); + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ptp_hdr)) + return NULL; + + return rte_pktmbuf_mtod_offset(m, + struct rte_ptp_hdr *, offset); + } + + return NULL; +} + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_ptp_classify, 26.07) +int +rte_ptp_classify(const struct rte_mbuf *m) +{ + struct rte_ptp_hdr *hdr = ptp_hdr_find(m); + + if (hdr == NULL) + return RTE_PTP_MSGTYPE_INVALID; + + return rte_ptp_msg_type(hdr); +} + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_ptp_hdr_get, 26.07) +struct rte_ptp_hdr * +rte_ptp_hdr_get(struct rte_mbuf *m) +{ + return ptp_hdr_find(m); +} + +static const char * const ptp_msg_names[] = { + [RTE_PTP_MSGTYPE_SYNC] = "Sync", + [RTE_PTP_MSGTYPE_DELAY_REQ] = "Delay_Req", + [RTE_PTP_MSGTYPE_PDELAY_REQ] = "PDelay_Req", + [RTE_PTP_MSGTYPE_PDELAY_RESP] = "PDelay_Resp", + [0x4] = "Reserved_4", + [0x5] = "Reserved_5", + [0x6] = "Reserved_6", + [0x7] = "Reserved_7", + [RTE_PTP_MSGTYPE_FOLLOW_UP] = "Follow_Up", + [RTE_PTP_MSGTYPE_DELAY_RESP] = "Delay_Resp", + [RTE_PTP_MSGTYPE_PDELAY_RESP_FU] = "PDelay_Resp_Follow_Up", + [RTE_PTP_MSGTYPE_ANNOUNCE] = "Announce", + [RTE_PTP_MSGTYPE_SIGNALING] = "Signaling", + [RTE_PTP_MSGTYPE_MANAGEMENT] = "Management", + [0xE] = "Reserved_E", + [0xF] = "Reserved_F", +}; + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_ptp_msg_type_str, 26.07) +const char * +rte_ptp_msg_type_str(int msg_type) +{ + if (msg_type < 0 || msg_type > 0xF) + return "Not_PTP"; + return ptp_msg_names[msg_type]; +} diff --git a/lib/ptp/rte_ptp.h b/lib/ptp/rte_ptp.h new file mode 100644 index 0000000000..526d7f3704 --- /dev/null +++ b/lib/ptp/rte_ptp.h @@ -0,0 +1,338 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2026 Intel Corporation + * + * IEEE 1588 / PTP Protocol Library + * + * Provides header structures, packet classification, and helper functions + * for Precision Time Protocol (IEEE 1588-2019) packet processing in DPDK. + * + * Supports: + * - L2 PTP (EtherType 0x88F7) + * - VLAN-tagged L2 PTP (single or double VLAN, TPIDs 0x8100/0x88A8) + * - PTP over UDP/IPv4 (ports 319/320) + * - PTP over UDP/IPv6 (ports 319/320) + * - VLAN-tagged PTP over UDP/IPv4 or UDP/IPv6 + * - Two-step and one-step message identification + * - correctionField manipulation (scaled nanoseconds, 48.16 fixed-point) + * + * Limitations: + * - IPv6 extension headers are not traversed. + */ + +#ifndef _RTE_PTP_H_ +#define _RTE_PTP_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdint.h> +#include <stdbool.h> +#include <rte_byteorder.h> +#include <rte_mbuf.h> +#include <rte_common.h> + +/** + * @file + * IEEE 1588 PTP protocol definitions and helpers. + */ + +/* ============================================================ + * PTP Constants + * ============================================================ + */ + +/** PTP EtherType (IEEE 802.1AS / IEEE 1588) */ +#define RTE_PTP_ETHERTYPE 0x88F7 + +/** PTP over UDP event port (Sync, Delay_Req, PDelay_Req, PDelay_Resp) */ +#define RTE_PTP_EVENT_PORT 319 + +/** PTP over UDP general port (Follow_Up, Delay_Resp, Announce, etc.) */ +#define RTE_PTP_GENERAL_PORT 320 + +/** PTP multicast MAC address: 01:1B:19:00:00:00 */ +#define RTE_PTP_MULTICAST_MAC { 0x01, 0x1B, 0x19, 0x00, 0x00, 0x00 } + +/** PTP peer delay multicast MAC: 01:80:C2:00:00:0E */ +#define RTE_PTP_PDELAY_MULTICAST_MAC { 0x01, 0x80, 0xC2, 0x00, 0x00, 0x0E } + +/* ============================================================ + * PTP Message Types (IEEE 1588-2019 Table 36) + * ============================================================ + */ + +#define RTE_PTP_MSGTYPE_SYNC 0x0 /**< Sync (event) */ +#define RTE_PTP_MSGTYPE_DELAY_REQ 0x1 /**< Delay_Req (event) */ +#define RTE_PTP_MSGTYPE_PDELAY_REQ 0x2 /**< Peer_Delay_Req (event) */ +#define RTE_PTP_MSGTYPE_PDELAY_RESP 0x3 /**< Peer_Delay_Resp (event) */ +#define RTE_PTP_MSGTYPE_FOLLOW_UP 0x8 /**< Follow_Up (general) */ +#define RTE_PTP_MSGTYPE_DELAY_RESP 0x9 /**< Delay_Resp (general) */ +#define RTE_PTP_MSGTYPE_PDELAY_RESP_FU 0xA /**< Peer_Delay_Resp_Follow_Up */ +#define RTE_PTP_MSGTYPE_ANNOUNCE 0xB /**< Announce (general) */ +#define RTE_PTP_MSGTYPE_SIGNALING 0xC /**< Signaling (general) */ +#define RTE_PTP_MSGTYPE_MANAGEMENT 0xD /**< Management (general) */ + +/** Invalid / not a PTP packet */ +#define RTE_PTP_MSGTYPE_INVALID (-1) + +/* ============================================================ + * PTP Flag Field Bits (IEEE 1588-2019 Table 37) + * + * These constants are for use after rte_be_to_cpu_16(hdr->flags). + * flagField[0] (octet 6) maps to host bits 8-15. + * flagField[1] (octet 7) maps to host bits 0-7. + * ============================================================ + */ + +#define RTE_PTP_FLAG_TWO_STEP (1 << 9) /**< Two-step flag (flagField[0] bit 1) */ +#define RTE_PTP_FLAG_UNICAST (1 << 10) /**< Unicast flag (flagField[0] bit 2) */ +#define RTE_PTP_FLAG_LI_61 (1 << 0) /**< Leap indicator 61 (flagField[1] bit 0) */ +#define RTE_PTP_FLAG_LI_59 (1 << 1) /**< Leap indicator 59 (flagField[1] bit 1) */ + +/* ============================================================ + * PTP Header Structures (IEEE 1588-2019) + * ============================================================ + */ + +/** + * PTP Port Identity (10 bytes). + */ +struct __rte_packed_begin rte_ptp_port_id { + uint8_t clock_id[8]; /**< clockIdentity (EUI-64) */ + uint16_t port_number; /**< portNumber */ +} __rte_packed_end; + +/** + * PTP Common Message Header (34 bytes). + * All PTP messages begin with this header. + */ +struct __rte_packed_begin rte_ptp_hdr { + uint8_t msg_type; /**< transportSpecific (4) | messageType (4) */ + uint8_t version; /**< reserved (4) | versionPTP (4) */ + uint16_t msg_length; /**< Total message length in bytes */ + uint8_t domain_number; /**< PTP domain (0-255) */ + uint8_t minor_sdo_id; /**< minorSdoId (IEEE 1588-2019) */ + uint16_t flags; /**< Flag field (see RTE_PTP_FLAG_*) */ + int64_t correction; /**< correctionField (scaled ns, 48.16 fixed) */ + uint32_t msg_type_specific; /**< messageTypeSpecific */ + struct rte_ptp_port_id source_port_id; /**< sourcePortIdentity */ + uint16_t sequence_id; /**< sequenceId */ + uint8_t control; /**< controlField (deprecated in 1588-2019) */ + int8_t log_msg_interval; /**< logMessageInterval */ +} __rte_packed_end; + +/** + * PTP Timestamp (10 bytes, used in Sync/Delay_Req/Follow_Up bodies). + */ +struct __rte_packed_begin rte_ptp_timestamp { + uint16_t seconds_hi; /**< Upper 16 bits of seconds */ + uint32_t seconds_lo; /**< Lower 32 bits of seconds */ + uint32_t nanoseconds; /**< Nanoseconds (0-999999999) */ +} __rte_packed_end; + +/* ============================================================ + * Inline Helpers + * ============================================================ + */ + +/** + * Extract PTP message type from header. + * + * @param hdr + * Pointer to PTP header. + * @return + * Message type (0x0-0xF). + */ +static inline uint8_t +rte_ptp_msg_type(const struct rte_ptp_hdr *hdr) +{ + return hdr->msg_type & 0x0F; +} + +/** + * Extract transport-specific field from header. + * + * @param hdr + * Pointer to PTP header. + * @return + * Transport-specific value (upper nibble, 0x0-0xF). + */ +static inline uint8_t +rte_ptp_transport_specific(const struct rte_ptp_hdr *hdr) +{ + return (hdr->msg_type >> 4) & 0x0F; +} + +/** + * Extract PTP version from header. + * + * @param hdr + * Pointer to PTP header. + * @return + * PTP version number (typically 2). + */ +static inline uint8_t +rte_ptp_version(const struct rte_ptp_hdr *hdr) +{ + return hdr->version & 0x0F; +} + +/** + * Get sequence ID from PTP header (host byte order). + * + * @param hdr + * Pointer to PTP header. + * @return + * Sequence ID in host byte order. + */ +static inline uint16_t +rte_ptp_seq_id(const struct rte_ptp_hdr *hdr) +{ + return rte_be_to_cpu_16(hdr->sequence_id); +} + +/** + * Get PTP domain number. + * + * @param hdr + * Pointer to PTP header. + * @return + * Domain number (0-255). + */ +static inline uint8_t +rte_ptp_domain(const struct rte_ptp_hdr *hdr) +{ + return hdr->domain_number; +} + +/** + * Check if PTP message type is an event message. + * Event messages (msg_type 0x0-0x3) require hardware timestamps. + * + * @param msg_type + * PTP message type value (0x0-0xF). + * @return + * true if event message, false otherwise. + */ +static inline bool +rte_ptp_is_event(int msg_type) +{ + return msg_type >= 0 && msg_type <= RTE_PTP_MSGTYPE_PDELAY_RESP; +} + +/** + * Check if the two-step flag is set in a PTP header. + * + * @param hdr + * Pointer to PTP header. + * @return + * true if two-step flag is set. + */ +static inline bool +rte_ptp_is_two_step(const struct rte_ptp_hdr *hdr) +{ + return (rte_be_to_cpu_16(hdr->flags) & RTE_PTP_FLAG_TWO_STEP) != 0; +} + +/** + * Get correctionField value in nanoseconds (from 48.16 fixed-point). + * + * @param hdr + * Pointer to PTP header. + * @return + * Correction value in nanoseconds. + */ +static inline int64_t +rte_ptp_correction_ns(const struct rte_ptp_hdr *hdr) +{ + return (int64_t)rte_be_to_cpu_64(hdr->correction) >> 16; +} + +/** + * Add a residence time (in nanoseconds) to the correctionField. + * Used by Transparent Clocks to account for relay transit delay. + * The correctionField uses IEEE 1588 scaled nanoseconds (48.16 fixed-point). + * + * @param hdr + * Pointer to PTP header (will be modified in-place). + * @param residence_ns + * Residence time in nanoseconds to add. + */ +static inline void +rte_ptp_add_correction(struct rte_ptp_hdr *hdr, int64_t residence_ns) +{ + int64_t cf = (int64_t)rte_be_to_cpu_64(hdr->correction); + cf += (int64_t)((uint64_t)residence_ns << 16); + hdr->correction = rte_cpu_to_be_64(cf); +} + +/** + * Convert a PTP timestamp structure to nanoseconds since epoch. + * + * @param ts + * Pointer to PTP timestamp. + * @return + * Time in nanoseconds since epoch. + */ +static inline uint64_t +rte_ptp_timestamp_to_ns(const struct rte_ptp_timestamp *ts) +{ + uint64_t sec = ((uint64_t)rte_be_to_cpu_16(ts->seconds_hi) << 32) | + rte_be_to_cpu_32(ts->seconds_lo); + return sec * 1000000000ULL + rte_be_to_cpu_32(ts->nanoseconds); +} + +/* ============================================================ + * Packet Classification Functions (implemented in rte_ptp.c) + * ============================================================ + */ + +/** + * Classify a packet as PTP and return the message type. + * + * Examines the mbuf to determine if it contains a PTP message. + * Supports L2 (EtherType 0x88F7), VLAN-tagged L2 (single/double, + * TPIDs 0x8100/0x88A8), PTP over UDP/IPv4, PTP over UDP/IPv6, + * and VLAN-tagged UDP/IPv4 or UDP/IPv6 (ports 319/320). + * + * @param m + * Pointer to the mbuf to classify. + * @return + * PTP message type (0x0-0xF) on success, RTE_PTP_MSGTYPE_INVALID (-1) + * if the packet is not PTP. + */ +__rte_experimental +int rte_ptp_classify(const struct rte_mbuf *m); + +/** + * Get a pointer to the PTP header inside an mbuf. + * + * Locates the PTP header within the packet, handling L2, VLAN-tagged L2 + * (single/double, TPIDs 0x8100/0x88A8), PTP over UDP/IPv4, + * PTP over UDP/IPv6, and VLAN-tagged UDP variants. + * + * @param m + * Pointer to the mbuf. + * @return + * Pointer to the PTP header, or NULL if not a PTP packet. + */ +__rte_experimental +struct rte_ptp_hdr *rte_ptp_hdr_get(struct rte_mbuf *m); + +/** + * Get a human-readable name for a PTP message type. + * + * @param msg_type + * PTP message type (0x0-0xF or RTE_PTP_MSGTYPE_INVALID). + * @return + * Static string with the message type name (e.g., "Sync", "Follow_Up"). + */ +__rte_experimental +const char *rte_ptp_msg_type_str(int msg_type); + +#ifdef __cplusplus +} +#endif + +#endif /* _RTE_PTP_H_ */ -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [RFC v2 2/6] doc: add PTP library programmer's guide 2026-04-28 22:28 ` [RFC v2 0/6] introduce PTP protocol library and software relay Rajesh Kumar 2026-04-28 22:28 ` [RFC v2 1/6] ptp: introduce PTP protocol library Rajesh Kumar @ 2026-04-28 22:28 ` Rajesh Kumar 2026-04-28 22:28 ` [RFC v2 3/6] examples/ptp_tap_relay_sw: add software PTP relay example Rajesh Kumar ` (4 subsequent siblings) 6 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-04-28 22:28 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, Rajesh Kumar [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #1: Type: text/plain; charset=yes, Size: 9532 bytes --] Add programmer's guide for the PTP protocol library covering message types, header structures, packet classification API, inline helpers, and usage examples. Add PTP header to Doxygen API index under layers section. Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- doc/api/doxy-api-index.md | 1 + doc/api/doxy-api.conf.in | 1 + doc/guides/prog_guide/index.rst | 1 + doc/guides/prog_guide/ptp_lib.rst | 205 ++++++++++++++++++++++++++++++ 4 files changed, 208 insertions(+) create mode 100644 doc/guides/prog_guide/ptp_lib.rst diff --git a/doc/api/doxy-api-index.md b/doc/api/doxy-api-index.md index 9296042119..bbc79168bb 100644 --- a/doc/api/doxy-api-index.md +++ b/doc/api/doxy-api-index.md @@ -137,6 +137,7 @@ The public API headers are grouped by topics: [eCPRI](@ref rte_ecpri.h), [PDCP hdr](@ref rte_pdcp_hdr.h), [PDCP](@ref rte_pdcp.h), + [PTP](@ref rte_ptp.h), [L2TPv2](@ref rte_l2tpv2.h), [PPP](@ref rte_ppp.h), [IB](@ref rte_ib.h) diff --git a/doc/api/doxy-api.conf.in b/doc/api/doxy-api.conf.in index bedd944681..f15d962733 100644 --- a/doc/api/doxy-api.conf.in +++ b/doc/api/doxy-api.conf.in @@ -72,6 +72,7 @@ INPUT = @TOPDIR@/doc/api/doxy-api-index.md \ @TOPDIR@/lib/pmu \ @TOPDIR@/lib/port \ @TOPDIR@/lib/power \ + @TOPDIR@/lib/ptp \ @TOPDIR@/lib/ptr_compress \ @TOPDIR@/lib/rawdev \ @TOPDIR@/lib/rcu \ diff --git a/doc/guides/prog_guide/index.rst b/doc/guides/prog_guide/index.rst index e6f24945b0..60dad4475c 100644 --- a/doc/guides/prog_guide/index.rst +++ b/doc/guides/prog_guide/index.rst @@ -97,6 +97,7 @@ Protocol Processing Libraries :maxdepth: 1 :numbered: + ptp_lib pdcp_lib ipsec_lib diff --git a/doc/guides/prog_guide/ptp_lib.rst b/doc/guides/prog_guide/ptp_lib.rst new file mode 100644 index 0000000000..3f2e80a9e2 --- /dev/null +++ b/doc/guides/prog_guide/ptp_lib.rst @@ -0,0 +1,205 @@ +.. SPDX-License-Identifier: BSD-3-Clause + Copyright(c) 2026 Intel Corporation. + +PTP Protocol Library +==================== + +The DPDK PTP library provides IEEE 1588 / Precision Time Protocol (PTP) +packet structures, constants, and helper functions for PTP packet processing. + +The library supports classification and header parsing of PTP messages +across multiple transport encapsulations: + +- L2 PTP (EtherType 0x88F7) +- VLAN-tagged L2 PTP (single or double VLAN, TPIDs 0x8100 and 0x88A8) +- PTP over UDP/IPv4 (destination ports 319 and 320) +- PTP over UDP/IPv6 (destination ports 319 and 320) +- VLAN-tagged PTP over UDP/IPv4 or UDP/IPv6 + +The library conforms to +`IEEE 1588-2019 <https://standards.ieee.org/ieee/1588/6825/>`_ +(Precision Time Protocol). + +Overview +-------- + +PTP is the foundation of time synchronization in networking. +DPDK applications that relay, classify, or timestamp PTP packets +currently duplicate header definitions and parsing logic. +This library provides a shared, tested implementation. + +The library provides: + +#. Packed header structures matching the IEEE 1588-2019 wire format +#. Constants for message types, flags, ports, and multicast addresses +#. Inline helpers for common field extraction and manipulation +#. Packet classification across L2, VLAN, UDP/IPv4, and UDP/IPv6 transports +#. Correction field manipulation for Transparent Clock residence time + +PTP Message Types +----------------- + +IEEE 1588-2019 defines the following message types, all supported by the library: + +.. csv-table:: PTP Message Types + :header: "Type", "Name", "Category", "Macro" + :widths: 5, 20, 10, 30 + + "0x0", "Sync", "Event", "``RTE_PTP_MSGTYPE_SYNC``" + "0x1", "Delay_Req", "Event", "``RTE_PTP_MSGTYPE_DELAY_REQ``" + "0x2", "Peer_Delay_Req", "Event", "``RTE_PTP_MSGTYPE_PDELAY_REQ``" + "0x3", "Peer_Delay_Resp", "Event", "``RTE_PTP_MSGTYPE_PDELAY_RESP``" + "0x8", "Follow_Up", "General", "``RTE_PTP_MSGTYPE_FOLLOW_UP``" + "0x9", "Delay_Resp", "General", "``RTE_PTP_MSGTYPE_DELAY_RESP``" + "0xA", "PDelay_Resp_Follow_Up", "General", "``RTE_PTP_MSGTYPE_PDELAY_RESP_FU``" + "0xB", "Announce", "General", "``RTE_PTP_MSGTYPE_ANNOUNCE``" + "0xC", "Signaling", "General", "``RTE_PTP_MSGTYPE_SIGNALING``" + "0xD", "Management", "General", "``RTE_PTP_MSGTYPE_MANAGEMENT``" + +Event messages (types 0x0–0x3) require hardware timestamps for accurate +time transfer. + +Header Structures +----------------- + +The library defines the following packed structures that map directly to +the IEEE 1588-2019 wire format: + +``struct rte_ptp_hdr`` + Common PTP message header (34 bytes). All PTP messages begin with this header. + Contains message type, version, flags, correction field, source port identity, + sequence ID, and log message interval. + +``struct rte_ptp_timestamp`` + PTP timestamp (10 bytes). Used in Sync, Delay_Req, and Follow_Up message bodies. + Contains seconds (48-bit) and nanoseconds (32-bit). + +``struct rte_ptp_port_id`` + PTP port identity (10 bytes). Contains an EUI-64 clock identity and a + 16-bit port number. + +Packet Classification API +-------------------------- + +``rte_ptp_classify()`` +~~~~~~~~~~~~~~~~~~~~~~ + +Classify a packet and return the PTP message type. + +.. code-block:: C + + int rte_ptp_classify(const struct rte_mbuf *m); + +Examines the mbuf to determine if it contains a PTP message. +Returns the PTP message type (0x0–0xF) on success, +or ``RTE_PTP_MSGTYPE_INVALID`` (-1) if the packet is not PTP. + +Supported encapsulations (VLAN TPIDs recognised: 0x8100 and 0x88A8): + +- EtherType 0x88F7 (L2 PTP) +- Single VLAN (0x8100 or 0x88A8) + EtherType 0x88F7 +- Double VLAN (any combination of 0x8100 / 0x88A8) + EtherType 0x88F7 +- IPv4 + UDP destination port 319 or 320 +- IPv6 + UDP destination port 319 or 320 +- Single or double VLAN + IPv4/IPv6 + UDP destination port 319 or 320 + +``rte_ptp_hdr_get()`` +~~~~~~~~~~~~~~~~~~~~~ + +Get a pointer to the PTP header inside a packet. + +.. code-block:: C + + struct rte_ptp_hdr *rte_ptp_hdr_get(const struct rte_mbuf *m); + +Returns a pointer to the PTP header, or NULL if the packet is not PTP. +Handles the same set of encapsulations as ``rte_ptp_classify()``. + +``rte_ptp_msg_type_str()`` +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Convert a PTP message type to a human-readable string. + +.. code-block:: C + + const char *rte_ptp_msg_type_str(int msg_type); + +Returns a string such as ``"Sync"``, ``"Delay_Req"``, ``"Follow_Up"``, etc. +Returns ``"Not_PTP"`` for invalid message types. + +Inline Helpers +-------------- + +The following inline functions operate on ``struct rte_ptp_hdr`` and require +no function call overhead: + +.. csv-table:: Inline Helper Functions + :header: "Function", "Returns", "Description" + :widths: 30, 15, 40 + + "``rte_ptp_msg_type()``", "``uint8_t``", "Extract message type (lower nibble)" + "``rte_ptp_transport_specific()``", "``uint8_t``", "Extract transport-specific field (upper nibble)" + "``rte_ptp_version()``", "``uint8_t``", "Extract PTP version number" + "``rte_ptp_seq_id()``", "``uint16_t``", "Get sequence ID in host byte order" + "``rte_ptp_domain()``", "``uint8_t``", "Get PTP domain number" + "``rte_ptp_is_event()``", "``bool``", "Check if message type is an event (0x0–0x3)" + "``rte_ptp_is_two_step()``", "``bool``", "Check if two-step flag is set" + "``rte_ptp_correction_ns()``", "``int64_t``", "Get correctionField in nanoseconds (from 48.16 fixed-point)" + "``rte_ptp_add_correction()``", "``void``", "Add residence time to correctionField (for Transparent Clocks)" + "``rte_ptp_timestamp_to_ns()``", "``uint64_t``", "Convert PTP timestamp struct to nanoseconds" + +Usage Example +------------- + +Classifying and processing PTP packets: + +.. code-block:: C + + #include <rte_ptp.h> + + void process_packets(struct rte_mbuf **pkts, uint16_t nb_pkts) + { + for (uint16_t i = 0; i < nb_pkts; i++) { + int ptp_type = rte_ptp_classify(pkts[i]); + if (ptp_type == RTE_PTP_MSGTYPE_INVALID) + continue; + + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(pkts[i]); + + printf("PTP %s seq=%u domain=%u\n", + rte_ptp_msg_type_str(ptp_type), + rte_ptp_seq_id(hdr), + rte_ptp_domain(hdr)); + + if (rte_ptp_is_event(ptp_type)) { + /* Event message — needs hardware timestamp */ + } + } + } + +.. note:: + + ``rte_ptp_classify()`` and ``rte_ptp_hdr_get()`` both parse the packet + internally. When the caller needs both the message type and a header + pointer, calling ``rte_ptp_hdr_get()`` alone and then using + ``rte_ptp_msg_type()`` on the returned header avoids parsing the + packet twice. + +Adding residence time for a Transparent Clock: + +.. code-block:: C + + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(pkt); + if (hdr != NULL) { + int64_t residence_ns = egress_ts - ingress_ts; + rte_ptp_add_correction(hdr, residence_ns); + } + +Limitations +----------- + +- IPv6 extension headers are not traversed. Only the base IPv6 header + with ``next_header == UDP`` is handled. +- Multi-segment mbufs are not supported. PTP event messages are + typically less than 128 bytes and fit in a single segment. +- PTP over MPLS or other tunneling protocols is not supported. -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [RFC v2 3/6] examples/ptp_tap_relay_sw: add software PTP relay example 2026-04-28 22:28 ` [RFC v2 0/6] introduce PTP protocol library and software relay Rajesh Kumar 2026-04-28 22:28 ` [RFC v2 1/6] ptp: introduce PTP protocol library Rajesh Kumar 2026-04-28 22:28 ` [RFC v2 2/6] doc: add PTP library programmer's guide Rajesh Kumar @ 2026-04-28 22:28 ` Rajesh Kumar 2026-04-28 22:28 ` [RFC v2 4/6] doc: add PTP software relay sample app guide Rajesh Kumar ` (3 subsequent siblings) 6 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-04-28 22:28 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, Rajesh Kumar [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #1: Type: text/plain; charset=yes, Size: 16411 bytes --] Add a minimal PTP Transparent Clock relay that bridges PTP packets between a DPDK-bound physical NIC and a kernel TAP interface using software timestamps only. The relay uses the rte_ptp library for packet classification and correctionField manipulation. For each PTP event message, it records software timestamps (CLOCK_MONOTONIC_RAW) at ingress and egress, then adds the residence time to the correctionField per IEEE 1588-2019 section 10.2. A two-pass design is used: first all packets are classified and PTP header pointers saved, then a single TX timestamp is taken immediately before applying corrections and calling rte_eth_tx_burst(). This minimises the gap between the measured timestamp and actual wire egress. Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- doc/guides/rel_notes/release_26_07.rst | 6 + examples/meson.build | 1 + examples/ptp_tap_relay_sw/Makefile | 41 ++ examples/ptp_tap_relay_sw/meson.build | 14 + examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c | 433 +++++++++++++++++++ 5 files changed, 495 insertions(+) create mode 100644 examples/ptp_tap_relay_sw/Makefile create mode 100644 examples/ptp_tap_relay_sw/meson.build create mode 100644 examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c diff --git a/doc/guides/rel_notes/release_26_07.rst b/doc/guides/rel_notes/release_26_07.rst index b2208d6fb3..5c2329d867 100644 --- a/doc/guides/rel_notes/release_26_07.rst +++ b/doc/guides/rel_notes/release_26_07.rst @@ -70,6 +70,12 @@ New Features parsing, and correctionField manipulation across L2, VLAN-tagged, UDP/IPv4, and UDP/IPv6 transports. +* **Added PTP software relay example application.** + + Added a new example application ``ptp_tap_relay_sw`` demonstrating a + software PTP Transparent Clock relay between a DPDK port and a kernel + TAP interface. + Removed Items ------------- diff --git a/examples/meson.build b/examples/meson.build index 25d9c88457..ab82cf5fbd 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -43,6 +43,7 @@ all_examples = [ 'packet_ordering', 'pipeline', 'ptpclient', + 'ptp_tap_relay_sw', 'qos_meter', 'qos_sched', 'rxtx_callbacks', diff --git a/examples/ptp_tap_relay_sw/Makefile b/examples/ptp_tap_relay_sw/Makefile new file mode 100644 index 0000000000..fd178f46ae --- /dev/null +++ b/examples/ptp_tap_relay_sw/Makefile @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2026 Intel Corporation + +# binary name +APP = dpdk-ptp_tap_relay_sw + +# all source are stored in SRCS-y +SRCS-y := ptp_tap_relay_sw.c + +PKGCONF ?= pkg-config + +# Build using pkg-config variables if possible +ifneq ($(shell $(PKGCONF) --exists libdpdk && echo 0),0) +$(error "no installation of DPDK found") +endif + +all: shared +.PHONY: shared static +shared: build/$(APP)-shared + ln -sf $(APP)-shared build/$(APP) +static: build/$(APP)-static + ln -sf $(APP)-static build/$(APP) + +PC_FILE := $(shell $(PKGCONF) --path libdpdk 2>/dev/null) +CFLAGS += -O3 $(shell $(PKGCONF) --cflags libdpdk) +LDFLAGS_SHARED = $(shell $(PKGCONF) --libs libdpdk) +LDFLAGS_STATIC = $(shell $(PKGCONF) --static --libs libdpdk) + +build/$(APP)-shared: $(SRCS-y) Makefile $(PC_FILE) | build + $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_SHARED) + +build/$(APP)-static: $(SRCS-y) Makefile $(PC_FILE) | build + $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_STATIC) + +build: + @mkdir -p $@ + +.PHONY: clean +clean: + rm -f build/$(APP) build/$(APP)-static build/$(APP)-shared + test -d build && rmdir -p build || true diff --git a/examples/ptp_tap_relay_sw/meson.build b/examples/ptp_tap_relay_sw/meson.build new file mode 100644 index 0000000000..56abad02ed --- /dev/null +++ b/examples/ptp_tap_relay_sw/meson.build @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2026 Intel Corporation + +# meson file, for building this example as part of a main DPDK build. +# +# To build this example as a standalone application with an already-installed +# DPDK instance, use 'make' + +allow_experimental_apis = true +sources = files( + 'ptp_tap_relay_sw.c', +) +deps += ['net', 'ptp'] +cflags += no_shadow_cflag diff --git a/examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c b/examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c new file mode 100644 index 0000000000..eb674e49b9 --- /dev/null +++ b/examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c @@ -0,0 +1,433 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2026 Intel Corporation + */ + +/* + * PTP Software Relay — lib/ptp usage example + * + * A minimal PTP relay between a DPDK-bound physical NIC and a kernel + * TAP interface using software timestamps only. No patched TUN driver + * or ptp_proxy kernel module is required. + * + * How it works: + * 1. Physical NIC receives PTP (and non-PTP) packets via DPDK RX. + * 2. For PTP event messages (Sync, Delay_Req, PDelay_Req, PDelay_Resp) + * the relay records an RX software timestamp (clock_gettime). + * 3. Just before TX on the other side it records a TX software timestamp. + * 4. The relay residence time (tx_ts − rx_ts) is added to the PTP + * correctionField via rte_ptp_add_correction() — standard + * Transparent Clock behaviour (IEEE 1588-2019 §10.2). + * 5. Packets are forwarded bi-directionally: + * PHY → TAP (network → ptp4l) + * TAP → PHY (ptp4l → network) + * + * ptp4l runs in software-timestamping mode on the TAP interface: + * + * ptp4l -i dtap0 -m -s -S # -S = software timestamps + * + * Topology: + * + * Time Transmitter (remote) ──L2── Physical NIC (DPDK) + * │ + * PTP SW Relay ← correctionField update + * │ + * TAP (kernel) ── ptp4l -S (time receiver) + * + * Usage: + * dpdk-ptp_tap_relay_sw -l 0-1 --vdev=net_tap0,iface=dtap0 -- \ + * -p 0 -t 1 + * + * Parameters: + * -p PORT Physical NIC port ID (default: 0) + * -t PORT TAP port ID (default: 1) + * -T SECS Stats print interval in seconds (default: 10) + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <stdbool.h> +#include <signal.h> +#include <getopt.h> +#include <time.h> + +#include <rte_eal.h> +#include <rte_ethdev.h> +#include <rte_mbuf.h> +#include <rte_cycles.h> +#include <rte_lcore.h> +#include <rte_ptp.h> + +/* Ring sizes */ +#define RX_RING_SIZE 1024 +#define TX_RING_SIZE 1024 + +/* Mempool */ +#define NUM_MBUFS 8191 +#define MBUF_CACHE 250 +#define BURST_SIZE 32 + +#define NSEC_PER_SEC 1000000000ULL + +/* Logging helpers */ +#define LOG_INFO(fmt, ...) \ + fprintf(stdout, "[PTP-SW] " fmt "\n", ##__VA_ARGS__) +#define LOG_ERR(fmt, ...) \ + fprintf(stderr, "[PTP-SW ERROR] " fmt "\n", ##__VA_ARGS__) + +static volatile bool force_quit; + +/* Port IDs */ +static uint16_t phy_port; +static uint16_t tap_port = 1; +static unsigned int stats_interval = 10; /* seconds */ + +/* Statistics */ +static struct { + uint64_t phy_rx; /* total packets from PHY */ + uint64_t phy_rx_ptp; /* PTP packets from PHY */ + uint64_t tap_tx; /* packets forwarded to TAP */ + uint64_t tap_rx; /* total packets from TAP */ + uint64_t tap_rx_ptp; /* PTP packets from TAP */ + uint64_t phy_tx; /* packets forwarded to PHY */ + uint64_t corrections; /* correctionField updates */ +} stats; + +static void +signal_handler(int signum) +{ + if (signum == SIGINT || signum == SIGTERM) { + LOG_INFO("Signal %d received, shutting down...", signum); + force_quit = true; + } +} + +/* Helpers */ + +/* Read monotonic clock in nanoseconds (for residence time). */ +static inline uint64_t +sw_timestamp_ns(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC_RAW, &ts); + return (uint64_t)ts.tv_sec * NSEC_PER_SEC + (uint64_t)ts.tv_nsec; +} + +/* Port Init */ + +static int +port_init(uint16_t port, struct rte_mempool *mp) +{ + struct rte_eth_conf port_conf; + struct rte_eth_dev_info dev_info; + uint16_t nb_rxd = RX_RING_SIZE; + uint16_t nb_txd = TX_RING_SIZE; + int ret; + + memset(&port_conf, 0, sizeof(port_conf)); + + ret = rte_eth_dev_info_get(port, &dev_info); + if (ret != 0) { + LOG_ERR("rte_eth_dev_info_get(port %u) failed: %d", port, ret); + return ret; + } + + if (dev_info.tx_offload_capa & RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE) + port_conf.txmode.offloads |= + RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE; + + ret = rte_eth_dev_configure(port, 1, 1, &port_conf); + if (ret != 0) + return ret; + + ret = rte_eth_dev_adjust_nb_rx_tx_desc(port, &nb_rxd, &nb_txd); + if (ret != 0) + return ret; + + ret = rte_eth_rx_queue_setup(port, 0, nb_rxd, + rte_eth_dev_socket_id(port), NULL, mp); + if (ret < 0) + return ret; + + ret = rte_eth_tx_queue_setup(port, 0, nb_txd, + rte_eth_dev_socket_id(port), NULL); + if (ret < 0) + return ret; + + ret = rte_eth_dev_start(port); + if (ret < 0) + return ret; + + ret = rte_eth_promiscuous_enable(port); + if (ret != 0) { + LOG_ERR("Failed to enable promiscuous on port %u: %s", + port, rte_strerror(-ret)); + return ret; + } + + return 0; +} + +/* Relay one direction */ + +/* + * Forward packets from src_port to dst_port. + * For PTP event messages, record SW timestamps around the + * relay path and add the residence time to the correctionField. + * + * This implements a Transparent Clock (IEEE 1588-2019 §10.2): + * correctionField += (t_egress − t_ingress) + * + * Note: a single rx_ts / tx_ts pair is used for the entire burst. + * At typical PTP rates (logSyncInterval >= -4, i.e. <= 16 pkt/s) + * bursts contain at most one packet, so this is exact. At higher + * rates, early packets in a burst are slightly under-corrected and + * late ones over-corrected by up to one poll-loop iteration. + */ +static void +relay_burst(uint16_t src_port, uint16_t dst_port, + uint64_t *rx_cnt, uint64_t *rx_ptp_cnt, + uint64_t *tx_cnt) +{ + struct rte_mbuf *bufs[BURST_SIZE]; + struct rte_ptp_hdr *ptp_hdrs[BURST_SIZE]; + uint64_t rx_ts; + uint16_t nb_rx, nb_tx, i; + + nb_rx = rte_eth_rx_burst(src_port, 0, bufs, BURST_SIZE); + if (nb_rx == 0) + return; + + /* Record a single RX software timestamp for the whole burst. + * All packets in one burst arrived at essentially the same instant + * from rte_eth_rx_burst()'s perspective. + */ + rx_ts = sw_timestamp_ns(); + + *rx_cnt += nb_rx; + + /* + * Pass 1: Classify packets and remember PTP event header pointers. + * This avoids taking the TX timestamp too early — we want it as + * close to the actual rte_eth_tx_burst() call as possible. + */ + memset(ptp_hdrs, 0, sizeof(ptp_hdrs[0]) * nb_rx); + for (i = 0; i < nb_rx; i++) { + int ptp_type = rte_ptp_classify(bufs[i]); + + if (ptp_type == RTE_PTP_MSGTYPE_INVALID) + continue; + + (*rx_ptp_cnt)++; + + /* Only event messages carry timestamps that need correction */ + if (!rte_ptp_is_event(ptp_type)) + continue; + + ptp_hdrs[i] = rte_ptp_hdr_get(bufs[i]); + } + + /* + * Pass 2: Take a single TX timestamp right before transmission. + * This minimises the gap between the measured tx_ts and the + * actual kernel write inside rte_eth_tx_burst(), giving the + * most accurate residence time we can achieve with SW timestamps. + * + * residence_time = tx_ts − rx_ts + * + * Remaining untracked delays: + * - Pre-RX: NIC DMA → rx_burst return (~1-5 µs, unavoidable) + * - Post-TX: tx_ts → kernel TAP write (~1-2 µs) + * Both are symmetric for Sync and Delay_Req so they largely + * cancel in the ptp4l offset calculation. + */ + uint64_t tx_ts = sw_timestamp_ns(); + int64_t residence_ns = (int64_t)(tx_ts - rx_ts); + + for (i = 0; i < nb_rx; i++) { + if (ptp_hdrs[i] == NULL) + continue; + rte_ptp_add_correction(ptp_hdrs[i], residence_ns); + stats.corrections++; + } + + /* Forward the burst */ + nb_tx = rte_eth_tx_burst(dst_port, 0, bufs, nb_rx); + *tx_cnt += nb_tx; + + /* Free any unsent packets */ + for (i = nb_tx; i < nb_rx; i++) + rte_pktmbuf_free(bufs[i]); +} + +/* Print statistics */ + +static void +print_stats(void) +{ + LOG_INFO("=== Statistics ==="); + LOG_INFO(" PHY RX total: %"PRIu64, stats.phy_rx); + LOG_INFO(" PHY RX PTP: %"PRIu64, stats.phy_rx_ptp); + LOG_INFO(" TAP TX: %"PRIu64, stats.tap_tx); + LOG_INFO(" TAP RX total: %"PRIu64, stats.tap_rx); + LOG_INFO(" TAP RX PTP: %"PRIu64, stats.tap_rx_ptp); + LOG_INFO(" PHY TX: %"PRIu64, stats.phy_tx); + LOG_INFO(" Corrections: %"PRIu64, stats.corrections); +} + +/* Main relay loop */ + +static int +relay_loop(__rte_unused void *arg) +{ + uint64_t last_stats = rte_rdtsc(); + uint64_t stats_tsc = rte_get_tsc_hz() * stats_interval; + + LOG_INFO("Relay loop started on lcore %u", rte_lcore_id()); + LOG_INFO(" PHY port %u <--> TAP port %u", phy_port, tap_port); + LOG_INFO(" Correction field updates: enabled for event messages"); + + while (!force_quit) { + /* PHY → TAP */ + relay_burst(phy_port, tap_port, + &stats.phy_rx, &stats.phy_rx_ptp, + &stats.tap_tx); + + /* TAP → PHY */ + relay_burst(tap_port, phy_port, + &stats.tap_rx, &stats.tap_rx_ptp, + &stats.phy_tx); + + /* Periodic stats */ + if (rte_rdtsc() - last_stats > stats_tsc) { + print_stats(); + last_stats = rte_rdtsc(); + } + } + + print_stats(); + return 0; +} + +/* Argument parsing */ + +static void +usage(const char *prog) +{ + fprintf(stderr, + "Usage: %s [EAL options] -- [options]\n" + " -p PORT Physical NIC port ID (default: 0)\n" + " -t PORT TAP port ID (default: 1)\n" + " -T SECS Stats interval in seconds (default: 10)\n" + "\n" + "Example:\n" + " %s -l 0-1 --vdev=net_tap0,iface=dtap0 -- -p 0 -t 1\n" + "\n" + "Then run ptp4l with software timestamps:\n" + " ptp4l -i dtap0 -m -s -S\n", + prog, prog); +} + +static int +parse_args(int argc, char **argv) +{ + int opt; + + while ((opt = getopt(argc, argv, "p:t:T:h")) != -1) { + switch (opt) { + case 'p': + phy_port = (uint16_t)atoi(optarg); + break; + case 't': + tap_port = (uint16_t)atoi(optarg); + break; + case 'T': + stats_interval = (unsigned int)atoi(optarg); + break; + case 'h': + default: + usage(argv[0]); + return -1; + } + } + + return 0; +} + +/* Main */ + +int +main(int argc, char **argv) +{ + struct rte_mempool *mp; + uint16_t nb_ports; + int ret; + + /* EAL init */ + ret = rte_eal_init(argc, argv); + if (ret < 0) + rte_exit(EXIT_FAILURE, "EAL init failed\n"); + argc -= ret; + argv += ret; + + /* App args */ + ret = parse_args(argc, argv); + if (ret < 0) + rte_exit(EXIT_FAILURE, "Invalid arguments\n"); + + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + + nb_ports = rte_eth_dev_count_avail(); + if (nb_ports < 2) + rte_exit(EXIT_FAILURE, + "Need at least 2 ports (PHY + TAP).\n" + "Use --vdev=net_tap0,iface=dtap0\n"); + + if (!rte_eth_dev_is_valid_port(phy_port)) + rte_exit(EXIT_FAILURE, "Invalid PHY port %u\n", phy_port); + if (!rte_eth_dev_is_valid_port(tap_port)) + rte_exit(EXIT_FAILURE, "Invalid TAP port %u\n", tap_port); + + mp = rte_pktmbuf_pool_create("MBUF_POOL", NUM_MBUFS * nb_ports, + MBUF_CACHE, 0, + RTE_MBUF_DEFAULT_BUF_SIZE, + rte_socket_id()); + if (mp == NULL) + rte_exit(EXIT_FAILURE, "Cannot create mbuf pool\n"); + + LOG_INFO("Initializing PHY port %u...", phy_port); + ret = port_init(phy_port, mp); + if (ret != 0) + rte_exit(EXIT_FAILURE, "Cannot init PHY port %u (%d)\n", + phy_port, ret); + + LOG_INFO("Initializing TAP port %u...", tap_port); + ret = port_init(tap_port, mp); + if (ret != 0) + rte_exit(EXIT_FAILURE, "Cannot init TAP port %u (%d)\n", + tap_port, ret); + + LOG_INFO("PTP Software Relay ready"); + LOG_INFO(" PHY port: %u", phy_port); + LOG_INFO(" TAP port: %u", tap_port); + LOG_INFO(" Stats every: %u seconds", stats_interval); + LOG_INFO(" Correction: Transparent Clock (SW timestamps)"); + LOG_INFO(""); + LOG_INFO("Run ptp4l: ptp4l -i dtap0 -m -s -S"); + + /* Run relay on main lcore */ + relay_loop(NULL); + + /* Cleanup */ + LOG_INFO("Stopping ports..."); + rte_eth_dev_stop(phy_port); + rte_eth_dev_stop(tap_port); + rte_eth_dev_close(phy_port); + rte_eth_dev_close(tap_port); + rte_eal_cleanup(); + + return 0; +} -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [RFC v2 4/6] doc: add PTP software relay sample app guide 2026-04-28 22:28 ` [RFC v2 0/6] introduce PTP protocol library and software relay Rajesh Kumar ` (2 preceding siblings ...) 2026-04-28 22:28 ` [RFC v2 3/6] examples/ptp_tap_relay_sw: add software PTP relay example Rajesh Kumar @ 2026-04-28 22:28 ` Rajesh Kumar 2026-04-28 22:28 ` [RFC v2 5/6] app/test: add PTP library unit tests Rajesh Kumar ` (2 subsequent siblings) 6 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-04-28 22:28 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, Rajesh Kumar [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #1: Type: text/plain; charset=yes, Size: 8007 bytes --] Add a sample application user guide for the ptp_tap_relay_sw example. The guide covers the application topology, packet flow, compilation, command-line options, and a code walkthrough explaining the two-pass relay burst design and CLOCK_MONOTONIC_RAW timestamp source selection. Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- doc/guides/sample_app_ug/index.rst | 1 + doc/guides/sample_app_ug/ptp_tap_relay_sw.rst | 210 ++++++++++++++++++ 2 files changed, 211 insertions(+) create mode 100644 doc/guides/sample_app_ug/ptp_tap_relay_sw.rst diff --git a/doc/guides/sample_app_ug/index.rst b/doc/guides/sample_app_ug/index.rst index e895f692f9..f12623bb66 100644 --- a/doc/guides/sample_app_ug/index.rst +++ b/doc/guides/sample_app_ug/index.rst @@ -51,6 +51,7 @@ Sample Applications User Guides dist_app vm_power_management ptpclient + ptp_tap_relay_sw fips_validation ipsec_secgw bbdev_app diff --git a/doc/guides/sample_app_ug/ptp_tap_relay_sw.rst b/doc/guides/sample_app_ug/ptp_tap_relay_sw.rst new file mode 100644 index 0000000000..519086585c --- /dev/null +++ b/doc/guides/sample_app_ug/ptp_tap_relay_sw.rst @@ -0,0 +1,210 @@ +.. SPDX-License-Identifier: BSD-3-Clause + Copyright(c) 2026 Intel Corporation. + +PTP Software Relay Sample Application +====================================== + +The PTP Software Relay sample application demonstrates how to use the +DPDK ``lib/ptp`` library to build a minimal PTP Transparent Clock relay +between a DPDK-bound physical NIC and a kernel TAP interface using +**software timestamps only**. + +No patched kernel modules or custom TAP PMD changes are required. +The application works with an unmodified Linux kernel and stock DPDK. + +For background on PTP see: +`Precision Time Protocol +<https://en.wikipedia.org/wiki/Precision_Time_Protocol>`_. + + +Limitations +----------- + +* Only L2 PTP (EtherType 0x88F7) is supported on the wire. +* Only PTP v2 messages are processed. +* Software timestamps have microsecond-class jitter; sub-microsecond + precision depends on system load and NIC-to-TAP forwarding latency. +* The PTP time transmitter must be reachable on the physical NIC's L2 network. +* Only one physical port and one TAP port are supported. + + +How the Application Works +------------------------- + +Topology +~~~~~~~~ + +:: + + PTP Time Transmitter Physical NIC TAP (kernel) + (ptp4l -H) ──L2── (DPDK vfio-pci) ────── dtap0 + │ │ + ptp_tap_relay_sw ptp4l -S + (correctionField += (SW timestamps, + residence time) adjusts CLOCK_REALTIME) + +The relay sits between a DPDK-owned physical NIC and a kernel TAP +virtual interface. ``ptp4l`` runs on the TAP interface in software +timestamp mode (``-S``) as a PTP time receiver. + +Packet Flow +~~~~~~~~~~~ + +1. The physical NIC receives PTP (and non-PTP) packets via DPDK RX. +2. A software RX timestamp is recorded using + ``clock_gettime(CLOCK_MONOTONIC_RAW)``. +3. Each packet is classified with ``rte_ptp_classify()``. +4. For PTP **event** messages (Sync, Delay_Req, PDelay_Req, PDelay_Resp), + a TX software timestamp is taken just before transmission. +5. The residence time (``tx_ts − rx_ts``) is added to the PTP + ``correctionField`` via ``rte_ptp_add_correction()`` — standard + IEEE 1588-2019 Transparent Clock behaviour (§10.2). +6. Packets are forwarded bidirectionally: + + * PHY → TAP (network → ptp4l) + * TAP → PHY (ptp4l → network) + +A two-pass design is used: first all packets are classified and PTP +header pointers saved, then a single TX timestamp is taken immediately +before applying corrections and calling ``rte_eth_tx_burst()``. +This minimises the gap between the measured timestamp and the actual +wire egress. + + +Compiling the Application +------------------------- + +To compile the sample application see :doc:`compiling`. + +The application is located in the ``ptp_tap_relay_sw`` sub-directory. + +.. note:: + + The application depends on the ``ptp`` library. + Ensure that ``lib/ptp`` is built (it is built by default). + + +Running the Application +----------------------- + +Prerequisites +~~~~~~~~~~~~~ + +* A PTP-capable physical NIC bound to DPDK (e.g. via ``vfio-pci``). +* ``linuxptp`` (``ptp4l``) installed on the system. +* A PTP time transmitter reachable on the same L2 network. + +Start the relay +~~~~~~~~~~~~~~~~ + +.. code-block:: console + + ./<build_dir>/examples/dpdk-ptp_tap_relay_sw \ + -l 18-19 -a 0000:cc:00.1 --vdev=net_tap0,iface=dtap0 -- \ + -p 0 -t 1 -T 10 + +Command-line Options +~~~~~~~~~~~~~~~~~~~~ + +* ``-p PORT`` — Physical NIC port ID (default: 0). +* ``-t PORT`` — TAP port ID (default: 1). +* ``-T SECS`` — Statistics print interval in seconds (default: 10). + +Start PTP time transmitter +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +On a separate terminal or remote host, start ``ptp4l`` as time +transmitter with hardware timestamps on the physical NIC: + +.. code-block:: console + + ptp4l -i <iface> -m -2 -H --serverOnly=1 \ + --logSyncInterval=-4 --logMinDelayReqInterval=-4 + +Start PTP time receiver +~~~~~~~~~~~~~~~~~~~~~~~ + +On the TAP interface, start ``ptp4l`` in software timestamp mode: + +.. code-block:: console + + ptp4l -i dtap0 -m -2 -s -S \ + --delay_filter=moving_median --delay_filter_length=10 + +The time receiver will enter UNCALIBRATED state for approximately 60 +seconds while the PI servo estimates the frequency offset, then step +the clock and enter time-receiver (synchronized) state. +Steady-state RMS offset of 500–1000 ns is typical on a lightly loaded +system with a hardware-timestamped time transmitter. + +Example Output +~~~~~~~~~~~~~~ + +Relay statistics printed every ``-T`` seconds: + +:: + + [PTP-SW] === Statistics === + [PTP-SW] PHY RX total: 5646 + [PTP-SW] PHY RX PTP: 5598 + [PTP-SW] TAP TX: 5646 + [PTP-SW] TAP RX total: 1800 + [PTP-SW] TAP RX PTP: 1788 + [PTP-SW] PHY TX: 1800 + [PTP-SW] Corrections: 3635 + +Time receiver ``ptp4l`` output after convergence: + +:: + + ptp4l[451534.520]: rms 630 max 1166 freq -44365 +/- 100 delay 37668 +/- 71 + ptp4l[451539.525]: rms 602 max 1177 freq -44339 +/- 119 delay 37517 +/- 43 + ptp4l[451544.530]: rms 535 max 1194 freq -44345 +/- 103 delay 37410 +/- 81 + + +Code Explanation +---------------- + +The following sections explain the main components of the application. + +Relay Burst Function +~~~~~~~~~~~~~~~~~~~~ + +The core relay logic is in ``relay_burst()``, which handles one direction +(PHY→TAP or TAP→PHY) per call: + +**Pass 1 — Classify:** + +For each received packet, ``rte_ptp_classify()`` determines if it is a +PTP message. For event messages, ``rte_ptp_hdr_get()`` retrieves a +pointer to the PTP header, which is saved for the second pass. + +**Pass 2 — Timestamp and correct:** + +A single software TX timestamp is taken via +``clock_gettime(CLOCK_MONOTONIC_RAW)``. The residence time +(``tx_ts − rx_ts``) is added to each saved PTP header's +``correctionField`` using ``rte_ptp_add_correction()``. +The burst is then transmitted with ``rte_eth_tx_burst()``. + +Main Loop +~~~~~~~~~ + +The ``relay_loop()`` function polls both directions in a tight loop: + +.. code-block:: c + + while (!force_quit) { + relay_burst(phy_port, tap_port, ...); /* PHY → TAP */ + relay_burst(tap_port, phy_port, ...); /* TAP → PHY */ + } + +Statistics are printed at the interval specified by ``-T``. + +Timestamp Source +~~~~~~~~~~~~~~~~ + +``CLOCK_MONOTONIC_RAW`` is used rather than ``CLOCK_REALTIME`` because +the PTP time receiver's servo continuously adjusts ``CLOCK_REALTIME``. +Using ``CLOCK_REALTIME`` would corrupt residence time measurements +during clock stepping or frequency slewing. -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [RFC v2 5/6] app/test: add PTP library unit tests 2026-04-28 22:28 ` [RFC v2 0/6] introduce PTP protocol library and software relay Rajesh Kumar ` (3 preceding siblings ...) 2026-04-28 22:28 ` [RFC v2 4/6] doc: add PTP software relay sample app guide Rajesh Kumar @ 2026-04-28 22:28 ` Rajesh Kumar 2026-04-28 22:28 ` [RFC v2 6/6] examples/ptpclient: use shared PTP library definitions Rajesh Kumar 2026-04-29 15:37 ` [RFC v2 0/6] introduce PTP protocol library and software relay Stephen Hemminger 6 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-04-28 22:28 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, Rajesh Kumar Add autotest for the PTP library covering: - Transport classification (L2, VLAN, QinQ, UDP/IPv4, UDP/IPv6) - Message type parsing and string conversion - Inline helpers (is_event, two_step, seq_id, version, domain) - Correction field arithmetic (add, accumulate, negative values) - Timestamp nanosecond conversion - Flag field extraction (TWO_STEP, UNICAST, LI_61, LI_59) Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- MAINTAINERS | 1 + app/test/meson.build | 1 + app/test/test_ptp.c | 1052 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1054 insertions(+) create mode 100644 app/test/test_ptp.c diff --git a/MAINTAINERS b/MAINTAINERS index 665e08dc90..8d509cbe8e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1668,6 +1668,7 @@ F: app/test-sad/ PTP - EXPERIMENTAL M: Rajesh Kumar <rajesh3.kumar@intel.com> F: lib/ptp/ +F: app/test/test_ptp.c F: doc/guides/prog_guide/ptp_lib.rst F: examples/ptp_tap_relay_sw/ F: doc/guides/sample_app_ug/ptp_tap_relay_sw.rst diff --git a/app/test/meson.build b/app/test/meson.build index 7d458f9c07..ecaa8d0839 100644 --- a/app/test/meson.build +++ b/app/test/meson.build @@ -154,6 +154,7 @@ source_file_deps = { 'test_power_kvm_vm.c': ['power', 'power_kvm_vm'], 'test_prefetch.c': [], 'test_ptr_compress.c': ['ptr_compress'], + 'test_ptp.c': ['ptp'], 'test_rand_perf.c': [], 'test_rawdev.c': ['rawdev', 'bus_vdev', 'raw_skeleton'], 'test_rcu_qsbr.c': ['rcu', 'hash'], diff --git a/app/test/test_ptp.c b/app/test/test_ptp.c new file mode 100644 index 0000000000..5c24804975 --- /dev/null +++ b/app/test/test_ptp.c @@ -0,0 +1,1052 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2026 Intel Corporation + */ + +#include "test.h" + +#include <string.h> +#include <stdio.h> +#include <stdint.h> +#include <inttypes.h> + +#include <rte_mbuf.h> +#include <rte_mempool.h> +#include <rte_memory.h> +#include <rte_ether.h> +#include <rte_ip.h> +#include <rte_udp.h> +#include <rte_byteorder.h> +#include <rte_ptp.h> + +#define PTP_TEST_MP_NAME "test_ptp_pool" +#define PTP_TEST_MP_SIZE 63 +#define PTP_TEST_BUF_SIZE RTE_MBUF_DEFAULT_BUF_SIZE + +static struct rte_mempool *ptp_mp; + +/* Helper: fill a minimal PTP header */ +static void +fill_ptp_hdr(struct rte_ptp_hdr *ptp, uint8_t msg_type, uint16_t flags_host, + int64_t correction_scaled_ns, uint16_t seq_id) +{ + memset(ptp, 0, sizeof(*ptp)); + ptp->msg_type = msg_type; + ptp->version = 0x02; + ptp->msg_length = rte_cpu_to_be_16(34); + ptp->flags = rte_cpu_to_be_16(flags_host); + ptp->correction = rte_cpu_to_be_64(correction_scaled_ns); + ptp->sequence_id = rte_cpu_to_be_16(seq_id); +} + +/* ================================================================ + * Packet builders + * ================================================================ + */ + +static struct rte_mbuf * +build_l2_ptp(uint8_t msg_type) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *) + rte_pktmbuf_append(m, sizeof(*eth) + sizeof(struct rte_ptp_hdr)); + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(0x88F7); + + struct rte_ptp_hdr *ptp = (struct rte_ptp_hdr *)((uint8_t *)eth + sizeof(*eth)); + fill_ptp_hdr(ptp, msg_type, RTE_PTP_FLAG_TWO_STEP, 0, 100); + return m; +} + +/* Build L2 PTP with no TWO_STEP flag */ +static struct rte_mbuf * +build_l2_ptp_noflags(uint8_t msg_type) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *) + rte_pktmbuf_append(m, sizeof(*eth) + sizeof(struct rte_ptp_hdr)); + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(0x88F7); + + struct rte_ptp_hdr *ptp = (struct rte_ptp_hdr *)((uint8_t *)eth + sizeof(*eth)); + fill_ptp_hdr(ptp, msg_type, 0, 0, 200); + return m; +} + +static struct rte_mbuf * +build_vlan_l2_ptp(uint8_t msg_type, uint16_t tpid) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + sizeof(struct rte_vlan_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(tpid); + + struct rte_vlan_hdr *vlan = (struct rte_vlan_hdr *)(data + sizeof(*eth)); + vlan->vlan_tci = rte_cpu_to_be_16(100); + vlan->eth_proto = rte_cpu_to_be_16(0x88F7); + + struct rte_ptp_hdr *ptp = (struct rte_ptp_hdr *) + (data + sizeof(*eth) + sizeof(*vlan)); + fill_ptp_hdr(ptp, msg_type, 0, 0, 200); + return m; +} + +static struct rte_mbuf * +build_qinq_l2_ptp(uint8_t msg_type, uint16_t outer_tpid, uint16_t inner_tpid) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + 2 * sizeof(struct rte_vlan_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(outer_tpid); + + uint32_t off = sizeof(*eth); + struct rte_vlan_hdr *vo = (struct rte_vlan_hdr *)(data + off); + vo->vlan_tci = rte_cpu_to_be_16(200); + vo->eth_proto = rte_cpu_to_be_16(inner_tpid); + off += sizeof(*vo); + + struct rte_vlan_hdr *vi = (struct rte_vlan_hdr *)(data + off); + vi->vlan_tci = rte_cpu_to_be_16(300); + vi->eth_proto = rte_cpu_to_be_16(0x88F7); + off += sizeof(*vi); + + struct rte_ptp_hdr *ptp = (struct rte_ptp_hdr *)(data + off); + fill_ptp_hdr(ptp, msg_type, 0, 0, 300); + return m; +} + +/* Helper: append IPv4 + UDP + PTP after offset */ +static void +fill_ipv4_udp_ptp(uint8_t *data, uint32_t off, uint8_t msg_type, + uint16_t dst_port, uint16_t seq_id) +{ + struct rte_ipv4_hdr *iph = (struct rte_ipv4_hdr *)(data + off); + memset(iph, 0, sizeof(*iph)); + iph->version_ihl = 0x45; + iph->next_proto_id = IPPROTO_UDP; + iph->total_length = rte_cpu_to_be_16( + sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr)); + iph->src_addr = rte_cpu_to_be_32(0x0A000001); + iph->dst_addr = rte_cpu_to_be_32(0xE0000181); + off += sizeof(*iph); + + struct rte_udp_hdr *udp = (struct rte_udp_hdr *)(data + off); + memset(udp, 0, sizeof(*udp)); + udp->src_port = rte_cpu_to_be_16(12345); + udp->dst_port = rte_cpu_to_be_16(dst_port); + udp->dgram_len = rte_cpu_to_be_16(sizeof(*udp) + + sizeof(struct rte_ptp_hdr)); + off += sizeof(*udp); + + struct rte_ptp_hdr *ptp = (struct rte_ptp_hdr *)(data + off); + fill_ptp_hdr(ptp, msg_type, 0, 0, seq_id); +} + +static struct rte_mbuf * +build_ipv4_udp_ptp(uint8_t msg_type, uint16_t dst_port) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + sizeof(struct rte_ipv4_hdr) + + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4); + + fill_ipv4_udp_ptp(data, sizeof(*eth), msg_type, dst_port, 400); + return m; +} + +static struct rte_mbuf * +build_vlan_ipv4_udp_ptp(uint8_t msg_type, uint16_t dst_port) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + sizeof(struct rte_vlan_hdr) + + sizeof(struct rte_ipv4_hdr) + + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN); + + uint32_t off = sizeof(*eth); + struct rte_vlan_hdr *vlan = (struct rte_vlan_hdr *)(data + off); + vlan->vlan_tci = rte_cpu_to_be_16(100); + vlan->eth_proto = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4); + off += sizeof(*vlan); + + fill_ipv4_udp_ptp(data, off, msg_type, dst_port, 500); + return m; +} + +static struct rte_mbuf * +build_qinq_ipv4_udp_ptp(uint8_t msg_type, uint16_t dst_port) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + 2 * sizeof(struct rte_vlan_hdr) + + sizeof(struct rte_ipv4_hdr) + + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_QINQ); + + uint32_t off = sizeof(*eth); + struct rte_vlan_hdr *vo = (struct rte_vlan_hdr *)(data + off); + vo->vlan_tci = rte_cpu_to_be_16(200); + vo->eth_proto = rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN); + off += sizeof(*vo); + + struct rte_vlan_hdr *vi = (struct rte_vlan_hdr *)(data + off); + vi->vlan_tci = rte_cpu_to_be_16(300); + vi->eth_proto = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4); + off += sizeof(*vi); + + fill_ipv4_udp_ptp(data, off, msg_type, dst_port, 600); + return m; +} + +/* Helper: append IPv6 + UDP + PTP */ +static void +fill_ipv6_udp_ptp(uint8_t *data, uint32_t off, uint8_t msg_type, + uint16_t dst_port, uint16_t seq_id) +{ + struct rte_ipv6_hdr *ip6 = (struct rte_ipv6_hdr *)(data + off); + memset(ip6, 0, sizeof(*ip6)); + ip6->vtc_flow = rte_cpu_to_be_32(0x60000000); + ip6->payload_len = rte_cpu_to_be_16( + sizeof(struct rte_udp_hdr) + sizeof(struct rte_ptp_hdr)); + ip6->proto = IPPROTO_UDP; + ip6->hop_limits = 64; + off += sizeof(*ip6); + + struct rte_udp_hdr *udp = (struct rte_udp_hdr *)(data + off); + memset(udp, 0, sizeof(*udp)); + udp->src_port = rte_cpu_to_be_16(12345); + udp->dst_port = rte_cpu_to_be_16(dst_port); + udp->dgram_len = rte_cpu_to_be_16(sizeof(*udp) + + sizeof(struct rte_ptp_hdr)); + off += sizeof(*udp); + + struct rte_ptp_hdr *ptp = (struct rte_ptp_hdr *)(data + off); + fill_ptp_hdr(ptp, msg_type, 0, 0, seq_id); +} + +static struct rte_mbuf * +build_ipv6_udp_ptp(uint8_t msg_type, uint16_t dst_port) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + sizeof(struct rte_ipv6_hdr) + + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV6); + + fill_ipv6_udp_ptp(data, sizeof(*eth), msg_type, dst_port, 700); + return m; +} + +static struct rte_mbuf * +build_vlan_ipv6_udp_ptp(uint8_t msg_type, uint16_t dst_port) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + sizeof(struct rte_vlan_hdr) + + sizeof(struct rte_ipv6_hdr) + + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN); + + uint32_t off = sizeof(*eth); + struct rte_vlan_hdr *vlan = (struct rte_vlan_hdr *)(data + off); + vlan->vlan_tci = rte_cpu_to_be_16(100); + vlan->eth_proto = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV6); + off += sizeof(*vlan); + + fill_ipv6_udp_ptp(data, off, msg_type, dst_port, 800); + return m; +} + +static struct rte_mbuf * +build_qinq_ipv6_udp_ptp(uint8_t msg_type, uint16_t dst_port) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + 2 * sizeof(struct rte_vlan_hdr) + + sizeof(struct rte_ipv6_hdr) + + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_QINQ); + + uint32_t off = sizeof(*eth); + struct rte_vlan_hdr *vo = (struct rte_vlan_hdr *)(data + off); + vo->vlan_tci = rte_cpu_to_be_16(200); + vo->eth_proto = rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN); + off += sizeof(*vo); + + struct rte_vlan_hdr *vi = (struct rte_vlan_hdr *)(data + off); + vi->vlan_tci = rte_cpu_to_be_16(300); + vi->eth_proto = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV6); + off += sizeof(*vi); + + fill_ipv6_udp_ptp(data, off, msg_type, dst_port, 900); + return m; +} + +static struct rte_mbuf * +build_non_ptp(void) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + 28; + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(data, 0, pkt_len); + eth->ether_type = rte_cpu_to_be_16(0x0806); + return m; +} + +static struct rte_mbuf * +build_ipv4_udp_non_ptp(void) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + sizeof(struct rte_ipv4_hdr) + + sizeof(struct rte_udp_hdr) + 20; + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(data, 0, pkt_len); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4); + + struct rte_ipv4_hdr *iph = (struct rte_ipv4_hdr *)(data + sizeof(*eth)); + iph->version_ihl = 0x45; + iph->next_proto_id = IPPROTO_UDP; + iph->total_length = rte_cpu_to_be_16( + sizeof(*iph) + sizeof(struct rte_udp_hdr) + 20); + + struct rte_udp_hdr *udp = (struct rte_udp_hdr *) + (data + sizeof(*eth) + sizeof(*iph)); + udp->dst_port = rte_cpu_to_be_16(53); + return m; +} + +/* ================================================================ + * Individual test cases + * ================================================================ + */ + +/* Helper: classify + hdr_get for a given mbuf */ +static int +check_classify_and_hdr_get(struct rte_mbuf *m, int expected_type) +{ + int ret; + + TEST_ASSERT_NOT_NULL(m, "mbuf allocation failed"); + + ret = rte_ptp_classify(m); + TEST_ASSERT_EQUAL(ret, expected_type, + "classify: expected %d, got %d", expected_type, ret); + + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + if (expected_type == RTE_PTP_MSGTYPE_INVALID) { + TEST_ASSERT_NULL(hdr, + "hdr_get: expected NULL for non-PTP packet"); + } else { + TEST_ASSERT_NOT_NULL(hdr, + "hdr_get: expected non-NULL for PTP packet"); + TEST_ASSERT_EQUAL(rte_ptp_msg_type(hdr), + (uint8_t)expected_type, + "hdr_get: msg_type mismatch: expected %d, got %d", + expected_type, rte_ptp_msg_type(hdr)); + } + + rte_pktmbuf_free(m); + return TEST_SUCCESS; +} + +/* Section 1: Transport classification */ +static int +test_ptp_classify_l2(void) +{ + return check_classify_and_hdr_get( + build_l2_ptp(RTE_PTP_MSGTYPE_SYNC), RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_l2_delay_req(void) +{ + return check_classify_and_hdr_get( + build_l2_ptp(RTE_PTP_MSGTYPE_DELAY_REQ), + RTE_PTP_MSGTYPE_DELAY_REQ); +} + +static int +test_ptp_classify_vlan_8100(void) +{ + return check_classify_and_hdr_get( + build_vlan_l2_ptp(RTE_PTP_MSGTYPE_SYNC, RTE_ETHER_TYPE_VLAN), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_vlan_88a8(void) +{ + return check_classify_and_hdr_get( + build_vlan_l2_ptp(RTE_PTP_MSGTYPE_SYNC, RTE_ETHER_TYPE_QINQ), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_qinq(void) +{ + return check_classify_and_hdr_get( + build_qinq_l2_ptp(RTE_PTP_MSGTYPE_SYNC, + RTE_ETHER_TYPE_QINQ, RTE_ETHER_TYPE_VLAN), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_double_8100(void) +{ + return check_classify_and_hdr_get( + build_qinq_l2_ptp(RTE_PTP_MSGTYPE_SYNC, + RTE_ETHER_TYPE_VLAN, RTE_ETHER_TYPE_VLAN), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_ipv4_udp_319(void) +{ + return check_classify_and_hdr_get( + build_ipv4_udp_ptp(RTE_PTP_MSGTYPE_SYNC, 319), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_ipv4_udp_320(void) +{ + return check_classify_and_hdr_get( + build_ipv4_udp_ptp(RTE_PTP_MSGTYPE_FOLLOW_UP, 320), + RTE_PTP_MSGTYPE_FOLLOW_UP); +} + +static int +test_ptp_classify_vlan_ipv4_udp(void) +{ + return check_classify_and_hdr_get( + build_vlan_ipv4_udp_ptp(RTE_PTP_MSGTYPE_SYNC, 319), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_qinq_ipv4_udp(void) +{ + return check_classify_and_hdr_get( + build_qinq_ipv4_udp_ptp(RTE_PTP_MSGTYPE_SYNC, 319), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_ipv6_udp_319(void) +{ + return check_classify_and_hdr_get( + build_ipv6_udp_ptp(RTE_PTP_MSGTYPE_SYNC, 319), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_ipv6_udp_320(void) +{ + return check_classify_and_hdr_get( + build_ipv6_udp_ptp(RTE_PTP_MSGTYPE_FOLLOW_UP, 320), + RTE_PTP_MSGTYPE_FOLLOW_UP); +} + +static int +test_ptp_classify_vlan_ipv6_udp(void) +{ + return check_classify_and_hdr_get( + build_vlan_ipv6_udp_ptp(RTE_PTP_MSGTYPE_SYNC, 319), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_qinq_ipv6_udp(void) +{ + return check_classify_and_hdr_get( + build_qinq_ipv6_udp_ptp(RTE_PTP_MSGTYPE_SYNC, 319), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_non_ptp_arp(void) +{ + return check_classify_and_hdr_get( + build_non_ptp(), RTE_PTP_MSGTYPE_INVALID); +} + +static int +test_ptp_classify_non_ptp_udp(void) +{ + return check_classify_and_hdr_get( + build_ipv4_udp_non_ptp(), RTE_PTP_MSGTYPE_INVALID); +} + +/* IPv4 with invalid IHL (< 5) should be rejected */ +static int +test_ptp_classify_ipv4_bad_ihl(void) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + sizeof(struct rte_ipv4_hdr) + + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4); + + fill_ipv4_udp_ptp(data, sizeof(*eth), RTE_PTP_MSGTYPE_SYNC, 319, 999); + + /* Corrupt the IHL to 3 (< minimum 5) */ + struct rte_ipv4_hdr *iph = (struct rte_ipv4_hdr *) + (data + sizeof(*eth)); + iph->version_ihl = 0x43; + + return check_classify_and_hdr_get(m, RTE_PTP_MSGTYPE_INVALID); +} + +/* Truncated packet: Ethernet header only, no payload */ +static int +test_ptp_classify_truncated(void) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, + sizeof(struct rte_ether_hdr)); + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(0x88F7); + + return check_classify_and_hdr_get(m, RTE_PTP_MSGTYPE_INVALID); +} + +/* Section 2: All 10 message types via L2 */ +static int +test_ptp_all_msg_types(void) +{ + static const uint8_t types[] = { + RTE_PTP_MSGTYPE_SYNC, + RTE_PTP_MSGTYPE_DELAY_REQ, + RTE_PTP_MSGTYPE_PDELAY_REQ, + RTE_PTP_MSGTYPE_PDELAY_RESP, + RTE_PTP_MSGTYPE_FOLLOW_UP, + RTE_PTP_MSGTYPE_DELAY_RESP, + RTE_PTP_MSGTYPE_PDELAY_RESP_FU, + RTE_PTP_MSGTYPE_ANNOUNCE, + RTE_PTP_MSGTYPE_SIGNALING, + RTE_PTP_MSGTYPE_MANAGEMENT, + }; + unsigned int i; + + for (i = 0; i < RTE_DIM(types); i++) { + int ret = check_classify_and_hdr_get( + build_l2_ptp(types[i]), types[i]); + if (ret != TEST_SUCCESS) + return ret; + } + + return TEST_SUCCESS; +} + +/* Section 3: Inline helpers */ +static int +test_ptp_is_event(void) +{ + TEST_ASSERT(rte_ptp_is_event(RTE_PTP_MSGTYPE_SYNC), + "Sync should be event"); + TEST_ASSERT(rte_ptp_is_event(RTE_PTP_MSGTYPE_DELAY_REQ), + "Delay_Req should be event"); + TEST_ASSERT(rte_ptp_is_event(RTE_PTP_MSGTYPE_PDELAY_REQ), + "Pdelay_Req should be event"); + TEST_ASSERT(rte_ptp_is_event(RTE_PTP_MSGTYPE_PDELAY_RESP), + "Pdelay_Resp should be event"); + TEST_ASSERT(!rte_ptp_is_event(RTE_PTP_MSGTYPE_FOLLOW_UP), + "Follow_Up should not be event"); + TEST_ASSERT(!rte_ptp_is_event(RTE_PTP_MSGTYPE_ANNOUNCE), + "Announce should not be event"); + TEST_ASSERT(!rte_ptp_is_event(RTE_PTP_MSGTYPE_INVALID), + "INVALID (-1) should not be event"); + + return TEST_SUCCESS; +} + +static int +test_ptp_two_step(void) +{ + struct rte_mbuf *m; + struct rte_ptp_hdr *hdr; + + m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + TEST_ASSERT(rte_ptp_is_two_step(hdr), + "TWO_STEP flag should be set"); + rte_pktmbuf_free(m); + + m = build_l2_ptp_noflags(RTE_PTP_MSGTYPE_SYNC); + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + TEST_ASSERT(!rte_ptp_is_two_step(hdr), + "TWO_STEP flag should not be set"); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_seq_id(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + TEST_ASSERT_EQUAL(rte_ptp_seq_id(hdr), 100, + "seq_id: expected 100, got %u", rte_ptp_seq_id(hdr)); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_version(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + TEST_ASSERT_EQUAL(rte_ptp_version(hdr), 2, + "version: expected 2, got %u", rte_ptp_version(hdr)); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_domain(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + TEST_ASSERT_EQUAL(rte_ptp_domain(hdr), 0, + "domain: expected 0, got %u", rte_ptp_domain(hdr)); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +/* Section 4: correctionField */ +static int +test_ptp_correction_zero(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + int64_t ns = rte_ptp_correction_ns(hdr); + TEST_ASSERT_EQUAL(ns, 0, + "correction_ns: expected 0, got %" PRId64, ns); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_correction_known(void) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, + sizeof(struct rte_ether_hdr) + sizeof(struct rte_ptp_hdr)); + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(0x88F7); + + struct rte_ptp_hdr *ptp = (struct rte_ptp_hdr *)(data + sizeof(*eth)); + int64_t scaled_1000 = (int64_t)1000 << 16; + fill_ptp_hdr(ptp, RTE_PTP_MSGTYPE_SYNC, 0, scaled_1000, 0); + + int64_t ns = rte_ptp_correction_ns(ptp); + TEST_ASSERT_EQUAL(ns, 1000, + "correction_ns: expected 1000, got %" PRId64, ns); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_add_correction(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + + rte_ptp_add_correction(hdr, 500); + int64_t ns = rte_ptp_correction_ns(hdr); + TEST_ASSERT_EQUAL(ns, 500, + "add 500: expected 500, got %" PRId64, ns); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_add_correction_accumulate(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + + rte_ptp_add_correction(hdr, 300); + rte_ptp_add_correction(hdr, 700); + int64_t ns = rte_ptp_correction_ns(hdr); + TEST_ASSERT_EQUAL(ns, 1000, + "accumulate: expected 1000, got %" PRId64, ns); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_add_correction_large(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + + rte_ptp_add_correction(hdr, 1000000000LL); + int64_t ns = rte_ptp_correction_ns(hdr); + TEST_ASSERT_EQUAL(ns, 1000000000LL, + "1s: expected 1000000000, got %" PRId64, ns); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_add_correction_negative(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + + rte_ptp_add_correction(hdr, -100LL); + int64_t ns = rte_ptp_correction_ns(hdr); + TEST_ASSERT_EQUAL(ns, -100LL, + "negative: expected -100, got %" PRId64, ns); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +/* Section 5: Timestamp conversion */ +static int +test_ptp_timestamp_to_ns(void) +{ + struct rte_ptp_timestamp ts; + uint64_t ns; + + /* Zero */ + memset(&ts, 0, sizeof(ts)); + ns = rte_ptp_timestamp_to_ns(&ts); + TEST_ASSERT_EQUAL(ns, 0ULL, + "zero: expected 0, got %" PRIu64, ns); + + /* 1 second */ + ts.seconds_hi = 0; + ts.seconds_lo = rte_cpu_to_be_32(1); + ts.nanoseconds = 0; + ns = rte_ptp_timestamp_to_ns(&ts); + TEST_ASSERT_EQUAL(ns, 1000000000ULL, + "1s: expected 1000000000, got %" PRIu64, ns); + + /* 1.5 seconds */ + ts.seconds_lo = rte_cpu_to_be_32(1); + ts.nanoseconds = rte_cpu_to_be_32(500000000); + ns = rte_ptp_timestamp_to_ns(&ts); + TEST_ASSERT_EQUAL(ns, 1500000000ULL, + "1.5s: expected 1500000000, got %" PRIu64, ns); + + /* Large value with seconds_hi */ + ts.seconds_hi = rte_cpu_to_be_16(1); + ts.seconds_lo = 0; + ts.nanoseconds = 0; + ns = rte_ptp_timestamp_to_ns(&ts); + uint64_t expected = ((uint64_t)1 << 32) * 1000000000ULL; + TEST_ASSERT_EQUAL(ns, expected, + "2^32s: expected %" PRIu64 ", got %" PRIu64, expected, ns); + + return TEST_SUCCESS; +} + +/* Section 6: msg_type_str */ +static int +test_ptp_msg_type_str(void) +{ + static const struct { + int type; + const char *expected; + } cases[] = { + { RTE_PTP_MSGTYPE_SYNC, "Sync" }, + { RTE_PTP_MSGTYPE_DELAY_REQ, "Delay_Req" }, + { RTE_PTP_MSGTYPE_PDELAY_REQ, "PDelay_Req" }, + { RTE_PTP_MSGTYPE_PDELAY_RESP, "PDelay_Resp" }, + { RTE_PTP_MSGTYPE_FOLLOW_UP, "Follow_Up" }, + { RTE_PTP_MSGTYPE_DELAY_RESP, "Delay_Resp" }, + { RTE_PTP_MSGTYPE_PDELAY_RESP_FU, "PDelay_Resp_Follow_Up" }, + { RTE_PTP_MSGTYPE_ANNOUNCE, "Announce" }, + { RTE_PTP_MSGTYPE_SIGNALING, "Signaling" }, + { RTE_PTP_MSGTYPE_MANAGEMENT, "Management" }, + }; + unsigned int i; + + for (i = 0; i < RTE_DIM(cases); i++) { + const char *str = rte_ptp_msg_type_str(cases[i].type); + TEST_ASSERT_NOT_NULL(str, + "msg_type_str(%d) returned NULL", cases[i].type); + TEST_ASSERT(strcmp(str, cases[i].expected) == 0, + "msg_type_str(%d): expected \"%s\", got \"%s\"", + cases[i].type, cases[i].expected, str); + } + + /* Invalid type should still return non-NULL */ + const char *inv = rte_ptp_msg_type_str(RTE_PTP_MSGTYPE_INVALID); + TEST_ASSERT_NOT_NULL(inv, + "msg_type_str(INVALID) returned NULL"); + + return TEST_SUCCESS; +} + +/* Section 7: Flag bit positions */ +static int +test_ptp_flags(void) +{ + struct rte_ptp_hdr hdr; + uint16_t f; + + /* TWO_STEP */ + memset(&hdr, 0, sizeof(hdr)); + hdr.flags = rte_cpu_to_be_16(RTE_PTP_FLAG_TWO_STEP); + f = rte_be_to_cpu_16(hdr.flags); + TEST_ASSERT(f & RTE_PTP_FLAG_TWO_STEP, + "TWO_STEP bit not set: 0x%04x", f); + TEST_ASSERT(rte_ptp_is_two_step(&hdr), + "is_two_step() should return true"); + + /* UNICAST */ + memset(&hdr, 0, sizeof(hdr)); + hdr.flags = rte_cpu_to_be_16(RTE_PTP_FLAG_UNICAST); + f = rte_be_to_cpu_16(hdr.flags); + TEST_ASSERT(f & RTE_PTP_FLAG_UNICAST, + "UNICAST bit not set: 0x%04x", f); + + /* LI_61 */ + memset(&hdr, 0, sizeof(hdr)); + hdr.flags = rte_cpu_to_be_16(RTE_PTP_FLAG_LI_61); + f = rte_be_to_cpu_16(hdr.flags); + TEST_ASSERT(f & RTE_PTP_FLAG_LI_61, + "LI_61 bit not set: 0x%04x", f); + + /* LI_59 */ + memset(&hdr, 0, sizeof(hdr)); + hdr.flags = rte_cpu_to_be_16(RTE_PTP_FLAG_LI_59); + f = rte_be_to_cpu_16(hdr.flags); + TEST_ASSERT(f & RTE_PTP_FLAG_LI_59, + "LI_59 bit not set: 0x%04x", f); + + /* Combined TWO_STEP + UNICAST */ + memset(&hdr, 0, sizeof(hdr)); + hdr.flags = rte_cpu_to_be_16( + RTE_PTP_FLAG_TWO_STEP | RTE_PTP_FLAG_UNICAST); + f = rte_be_to_cpu_16(hdr.flags); + TEST_ASSERT((f & RTE_PTP_FLAG_TWO_STEP) && + (f & RTE_PTP_FLAG_UNICAST) && + !(f & RTE_PTP_FLAG_LI_61) && + !(f & RTE_PTP_FLAG_LI_59), + "combined flags incorrect: 0x%04x", f); + + return TEST_SUCCESS; +} + +/* ================================================================ + * Suite setup / teardown + * ================================================================ + */ + +static int +test_ptp_setup(void) +{ + ptp_mp = rte_pktmbuf_pool_create(PTP_TEST_MP_NAME, PTP_TEST_MP_SIZE, + 0, 0, PTP_TEST_BUF_SIZE, SOCKET_ID_ANY); + if (ptp_mp == NULL) { + printf("Cannot create ptp test mempool\n"); + return TEST_FAILED; + } + return TEST_SUCCESS; +} + +static void +test_ptp_teardown(void) +{ + rte_mempool_free(ptp_mp); + ptp_mp = NULL; +} + +static struct unit_test_suite ptp_test_suite = { + .suite_name = "PTP Library Unit Tests", + .setup = test_ptp_setup, + .teardown = test_ptp_teardown, + .unit_test_cases = { + /* Transport classification */ + TEST_CASE(test_ptp_classify_l2), + TEST_CASE(test_ptp_classify_l2_delay_req), + TEST_CASE(test_ptp_classify_vlan_8100), + TEST_CASE(test_ptp_classify_vlan_88a8), + TEST_CASE(test_ptp_classify_qinq), + TEST_CASE(test_ptp_classify_double_8100), + TEST_CASE(test_ptp_classify_ipv4_udp_319), + TEST_CASE(test_ptp_classify_ipv4_udp_320), + TEST_CASE(test_ptp_classify_vlan_ipv4_udp), + TEST_CASE(test_ptp_classify_qinq_ipv4_udp), + TEST_CASE(test_ptp_classify_ipv6_udp_319), + TEST_CASE(test_ptp_classify_ipv6_udp_320), + TEST_CASE(test_ptp_classify_vlan_ipv6_udp), + TEST_CASE(test_ptp_classify_qinq_ipv6_udp), + TEST_CASE(test_ptp_classify_non_ptp_arp), + TEST_CASE(test_ptp_classify_non_ptp_udp), + TEST_CASE(test_ptp_classify_ipv4_bad_ihl), + TEST_CASE(test_ptp_classify_truncated), + + /* All message types */ + TEST_CASE(test_ptp_all_msg_types), + + /* Inline helpers */ + TEST_CASE(test_ptp_is_event), + TEST_CASE(test_ptp_two_step), + TEST_CASE(test_ptp_seq_id), + TEST_CASE(test_ptp_version), + TEST_CASE(test_ptp_domain), + + /* correctionField */ + TEST_CASE(test_ptp_correction_zero), + TEST_CASE(test_ptp_correction_known), + TEST_CASE(test_ptp_add_correction), + TEST_CASE(test_ptp_add_correction_accumulate), + TEST_CASE(test_ptp_add_correction_large), + TEST_CASE(test_ptp_add_correction_negative), + + /* Timestamp conversion */ + TEST_CASE(test_ptp_timestamp_to_ns), + + /* msg_type_str */ + TEST_CASE(test_ptp_msg_type_str), + + /* Flag field bit positions */ + TEST_CASE(test_ptp_flags), + + TEST_CASES_END() + }, +}; + +static int +test_ptp(void) +{ + return unit_test_suite_runner(&ptp_test_suite); +} + +REGISTER_FAST_TEST(ptp_autotest, NOHUGE_SKIP, ASAN_OK, test_ptp); -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [RFC v2 6/6] examples/ptpclient: use shared PTP library definitions 2026-04-28 22:28 ` [RFC v2 0/6] introduce PTP protocol library and software relay Rajesh Kumar ` (4 preceding siblings ...) 2026-04-28 22:28 ` [RFC v2 5/6] app/test: add PTP library unit tests Rajesh Kumar @ 2026-04-28 22:28 ` Rajesh Kumar 2026-04-29 15:37 ` [RFC v2 0/6] introduce PTP protocol library and software relay Stephen Hemminger 6 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-04-28 22:28 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, Rajesh Kumar Replace local PTP struct and constant definitions with the shared rte_ptp.h library types introduced earlier in this series: - struct ptp_header -> struct rte_ptp_hdr - struct tstamp -> struct rte_ptp_timestamp - struct clock_id -> uint8_t clock_id[8] (from rte_ptp_port_id) - SYNC/FOLLOW_UP/DELAY_* defines -> RTE_PTP_MSGTYPE_* constants - PTP_PROTOCOL -> RTE_PTP_ETHERTYPE - hardcoded multicast -> RTE_PTP_MULTICAST_MAC - ptp_hdr->msg_type switch -> rte_ptp_msg_type() accessor No functional change. Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- examples/ptpclient/meson.build | 1 + examples/ptpclient/ptpclient.c | 188 +++++++++++++-------------------- 2 files changed, 73 insertions(+), 116 deletions(-) diff --git a/examples/ptpclient/meson.build b/examples/ptpclient/meson.build index 2e9b7625fc..ab074c9cb1 100644 --- a/examples/ptpclient/meson.build +++ b/examples/ptpclient/meson.build @@ -7,6 +7,7 @@ # DPDK instance, use 'make' allow_experimental_apis = true +deps += ['ptp'] sources = files( 'ptpclient.c', ) diff --git a/examples/ptpclient/ptpclient.c b/examples/ptpclient/ptpclient.c index 174ca5dd70..ee93f511d7 100644 --- a/examples/ptpclient/ptpclient.c +++ b/examples/ptpclient/ptpclient.c @@ -17,6 +17,7 @@ #include <rte_lcore.h> #include <rte_mbuf.h> #include <rte_ip.h> +#include <rte_ptp.h> #include <limits.h> #include <sys/time.h> #include <getopt.h> @@ -30,21 +31,8 @@ static volatile bool force_quit; #define NUM_MBUFS 8191 #define MBUF_CACHE_SIZE 250 -/* Values for the PTP messageType field. */ -#define SYNC 0x0 -#define DELAY_REQ 0x1 -#define PDELAY_REQ 0x2 -#define PDELAY_RESP 0x3 -#define FOLLOW_UP 0x8 -#define DELAY_RESP 0x9 -#define PDELAY_RESP_FOLLOW_UP 0xA -#define ANNOUNCE 0xB -#define SIGNALING 0xC -#define MANAGEMENT 0xD - #define NSEC_PER_SEC 1000000000L #define KERNEL_TIME_ADJUST_LIMIT 20000 -#define PTP_PROTOCOL 0x88F7 struct rte_mempool *mbuf_pool; uint32_t ptp_enabled_port_mask; @@ -52,66 +40,36 @@ uint8_t ptp_enabled_port_nb; static uint8_t ptp_enabled_ports[RTE_MAX_ETHPORTS]; static const struct rte_ether_addr ether_multicast = { - .addr_bytes = {0x01, 0x1b, 0x19, 0x0, 0x0, 0x0} -}; - -/* Structs used for PTP handling. */ -struct __rte_packed_begin tstamp { - uint16_t sec_msb; - uint32_t sec_lsb; - uint32_t ns; -} __rte_packed_end; - -struct clock_id { - uint8_t id[8]; + .addr_bytes = RTE_PTP_MULTICAST_MAC }; -struct __rte_packed_begin port_id { - struct clock_id clock_id; - uint16_t port_number; -} __rte_packed_end; - -struct __rte_packed_begin ptp_header { - uint8_t msg_type; - uint8_t ver; - uint16_t message_length; - uint8_t domain_number; - uint8_t reserved1; - uint8_t flag_field[2]; - int64_t correction; - uint32_t reserved2; - struct port_id source_port_id; - uint16_t seq_id; - uint8_t control; - int8_t log_message_interval; -} __rte_packed_end; - +/* Message body structs using the library PTP header and timestamp. */ struct __rte_packed_begin sync_msg { - struct ptp_header hdr; - struct tstamp origin_tstamp; + struct rte_ptp_hdr hdr; + struct rte_ptp_timestamp origin_tstamp; } __rte_packed_end; struct __rte_packed_begin follow_up_msg { - struct ptp_header hdr; - struct tstamp precise_origin_tstamp; - uint8_t suffix[]; + struct rte_ptp_hdr hdr; + struct rte_ptp_timestamp precise_origin_tstamp; + uint8_t suffix[]; } __rte_packed_end; struct __rte_packed_begin delay_req_msg { - struct ptp_header hdr; - struct tstamp origin_tstamp; + struct rte_ptp_hdr hdr; + struct rte_ptp_timestamp origin_tstamp; } __rte_packed_end; struct __rte_packed_begin delay_resp_msg { - struct ptp_header hdr; - struct tstamp rx_tstamp; - struct port_id req_port_id; - uint8_t suffix[]; + struct rte_ptp_hdr hdr; + struct rte_ptp_timestamp rx_tstamp; + struct rte_ptp_port_id req_port_id; + uint8_t suffix[]; } __rte_packed_end; struct ptp_message { union __rte_packed_begin { - struct ptp_header header; + struct rte_ptp_hdr header; struct sync_msg sync; struct delay_req_msg delay_req; struct follow_up_msg follow_up; @@ -125,8 +83,8 @@ struct ptpv2_time_receiver_ordinary { struct timespec tstamp2; struct timespec tstamp3; struct timespec tstamp4; - struct clock_id client_clock_id; - struct clock_id transmitter_clock_id; + uint8_t client_clock_id[8]; + uint8_t transmitter_clock_id[8]; struct timeval new_adj; int64_t delta; uint16_t portid; @@ -272,14 +230,14 @@ print_clock_info(struct ptpv2_time_receiver_ordinary *ptp_data) struct timespec net_time, sys_time; printf("time transmitter clock id: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", - ptp_data->transmitter_clock_id.id[0], - ptp_data->transmitter_clock_id.id[1], - ptp_data->transmitter_clock_id.id[2], - ptp_data->transmitter_clock_id.id[3], - ptp_data->transmitter_clock_id.id[4], - ptp_data->transmitter_clock_id.id[5], - ptp_data->transmitter_clock_id.id[6], - ptp_data->transmitter_clock_id.id[7]); + ptp_data->transmitter_clock_id[0], + ptp_data->transmitter_clock_id[1], + ptp_data->transmitter_clock_id[2], + ptp_data->transmitter_clock_id[3], + ptp_data->transmitter_clock_id[4], + ptp_data->transmitter_clock_id[5], + ptp_data->transmitter_clock_id[6], + ptp_data->transmitter_clock_id[7]); printf("\nT2 - time receiver clock. %lds %ldns", (ptp_data->tstamp2.tv_sec), @@ -356,20 +314,20 @@ delta_eval(struct ptpv2_time_receiver_ordinary *ptp_data) static void parse_sync(struct ptpv2_time_receiver_ordinary *ptp_data, uint16_t rx_tstamp_idx) { - struct ptp_header *ptp_hdr; + struct rte_ptp_hdr *ptp_hdr; - ptp_hdr = rte_pktmbuf_mtod_offset(ptp_data->m, struct ptp_header *, + ptp_hdr = rte_pktmbuf_mtod_offset(ptp_data->m, struct rte_ptp_hdr *, sizeof(struct rte_ether_hdr)); - ptp_data->seqID_SYNC = rte_be_to_cpu_16(ptp_hdr->seq_id); + ptp_data->seqID_SYNC = rte_be_to_cpu_16(ptp_hdr->sequence_id); if (ptp_data->ptpset == 0) { - ptp_data->transmitter_clock_id = ptp_hdr->source_port_id.clock_id; + memcpy(ptp_data->transmitter_clock_id, + ptp_hdr->source_port_id.clock_id, 8); ptp_data->ptpset = 1; } - if (memcmp(&ptp_data->transmitter_clock_id, - &ptp_hdr->source_port_id.clock_id, - sizeof(struct clock_id)) == 0) { + if (memcmp(ptp_data->transmitter_clock_id, + ptp_hdr->source_port_id.clock_id, 8) == 0) { if (ptp_data->ptpset == 1) rte_eth_timesync_read_rx_timestamp(ptp_data->portid, @@ -386,12 +344,12 @@ parse_fup(struct ptpv2_time_receiver_ordinary *ptp_data) { struct rte_ether_hdr *eth_hdr; struct rte_ether_addr eth_addr; - struct ptp_header *ptp_hdr; - struct clock_id *client_clkid; + struct rte_ptp_hdr *ptp_hdr; + uint8_t *client_clkid; struct ptp_message *ptp_msg; struct delay_req_msg *req_msg; struct rte_mbuf *created_pkt; - struct tstamp *origin_tstamp; + struct rte_ptp_timestamp *origin_tstamp; struct rte_ether_addr eth_multicast = ether_multicast; size_t pkt_size; int wait_us; @@ -399,22 +357,21 @@ parse_fup(struct ptpv2_time_receiver_ordinary *ptp_data) int ret; eth_hdr = rte_pktmbuf_mtod(m, struct rte_ether_hdr *); - ptp_hdr = rte_pktmbuf_mtod_offset(m, struct ptp_header *, + ptp_hdr = rte_pktmbuf_mtod_offset(m, struct rte_ptp_hdr *, sizeof(struct rte_ether_hdr)); - if (memcmp(&ptp_data->transmitter_clock_id, - &ptp_hdr->source_port_id.clock_id, - sizeof(struct clock_id)) != 0) + if (memcmp(ptp_data->transmitter_clock_id, + ptp_hdr->source_port_id.clock_id, 8) != 0) return; - ptp_data->seqID_FOLLOWUP = rte_be_to_cpu_16(ptp_hdr->seq_id); + ptp_data->seqID_FOLLOWUP = rte_be_to_cpu_16(ptp_hdr->sequence_id); ptp_msg = rte_pktmbuf_mtod_offset(m, struct ptp_message *, sizeof(struct rte_ether_hdr)); origin_tstamp = &ptp_msg->follow_up.precise_origin_tstamp; - ptp_data->tstamp1.tv_nsec = ntohl(origin_tstamp->ns); + ptp_data->tstamp1.tv_nsec = ntohl(origin_tstamp->nanoseconds); ptp_data->tstamp1.tv_sec = - ((uint64_t)ntohl(origin_tstamp->sec_lsb)) | - (((uint64_t)ntohs(origin_tstamp->sec_msb)) << 32); + ((uint64_t)ntohl(origin_tstamp->seconds_lo)) | + (((uint64_t)ntohs(origin_tstamp->seconds_hi)) << 32); if (ptp_data->seqID_FOLLOWUP == ptp_data->seqID_SYNC) { ret = rte_eth_macaddr_get(ptp_data->portid, ð_addr); @@ -441,34 +398,34 @@ parse_fup(struct ptpv2_time_receiver_ordinary *ptp_data) /* Set multicast address 01-1B-19-00-00-00. */ rte_ether_addr_copy(ð_multicast, ð_hdr->dst_addr); - eth_hdr->ether_type = htons(PTP_PROTOCOL); + eth_hdr->ether_type = htons(RTE_PTP_ETHERTYPE); req_msg = rte_pktmbuf_mtod_offset(created_pkt, struct delay_req_msg *, sizeof(struct rte_ether_hdr)); - req_msg->hdr.seq_id = htons(ptp_data->seqID_SYNC); - req_msg->hdr.msg_type = DELAY_REQ; - req_msg->hdr.ver = 2; + req_msg->hdr.sequence_id = htons(ptp_data->seqID_SYNC); + req_msg->hdr.msg_type = RTE_PTP_MSGTYPE_DELAY_REQ; + req_msg->hdr.version = 2; req_msg->hdr.control = 1; - req_msg->hdr.log_message_interval = 127; - req_msg->hdr.message_length = + req_msg->hdr.log_msg_interval = 127; + req_msg->hdr.msg_length = htons(sizeof(struct delay_req_msg)); req_msg->hdr.domain_number = ptp_hdr->domain_number; /* Set up clock id. */ client_clkid = - &req_msg->hdr.source_port_id.clock_id; + req_msg->hdr.source_port_id.clock_id; - client_clkid->id[0] = eth_hdr->src_addr.addr_bytes[0]; - client_clkid->id[1] = eth_hdr->src_addr.addr_bytes[1]; - client_clkid->id[2] = eth_hdr->src_addr.addr_bytes[2]; - client_clkid->id[3] = 0xFF; - client_clkid->id[4] = 0xFE; - client_clkid->id[5] = eth_hdr->src_addr.addr_bytes[3]; - client_clkid->id[6] = eth_hdr->src_addr.addr_bytes[4]; - client_clkid->id[7] = eth_hdr->src_addr.addr_bytes[5]; + client_clkid[0] = eth_hdr->src_addr.addr_bytes[0]; + client_clkid[1] = eth_hdr->src_addr.addr_bytes[1]; + client_clkid[2] = eth_hdr->src_addr.addr_bytes[2]; + client_clkid[3] = 0xFF; + client_clkid[4] = 0xFE; + client_clkid[5] = eth_hdr->src_addr.addr_bytes[3]; + client_clkid[6] = eth_hdr->src_addr.addr_bytes[4]; + client_clkid[7] = eth_hdr->src_addr.addr_bytes[5]; - ptp_data->client_clock_id = *client_clkid; + memcpy(ptp_data->client_clock_id, client_clkid, 8); /* Enable flag for hardware timestamping. */ created_pkt->ol_flags |= RTE_MBUF_F_TX_IEEE1588_TMST; @@ -534,21 +491,20 @@ parse_drsp(struct ptpv2_time_receiver_ordinary *ptp_data) { struct rte_mbuf *m = ptp_data->m; struct ptp_message *ptp_msg; - struct tstamp *rx_tstamp; + struct rte_ptp_timestamp *rx_tstamp; uint16_t seq_id; ptp_msg = rte_pktmbuf_mtod_offset(m, struct ptp_message *, sizeof(struct rte_ether_hdr)); - seq_id = rte_be_to_cpu_16(ptp_msg->delay_resp.hdr.seq_id); - if (memcmp(&ptp_data->client_clock_id, - &ptp_msg->delay_resp.req_port_id.clock_id, - sizeof(struct clock_id)) == 0) { + seq_id = rte_be_to_cpu_16(ptp_msg->delay_resp.hdr.sequence_id); + if (memcmp(ptp_data->client_clock_id, + ptp_msg->delay_resp.req_port_id.clock_id, 8) == 0) { if (seq_id == ptp_data->seqID_FOLLOWUP) { rx_tstamp = &ptp_msg->delay_resp.rx_tstamp; - ptp_data->tstamp4.tv_nsec = ntohl(rx_tstamp->ns); + ptp_data->tstamp4.tv_nsec = ntohl(rx_tstamp->nanoseconds); ptp_data->tstamp4.tv_sec = - ((uint64_t)ntohl(rx_tstamp->sec_lsb)) | - (((uint64_t)ntohs(rx_tstamp->sec_msb)) << 32); + ((uint64_t)ntohl(rx_tstamp->seconds_lo)) | + (((uint64_t)ntohs(rx_tstamp->seconds_hi)) << 32); /* Evaluate the delta for adjustment. */ ptp_data->delta = delta_eval(ptp_data); @@ -575,27 +531,27 @@ parse_drsp(struct ptpv2_time_receiver_ordinary *ptp_data) /* Parse ptp frames. 8< */ static void parse_ptp_frames(uint16_t portid, struct rte_mbuf *m) { - struct ptp_header *ptp_hdr; + struct rte_ptp_hdr *ptp_hdr; struct rte_ether_hdr *eth_hdr; uint16_t eth_type; eth_hdr = rte_pktmbuf_mtod(m, struct rte_ether_hdr *); eth_type = rte_be_to_cpu_16(eth_hdr->ether_type); - if (eth_type == PTP_PROTOCOL) { + if (eth_type == RTE_PTP_ETHERTYPE) { ptp_data.m = m; ptp_data.portid = portid; - ptp_hdr = rte_pktmbuf_mtod_offset(m, struct ptp_header *, + ptp_hdr = rte_pktmbuf_mtod_offset(m, struct rte_ptp_hdr *, sizeof(struct rte_ether_hdr)); - switch (ptp_hdr->msg_type) { - case SYNC: + switch (rte_ptp_msg_type(ptp_hdr)) { + case RTE_PTP_MSGTYPE_SYNC: parse_sync(&ptp_data, m->timesync); break; - case FOLLOW_UP: + case RTE_PTP_MSGTYPE_FOLLOW_UP: parse_fup(&ptp_data); break; - case DELAY_RESP: + case RTE_PTP_MSGTYPE_DELAY_RESP: parse_drsp(&ptp_data); print_clock_info(&ptp_data); break; -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* Re: [RFC v2 0/6] introduce PTP protocol library and software relay 2026-04-28 22:28 ` [RFC v2 0/6] introduce PTP protocol library and software relay Rajesh Kumar ` (5 preceding siblings ...) 2026-04-28 22:28 ` [RFC v2 6/6] examples/ptpclient: use shared PTP library definitions Rajesh Kumar @ 2026-04-29 15:37 ` Stephen Hemminger 2026-05-04 3:49 ` Kumar, Rajesh3 6 siblings, 1 reply; 60+ messages in thread From: Stephen Hemminger @ 2026-04-29 15:37 UTC (permalink / raw) To: Rajesh Kumar; +Cc: dev, bruce.richardson, aman.deep.singh On Wed, 29 Apr 2026 03:58:31 +0530 Rajesh Kumar <rajesh3.kumar@intel.com> wrote: > This series introduces a new DPDK library (lib/ptp) for IEEE 1588-2019 > PTP protocol packet processing and a companion example application > (ptp_tap_relay_sw) that demonstrates its usage. > > Motivation > ---------- > Several DPDK applications need to classify and manipulate PTP packets > (e.g. ptpclient, ptp_tap_relay, custom Transparent Clocks). Today each > application re-implements its own PTP header parsing and correctionField > handling. A shared library avoids duplication and provides a tested, > standards-compliant foundation. It would be great to have this, though not sure who would use it now. AI review spotted some things. Reviewed v2 of the PTP library series. Findings below. Patch 1/6 (lib/ptp): RTE_PTP_ETHERTYPE 0x88F7 duplicates RTE_ETHER_TYPE_1588 already in lib/net/rte_ether.h. Use the existing constant or alias to it. Comment on the version byte says "reserved (4) | versionPTP (4)"; IEEE 1588-2019 redefined the upper nibble as minorVersionPTP. Patch 2/6 (prog guide): The guide declares rte_ptp_hdr_get(const struct rte_mbuf *m) but the header has it non-const. Fix the doc, or split into const and non-const variants since the function returns a writable interior pointer. Patch 3/6 (ptp_tap_relay_sw): The standalone Makefile is missing CFLAGS += -DALLOW_EXPERIMENTAL_API. All three rte_ptp_* APIs the relay uses are __rte_experimental, so make-based builds will warn or fail under -Werror. meson is fine because meson.build sets allow_experimental_apis = true. Match examples/ptpclient/Makefile. relay_burst() calls rte_ptp_classify() and then rte_ptp_hdr_get() on every event packet, parsing it twice. The prog guide added in 2/6 explicitly tells callers to use rte_ptp_hdr_get() once and then rte_ptp_msg_type(hdr). The reference example should follow its own guidance. Minor: stats.corrections is updated via the global while the other counters are passed in by pointer. Inconsistent. Patch 4/6 (sample app guide): Limitations section claims L2-only and PTPv2-only, but the relay uses lib/ptp classification which handles VLAN/QinQ/UDPv4/UDPv6 with no version filter. Reword to "tested with L2 PTP" or drop the restriction. Patch 5/6 (test_ptp.c): Several correction tests dereference the rte_ptp_hdr_get() return without TEST_ASSERT_NOT_NULL: test_ptp_correction_zero, test_ptp_correction_known, and the four test_ptp_add_correction_* tests. test_ptp_two_step and test_ptp_seq_id get this right. No coverage for IPv4 with options (IHL > 5). The library's offset += ihl path is exercised by no test. Patch 6/6: no issues. ^ permalink raw reply [flat|nested] 60+ messages in thread
* RE: [RFC v2 0/6] introduce PTP protocol library and software relay 2026-04-29 15:37 ` [RFC v2 0/6] introduce PTP protocol library and software relay Stephen Hemminger @ 2026-05-04 3:49 ` Kumar, Rajesh3 0 siblings, 0 replies; 60+ messages in thread From: Kumar, Rajesh3 @ 2026-05-04 3:49 UTC (permalink / raw) To: Stephen Hemminger; +Cc: dev@dpdk.org, Richardson, Bruce, Singh, Aman Deep -----Original Message----- From: Stephen Hemminger <stephen@networkplumber.org> Sent: 29 April 2026 09:07 PM To: Kumar, Rajesh3 <rajesh3.kumar@intel.com> Cc: dev@dpdk.org; Richardson, Bruce <bruce.richardson@intel.com>; Singh, Aman Deep <aman.deep.singh@intel.com> Subject: Re: [RFC v2 0/6] introduce PTP protocol library and software relay On Wed, 29 Apr 2026 03:58:31 +0530 Rajesh Kumar <rajesh3.kumar@intel.com> wrote: > This series introduces a new DPDK library (lib/ptp) for IEEE 1588-2019 > PTP protocol packet processing and a companion example application > (ptp_tap_relay_sw) that demonstrates its usage. > > Motivation > ---------- > Several DPDK applications need to classify and manipulate PTP packets > (e.g. ptpclient, ptp_tap_relay, custom Transparent Clocks). Today each > application re-implements its own PTP header parsing and > correctionField handling. A shared library avoids duplication and > provides a tested, standards-compliant foundation. It would be great to have this, though not sure who would use it now. AI review spotted some things. Reviewed v2 of the PTP library series. Findings below. Patch 1/6 (lib/ptp): RTE_PTP_ETHERTYPE 0x88F7 duplicates RTE_ETHER_TYPE_1588 already in lib/net/rte_ether.h. Use the existing constant or alias to it. Comment on the version byte says "reserved (4) | versionPTP (4)"; IEEE 1588-2019 redefined the upper nibble as minorVersionPTP. Patch 2/6 (prog guide): The guide declares rte_ptp_hdr_get(const struct rte_mbuf *m) but the header has it non-const. Fix the doc, or split into const and non-const variants since the function returns a writable interior pointer. Patch 3/6 (ptp_tap_relay_sw): The standalone Makefile is missing CFLAGS += -DALLOW_EXPERIMENTAL_API. All three rte_ptp_* APIs the relay uses are __rte_experimental, so make-based builds will warn or fail under -Werror. meson is fine because meson.build sets allow_experimental_apis = true. Match examples/ptpclient/Makefile. relay_burst() calls rte_ptp_classify() and then rte_ptp_hdr_get() on every event packet, parsing it twice. The prog guide added in 2/6 explicitly tells callers to use rte_ptp_hdr_get() once and then rte_ptp_msg_type(hdr). The reference example should follow its own guidance. Minor: stats.corrections is updated via the global while the other counters are passed in by pointer. Inconsistent. Patch 4/6 (sample app guide): Limitations section claims L2-only and PTPv2-only, but the relay uses lib/ptp classification which handles VLAN/QinQ/UDPv4/UDPv6 with no version filter. Reword to "tested with L2 PTP" or drop the restriction. Patch 5/6 (test_ptp.c): Several correction tests dereference the rte_ptp_hdr_get() return without TEST_ASSERT_NOT_NULL: test_ptp_correction_zero, test_ptp_correction_known, and the four test_ptp_add_correction_* tests. test_ptp_two_step and test_ptp_seq_id get this right. No coverage for IPv4 with options (IHL > 5). The library's offset += ihl path is exercised by no test. Patch 6/6: no issues. >>>>Fixed. ^ permalink raw reply [flat|nested] 60+ messages in thread
* [RFC v3 0/6] introduce PTP protocol library and software relay 2026-04-28 1:01 [RFC v1 0/4] introduce PTP protocol library and software relay example Rajesh Kumar ` (5 preceding siblings ...) 2026-04-28 22:28 ` [RFC v2 0/6] introduce PTP protocol library and software relay Rajesh Kumar @ 2026-05-04 9:17 ` Rajesh Kumar 2026-05-04 9:17 ` [RFC v3 1/6] ptp: introduce PTP protocol library Rajesh Kumar ` (6 more replies) 2026-05-05 16:38 ` [RFC v4 " Rajesh Kumar ` (4 subsequent siblings) 11 siblings, 7 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-04 9:17 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, Rajesh Kumar This series introduces a new DPDK library (lib/ptp) for IEEE 1588-2019 PTP protocol packet processing and a companion example application (ptp_tap_relay_sw) that demonstrates its usage. Motivation ---------- Several DPDK applications need to classify and manipulate PTP packets (e.g. ptpclient, ptp_tap_relay, custom Transparent Clocks). Today each application re-implements its own PTP header parsing and correctionField handling. A shared library avoids duplication and provides a tested, standards-compliant foundation. Library: lib/ptp ---------------- The library provides: - PTP header structures (IEEE 1588-2019 common header, timestamp, port identity) - Packet classification: rte_ptp_classify() detects PTP over L2 (EtherType 0x88F7), VLAN-tagged L2 (TPIDs 0x8100/0x88A8, single or double), UDP/IPv4, UDP/IPv6 (ports 319/320), and VLAN-tagged UDP variants - Header access: rte_ptp_hdr_get() returns a pointer to the PTP header inside an mbuf - Inline helpers: correctionField manipulation (48.16 fixed-point), message type extraction, two-step flag check, timestamp conversion - Debug: rte_ptp_msg_type_str() for human-readable message names Example: ptp_tap_relay_sw ------------------------- A minimal PTP Transparent Clock relay between a DPDK-bound physical NIC and a kernel TAP interface using software timestamps only. No patched kernel modules, custom TAP PMD, or hardware timestamp support is required. The relay: 1. Receives packets on the physical NIC via DPDK 2. Parses packets using rte_ptp_hdr_get() 3. For event messages, records software timestamps (clock_gettime(CLOCK_MONOTONIC)) at ingress and egress 4. Adds residence time to correctionField via rte_ptp_add_correction() (IEEE 1588-2019 10.2 Transparent Clock) 5. Forwards bidirectionally: PHY <-> TAP Unit Tests: app/test -------------------- A comprehensive test suite (ptp_autotest) covers all library APIs, including VLAN/QinQ/IPv4/IPv6 transports, correctionField helpers, flags, and negative tests. This v3 also adds coverage for IPv4 options (IHL > 5). v3: - Reused RTE_ETHER_TYPE_1588 via RTE_PTP_ETHERTYPE alias - Updated version field comment to minorVersionPTP|versionPTP - Fixed prog guide rte_ptp_hdr_get() signature (non-const mbuf) - Added ALLOW_EXPERIMENTAL_API to ptp_tap_relay_sw Makefile - Updated relay example to parse once (rte_ptp_hdr_get + msg_type) - Updated relay sample app limitations wording to match capabilities - Added hdr_get NULL checks in correction tests - Added IPv4 options classification test (IHL > 5) - Made correction counter update style consistent via pointer arg v2: - Fixed flag bit positions for host-order representation after rte_be_to_cpu_16(): TWO_STEP (1<<9), UNICAST (1<<10), LI_61 (1<<0), LI_59 (1<<1) - Fixed QinQ classification: outer 0x88A8 now enters VLAN parser - Fixed signed left-shift UB in rte_ptp_add_correction() - Fixed promiscuous enable failure handling in relay port_init() - Replaced deprecated master/slave terminology with IEEE terms - Added and documented IHL validation for IPv4 PTP-over-UDP - Changed rte_ptp_is_event() parameter to int with negative guard - Changed rte_ptp_hdr_get() parameter to non-const mbuf - Added programmer's guide note about avoiding double-parse Rajesh Kumar (6): ptp: introduce PTP protocol library doc: add PTP library programmer's guide examples/ptp_tap_relay_sw: add software PTP relay example doc: add PTP software relay sample app guide app/test: add PTP library unit tests examples/ptpclient: use shared PTP library definitions MAINTAINERS | 8 + app/test/meson.build | 1 + app/test/test_ptp.c | 1106 +++++++++++++++++ doc/api/doxy-api-index.md | 1 + doc/api/doxy-api.conf.in | 1 + doc/guides/prog_guide/index.rst | 1 + doc/guides/prog_guide/ptp_lib.rst | 205 +++ doc/guides/rel_notes/release_26_07.rst | 13 + doc/guides/sample_app_ug/index.rst | 1 + doc/guides/sample_app_ug/ptp_tap_relay_sw.rst | 212 ++++ examples/meson.build | 1 + examples/ptp_tap_relay_sw/Makefile | 43 + examples/ptp_tap_relay_sw/meson.build | 14 + examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c | 433 +++++++ examples/ptpclient/meson.build | 1 + examples/ptpclient/ptpclient.c | 188 ++- lib/meson.build | 1 + lib/ptp/meson.build | 6 + lib/ptp/rte_ptp.c | 185 +++ lib/ptp/rte_ptp.h | 339 +++++ 20 files changed, 2644 insertions(+), 116 deletions(-) create mode 100644 app/test/test_ptp.c create mode 100644 doc/guides/prog_guide/ptp_lib.rst create mode 100644 doc/guides/sample_app_ug/ptp_tap_relay_sw.rst create mode 100644 examples/ptp_tap_relay_sw/Makefile create mode 100644 examples/ptp_tap_relay_sw/meson.build create mode 100644 examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c create mode 100644 lib/ptp/meson.build create mode 100644 lib/ptp/rte_ptp.c create mode 100644 lib/ptp/rte_ptp.h base-commit: 7baf81674a011ab8a2fe329566b6d43d7377244c -- 2.53.0 ^ permalink raw reply [flat|nested] 60+ messages in thread
* [RFC v3 1/6] ptp: introduce PTP protocol library 2026-05-04 9:17 ` [RFC v3 " Rajesh Kumar @ 2026-05-04 9:17 ` Rajesh Kumar 2026-05-04 9:17 ` [RFC v3 2/6] doc: add PTP library programmer's guide Rajesh Kumar ` (5 subsequent siblings) 6 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-04 9:17 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, Rajesh Kumar [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #1: Type: text/plain; charset=yes, Size: 19055 bytes --] Add IEEE 1588-2019 Precision Time Protocol (PTP) processing library. The library provides packet classification, header structures, and helper functions for PTP packet processing in DPDK. This avoids duplicate PTP header definitions across multiple applications and drivers. Supported transports: 1. L2 PTP (EtherType 0x88F7) 2. VLAN-tagged L2 PTP (single and QinQ) 3. PTP over UDP/IPv4 (ports 319/320) 4. PTP over UDP/IPv6 (ports 319/320) Public APIs: 1. rte_ptp_classify() 2. rte_ptp_hdr_get() 3. rte_ptp_msg_type_str() Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- MAINTAINERS | 7 + doc/guides/rel_notes/release_26_07.rst | 7 + lib/meson.build | 1 + lib/ptp/meson.build | 6 + lib/ptp/rte_ptp.c | 185 ++++++++++++++ lib/ptp/rte_ptp.h | 339 +++++++++++++++++++++++++ 6 files changed, 545 insertions(+) create mode 100644 lib/ptp/meson.build create mode 100644 lib/ptp/rte_ptp.c create mode 100644 lib/ptp/rte_ptp.h diff --git a/MAINTAINERS b/MAINTAINERS index 0f5539f851..665e08dc90 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1665,6 +1665,13 @@ F: doc/guides/prog_guide/ipsec_lib.rst M: Vladimir Medvedkin <vladimir.medvedkin@intel.com> F: app/test-sad/ +PTP - EXPERIMENTAL +M: Rajesh Kumar <rajesh3.kumar@intel.com> +F: lib/ptp/ +F: doc/guides/prog_guide/ptp_lib.rst +F: examples/ptp_tap_relay_sw/ +F: doc/guides/sample_app_ug/ptp_tap_relay_sw.rst + PDCP - EXPERIMENTAL M: Anoob Joseph <anoobj@marvell.com> M: Volodymyr Fialko <vfialko@marvell.com> diff --git a/doc/guides/rel_notes/release_26_07.rst b/doc/guides/rel_notes/release_26_07.rst index f012d47a4b..b2208d6fb3 100644 --- a/doc/guides/rel_notes/release_26_07.rst +++ b/doc/guides/rel_notes/release_26_07.rst @@ -63,6 +63,13 @@ New Features ``rte_eal_init`` and the application is responsible for probing each device, * ``--auto-probing`` enables the initial bus probing, which is the current default behavior. +* **Added PTP protocol library.** + + Added a new library ``rte_ptp`` for IEEE 1588 Precision Time Protocol + packet processing. The library provides packet classification, header + parsing, and correctionField manipulation across L2, VLAN-tagged, + UDP/IPv4, and UDP/IPv6 transports. + Removed Items ------------- diff --git a/lib/meson.build b/lib/meson.build index 8f5cfd28a5..78d694f951 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -50,6 +50,7 @@ libraries = [ 'member', 'pcapng', 'power', + 'ptp', 'rawdev', 'regexdev', 'mldev', diff --git a/lib/ptp/meson.build b/lib/ptp/meson.build new file mode 100644 index 0000000000..05f9d87cbe --- /dev/null +++ b/lib/ptp/meson.build @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2026 Intel Corporation + +sources = files('rte_ptp.c') +headers = files('rte_ptp.h') +deps += ['mbuf', 'net'] diff --git a/lib/ptp/rte_ptp.c b/lib/ptp/rte_ptp.c new file mode 100644 index 0000000000..5e4fc666fb --- /dev/null +++ b/lib/ptp/rte_ptp.c @@ -0,0 +1,185 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2026 Intel Corporation + * + * IEEE 1588 / PTP Protocol Library — Implementation + */ + +#include <eal_export.h> +#include <rte_ether.h> +#include <rte_ip.h> +#include <rte_udp.h> + +#include "rte_ptp.h" + +/* + * Internal: find PTP header offset within a packet. + * Returns pointer to PTP header or NULL. + */ +static struct rte_ptp_hdr * +ptp_hdr_find(const struct rte_mbuf *m) +{ + const struct rte_ether_hdr *eth; + uint16_t ether_type; + uint32_t offset; + + if (rte_pktmbuf_data_len(m) < sizeof(struct rte_ether_hdr)) + return NULL; + + eth = rte_pktmbuf_mtod(m, const struct rte_ether_hdr *); + ether_type = rte_be_to_cpu_16(eth->ether_type); + offset = sizeof(struct rte_ether_hdr); + + /* Strip VLAN / QinQ tags to reach the inner EtherType */ + if (ether_type == RTE_ETHER_TYPE_VLAN || + ether_type == RTE_ETHER_TYPE_QINQ) { + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_vlan_hdr)) + return NULL; + const struct rte_vlan_hdr *vlan = + rte_pktmbuf_mtod_offset(m, + const struct rte_vlan_hdr *, offset); + ether_type = rte_be_to_cpu_16(vlan->eth_proto); + offset += sizeof(struct rte_vlan_hdr); + + /* Second tag (QinQ inner or stacked VLAN) */ + if (ether_type == RTE_ETHER_TYPE_VLAN || + ether_type == RTE_ETHER_TYPE_QINQ) { + if (rte_pktmbuf_data_len(m) < + offset + sizeof(struct rte_vlan_hdr)) + return NULL; + vlan = rte_pktmbuf_mtod_offset(m, + const struct rte_vlan_hdr *, offset); + ether_type = rte_be_to_cpu_16(vlan->eth_proto); + offset += sizeof(struct rte_vlan_hdr); + } + } + + /* L2 PTP: EtherType 0x88F7 (plain, VLAN, or QinQ) */ + if (ether_type == RTE_PTP_ETHERTYPE) { + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ptp_hdr)) + return NULL; + return rte_pktmbuf_mtod_offset(m, + struct rte_ptp_hdr *, offset); + } + + /* PTP over UDP/IPv4 (plain or VLAN-tagged) */ + if (ether_type == RTE_ETHER_TYPE_IPV4) { + const struct rte_ipv4_hdr *iph; + uint16_t ihl; + + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ipv4_hdr)) + return NULL; + + iph = rte_pktmbuf_mtod_offset(m, + const struct rte_ipv4_hdr *, offset); + if (iph->next_proto_id != IPPROTO_UDP) + return NULL; + + ihl = (iph->version_ihl & 0x0F) * 4; + if (ihl < 20) + return NULL; + offset += ihl; + + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_udp_hdr)) + return NULL; + + const struct rte_udp_hdr *udp = + rte_pktmbuf_mtod_offset(m, + const struct rte_udp_hdr *, offset); + uint16_t dst_port = rte_be_to_cpu_16(udp->dst_port); + + if (dst_port != RTE_PTP_EVENT_PORT && + dst_port != RTE_PTP_GENERAL_PORT) + return NULL; + + offset += sizeof(struct rte_udp_hdr); + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ptp_hdr)) + return NULL; + + return rte_pktmbuf_mtod_offset(m, + struct rte_ptp_hdr *, offset); + } + + /* PTP over UDP/IPv6 (plain or VLAN-tagged) */ + if (ether_type == RTE_ETHER_TYPE_IPV6) { + const struct rte_ipv6_hdr *ip6h; + + if (rte_pktmbuf_data_len(m) < + offset + sizeof(struct rte_ipv6_hdr)) + return NULL; + + ip6h = rte_pktmbuf_mtod_offset(m, + const struct rte_ipv6_hdr *, offset); + if (ip6h->proto != IPPROTO_UDP) + return NULL; + + offset += sizeof(struct rte_ipv6_hdr); + + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_udp_hdr)) + return NULL; + + const struct rte_udp_hdr *udp = + rte_pktmbuf_mtod_offset(m, + const struct rte_udp_hdr *, offset); + uint16_t dst_port = rte_be_to_cpu_16(udp->dst_port); + + if (dst_port != RTE_PTP_EVENT_PORT && + dst_port != RTE_PTP_GENERAL_PORT) + return NULL; + + offset += sizeof(struct rte_udp_hdr); + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ptp_hdr)) + return NULL; + + return rte_pktmbuf_mtod_offset(m, + struct rte_ptp_hdr *, offset); + } + + return NULL; +} + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_ptp_classify, 26.07) +int +rte_ptp_classify(const struct rte_mbuf *m) +{ + struct rte_ptp_hdr *hdr = ptp_hdr_find(m); + + if (hdr == NULL) + return RTE_PTP_MSGTYPE_INVALID; + + return rte_ptp_msg_type(hdr); +} + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_ptp_hdr_get, 26.07) +struct rte_ptp_hdr * +rte_ptp_hdr_get(struct rte_mbuf *m) +{ + return ptp_hdr_find(m); +} + +static const char * const ptp_msg_names[] = { + [RTE_PTP_MSGTYPE_SYNC] = "Sync", + [RTE_PTP_MSGTYPE_DELAY_REQ] = "Delay_Req", + [RTE_PTP_MSGTYPE_PDELAY_REQ] = "PDelay_Req", + [RTE_PTP_MSGTYPE_PDELAY_RESP] = "PDelay_Resp", + [0x4] = "Reserved_4", + [0x5] = "Reserved_5", + [0x6] = "Reserved_6", + [0x7] = "Reserved_7", + [RTE_PTP_MSGTYPE_FOLLOW_UP] = "Follow_Up", + [RTE_PTP_MSGTYPE_DELAY_RESP] = "Delay_Resp", + [RTE_PTP_MSGTYPE_PDELAY_RESP_FU] = "PDelay_Resp_Follow_Up", + [RTE_PTP_MSGTYPE_ANNOUNCE] = "Announce", + [RTE_PTP_MSGTYPE_SIGNALING] = "Signaling", + [RTE_PTP_MSGTYPE_MANAGEMENT] = "Management", + [0xE] = "Reserved_E", + [0xF] = "Reserved_F", +}; + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_ptp_msg_type_str, 26.07) +const char * +rte_ptp_msg_type_str(int msg_type) +{ + if (msg_type < 0 || msg_type > 0xF) + return "Not_PTP"; + return ptp_msg_names[msg_type]; +} diff --git a/lib/ptp/rte_ptp.h b/lib/ptp/rte_ptp.h new file mode 100644 index 0000000000..b13dc54d43 --- /dev/null +++ b/lib/ptp/rte_ptp.h @@ -0,0 +1,339 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2026 Intel Corporation + * + * IEEE 1588 / PTP Protocol Library + * + * Provides header structures, packet classification, and helper functions + * for Precision Time Protocol (IEEE 1588-2019) packet processing in DPDK. + * + * Supports: + * - L2 PTP (EtherType 0x88F7) + * - VLAN-tagged L2 PTP (single or double VLAN, TPIDs 0x8100/0x88A8) + * - PTP over UDP/IPv4 (ports 319/320) + * - PTP over UDP/IPv6 (ports 319/320) + * - VLAN-tagged PTP over UDP/IPv4 or UDP/IPv6 + * - Two-step and one-step message identification + * - correctionField manipulation (scaled nanoseconds, 48.16 fixed-point) + * + * Limitations: + * - IPv6 extension headers are not traversed. + */ + +#ifndef _RTE_PTP_H_ +#define _RTE_PTP_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdint.h> +#include <stdbool.h> +#include <rte_byteorder.h> +#include <rte_ether.h> +#include <rte_mbuf.h> +#include <rte_common.h> + +/** + * @file + * IEEE 1588 PTP protocol definitions and helpers. + */ + +/* ============================================================ + * PTP Constants + * ============================================================ + */ + +/** PTP EtherType (IEEE 802.1AS / IEEE 1588) */ +#define RTE_PTP_ETHERTYPE RTE_ETHER_TYPE_1588 + +/** PTP over UDP event port (Sync, Delay_Req, PDelay_Req, PDelay_Resp) */ +#define RTE_PTP_EVENT_PORT 319 + +/** PTP over UDP general port (Follow_Up, Delay_Resp, Announce, etc.) */ +#define RTE_PTP_GENERAL_PORT 320 + +/** PTP multicast MAC address: 01:1B:19:00:00:00 */ +#define RTE_PTP_MULTICAST_MAC { 0x01, 0x1B, 0x19, 0x00, 0x00, 0x00 } + +/** PTP peer delay multicast MAC: 01:80:C2:00:00:0E */ +#define RTE_PTP_PDELAY_MULTICAST_MAC { 0x01, 0x80, 0xC2, 0x00, 0x00, 0x0E } + +/* ============================================================ + * PTP Message Types (IEEE 1588-2019 Table 36) + * ============================================================ + */ + +#define RTE_PTP_MSGTYPE_SYNC 0x0 /**< Sync (event) */ +#define RTE_PTP_MSGTYPE_DELAY_REQ 0x1 /**< Delay_Req (event) */ +#define RTE_PTP_MSGTYPE_PDELAY_REQ 0x2 /**< Peer_Delay_Req (event) */ +#define RTE_PTP_MSGTYPE_PDELAY_RESP 0x3 /**< Peer_Delay_Resp (event) */ +#define RTE_PTP_MSGTYPE_FOLLOW_UP 0x8 /**< Follow_Up (general) */ +#define RTE_PTP_MSGTYPE_DELAY_RESP 0x9 /**< Delay_Resp (general) */ +#define RTE_PTP_MSGTYPE_PDELAY_RESP_FU 0xA /**< Peer_Delay_Resp_Follow_Up */ +#define RTE_PTP_MSGTYPE_ANNOUNCE 0xB /**< Announce (general) */ +#define RTE_PTP_MSGTYPE_SIGNALING 0xC /**< Signaling (general) */ +#define RTE_PTP_MSGTYPE_MANAGEMENT 0xD /**< Management (general) */ + +/** Invalid / not a PTP packet */ +#define RTE_PTP_MSGTYPE_INVALID (-1) + +/* ============================================================ + * PTP Flag Field Bits (IEEE 1588-2019 Table 37) + * + * These constants are for use after rte_be_to_cpu_16(hdr->flags). + * flagField[0] (octet 6) maps to host bits 8-15. + * flagField[1] (octet 7) maps to host bits 0-7. + * ============================================================ + */ + +#define RTE_PTP_FLAG_TWO_STEP (1 << 9) /**< Two-step flag (flagField[0] bit 1) */ +#define RTE_PTP_FLAG_UNICAST (1 << 10) /**< Unicast flag (flagField[0] bit 2) */ +#define RTE_PTP_FLAG_LI_61 (1 << 0) /**< Leap indicator 61 (flagField[1] bit 0) */ +#define RTE_PTP_FLAG_LI_59 (1 << 1) /**< Leap indicator 59 (flagField[1] bit 1) */ + +/* ============================================================ + * PTP Header Structures (IEEE 1588-2019) + * ============================================================ + */ + +/** + * PTP Port Identity (10 bytes). + */ +struct __rte_packed_begin rte_ptp_port_id { + uint8_t clock_id[8]; /**< clockIdentity (EUI-64) */ + uint16_t port_number; /**< portNumber */ +} __rte_packed_end; + +/** + * PTP Common Message Header (34 bytes). + * All PTP messages begin with this header. + */ +struct __rte_packed_begin rte_ptp_hdr { + uint8_t msg_type; /**< transportSpecific (4) | messageType (4) */ + uint8_t version; /**< minorVersionPTP (4) | versionPTP (4) */ + uint16_t msg_length; /**< Total message length in bytes */ + uint8_t domain_number; /**< PTP domain (0-255) */ + uint8_t minor_sdo_id; /**< minorSdoId (IEEE 1588-2019) */ + uint16_t flags; /**< Flag field (see RTE_PTP_FLAG_*) */ + int64_t correction; /**< correctionField (scaled ns, 48.16 fixed) */ + uint32_t msg_type_specific; /**< messageTypeSpecific */ + struct rte_ptp_port_id source_port_id; /**< sourcePortIdentity */ + uint16_t sequence_id; /**< sequenceId */ + uint8_t control; /**< controlField (deprecated in 1588-2019) */ + int8_t log_msg_interval; /**< logMessageInterval */ +} __rte_packed_end; + +/** + * PTP Timestamp (10 bytes, used in Sync/Delay_Req/Follow_Up bodies). + */ +struct __rte_packed_begin rte_ptp_timestamp { + uint16_t seconds_hi; /**< Upper 16 bits of seconds */ + uint32_t seconds_lo; /**< Lower 32 bits of seconds */ + uint32_t nanoseconds; /**< Nanoseconds (0-999999999) */ +} __rte_packed_end; + +/* ============================================================ + * Inline Helpers + * ============================================================ + */ + +/** + * Extract PTP message type from header. + * + * @param hdr + * Pointer to PTP header. + * @return + * Message type (0x0-0xF). + */ +static inline uint8_t +rte_ptp_msg_type(const struct rte_ptp_hdr *hdr) +{ + return hdr->msg_type & 0x0F; +} + +/** + * Extract transport-specific field from header. + * + * @param hdr + * Pointer to PTP header. + * @return + * Transport-specific value (upper nibble, 0x0-0xF). + */ +static inline uint8_t +rte_ptp_transport_specific(const struct rte_ptp_hdr *hdr) +{ + return (hdr->msg_type >> 4) & 0x0F; +} + +/** + * Extract PTP version from header. + * + * @param hdr + * Pointer to PTP header. + * @return + * PTP version number (typically 2). + */ +static inline uint8_t +rte_ptp_version(const struct rte_ptp_hdr *hdr) +{ + return hdr->version & 0x0F; +} + +/** + * Get sequence ID from PTP header (host byte order). + * + * @param hdr + * Pointer to PTP header. + * @return + * Sequence ID in host byte order. + */ +static inline uint16_t +rte_ptp_seq_id(const struct rte_ptp_hdr *hdr) +{ + return rte_be_to_cpu_16(hdr->sequence_id); +} + +/** + * Get PTP domain number. + * + * @param hdr + * Pointer to PTP header. + * @return + * Domain number (0-255). + */ +static inline uint8_t +rte_ptp_domain(const struct rte_ptp_hdr *hdr) +{ + return hdr->domain_number; +} + +/** + * Check if PTP message type is an event message. + * Event messages (msg_type 0x0-0x3) require hardware timestamps. + * + * @param msg_type + * PTP message type value (0x0-0xF). + * @return + * true if event message, false otherwise. + */ +static inline bool +rte_ptp_is_event(int msg_type) +{ + return msg_type >= 0 && msg_type <= RTE_PTP_MSGTYPE_PDELAY_RESP; +} + +/** + * Check if the two-step flag is set in a PTP header. + * + * @param hdr + * Pointer to PTP header. + * @return + * true if two-step flag is set. + */ +static inline bool +rte_ptp_is_two_step(const struct rte_ptp_hdr *hdr) +{ + return (rte_be_to_cpu_16(hdr->flags) & RTE_PTP_FLAG_TWO_STEP) != 0; +} + +/** + * Get correctionField value in nanoseconds (from 48.16 fixed-point). + * + * @param hdr + * Pointer to PTP header. + * @return + * Correction value in nanoseconds. + */ +static inline int64_t +rte_ptp_correction_ns(const struct rte_ptp_hdr *hdr) +{ + return (int64_t)rte_be_to_cpu_64(hdr->correction) >> 16; +} + +/** + * Add a residence time (in nanoseconds) to the correctionField. + * Used by Transparent Clocks to account for relay transit delay. + * The correctionField uses IEEE 1588 scaled nanoseconds (48.16 fixed-point). + * + * @param hdr + * Pointer to PTP header (will be modified in-place). + * @param residence_ns + * Residence time in nanoseconds to add. + */ +static inline void +rte_ptp_add_correction(struct rte_ptp_hdr *hdr, int64_t residence_ns) +{ + int64_t cf = (int64_t)rte_be_to_cpu_64(hdr->correction); + cf += (int64_t)((uint64_t)residence_ns << 16); + hdr->correction = rte_cpu_to_be_64(cf); +} + +/** + * Convert a PTP timestamp structure to nanoseconds since epoch. + * + * @param ts + * Pointer to PTP timestamp. + * @return + * Time in nanoseconds since epoch. + */ +static inline uint64_t +rte_ptp_timestamp_to_ns(const struct rte_ptp_timestamp *ts) +{ + uint64_t sec = ((uint64_t)rte_be_to_cpu_16(ts->seconds_hi) << 32) | + rte_be_to_cpu_32(ts->seconds_lo); + return sec * 1000000000ULL + rte_be_to_cpu_32(ts->nanoseconds); +} + +/* ============================================================ + * Packet Classification Functions (implemented in rte_ptp.c) + * ============================================================ + */ + +/** + * Classify a packet as PTP and return the message type. + * + * Examines the mbuf to determine if it contains a PTP message. + * Supports L2 (EtherType 0x88F7), VLAN-tagged L2 (single/double, + * TPIDs 0x8100/0x88A8), PTP over UDP/IPv4, PTP over UDP/IPv6, + * and VLAN-tagged UDP/IPv4 or UDP/IPv6 (ports 319/320). + * + * @param m + * Pointer to the mbuf to classify. + * @return + * PTP message type (0x0-0xF) on success, RTE_PTP_MSGTYPE_INVALID (-1) + * if the packet is not PTP. + */ +__rte_experimental +int rte_ptp_classify(const struct rte_mbuf *m); + +/** + * Get a pointer to the PTP header inside an mbuf. + * + * Locates the PTP header within the packet, handling L2, VLAN-tagged L2 + * (single/double, TPIDs 0x8100/0x88A8), PTP over UDP/IPv4, + * PTP over UDP/IPv6, and VLAN-tagged UDP variants. + * + * @param m + * Pointer to the mbuf. + * @return + * Pointer to the PTP header, or NULL if not a PTP packet. + */ +__rte_experimental +struct rte_ptp_hdr *rte_ptp_hdr_get(struct rte_mbuf *m); + +/** + * Get a human-readable name for a PTP message type. + * + * @param msg_type + * PTP message type (0x0-0xF or RTE_PTP_MSGTYPE_INVALID). + * @return + * Static string with the message type name (e.g., "Sync", "Follow_Up"). + */ +__rte_experimental +const char *rte_ptp_msg_type_str(int msg_type); + +#ifdef __cplusplus +} +#endif + +#endif /* _RTE_PTP_H_ */ -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [RFC v3 2/6] doc: add PTP library programmer's guide 2026-05-04 9:17 ` [RFC v3 " Rajesh Kumar 2026-05-04 9:17 ` [RFC v3 1/6] ptp: introduce PTP protocol library Rajesh Kumar @ 2026-05-04 9:17 ` Rajesh Kumar 2026-05-04 9:17 ` [RFC v3 3/6] examples/ptp_tap_relay_sw: add software PTP relay example Rajesh Kumar ` (4 subsequent siblings) 6 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-04 9:17 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, Rajesh Kumar [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #1: Type: text/plain; charset=yes, Size: 9526 bytes --] Add programmer's guide for the PTP protocol library covering message types, header structures, packet classification API, inline helpers, and usage examples. Add PTP header to Doxygen API index under layers section. Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- doc/api/doxy-api-index.md | 1 + doc/api/doxy-api.conf.in | 1 + doc/guides/prog_guide/index.rst | 1 + doc/guides/prog_guide/ptp_lib.rst | 205 ++++++++++++++++++++++++++++++ 4 files changed, 208 insertions(+) create mode 100644 doc/guides/prog_guide/ptp_lib.rst diff --git a/doc/api/doxy-api-index.md b/doc/api/doxy-api-index.md index 9296042119..bbc79168bb 100644 --- a/doc/api/doxy-api-index.md +++ b/doc/api/doxy-api-index.md @@ -137,6 +137,7 @@ The public API headers are grouped by topics: [eCPRI](@ref rte_ecpri.h), [PDCP hdr](@ref rte_pdcp_hdr.h), [PDCP](@ref rte_pdcp.h), + [PTP](@ref rte_ptp.h), [L2TPv2](@ref rte_l2tpv2.h), [PPP](@ref rte_ppp.h), [IB](@ref rte_ib.h) diff --git a/doc/api/doxy-api.conf.in b/doc/api/doxy-api.conf.in index bedd944681..f15d962733 100644 --- a/doc/api/doxy-api.conf.in +++ b/doc/api/doxy-api.conf.in @@ -72,6 +72,7 @@ INPUT = @TOPDIR@/doc/api/doxy-api-index.md \ @TOPDIR@/lib/pmu \ @TOPDIR@/lib/port \ @TOPDIR@/lib/power \ + @TOPDIR@/lib/ptp \ @TOPDIR@/lib/ptr_compress \ @TOPDIR@/lib/rawdev \ @TOPDIR@/lib/rcu \ diff --git a/doc/guides/prog_guide/index.rst b/doc/guides/prog_guide/index.rst index e6f24945b0..60dad4475c 100644 --- a/doc/guides/prog_guide/index.rst +++ b/doc/guides/prog_guide/index.rst @@ -97,6 +97,7 @@ Protocol Processing Libraries :maxdepth: 1 :numbered: + ptp_lib pdcp_lib ipsec_lib diff --git a/doc/guides/prog_guide/ptp_lib.rst b/doc/guides/prog_guide/ptp_lib.rst new file mode 100644 index 0000000000..fb6794781a --- /dev/null +++ b/doc/guides/prog_guide/ptp_lib.rst @@ -0,0 +1,205 @@ +.. SPDX-License-Identifier: BSD-3-Clause + Copyright(c) 2026 Intel Corporation. + +PTP Protocol Library +==================== + +The DPDK PTP library provides IEEE 1588 / Precision Time Protocol (PTP) +packet structures, constants, and helper functions for PTP packet processing. + +The library supports classification and header parsing of PTP messages +across multiple transport encapsulations: + +- L2 PTP (EtherType 0x88F7) +- VLAN-tagged L2 PTP (single or double VLAN, TPIDs 0x8100 and 0x88A8) +- PTP over UDP/IPv4 (destination ports 319 and 320) +- PTP over UDP/IPv6 (destination ports 319 and 320) +- VLAN-tagged PTP over UDP/IPv4 or UDP/IPv6 + +The library conforms to +`IEEE 1588-2019 <https://standards.ieee.org/ieee/1588/6825/>`_ +(Precision Time Protocol). + +Overview +-------- + +PTP is the foundation of time synchronization in networking. +DPDK applications that relay, classify, or timestamp PTP packets +currently duplicate header definitions and parsing logic. +This library provides a shared, tested implementation. + +The library provides: + +#. Packed header structures matching the IEEE 1588-2019 wire format +#. Constants for message types, flags, ports, and multicast addresses +#. Inline helpers for common field extraction and manipulation +#. Packet classification across L2, VLAN, UDP/IPv4, and UDP/IPv6 transports +#. Correction field manipulation for Transparent Clock residence time + +PTP Message Types +----------------- + +IEEE 1588-2019 defines the following message types, all supported by the library: + +.. csv-table:: PTP Message Types + :header: "Type", "Name", "Category", "Macro" + :widths: 5, 20, 10, 30 + + "0x0", "Sync", "Event", "``RTE_PTP_MSGTYPE_SYNC``" + "0x1", "Delay_Req", "Event", "``RTE_PTP_MSGTYPE_DELAY_REQ``" + "0x2", "Peer_Delay_Req", "Event", "``RTE_PTP_MSGTYPE_PDELAY_REQ``" + "0x3", "Peer_Delay_Resp", "Event", "``RTE_PTP_MSGTYPE_PDELAY_RESP``" + "0x8", "Follow_Up", "General", "``RTE_PTP_MSGTYPE_FOLLOW_UP``" + "0x9", "Delay_Resp", "General", "``RTE_PTP_MSGTYPE_DELAY_RESP``" + "0xA", "PDelay_Resp_Follow_Up", "General", "``RTE_PTP_MSGTYPE_PDELAY_RESP_FU``" + "0xB", "Announce", "General", "``RTE_PTP_MSGTYPE_ANNOUNCE``" + "0xC", "Signaling", "General", "``RTE_PTP_MSGTYPE_SIGNALING``" + "0xD", "Management", "General", "``RTE_PTP_MSGTYPE_MANAGEMENT``" + +Event messages (types 0x0–0x3) require hardware timestamps for accurate +time transfer. + +Header Structures +----------------- + +The library defines the following packed structures that map directly to +the IEEE 1588-2019 wire format: + +``struct rte_ptp_hdr`` + Common PTP message header (34 bytes). All PTP messages begin with this header. + Contains message type, version, flags, correction field, source port identity, + sequence ID, and log message interval. + +``struct rte_ptp_timestamp`` + PTP timestamp (10 bytes). Used in Sync, Delay_Req, and Follow_Up message bodies. + Contains seconds (48-bit) and nanoseconds (32-bit). + +``struct rte_ptp_port_id`` + PTP port identity (10 bytes). Contains an EUI-64 clock identity and a + 16-bit port number. + +Packet Classification API +-------------------------- + +``rte_ptp_classify()`` +~~~~~~~~~~~~~~~~~~~~~~ + +Classify a packet and return the PTP message type. + +.. code-block:: C + + int rte_ptp_classify(const struct rte_mbuf *m); + +Examines the mbuf to determine if it contains a PTP message. +Returns the PTP message type (0x0–0xF) on success, +or ``RTE_PTP_MSGTYPE_INVALID`` (-1) if the packet is not PTP. + +Supported encapsulations (VLAN TPIDs recognised: 0x8100 and 0x88A8): + +- EtherType 0x88F7 (L2 PTP) +- Single VLAN (0x8100 or 0x88A8) + EtherType 0x88F7 +- Double VLAN (any combination of 0x8100 / 0x88A8) + EtherType 0x88F7 +- IPv4 + UDP destination port 319 or 320 +- IPv6 + UDP destination port 319 or 320 +- Single or double VLAN + IPv4/IPv6 + UDP destination port 319 or 320 + +``rte_ptp_hdr_get()`` +~~~~~~~~~~~~~~~~~~~~~ + +Get a pointer to the PTP header inside a packet. + +.. code-block:: C + + struct rte_ptp_hdr *rte_ptp_hdr_get(struct rte_mbuf *m); + +Returns a pointer to the PTP header, or NULL if the packet is not PTP. +Handles the same set of encapsulations as ``rte_ptp_classify()``. + +``rte_ptp_msg_type_str()`` +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Convert a PTP message type to a human-readable string. + +.. code-block:: C + + const char *rte_ptp_msg_type_str(int msg_type); + +Returns a string such as ``"Sync"``, ``"Delay_Req"``, ``"Follow_Up"``, etc. +Returns ``"Not_PTP"`` for invalid message types. + +Inline Helpers +-------------- + +The following inline functions operate on ``struct rte_ptp_hdr`` and require +no function call overhead: + +.. csv-table:: Inline Helper Functions + :header: "Function", "Returns", "Description" + :widths: 30, 15, 40 + + "``rte_ptp_msg_type()``", "``uint8_t``", "Extract message type (lower nibble)" + "``rte_ptp_transport_specific()``", "``uint8_t``", "Extract transport-specific field (upper nibble)" + "``rte_ptp_version()``", "``uint8_t``", "Extract PTP version number" + "``rte_ptp_seq_id()``", "``uint16_t``", "Get sequence ID in host byte order" + "``rte_ptp_domain()``", "``uint8_t``", "Get PTP domain number" + "``rte_ptp_is_event()``", "``bool``", "Check if message type is an event (0x0–0x3)" + "``rte_ptp_is_two_step()``", "``bool``", "Check if two-step flag is set" + "``rte_ptp_correction_ns()``", "``int64_t``", "Get correctionField in nanoseconds (from 48.16 fixed-point)" + "``rte_ptp_add_correction()``", "``void``", "Add residence time to correctionField (for Transparent Clocks)" + "``rte_ptp_timestamp_to_ns()``", "``uint64_t``", "Convert PTP timestamp struct to nanoseconds" + +Usage Example +------------- + +Classifying and processing PTP packets: + +.. code-block:: C + + #include <rte_ptp.h> + + void process_packets(struct rte_mbuf **pkts, uint16_t nb_pkts) + { + for (uint16_t i = 0; i < nb_pkts; i++) { + int ptp_type = rte_ptp_classify(pkts[i]); + if (ptp_type == RTE_PTP_MSGTYPE_INVALID) + continue; + + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(pkts[i]); + + printf("PTP %s seq=%u domain=%u\n", + rte_ptp_msg_type_str(ptp_type), + rte_ptp_seq_id(hdr), + rte_ptp_domain(hdr)); + + if (rte_ptp_is_event(ptp_type)) { + /* Event message — needs hardware timestamp */ + } + } + } + +.. note:: + + ``rte_ptp_classify()`` and ``rte_ptp_hdr_get()`` both parse the packet + internally. When the caller needs both the message type and a header + pointer, calling ``rte_ptp_hdr_get()`` alone and then using + ``rte_ptp_msg_type()`` on the returned header avoids parsing the + packet twice. + +Adding residence time for a Transparent Clock: + +.. code-block:: C + + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(pkt); + if (hdr != NULL) { + int64_t residence_ns = egress_ts - ingress_ts; + rte_ptp_add_correction(hdr, residence_ns); + } + +Limitations +----------- + +- IPv6 extension headers are not traversed. Only the base IPv6 header + with ``next_header == UDP`` is handled. +- Multi-segment mbufs are not supported. PTP event messages are + typically less than 128 bytes and fit in a single segment. +- PTP over MPLS or other tunneling protocols is not supported. -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [RFC v3 3/6] examples/ptp_tap_relay_sw: add software PTP relay example 2026-05-04 9:17 ` [RFC v3 " Rajesh Kumar 2026-05-04 9:17 ` [RFC v3 1/6] ptp: introduce PTP protocol library Rajesh Kumar 2026-05-04 9:17 ` [RFC v3 2/6] doc: add PTP library programmer's guide Rajesh Kumar @ 2026-05-04 9:17 ` Rajesh Kumar 2026-05-04 9:17 ` [RFC v3 4/6] doc: add PTP software relay sample app guide Rajesh Kumar ` (3 subsequent siblings) 6 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-04 9:17 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, Rajesh Kumar [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #1: Type: text/plain; charset=yes, Size: 16475 bytes --] Add a minimal PTP Transparent Clock relay that bridges PTP packets between a DPDK-bound physical NIC and a kernel TAP interface using software timestamps only. The relay uses the rte_ptp library for packet classification and correctionField manipulation. For each PTP event message, it records software timestamps (CLOCK_MONOTONIC_RAW) at ingress and egress, then adds the residence time to the correctionField per IEEE 1588-2019 section 10.2. A two-pass design is used: first all packets are classified and PTP header pointers saved, then a single TX timestamp is taken immediately before applying corrections and calling rte_eth_tx_burst(). This minimises the gap between the measured timestamp and actual wire egress. Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- doc/guides/rel_notes/release_26_07.rst | 6 + examples/meson.build | 1 + examples/ptp_tap_relay_sw/Makefile | 43 ++ examples/ptp_tap_relay_sw/meson.build | 14 + examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c | 433 +++++++++++++++++++ 5 files changed, 497 insertions(+) create mode 100644 examples/ptp_tap_relay_sw/Makefile create mode 100644 examples/ptp_tap_relay_sw/meson.build create mode 100644 examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c diff --git a/doc/guides/rel_notes/release_26_07.rst b/doc/guides/rel_notes/release_26_07.rst index b2208d6fb3..5c2329d867 100644 --- a/doc/guides/rel_notes/release_26_07.rst +++ b/doc/guides/rel_notes/release_26_07.rst @@ -70,6 +70,12 @@ New Features parsing, and correctionField manipulation across L2, VLAN-tagged, UDP/IPv4, and UDP/IPv6 transports. +* **Added PTP software relay example application.** + + Added a new example application ``ptp_tap_relay_sw`` demonstrating a + software PTP Transparent Clock relay between a DPDK port and a kernel + TAP interface. + Removed Items ------------- diff --git a/examples/meson.build b/examples/meson.build index 25d9c88457..ab82cf5fbd 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -43,6 +43,7 @@ all_examples = [ 'packet_ordering', 'pipeline', 'ptpclient', + 'ptp_tap_relay_sw', 'qos_meter', 'qos_sched', 'rxtx_callbacks', diff --git a/examples/ptp_tap_relay_sw/Makefile b/examples/ptp_tap_relay_sw/Makefile new file mode 100644 index 0000000000..259e53bd18 --- /dev/null +++ b/examples/ptp_tap_relay_sw/Makefile @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2026 Intel Corporation + +# binary name +APP = dpdk-ptp_tap_relay_sw + +# all source are stored in SRCS-y +SRCS-y := ptp_tap_relay_sw.c + +PKGCONF ?= pkg-config + +# Build using pkg-config variables if possible +ifneq ($(shell $(PKGCONF) --exists libdpdk && echo 0),0) +$(error "no installation of DPDK found") +endif + +all: shared +.PHONY: shared static +shared: build/$(APP)-shared + ln -sf $(APP)-shared build/$(APP) +static: build/$(APP)-static + ln -sf $(APP)-static build/$(APP) + +PC_FILE := $(shell $(PKGCONF) --path libdpdk 2>/dev/null) +CFLAGS += -O3 $(shell $(PKGCONF) --cflags libdpdk) +LDFLAGS_SHARED = $(shell $(PKGCONF) --libs libdpdk) +LDFLAGS_STATIC = $(shell $(PKGCONF) --static --libs libdpdk) + +CFLAGS += -DALLOW_EXPERIMENTAL_API + +build/$(APP)-shared: $(SRCS-y) Makefile $(PC_FILE) | build + $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_SHARED) + +build/$(APP)-static: $(SRCS-y) Makefile $(PC_FILE) | build + $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_STATIC) + +build: + @mkdir -p $@ + +.PHONY: clean +clean: + rm -f build/$(APP) build/$(APP)-static build/$(APP)-shared + test -d build && rmdir -p build || true diff --git a/examples/ptp_tap_relay_sw/meson.build b/examples/ptp_tap_relay_sw/meson.build new file mode 100644 index 0000000000..56abad02ed --- /dev/null +++ b/examples/ptp_tap_relay_sw/meson.build @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2026 Intel Corporation + +# meson file, for building this example as part of a main DPDK build. +# +# To build this example as a standalone application with an already-installed +# DPDK instance, use 'make' + +allow_experimental_apis = true +sources = files( + 'ptp_tap_relay_sw.c', +) +deps += ['net', 'ptp'] +cflags += no_shadow_cflag diff --git a/examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c b/examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c new file mode 100644 index 0000000000..591007b1d5 --- /dev/null +++ b/examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c @@ -0,0 +1,433 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2026 Intel Corporation + */ + +/* + * PTP Software Relay — lib/ptp usage example + * + * A minimal PTP relay between a DPDK-bound physical NIC and a kernel + * TAP interface using software timestamps only. No patched TUN driver + * or ptp_proxy kernel module is required. + * + * How it works: + * 1. Physical NIC receives PTP (and non-PTP) packets via DPDK RX. + * 2. For PTP event messages (Sync, Delay_Req, PDelay_Req, PDelay_Resp) + * the relay records an RX software timestamp (clock_gettime). + * 3. Just before TX on the other side it records a TX software timestamp. + * 4. The relay residence time (tx_ts − rx_ts) is added to the PTP + * correctionField via rte_ptp_add_correction() — standard + * Transparent Clock behaviour (IEEE 1588-2019 §10.2). + * 5. Packets are forwarded bi-directionally: + * PHY → TAP (network → ptp4l) + * TAP → PHY (ptp4l → network) + * + * ptp4l runs in software-timestamping mode on the TAP interface: + * + * ptp4l -i dtap0 -m -s -S # -S = software timestamps + * + * Topology: + * + * Time Transmitter (remote) ──L2── Physical NIC (DPDK) + * │ + * PTP SW Relay ← correctionField update + * │ + * TAP (kernel) ── ptp4l -S (time receiver) + * + * Usage: + * dpdk-ptp_tap_relay_sw -l 0-1 --vdev=net_tap0,iface=dtap0 -- \ + * -p 0 -t 1 + * + * Parameters: + * -p PORT Physical NIC port ID (default: 0) + * -t PORT TAP port ID (default: 1) + * -T SECS Stats print interval in seconds (default: 10) + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <stdbool.h> +#include <signal.h> +#include <getopt.h> +#include <time.h> + +#include <rte_eal.h> +#include <rte_ethdev.h> +#include <rte_mbuf.h> +#include <rte_cycles.h> +#include <rte_lcore.h> +#include <rte_ptp.h> + +/* Ring sizes */ +#define RX_RING_SIZE 1024 +#define TX_RING_SIZE 1024 + +/* Mempool */ +#define NUM_MBUFS 8191 +#define MBUF_CACHE 250 +#define BURST_SIZE 32 + +#define NSEC_PER_SEC 1000000000ULL + +/* Logging helpers */ +#define LOG_INFO(fmt, ...) \ + fprintf(stdout, "[PTP-SW] " fmt "\n", ##__VA_ARGS__) +#define LOG_ERR(fmt, ...) \ + fprintf(stderr, "[PTP-SW ERROR] " fmt "\n", ##__VA_ARGS__) + +static volatile bool force_quit; + +/* Port IDs */ +static uint16_t phy_port; +static uint16_t tap_port = 1; +static unsigned int stats_interval = 10; /* seconds */ + +/* Statistics */ +static struct { + uint64_t phy_rx; /* total packets from PHY */ + uint64_t phy_rx_ptp; /* PTP packets from PHY */ + uint64_t tap_tx; /* packets forwarded to TAP */ + uint64_t tap_rx; /* total packets from TAP */ + uint64_t tap_rx_ptp; /* PTP packets from TAP */ + uint64_t phy_tx; /* packets forwarded to PHY */ + uint64_t corrections; /* correctionField updates */ +} stats; + +static void +signal_handler(int signum) +{ + if (signum == SIGINT || signum == SIGTERM) { + LOG_INFO("Signal %d received, shutting down...", signum); + force_quit = true; + } +} + +/* Helpers */ + +/* Read monotonic clock in nanoseconds (for residence time). */ +static inline uint64_t +sw_timestamp_ns(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * NSEC_PER_SEC + (uint64_t)ts.tv_nsec; +} + +/* Port Init */ + +static int +port_init(uint16_t port, struct rte_mempool *mp) +{ + struct rte_eth_conf port_conf; + struct rte_eth_dev_info dev_info; + uint16_t nb_rxd = RX_RING_SIZE; + uint16_t nb_txd = TX_RING_SIZE; + int ret; + + memset(&port_conf, 0, sizeof(port_conf)); + + ret = rte_eth_dev_info_get(port, &dev_info); + if (ret != 0) { + LOG_ERR("rte_eth_dev_info_get(port %u) failed: %d", port, ret); + return ret; + } + + if (dev_info.tx_offload_capa & RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE) + port_conf.txmode.offloads |= + RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE; + + ret = rte_eth_dev_configure(port, 1, 1, &port_conf); + if (ret != 0) + return ret; + + ret = rte_eth_dev_adjust_nb_rx_tx_desc(port, &nb_rxd, &nb_txd); + if (ret != 0) + return ret; + + ret = rte_eth_rx_queue_setup(port, 0, nb_rxd, + rte_eth_dev_socket_id(port), NULL, mp); + if (ret < 0) + return ret; + + ret = rte_eth_tx_queue_setup(port, 0, nb_txd, + rte_eth_dev_socket_id(port), NULL); + if (ret < 0) + return ret; + + ret = rte_eth_dev_start(port); + if (ret < 0) + return ret; + + ret = rte_eth_promiscuous_enable(port); + if (ret != 0) { + LOG_ERR("Failed to enable promiscuous on port %u: %s", + port, rte_strerror(-ret)); + return ret; + } + + return 0; +} + +/* Relay one direction */ + +/* + * Forward packets from src_port to dst_port. + * For PTP event messages, record SW timestamps around the + * relay path and add the residence time to the correctionField. + * + * This implements a Transparent Clock (IEEE 1588-2019 §10.2): + * correctionField += (t_egress − t_ingress) + * + * Note: a single rx_ts / tx_ts pair is used for the entire burst. + * At typical PTP rates (logSyncInterval >= -4, i.e. <= 16 pkt/s) + * bursts contain at most one packet, so this is exact. At higher + * rates, early packets in a burst are slightly under-corrected and + * late ones over-corrected by up to one poll-loop iteration. + */ +static void +relay_burst(uint16_t src_port, uint16_t dst_port, + uint64_t *rx_cnt, uint64_t *rx_ptp_cnt, + uint64_t *tx_cnt, uint64_t *corr_cnt) +{ + struct rte_mbuf *bufs[BURST_SIZE]; + struct rte_ptp_hdr *ptp_hdrs[BURST_SIZE]; + uint64_t rx_ts; + uint16_t nb_rx, nb_tx, i; + + nb_rx = rte_eth_rx_burst(src_port, 0, bufs, BURST_SIZE); + if (nb_rx == 0) + return; + + /* Record a single RX software timestamp for the whole burst. + * All packets in one burst arrived at essentially the same instant + * from rte_eth_rx_burst()'s perspective. + */ + rx_ts = sw_timestamp_ns(); + + *rx_cnt += nb_rx; + + /* + * Pass 1: Parse each packet once and remember PTP event headers. + * This avoids taking the TX timestamp too early — we want it as + * close to the actual rte_eth_tx_burst() call as possible. + */ + memset(ptp_hdrs, 0, sizeof(ptp_hdrs[0]) * nb_rx); + for (i = 0; i < nb_rx; i++) { + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(bufs[i]); + + if (hdr == NULL) + continue; + + (*rx_ptp_cnt)++; + + /* Only event messages carry timestamps that need correction */ + if (!rte_ptp_is_event(rte_ptp_msg_type(hdr))) + continue; + + ptp_hdrs[i] = hdr; + } + + /* + * Pass 2: Take a single TX timestamp right before transmission. + * This minimises the gap between the measured tx_ts and the + * actual kernel write inside rte_eth_tx_burst(), giving the + * most accurate residence time we can achieve with SW timestamps. + * + * residence_time = tx_ts − rx_ts + * + * Remaining untracked delays: + * - Pre-RX: NIC DMA → rx_burst return (~1-5 µs, unavoidable) + * - Post-TX: tx_ts → kernel TAP write (~1-2 µs) + * Both are symmetric for Sync and Delay_Req so they largely + * cancel in the ptp4l offset calculation. + */ + uint64_t tx_ts = sw_timestamp_ns(); + int64_t residence_ns = (int64_t)(tx_ts - rx_ts); + + for (i = 0; i < nb_rx; i++) { + if (ptp_hdrs[i] == NULL) + continue; + rte_ptp_add_correction(ptp_hdrs[i], residence_ns); + (*corr_cnt)++; + } + + /* Forward the burst */ + nb_tx = rte_eth_tx_burst(dst_port, 0, bufs, nb_rx); + *tx_cnt += nb_tx; + + /* Free any unsent packets */ + for (i = nb_tx; i < nb_rx; i++) + rte_pktmbuf_free(bufs[i]); +} + +/* Print statistics */ + +static void +print_stats(void) +{ + LOG_INFO("=== Statistics ==="); + LOG_INFO(" PHY RX total: %"PRIu64, stats.phy_rx); + LOG_INFO(" PHY RX PTP: %"PRIu64, stats.phy_rx_ptp); + LOG_INFO(" TAP TX: %"PRIu64, stats.tap_tx); + LOG_INFO(" TAP RX total: %"PRIu64, stats.tap_rx); + LOG_INFO(" TAP RX PTP: %"PRIu64, stats.tap_rx_ptp); + LOG_INFO(" PHY TX: %"PRIu64, stats.phy_tx); + LOG_INFO(" Corrections: %"PRIu64, stats.corrections); +} + +/* Main relay loop */ + +static int +relay_loop(__rte_unused void *arg) +{ + uint64_t last_stats = rte_rdtsc(); + uint64_t stats_tsc = rte_get_tsc_hz() * stats_interval; + + LOG_INFO("Relay loop started on lcore %u", rte_lcore_id()); + LOG_INFO(" PHY port %u <--> TAP port %u", phy_port, tap_port); + LOG_INFO(" Correction field updates: enabled for event messages"); + + while (!force_quit) { + /* PHY → TAP */ + relay_burst(phy_port, tap_port, + &stats.phy_rx, &stats.phy_rx_ptp, + &stats.tap_tx, &stats.corrections); + + /* TAP → PHY */ + relay_burst(tap_port, phy_port, + &stats.tap_rx, &stats.tap_rx_ptp, + &stats.phy_tx, &stats.corrections); + + /* Periodic stats */ + if (rte_rdtsc() - last_stats > stats_tsc) { + print_stats(); + last_stats = rte_rdtsc(); + } + } + + print_stats(); + return 0; +} + +/* Argument parsing */ + +static void +usage(const char *prog) +{ + fprintf(stderr, + "Usage: %s [EAL options] -- [options]\n" + " -p PORT Physical NIC port ID (default: 0)\n" + " -t PORT TAP port ID (default: 1)\n" + " -T SECS Stats interval in seconds (default: 10)\n" + "\n" + "Example:\n" + " %s -l 0-1 --vdev=net_tap0,iface=dtap0 -- -p 0 -t 1\n" + "\n" + "Then run ptp4l with software timestamps:\n" + " ptp4l -i dtap0 -m -s -S\n", + prog, prog); +} + +static int +parse_args(int argc, char **argv) +{ + int opt; + + while ((opt = getopt(argc, argv, "p:t:T:h")) != -1) { + switch (opt) { + case 'p': + phy_port = (uint16_t)atoi(optarg); + break; + case 't': + tap_port = (uint16_t)atoi(optarg); + break; + case 'T': + stats_interval = (unsigned int)atoi(optarg); + break; + case 'h': + default: + usage(argv[0]); + return -1; + } + } + + return 0; +} + +/* Main */ + +int +main(int argc, char **argv) +{ + struct rte_mempool *mp; + uint16_t nb_ports; + int ret; + + /* EAL init */ + ret = rte_eal_init(argc, argv); + if (ret < 0) + rte_exit(EXIT_FAILURE, "EAL init failed\n"); + argc -= ret; + argv += ret; + + /* App args */ + ret = parse_args(argc, argv); + if (ret < 0) + rte_exit(EXIT_FAILURE, "Invalid arguments\n"); + + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + + nb_ports = rte_eth_dev_count_avail(); + if (nb_ports < 2) + rte_exit(EXIT_FAILURE, + "Need at least 2 ports (PHY + TAP).\n" + "Use --vdev=net_tap0,iface=dtap0\n"); + + if (!rte_eth_dev_is_valid_port(phy_port)) + rte_exit(EXIT_FAILURE, "Invalid PHY port %u\n", phy_port); + if (!rte_eth_dev_is_valid_port(tap_port)) + rte_exit(EXIT_FAILURE, "Invalid TAP port %u\n", tap_port); + + mp = rte_pktmbuf_pool_create("MBUF_POOL", NUM_MBUFS * nb_ports, + MBUF_CACHE, 0, + RTE_MBUF_DEFAULT_BUF_SIZE, + rte_socket_id()); + if (mp == NULL) + rte_exit(EXIT_FAILURE, "Cannot create mbuf pool\n"); + + LOG_INFO("Initializing PHY port %u...", phy_port); + ret = port_init(phy_port, mp); + if (ret != 0) + rte_exit(EXIT_FAILURE, "Cannot init PHY port %u (%d)\n", + phy_port, ret); + + LOG_INFO("Initializing TAP port %u...", tap_port); + ret = port_init(tap_port, mp); + if (ret != 0) + rte_exit(EXIT_FAILURE, "Cannot init TAP port %u (%d)\n", + tap_port, ret); + + LOG_INFO("PTP Software Relay ready"); + LOG_INFO(" PHY port: %u", phy_port); + LOG_INFO(" TAP port: %u", tap_port); + LOG_INFO(" Stats every: %u seconds", stats_interval); + LOG_INFO(" Correction: Transparent Clock (SW timestamps)"); + LOG_INFO(""); + LOG_INFO("Run ptp4l: ptp4l -i dtap0 -m -s -S"); + + /* Run relay on main lcore */ + relay_loop(NULL); + + /* Cleanup */ + LOG_INFO("Stopping ports..."); + rte_eth_dev_stop(phy_port); + rte_eth_dev_stop(tap_port); + rte_eth_dev_close(phy_port); + rte_eth_dev_close(tap_port); + rte_eal_cleanup(); + + return 0; +} -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [RFC v3 4/6] doc: add PTP software relay sample app guide 2026-05-04 9:17 ` [RFC v3 " Rajesh Kumar ` (2 preceding siblings ...) 2026-05-04 9:17 ` [RFC v3 3/6] examples/ptp_tap_relay_sw: add software PTP relay example Rajesh Kumar @ 2026-05-04 9:17 ` Rajesh Kumar 2026-05-04 9:17 ` [RFC v3 5/6] app/test: add PTP library unit tests Rajesh Kumar ` (2 subsequent siblings) 6 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-04 9:17 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, Rajesh Kumar [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #1: Type: text/plain; charset=yes, Size: 8125 bytes --] Add a sample application user guide for the ptp_tap_relay_sw example. The guide covers the application topology, packet flow, compilation, command-line options, and a code walkthrough explaining the two-pass relay burst design and CLOCK_MONOTONIC_RAW timestamp source selection. Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- doc/guides/sample_app_ug/index.rst | 1 + doc/guides/sample_app_ug/ptp_tap_relay_sw.rst | 212 ++++++++++++++++++ 2 files changed, 213 insertions(+) create mode 100644 doc/guides/sample_app_ug/ptp_tap_relay_sw.rst diff --git a/doc/guides/sample_app_ug/index.rst b/doc/guides/sample_app_ug/index.rst index e895f692f9..f12623bb66 100644 --- a/doc/guides/sample_app_ug/index.rst +++ b/doc/guides/sample_app_ug/index.rst @@ -51,6 +51,7 @@ Sample Applications User Guides dist_app vm_power_management ptpclient + ptp_tap_relay_sw fips_validation ipsec_secgw bbdev_app diff --git a/doc/guides/sample_app_ug/ptp_tap_relay_sw.rst b/doc/guides/sample_app_ug/ptp_tap_relay_sw.rst new file mode 100644 index 0000000000..1af2601fdb --- /dev/null +++ b/doc/guides/sample_app_ug/ptp_tap_relay_sw.rst @@ -0,0 +1,212 @@ +.. SPDX-License-Identifier: BSD-3-Clause + Copyright(c) 2026 Intel Corporation. + +PTP Software Relay Sample Application +====================================== + +The PTP Software Relay sample application demonstrates how to use the +DPDK ``lib/ptp`` library to build a minimal PTP Transparent Clock relay +between a DPDK-bound physical NIC and a kernel TAP interface using +**software timestamps only**. + +No patched kernel modules or custom TAP PMD changes are required. +The application works with an unmodified Linux kernel and stock DPDK. + +For background on PTP see: +`Precision Time Protocol +<https://en.wikipedia.org/wiki/Precision_Time_Protocol>`_. + + +Limitations +----------- + +* Tested with L2 PTP (EtherType 0x88F7) on the wire. + The underlying PTP library also classifies VLAN/QinQ and UDP/IPv4/IPv6. +* Only PTP v2 messages are processed. +* Software timestamps have microsecond-class jitter; sub-microsecond + precision depends on system load and NIC-to-TAP forwarding latency. +* The PTP time transmitter must be reachable on the physical NIC's L2 network. +* Only one physical port and one TAP port are supported. + + +How the Application Works +------------------------- + +Topology +~~~~~~~~ + +:: + + PTP Time Transmitter Physical NIC TAP (kernel) + (ptp4l -H) ──L2── (DPDK vfio-pci) ────── dtap0 + │ │ + ptp_tap_relay_sw ptp4l -S + (correctionField += (SW timestamps, + residence time) adjusts CLOCK_REALTIME) + +The relay sits between a DPDK-owned physical NIC and a kernel TAP +virtual interface. ``ptp4l`` runs on the TAP interface in software +timestamp mode (``-S``) as a PTP time receiver. + +Packet Flow +~~~~~~~~~~~ + +1. The physical NIC receives PTP (and non-PTP) packets via DPDK RX. +2. A software RX timestamp is recorded using + ``clock_gettime(CLOCK_MONOTONIC)``. +3. Each packet is classified with ``rte_ptp_classify()``. +4. For PTP **event** messages (Sync, Delay_Req, PDelay_Req, PDelay_Resp), + a TX software timestamp is taken just before transmission. +5. The residence time (``tx_ts − rx_ts``) is added to the PTP + ``correctionField`` via ``rte_ptp_add_correction()`` — standard + IEEE 1588-2019 Transparent Clock behaviour (§10.2). +6. Packets are forwarded bidirectionally: + + * PHY → TAP (network → ptp4l) + * TAP → PHY (ptp4l → network) + +A two-pass design is used: first all packets are classified and PTP +header pointers saved, then a single TX timestamp is taken immediately +before applying corrections and calling ``rte_eth_tx_burst()``. +This minimises the gap between the measured timestamp and the actual +wire egress. + + +Compiling the Application +------------------------- + +To compile the sample application see :doc:`compiling`. + +The application is located in the ``ptp_tap_relay_sw`` sub-directory. + +.. note:: + + The application depends on the ``ptp`` library. + Ensure that ``lib/ptp`` is built (it is built by default). + + +Running the Application +----------------------- + +Prerequisites +~~~~~~~~~~~~~ + +* A PTP-capable physical NIC bound to DPDK (e.g. via ``vfio-pci``). +* ``linuxptp`` (``ptp4l``) installed on the system. +* A PTP time transmitter reachable on the same L2 network. + +Start the relay +~~~~~~~~~~~~~~~~ + +.. code-block:: console + + ./<build_dir>/examples/dpdk-ptp_tap_relay_sw \ + -l 18-19 -a 0000:cc:00.1 --vdev=net_tap0,iface=dtap0 -- \ + -p 0 -t 1 -T 10 + +Command-line Options +~~~~~~~~~~~~~~~~~~~~ + +* ``-p PORT`` — Physical NIC port ID (default: 0). +* ``-t PORT`` — TAP port ID (default: 1). +* ``-T SECS`` — Statistics print interval in seconds (default: 10). + +Start PTP time transmitter +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +On a separate terminal or remote host, start ``ptp4l`` as time +transmitter with hardware timestamps on the physical NIC: + +.. code-block:: console + + ptp4l -i <iface> -m -2 -H --serverOnly=1 \ + --logSyncInterval=-4 --logMinDelayReqInterval=-4 + +Start PTP time receiver +~~~~~~~~~~~~~~~~~~~~~~~ + +On the TAP interface, start ``ptp4l`` in software timestamp mode: + +.. code-block:: console + + ptp4l -i dtap0 -m -2 -s -S \ + --delay_filter=moving_median --delay_filter_length=10 + +The time receiver will enter UNCALIBRATED state for approximately 60 +seconds while the PI servo estimates the frequency offset, then step +the clock and enter time-receiver (synchronized) state. +Steady-state RMS offset of 500–1000 ns is typical on a lightly loaded +system with a hardware-timestamped time transmitter. + +Example Output +~~~~~~~~~~~~~~ + +Relay statistics printed every ``-T`` seconds: + +:: + + [PTP-SW] === Statistics === + [PTP-SW] PHY RX total: 5646 + [PTP-SW] PHY RX PTP: 5598 + [PTP-SW] TAP TX: 5646 + [PTP-SW] TAP RX total: 1800 + [PTP-SW] TAP RX PTP: 1788 + [PTP-SW] PHY TX: 1800 + [PTP-SW] Corrections: 3635 + +Time receiver ``ptp4l`` output after convergence: + +:: + + ptp4l[451534.520]: rms 630 max 1166 freq -44365 +/- 100 delay 37668 +/- 71 + ptp4l[451539.525]: rms 602 max 1177 freq -44339 +/- 119 delay 37517 +/- 43 + ptp4l[451544.530]: rms 535 max 1194 freq -44345 +/- 103 delay 37410 +/- 81 + + +Code Explanation +---------------- + +The following sections explain the main components of the application. + +Relay Burst Function +~~~~~~~~~~~~~~~~~~~~ + +The core relay logic is in ``relay_burst()``, which handles one direction +(PHY→TAP or TAP→PHY) per call: + +**Pass 1 — Classify:** + +For each received packet, ``rte_ptp_classify()`` determines if it is a +PTP message. For event messages, ``rte_ptp_hdr_get()`` retrieves a +pointer to the PTP header, which is saved for the second pass. + +**Pass 2 — Timestamp and correct:** + +A single software TX timestamp is taken via +``clock_gettime(CLOCK_MONOTONIC)``. The residence time +(``tx_ts − rx_ts``) is added to each saved PTP header's +``correctionField`` using ``rte_ptp_add_correction()``. +The burst is then transmitted with ``rte_eth_tx_burst()``. + +Main Loop +~~~~~~~~~ + +The ``relay_loop()`` function polls both directions in a tight loop: + +.. code-block:: c + + while (!force_quit) { + relay_burst(phy_port, tap_port, ...); /* PHY → TAP */ + relay_burst(tap_port, phy_port, ...); /* TAP → PHY */ + } + +Statistics are printed at the interval specified by ``-T``. + +Timestamp Source +~~~~~~~~~~~~~~~~ + +``CLOCK_MONOTONIC`` is used rather than ``CLOCK_REALTIME`` because +the PTP time receiver's servo continuously adjusts ``CLOCK_REALTIME``. +Using ``CLOCK_REALTIME`` would corrupt residence time measurements +during clock stepping or frequency slewing. ``CLOCK_MONOTONIC`` is +portable across Linux and FreeBSD. -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [RFC v3 5/6] app/test: add PTP library unit tests 2026-05-04 9:17 ` [RFC v3 " Rajesh Kumar ` (3 preceding siblings ...) 2026-05-04 9:17 ` [RFC v3 4/6] doc: add PTP software relay sample app guide Rajesh Kumar @ 2026-05-04 9:17 ` Rajesh Kumar 2026-05-04 9:17 ` [RFC v3 6/6] examples/ptpclient: use shared PTP library definitions Rajesh Kumar 2026-05-04 17:56 ` [RFC v3 0/6] introduce PTP protocol library and software relay Stephen Hemminger 6 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-04 9:17 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, Rajesh Kumar Add autotest for the PTP library covering: - Transport classification (L2, VLAN, QinQ, UDP/IPv4, UDP/IPv6) - Message type parsing and string conversion - Inline helpers (is_event, two_step, seq_id, version, domain) - Correction field arithmetic (add, accumulate, negative values) - Timestamp nanosecond conversion - Flag field extraction (TWO_STEP, UNICAST, LI_61, LI_59) Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- MAINTAINERS | 1 + app/test/meson.build | 1 + app/test/test_ptp.c | 1106 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1108 insertions(+) create mode 100644 app/test/test_ptp.c diff --git a/MAINTAINERS b/MAINTAINERS index 665e08dc90..8d509cbe8e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1668,6 +1668,7 @@ F: app/test-sad/ PTP - EXPERIMENTAL M: Rajesh Kumar <rajesh3.kumar@intel.com> F: lib/ptp/ +F: app/test/test_ptp.c F: doc/guides/prog_guide/ptp_lib.rst F: examples/ptp_tap_relay_sw/ F: doc/guides/sample_app_ug/ptp_tap_relay_sw.rst diff --git a/app/test/meson.build b/app/test/meson.build index 7d458f9c07..ecaa8d0839 100644 --- a/app/test/meson.build +++ b/app/test/meson.build @@ -154,6 +154,7 @@ source_file_deps = { 'test_power_kvm_vm.c': ['power', 'power_kvm_vm'], 'test_prefetch.c': [], 'test_ptr_compress.c': ['ptr_compress'], + 'test_ptp.c': ['ptp'], 'test_rand_perf.c': [], 'test_rawdev.c': ['rawdev', 'bus_vdev', 'raw_skeleton'], 'test_rcu_qsbr.c': ['rcu', 'hash'], diff --git a/app/test/test_ptp.c b/app/test/test_ptp.c new file mode 100644 index 0000000000..803951b1fa --- /dev/null +++ b/app/test/test_ptp.c @@ -0,0 +1,1106 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2026 Intel Corporation + */ + +#include "test.h" + +#include <string.h> +#include <stdio.h> +#include <stdint.h> +#include <inttypes.h> + +#include <rte_mbuf.h> +#include <rte_mempool.h> +#include <rte_memory.h> +#include <rte_ether.h> +#include <rte_ip.h> +#include <rte_udp.h> +#include <rte_byteorder.h> +#include <rte_ptp.h> + +#define PTP_TEST_MP_NAME "test_ptp_pool" +#define PTP_TEST_MP_SIZE 63 +#define PTP_TEST_BUF_SIZE RTE_MBUF_DEFAULT_BUF_SIZE + +static struct rte_mempool *ptp_mp; + +/* Helper: fill a minimal PTP header */ +static void +fill_ptp_hdr(struct rte_ptp_hdr *ptp, uint8_t msg_type, uint16_t flags_host, + int64_t correction_scaled_ns, uint16_t seq_id) +{ + memset(ptp, 0, sizeof(*ptp)); + ptp->msg_type = msg_type; + ptp->version = 0x02; + ptp->msg_length = rte_cpu_to_be_16(34); + ptp->flags = rte_cpu_to_be_16(flags_host); + ptp->correction = rte_cpu_to_be_64(correction_scaled_ns); + ptp->sequence_id = rte_cpu_to_be_16(seq_id); +} + +/* ================================================================ + * Packet builders + * ================================================================ + */ + +static struct rte_mbuf * +build_l2_ptp(uint8_t msg_type) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *) + rte_pktmbuf_append(m, sizeof(*eth) + sizeof(struct rte_ptp_hdr)); + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(0x88F7); + + struct rte_ptp_hdr *ptp = (struct rte_ptp_hdr *)((uint8_t *)eth + sizeof(*eth)); + fill_ptp_hdr(ptp, msg_type, RTE_PTP_FLAG_TWO_STEP, 0, 100); + return m; +} + +/* Build L2 PTP with no TWO_STEP flag */ +static struct rte_mbuf * +build_l2_ptp_noflags(uint8_t msg_type) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *) + rte_pktmbuf_append(m, sizeof(*eth) + sizeof(struct rte_ptp_hdr)); + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(0x88F7); + + struct rte_ptp_hdr *ptp = (struct rte_ptp_hdr *)((uint8_t *)eth + sizeof(*eth)); + fill_ptp_hdr(ptp, msg_type, 0, 0, 200); + return m; +} + +static struct rte_mbuf * +build_vlan_l2_ptp(uint8_t msg_type, uint16_t tpid) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + sizeof(struct rte_vlan_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(tpid); + + struct rte_vlan_hdr *vlan = (struct rte_vlan_hdr *)(data + sizeof(*eth)); + vlan->vlan_tci = rte_cpu_to_be_16(100); + vlan->eth_proto = rte_cpu_to_be_16(0x88F7); + + struct rte_ptp_hdr *ptp = (struct rte_ptp_hdr *) + (data + sizeof(*eth) + sizeof(*vlan)); + fill_ptp_hdr(ptp, msg_type, 0, 0, 200); + return m; +} + +static struct rte_mbuf * +build_qinq_l2_ptp(uint8_t msg_type, uint16_t outer_tpid, uint16_t inner_tpid) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + 2 * sizeof(struct rte_vlan_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(outer_tpid); + + uint32_t off = sizeof(*eth); + struct rte_vlan_hdr *vo = (struct rte_vlan_hdr *)(data + off); + vo->vlan_tci = rte_cpu_to_be_16(200); + vo->eth_proto = rte_cpu_to_be_16(inner_tpid); + off += sizeof(*vo); + + struct rte_vlan_hdr *vi = (struct rte_vlan_hdr *)(data + off); + vi->vlan_tci = rte_cpu_to_be_16(300); + vi->eth_proto = rte_cpu_to_be_16(0x88F7); + off += sizeof(*vi); + + struct rte_ptp_hdr *ptp = (struct rte_ptp_hdr *)(data + off); + fill_ptp_hdr(ptp, msg_type, 0, 0, 300); + return m; +} + +/* Helper: append IPv4 + UDP + PTP after offset */ +static void +fill_ipv4_udp_ptp(uint8_t *data, uint32_t off, uint8_t msg_type, + uint16_t dst_port, uint16_t seq_id) +{ + struct rte_ipv4_hdr *iph = (struct rte_ipv4_hdr *)(data + off); + memset(iph, 0, sizeof(*iph)); + iph->version_ihl = 0x45; + iph->next_proto_id = IPPROTO_UDP; + iph->total_length = rte_cpu_to_be_16( + sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr)); + iph->src_addr = rte_cpu_to_be_32(0x0A000001); + iph->dst_addr = rte_cpu_to_be_32(0xE0000181); + off += sizeof(*iph); + + struct rte_udp_hdr *udp = (struct rte_udp_hdr *)(data + off); + memset(udp, 0, sizeof(*udp)); + udp->src_port = rte_cpu_to_be_16(12345); + udp->dst_port = rte_cpu_to_be_16(dst_port); + udp->dgram_len = rte_cpu_to_be_16(sizeof(*udp) + + sizeof(struct rte_ptp_hdr)); + off += sizeof(*udp); + + struct rte_ptp_hdr *ptp = (struct rte_ptp_hdr *)(data + off); + fill_ptp_hdr(ptp, msg_type, 0, 0, seq_id); +} + +static struct rte_mbuf * +build_ipv4_udp_ptp(uint8_t msg_type, uint16_t dst_port) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + sizeof(struct rte_ipv4_hdr) + + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4); + + fill_ipv4_udp_ptp(data, sizeof(*eth), msg_type, dst_port, 400); + return m; +} + +static struct rte_mbuf * +build_vlan_ipv4_udp_ptp(uint8_t msg_type, uint16_t dst_port) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + sizeof(struct rte_vlan_hdr) + + sizeof(struct rte_ipv4_hdr) + + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN); + + uint32_t off = sizeof(*eth); + struct rte_vlan_hdr *vlan = (struct rte_vlan_hdr *)(data + off); + vlan->vlan_tci = rte_cpu_to_be_16(100); + vlan->eth_proto = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4); + off += sizeof(*vlan); + + fill_ipv4_udp_ptp(data, off, msg_type, dst_port, 500); + return m; +} + +static struct rte_mbuf * +build_qinq_ipv4_udp_ptp(uint8_t msg_type, uint16_t dst_port) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + 2 * sizeof(struct rte_vlan_hdr) + + sizeof(struct rte_ipv4_hdr) + + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_QINQ); + + uint32_t off = sizeof(*eth); + struct rte_vlan_hdr *vo = (struct rte_vlan_hdr *)(data + off); + vo->vlan_tci = rte_cpu_to_be_16(200); + vo->eth_proto = rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN); + off += sizeof(*vo); + + struct rte_vlan_hdr *vi = (struct rte_vlan_hdr *)(data + off); + vi->vlan_tci = rte_cpu_to_be_16(300); + vi->eth_proto = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4); + off += sizeof(*vi); + + fill_ipv4_udp_ptp(data, off, msg_type, dst_port, 600); + return m; +} + +/* Helper: append IPv6 + UDP + PTP */ +static void +fill_ipv6_udp_ptp(uint8_t *data, uint32_t off, uint8_t msg_type, + uint16_t dst_port, uint16_t seq_id) +{ + struct rte_ipv6_hdr *ip6 = (struct rte_ipv6_hdr *)(data + off); + memset(ip6, 0, sizeof(*ip6)); + ip6->vtc_flow = rte_cpu_to_be_32(0x60000000); + ip6->payload_len = rte_cpu_to_be_16( + sizeof(struct rte_udp_hdr) + sizeof(struct rte_ptp_hdr)); + ip6->proto = IPPROTO_UDP; + ip6->hop_limits = 64; + off += sizeof(*ip6); + + struct rte_udp_hdr *udp = (struct rte_udp_hdr *)(data + off); + memset(udp, 0, sizeof(*udp)); + udp->src_port = rte_cpu_to_be_16(12345); + udp->dst_port = rte_cpu_to_be_16(dst_port); + udp->dgram_len = rte_cpu_to_be_16(sizeof(*udp) + + sizeof(struct rte_ptp_hdr)); + off += sizeof(*udp); + + struct rte_ptp_hdr *ptp = (struct rte_ptp_hdr *)(data + off); + fill_ptp_hdr(ptp, msg_type, 0, 0, seq_id); +} + +static struct rte_mbuf * +build_ipv6_udp_ptp(uint8_t msg_type, uint16_t dst_port) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + sizeof(struct rte_ipv6_hdr) + + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV6); + + fill_ipv6_udp_ptp(data, sizeof(*eth), msg_type, dst_port, 700); + return m; +} + +static struct rte_mbuf * +build_vlan_ipv6_udp_ptp(uint8_t msg_type, uint16_t dst_port) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + sizeof(struct rte_vlan_hdr) + + sizeof(struct rte_ipv6_hdr) + + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN); + + uint32_t off = sizeof(*eth); + struct rte_vlan_hdr *vlan = (struct rte_vlan_hdr *)(data + off); + vlan->vlan_tci = rte_cpu_to_be_16(100); + vlan->eth_proto = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV6); + off += sizeof(*vlan); + + fill_ipv6_udp_ptp(data, off, msg_type, dst_port, 800); + return m; +} + +static struct rte_mbuf * +build_qinq_ipv6_udp_ptp(uint8_t msg_type, uint16_t dst_port) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + 2 * sizeof(struct rte_vlan_hdr) + + sizeof(struct rte_ipv6_hdr) + + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_QINQ); + + uint32_t off = sizeof(*eth); + struct rte_vlan_hdr *vo = (struct rte_vlan_hdr *)(data + off); + vo->vlan_tci = rte_cpu_to_be_16(200); + vo->eth_proto = rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN); + off += sizeof(*vo); + + struct rte_vlan_hdr *vi = (struct rte_vlan_hdr *)(data + off); + vi->vlan_tci = rte_cpu_to_be_16(300); + vi->eth_proto = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV6); + off += sizeof(*vi); + + fill_ipv6_udp_ptp(data, off, msg_type, dst_port, 900); + return m; +} + +static struct rte_mbuf * +build_non_ptp(void) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + 28; + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(data, 0, pkt_len); + eth->ether_type = rte_cpu_to_be_16(0x0806); + return m; +} + +static struct rte_mbuf * +build_ipv4_udp_non_ptp(void) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + sizeof(struct rte_ipv4_hdr) + + sizeof(struct rte_udp_hdr) + 20; + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(data, 0, pkt_len); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4); + + struct rte_ipv4_hdr *iph = (struct rte_ipv4_hdr *)(data + sizeof(*eth)); + iph->version_ihl = 0x45; + iph->next_proto_id = IPPROTO_UDP; + iph->total_length = rte_cpu_to_be_16( + sizeof(*iph) + sizeof(struct rte_udp_hdr) + 20); + + struct rte_udp_hdr *udp = (struct rte_udp_hdr *) + (data + sizeof(*eth) + sizeof(*iph)); + udp->dst_port = rte_cpu_to_be_16(53); + return m; +} + +/* ================================================================ + * Individual test cases + * ================================================================ + */ + +/* Helper: classify + hdr_get for a given mbuf */ +static int +check_classify_and_hdr_get(struct rte_mbuf *m, int expected_type) +{ + int ret; + + TEST_ASSERT_NOT_NULL(m, "mbuf allocation failed"); + + ret = rte_ptp_classify(m); + TEST_ASSERT_EQUAL(ret, expected_type, + "classify: expected %d, got %d", expected_type, ret); + + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + if (expected_type == RTE_PTP_MSGTYPE_INVALID) { + TEST_ASSERT_NULL(hdr, + "hdr_get: expected NULL for non-PTP packet"); + } else { + TEST_ASSERT_NOT_NULL(hdr, + "hdr_get: expected non-NULL for PTP packet"); + TEST_ASSERT_EQUAL(rte_ptp_msg_type(hdr), + (uint8_t)expected_type, + "hdr_get: msg_type mismatch: expected %d, got %d", + expected_type, rte_ptp_msg_type(hdr)); + } + + rte_pktmbuf_free(m); + return TEST_SUCCESS; +} + +/* Section 1: Transport classification */ +static int +test_ptp_classify_l2(void) +{ + return check_classify_and_hdr_get( + build_l2_ptp(RTE_PTP_MSGTYPE_SYNC), RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_l2_delay_req(void) +{ + return check_classify_and_hdr_get( + build_l2_ptp(RTE_PTP_MSGTYPE_DELAY_REQ), + RTE_PTP_MSGTYPE_DELAY_REQ); +} + +static int +test_ptp_classify_vlan_8100(void) +{ + return check_classify_and_hdr_get( + build_vlan_l2_ptp(RTE_PTP_MSGTYPE_SYNC, RTE_ETHER_TYPE_VLAN), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_vlan_88a8(void) +{ + return check_classify_and_hdr_get( + build_vlan_l2_ptp(RTE_PTP_MSGTYPE_SYNC, RTE_ETHER_TYPE_QINQ), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_qinq(void) +{ + return check_classify_and_hdr_get( + build_qinq_l2_ptp(RTE_PTP_MSGTYPE_SYNC, + RTE_ETHER_TYPE_QINQ, RTE_ETHER_TYPE_VLAN), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_double_8100(void) +{ + return check_classify_and_hdr_get( + build_qinq_l2_ptp(RTE_PTP_MSGTYPE_SYNC, + RTE_ETHER_TYPE_VLAN, RTE_ETHER_TYPE_VLAN), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_ipv4_udp_319(void) +{ + return check_classify_and_hdr_get( + build_ipv4_udp_ptp(RTE_PTP_MSGTYPE_SYNC, 319), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_ipv4_udp_320(void) +{ + return check_classify_and_hdr_get( + build_ipv4_udp_ptp(RTE_PTP_MSGTYPE_FOLLOW_UP, 320), + RTE_PTP_MSGTYPE_FOLLOW_UP); +} + +static int +test_ptp_classify_vlan_ipv4_udp(void) +{ + return check_classify_and_hdr_get( + build_vlan_ipv4_udp_ptp(RTE_PTP_MSGTYPE_SYNC, 319), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_qinq_ipv4_udp(void) +{ + return check_classify_and_hdr_get( + build_qinq_ipv4_udp_ptp(RTE_PTP_MSGTYPE_SYNC, 319), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_ipv6_udp_319(void) +{ + return check_classify_and_hdr_get( + build_ipv6_udp_ptp(RTE_PTP_MSGTYPE_SYNC, 319), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_ipv6_udp_320(void) +{ + return check_classify_and_hdr_get( + build_ipv6_udp_ptp(RTE_PTP_MSGTYPE_FOLLOW_UP, 320), + RTE_PTP_MSGTYPE_FOLLOW_UP); +} + +static int +test_ptp_classify_vlan_ipv6_udp(void) +{ + return check_classify_and_hdr_get( + build_vlan_ipv6_udp_ptp(RTE_PTP_MSGTYPE_SYNC, 319), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_qinq_ipv6_udp(void) +{ + return check_classify_and_hdr_get( + build_qinq_ipv6_udp_ptp(RTE_PTP_MSGTYPE_SYNC, 319), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_non_ptp_arp(void) +{ + return check_classify_and_hdr_get( + build_non_ptp(), RTE_PTP_MSGTYPE_INVALID); +} + +static int +test_ptp_classify_non_ptp_udp(void) +{ + return check_classify_and_hdr_get( + build_ipv4_udp_non_ptp(), RTE_PTP_MSGTYPE_INVALID); +} + +/* IPv4 with invalid IHL (< 5) should be rejected */ +static int +test_ptp_classify_ipv4_bad_ihl(void) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + sizeof(struct rte_ipv4_hdr) + + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4); + + fill_ipv4_udp_ptp(data, sizeof(*eth), RTE_PTP_MSGTYPE_SYNC, 319, 999); + + /* Corrupt the IHL to 3 (< minimum 5) */ + struct rte_ipv4_hdr *iph = (struct rte_ipv4_hdr *) + (data + sizeof(*eth)); + iph->version_ihl = 0x43; + + return check_classify_and_hdr_get(m, RTE_PTP_MSGTYPE_INVALID); +} + +/* IPv4 with options (IHL > 5) should still be parsed correctly */ +static int +test_ptp_classify_ipv4_options(void) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + uint32_t pkt_len; + uint32_t off; + uint8_t *data; + struct rte_ether_hdr *eth; + struct rte_ipv4_hdr *iph; + struct rte_udp_hdr *udp; + struct rte_ptp_hdr *ptp; + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + + pkt_len = sizeof(struct rte_ether_hdr) + + sizeof(struct rte_ipv4_hdr) + 4 + + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr); + data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + TEST_ASSERT_NOT_NULL(data, "append failed"); + + eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4); + + off = sizeof(*eth); + iph = (struct rte_ipv4_hdr *)(data + off); + memset(iph, 0, sizeof(*iph)); + iph->version_ihl = 0x46; /* IHL = 6 words = 24 bytes */ + iph->next_proto_id = IPPROTO_UDP; + iph->total_length = rte_cpu_to_be_16(sizeof(struct rte_ipv4_hdr) + 4 + + sizeof(struct rte_udp_hdr) + sizeof(struct rte_ptp_hdr)); + iph->src_addr = rte_cpu_to_be_32(0x0A000001); + iph->dst_addr = rte_cpu_to_be_32(0xE0000181); + off += sizeof(*iph); + + memset(data + off, 0, 4); /* IPv4 options bytes */ + off += 4; + + udp = (struct rte_udp_hdr *)(data + off); + memset(udp, 0, sizeof(*udp)); + udp->src_port = rte_cpu_to_be_16(12345); + udp->dst_port = rte_cpu_to_be_16(RTE_PTP_EVENT_PORT); + udp->dgram_len = rte_cpu_to_be_16(sizeof(*udp) + sizeof(struct rte_ptp_hdr)); + off += sizeof(*udp); + + ptp = (struct rte_ptp_hdr *)(data + off); + fill_ptp_hdr(ptp, RTE_PTP_MSGTYPE_SYNC, 0, 0, 1001); + + return check_classify_and_hdr_get(m, RTE_PTP_MSGTYPE_SYNC); +} + +/* Truncated packet: Ethernet header only, no payload */ +static int +test_ptp_classify_truncated(void) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, + sizeof(struct rte_ether_hdr)); + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(0x88F7); + + return check_classify_and_hdr_get(m, RTE_PTP_MSGTYPE_INVALID); +} + +/* Section 2: All 10 message types via L2 */ +static int +test_ptp_all_msg_types(void) +{ + static const uint8_t types[] = { + RTE_PTP_MSGTYPE_SYNC, + RTE_PTP_MSGTYPE_DELAY_REQ, + RTE_PTP_MSGTYPE_PDELAY_REQ, + RTE_PTP_MSGTYPE_PDELAY_RESP, + RTE_PTP_MSGTYPE_FOLLOW_UP, + RTE_PTP_MSGTYPE_DELAY_RESP, + RTE_PTP_MSGTYPE_PDELAY_RESP_FU, + RTE_PTP_MSGTYPE_ANNOUNCE, + RTE_PTP_MSGTYPE_SIGNALING, + RTE_PTP_MSGTYPE_MANAGEMENT, + }; + unsigned int i; + + for (i = 0; i < RTE_DIM(types); i++) { + int ret = check_classify_and_hdr_get( + build_l2_ptp(types[i]), types[i]); + if (ret != TEST_SUCCESS) + return ret; + } + + return TEST_SUCCESS; +} + +/* Section 3: Inline helpers */ +static int +test_ptp_is_event(void) +{ + TEST_ASSERT(rte_ptp_is_event(RTE_PTP_MSGTYPE_SYNC), + "Sync should be event"); + TEST_ASSERT(rte_ptp_is_event(RTE_PTP_MSGTYPE_DELAY_REQ), + "Delay_Req should be event"); + TEST_ASSERT(rte_ptp_is_event(RTE_PTP_MSGTYPE_PDELAY_REQ), + "Pdelay_Req should be event"); + TEST_ASSERT(rte_ptp_is_event(RTE_PTP_MSGTYPE_PDELAY_RESP), + "Pdelay_Resp should be event"); + TEST_ASSERT(!rte_ptp_is_event(RTE_PTP_MSGTYPE_FOLLOW_UP), + "Follow_Up should not be event"); + TEST_ASSERT(!rte_ptp_is_event(RTE_PTP_MSGTYPE_ANNOUNCE), + "Announce should not be event"); + TEST_ASSERT(!rte_ptp_is_event(RTE_PTP_MSGTYPE_INVALID), + "INVALID (-1) should not be event"); + + return TEST_SUCCESS; +} + +static int +test_ptp_two_step(void) +{ + struct rte_mbuf *m; + struct rte_ptp_hdr *hdr; + + m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + TEST_ASSERT(rte_ptp_is_two_step(hdr), + "TWO_STEP flag should be set"); + rte_pktmbuf_free(m); + + m = build_l2_ptp_noflags(RTE_PTP_MSGTYPE_SYNC); + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + TEST_ASSERT(!rte_ptp_is_two_step(hdr), + "TWO_STEP flag should not be set"); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_seq_id(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + TEST_ASSERT_EQUAL(rte_ptp_seq_id(hdr), 100, + "seq_id: expected 100, got %u", rte_ptp_seq_id(hdr)); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_version(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + TEST_ASSERT_EQUAL(rte_ptp_version(hdr), 2, + "version: expected 2, got %u", rte_ptp_version(hdr)); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_domain(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + TEST_ASSERT_EQUAL(rte_ptp_domain(hdr), 0, + "domain: expected 0, got %u", rte_ptp_domain(hdr)); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +/* Section 4: correctionField */ +static int +test_ptp_correction_zero(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + int64_t ns = rte_ptp_correction_ns(hdr); + TEST_ASSERT_EQUAL(ns, 0, + "correction_ns: expected 0, got %" PRId64, ns); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_correction_known(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *ptp = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(ptp, "hdr_get failed"); + int64_t scaled_1000 = (int64_t)1000 << 16; + fill_ptp_hdr(ptp, RTE_PTP_MSGTYPE_SYNC, 0, scaled_1000, 0); + + int64_t ns = rte_ptp_correction_ns(ptp); + TEST_ASSERT_EQUAL(ns, 1000, + "correction_ns: expected 1000, got %" PRId64, ns); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_add_correction(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + + rte_ptp_add_correction(hdr, 500); + int64_t ns = rte_ptp_correction_ns(hdr); + TEST_ASSERT_EQUAL(ns, 500, + "add 500: expected 500, got %" PRId64, ns); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_add_correction_accumulate(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + + rte_ptp_add_correction(hdr, 300); + rte_ptp_add_correction(hdr, 700); + int64_t ns = rte_ptp_correction_ns(hdr); + TEST_ASSERT_EQUAL(ns, 1000, + "accumulate: expected 1000, got %" PRId64, ns); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_add_correction_large(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + + rte_ptp_add_correction(hdr, 1000000000LL); + int64_t ns = rte_ptp_correction_ns(hdr); + TEST_ASSERT_EQUAL(ns, 1000000000LL, + "1s: expected 1000000000, got %" PRId64, ns); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_add_correction_negative(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + + rte_ptp_add_correction(hdr, -100LL); + int64_t ns = rte_ptp_correction_ns(hdr); + TEST_ASSERT_EQUAL(ns, -100LL, + "negative: expected -100, got %" PRId64, ns); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +/* Section 5: Timestamp conversion */ +static int +test_ptp_timestamp_to_ns(void) +{ + struct rte_ptp_timestamp ts; + uint64_t ns; + + /* Zero */ + memset(&ts, 0, sizeof(ts)); + ns = rte_ptp_timestamp_to_ns(&ts); + TEST_ASSERT_EQUAL(ns, 0ULL, + "zero: expected 0, got %" PRIu64, ns); + + /* 1 second */ + ts.seconds_hi = 0; + ts.seconds_lo = rte_cpu_to_be_32(1); + ts.nanoseconds = 0; + ns = rte_ptp_timestamp_to_ns(&ts); + TEST_ASSERT_EQUAL(ns, 1000000000ULL, + "1s: expected 1000000000, got %" PRIu64, ns); + + /* 1.5 seconds */ + ts.seconds_lo = rte_cpu_to_be_32(1); + ts.nanoseconds = rte_cpu_to_be_32(500000000); + ns = rte_ptp_timestamp_to_ns(&ts); + TEST_ASSERT_EQUAL(ns, 1500000000ULL, + "1.5s: expected 1500000000, got %" PRIu64, ns); + + /* Large value with seconds_hi */ + ts.seconds_hi = rte_cpu_to_be_16(1); + ts.seconds_lo = 0; + ts.nanoseconds = 0; + ns = rte_ptp_timestamp_to_ns(&ts); + uint64_t expected = ((uint64_t)1 << 32) * 1000000000ULL; + TEST_ASSERT_EQUAL(ns, expected, + "2^32s: expected %" PRIu64 ", got %" PRIu64, expected, ns); + + return TEST_SUCCESS; +} + +/* Section 6: msg_type_str */ +static int +test_ptp_msg_type_str(void) +{ + static const struct { + int type; + const char *expected; + } cases[] = { + { RTE_PTP_MSGTYPE_SYNC, "Sync" }, + { RTE_PTP_MSGTYPE_DELAY_REQ, "Delay_Req" }, + { RTE_PTP_MSGTYPE_PDELAY_REQ, "PDelay_Req" }, + { RTE_PTP_MSGTYPE_PDELAY_RESP, "PDelay_Resp" }, + { RTE_PTP_MSGTYPE_FOLLOW_UP, "Follow_Up" }, + { RTE_PTP_MSGTYPE_DELAY_RESP, "Delay_Resp" }, + { RTE_PTP_MSGTYPE_PDELAY_RESP_FU, "PDelay_Resp_Follow_Up" }, + { RTE_PTP_MSGTYPE_ANNOUNCE, "Announce" }, + { RTE_PTP_MSGTYPE_SIGNALING, "Signaling" }, + { RTE_PTP_MSGTYPE_MANAGEMENT, "Management" }, + }; + unsigned int i; + + for (i = 0; i < RTE_DIM(cases); i++) { + const char *str = rte_ptp_msg_type_str(cases[i].type); + TEST_ASSERT_NOT_NULL(str, + "msg_type_str(%d) returned NULL", cases[i].type); + TEST_ASSERT(strcmp(str, cases[i].expected) == 0, + "msg_type_str(%d): expected \"%s\", got \"%s\"", + cases[i].type, cases[i].expected, str); + } + + /* Invalid type should still return non-NULL */ + const char *inv = rte_ptp_msg_type_str(RTE_PTP_MSGTYPE_INVALID); + TEST_ASSERT_NOT_NULL(inv, + "msg_type_str(INVALID) returned NULL"); + + return TEST_SUCCESS; +} + +/* Section 7: Flag bit positions */ +static int +test_ptp_flags(void) +{ + struct rte_ptp_hdr hdr; + uint16_t f; + + /* TWO_STEP */ + memset(&hdr, 0, sizeof(hdr)); + hdr.flags = rte_cpu_to_be_16(RTE_PTP_FLAG_TWO_STEP); + f = rte_be_to_cpu_16(hdr.flags); + TEST_ASSERT(f & RTE_PTP_FLAG_TWO_STEP, + "TWO_STEP bit not set: 0x%04x", f); + TEST_ASSERT(rte_ptp_is_two_step(&hdr), + "is_two_step() should return true"); + + /* UNICAST */ + memset(&hdr, 0, sizeof(hdr)); + hdr.flags = rte_cpu_to_be_16(RTE_PTP_FLAG_UNICAST); + f = rte_be_to_cpu_16(hdr.flags); + TEST_ASSERT(f & RTE_PTP_FLAG_UNICAST, + "UNICAST bit not set: 0x%04x", f); + + /* LI_61 */ + memset(&hdr, 0, sizeof(hdr)); + hdr.flags = rte_cpu_to_be_16(RTE_PTP_FLAG_LI_61); + f = rte_be_to_cpu_16(hdr.flags); + TEST_ASSERT(f & RTE_PTP_FLAG_LI_61, + "LI_61 bit not set: 0x%04x", f); + + /* LI_59 */ + memset(&hdr, 0, sizeof(hdr)); + hdr.flags = rte_cpu_to_be_16(RTE_PTP_FLAG_LI_59); + f = rte_be_to_cpu_16(hdr.flags); + TEST_ASSERT(f & RTE_PTP_FLAG_LI_59, + "LI_59 bit not set: 0x%04x", f); + + /* Combined TWO_STEP + UNICAST */ + memset(&hdr, 0, sizeof(hdr)); + hdr.flags = rte_cpu_to_be_16( + RTE_PTP_FLAG_TWO_STEP | RTE_PTP_FLAG_UNICAST); + f = rte_be_to_cpu_16(hdr.flags); + TEST_ASSERT((f & RTE_PTP_FLAG_TWO_STEP) && + (f & RTE_PTP_FLAG_UNICAST) && + !(f & RTE_PTP_FLAG_LI_61) && + !(f & RTE_PTP_FLAG_LI_59), + "combined flags incorrect: 0x%04x", f); + + return TEST_SUCCESS; +} + +/* ================================================================ + * Suite setup / teardown + * ================================================================ + */ + +static int +test_ptp_setup(void) +{ + ptp_mp = rte_pktmbuf_pool_create(PTP_TEST_MP_NAME, PTP_TEST_MP_SIZE, + 0, 0, PTP_TEST_BUF_SIZE, SOCKET_ID_ANY); + if (ptp_mp == NULL) { + printf("Cannot create ptp test mempool\n"); + return TEST_FAILED; + } + return TEST_SUCCESS; +} + +static void +test_ptp_teardown(void) +{ + rte_mempool_free(ptp_mp); + ptp_mp = NULL; +} + +static struct unit_test_suite ptp_test_suite = { + .suite_name = "PTP Library Unit Tests", + .setup = test_ptp_setup, + .teardown = test_ptp_teardown, + .unit_test_cases = { + /* Transport classification */ + TEST_CASE(test_ptp_classify_l2), + TEST_CASE(test_ptp_classify_l2_delay_req), + TEST_CASE(test_ptp_classify_vlan_8100), + TEST_CASE(test_ptp_classify_vlan_88a8), + TEST_CASE(test_ptp_classify_qinq), + TEST_CASE(test_ptp_classify_double_8100), + TEST_CASE(test_ptp_classify_ipv4_udp_319), + TEST_CASE(test_ptp_classify_ipv4_udp_320), + TEST_CASE(test_ptp_classify_vlan_ipv4_udp), + TEST_CASE(test_ptp_classify_qinq_ipv4_udp), + TEST_CASE(test_ptp_classify_ipv6_udp_319), + TEST_CASE(test_ptp_classify_ipv6_udp_320), + TEST_CASE(test_ptp_classify_vlan_ipv6_udp), + TEST_CASE(test_ptp_classify_qinq_ipv6_udp), + TEST_CASE(test_ptp_classify_non_ptp_arp), + TEST_CASE(test_ptp_classify_non_ptp_udp), + TEST_CASE(test_ptp_classify_ipv4_bad_ihl), + TEST_CASE(test_ptp_classify_ipv4_options), + TEST_CASE(test_ptp_classify_truncated), + + /* All message types */ + TEST_CASE(test_ptp_all_msg_types), + + /* Inline helpers */ + TEST_CASE(test_ptp_is_event), + TEST_CASE(test_ptp_two_step), + TEST_CASE(test_ptp_seq_id), + TEST_CASE(test_ptp_version), + TEST_CASE(test_ptp_domain), + + /* correctionField */ + TEST_CASE(test_ptp_correction_zero), + TEST_CASE(test_ptp_correction_known), + TEST_CASE(test_ptp_add_correction), + TEST_CASE(test_ptp_add_correction_accumulate), + TEST_CASE(test_ptp_add_correction_large), + TEST_CASE(test_ptp_add_correction_negative), + + /* Timestamp conversion */ + TEST_CASE(test_ptp_timestamp_to_ns), + + /* msg_type_str */ + TEST_CASE(test_ptp_msg_type_str), + + /* Flag field bit positions */ + TEST_CASE(test_ptp_flags), + + TEST_CASES_END() + }, +}; + +static int +test_ptp(void) +{ + return unit_test_suite_runner(&ptp_test_suite); +} + +REGISTER_FAST_TEST(ptp_autotest, NOHUGE_SKIP, ASAN_OK, test_ptp); -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [RFC v3 6/6] examples/ptpclient: use shared PTP library definitions 2026-05-04 9:17 ` [RFC v3 " Rajesh Kumar ` (4 preceding siblings ...) 2026-05-04 9:17 ` [RFC v3 5/6] app/test: add PTP library unit tests Rajesh Kumar @ 2026-05-04 9:17 ` Rajesh Kumar 2026-05-04 17:56 ` [RFC v3 0/6] introduce PTP protocol library and software relay Stephen Hemminger 6 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-04 9:17 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, Rajesh Kumar Replace local PTP struct and constant definitions with the shared rte_ptp.h library types introduced earlier in this series: - struct ptp_header -> struct rte_ptp_hdr - struct tstamp -> struct rte_ptp_timestamp - struct clock_id -> uint8_t clock_id[8] (from rte_ptp_port_id) - SYNC/FOLLOW_UP/DELAY_* defines -> RTE_PTP_MSGTYPE_* constants - PTP_PROTOCOL -> RTE_PTP_ETHERTYPE - hardcoded multicast -> RTE_PTP_MULTICAST_MAC - ptp_hdr->msg_type switch -> rte_ptp_msg_type() accessor No functional change. Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- examples/ptpclient/meson.build | 1 + examples/ptpclient/ptpclient.c | 188 +++++++++++++-------------------- 2 files changed, 73 insertions(+), 116 deletions(-) diff --git a/examples/ptpclient/meson.build b/examples/ptpclient/meson.build index 2e9b7625fc..ab074c9cb1 100644 --- a/examples/ptpclient/meson.build +++ b/examples/ptpclient/meson.build @@ -7,6 +7,7 @@ # DPDK instance, use 'make' allow_experimental_apis = true +deps += ['ptp'] sources = files( 'ptpclient.c', ) diff --git a/examples/ptpclient/ptpclient.c b/examples/ptpclient/ptpclient.c index 174ca5dd70..ee93f511d7 100644 --- a/examples/ptpclient/ptpclient.c +++ b/examples/ptpclient/ptpclient.c @@ -17,6 +17,7 @@ #include <rte_lcore.h> #include <rte_mbuf.h> #include <rte_ip.h> +#include <rte_ptp.h> #include <limits.h> #include <sys/time.h> #include <getopt.h> @@ -30,21 +31,8 @@ static volatile bool force_quit; #define NUM_MBUFS 8191 #define MBUF_CACHE_SIZE 250 -/* Values for the PTP messageType field. */ -#define SYNC 0x0 -#define DELAY_REQ 0x1 -#define PDELAY_REQ 0x2 -#define PDELAY_RESP 0x3 -#define FOLLOW_UP 0x8 -#define DELAY_RESP 0x9 -#define PDELAY_RESP_FOLLOW_UP 0xA -#define ANNOUNCE 0xB -#define SIGNALING 0xC -#define MANAGEMENT 0xD - #define NSEC_PER_SEC 1000000000L #define KERNEL_TIME_ADJUST_LIMIT 20000 -#define PTP_PROTOCOL 0x88F7 struct rte_mempool *mbuf_pool; uint32_t ptp_enabled_port_mask; @@ -52,66 +40,36 @@ uint8_t ptp_enabled_port_nb; static uint8_t ptp_enabled_ports[RTE_MAX_ETHPORTS]; static const struct rte_ether_addr ether_multicast = { - .addr_bytes = {0x01, 0x1b, 0x19, 0x0, 0x0, 0x0} -}; - -/* Structs used for PTP handling. */ -struct __rte_packed_begin tstamp { - uint16_t sec_msb; - uint32_t sec_lsb; - uint32_t ns; -} __rte_packed_end; - -struct clock_id { - uint8_t id[8]; + .addr_bytes = RTE_PTP_MULTICAST_MAC }; -struct __rte_packed_begin port_id { - struct clock_id clock_id; - uint16_t port_number; -} __rte_packed_end; - -struct __rte_packed_begin ptp_header { - uint8_t msg_type; - uint8_t ver; - uint16_t message_length; - uint8_t domain_number; - uint8_t reserved1; - uint8_t flag_field[2]; - int64_t correction; - uint32_t reserved2; - struct port_id source_port_id; - uint16_t seq_id; - uint8_t control; - int8_t log_message_interval; -} __rte_packed_end; - +/* Message body structs using the library PTP header and timestamp. */ struct __rte_packed_begin sync_msg { - struct ptp_header hdr; - struct tstamp origin_tstamp; + struct rte_ptp_hdr hdr; + struct rte_ptp_timestamp origin_tstamp; } __rte_packed_end; struct __rte_packed_begin follow_up_msg { - struct ptp_header hdr; - struct tstamp precise_origin_tstamp; - uint8_t suffix[]; + struct rte_ptp_hdr hdr; + struct rte_ptp_timestamp precise_origin_tstamp; + uint8_t suffix[]; } __rte_packed_end; struct __rte_packed_begin delay_req_msg { - struct ptp_header hdr; - struct tstamp origin_tstamp; + struct rte_ptp_hdr hdr; + struct rte_ptp_timestamp origin_tstamp; } __rte_packed_end; struct __rte_packed_begin delay_resp_msg { - struct ptp_header hdr; - struct tstamp rx_tstamp; - struct port_id req_port_id; - uint8_t suffix[]; + struct rte_ptp_hdr hdr; + struct rte_ptp_timestamp rx_tstamp; + struct rte_ptp_port_id req_port_id; + uint8_t suffix[]; } __rte_packed_end; struct ptp_message { union __rte_packed_begin { - struct ptp_header header; + struct rte_ptp_hdr header; struct sync_msg sync; struct delay_req_msg delay_req; struct follow_up_msg follow_up; @@ -125,8 +83,8 @@ struct ptpv2_time_receiver_ordinary { struct timespec tstamp2; struct timespec tstamp3; struct timespec tstamp4; - struct clock_id client_clock_id; - struct clock_id transmitter_clock_id; + uint8_t client_clock_id[8]; + uint8_t transmitter_clock_id[8]; struct timeval new_adj; int64_t delta; uint16_t portid; @@ -272,14 +230,14 @@ print_clock_info(struct ptpv2_time_receiver_ordinary *ptp_data) struct timespec net_time, sys_time; printf("time transmitter clock id: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", - ptp_data->transmitter_clock_id.id[0], - ptp_data->transmitter_clock_id.id[1], - ptp_data->transmitter_clock_id.id[2], - ptp_data->transmitter_clock_id.id[3], - ptp_data->transmitter_clock_id.id[4], - ptp_data->transmitter_clock_id.id[5], - ptp_data->transmitter_clock_id.id[6], - ptp_data->transmitter_clock_id.id[7]); + ptp_data->transmitter_clock_id[0], + ptp_data->transmitter_clock_id[1], + ptp_data->transmitter_clock_id[2], + ptp_data->transmitter_clock_id[3], + ptp_data->transmitter_clock_id[4], + ptp_data->transmitter_clock_id[5], + ptp_data->transmitter_clock_id[6], + ptp_data->transmitter_clock_id[7]); printf("\nT2 - time receiver clock. %lds %ldns", (ptp_data->tstamp2.tv_sec), @@ -356,20 +314,20 @@ delta_eval(struct ptpv2_time_receiver_ordinary *ptp_data) static void parse_sync(struct ptpv2_time_receiver_ordinary *ptp_data, uint16_t rx_tstamp_idx) { - struct ptp_header *ptp_hdr; + struct rte_ptp_hdr *ptp_hdr; - ptp_hdr = rte_pktmbuf_mtod_offset(ptp_data->m, struct ptp_header *, + ptp_hdr = rte_pktmbuf_mtod_offset(ptp_data->m, struct rte_ptp_hdr *, sizeof(struct rte_ether_hdr)); - ptp_data->seqID_SYNC = rte_be_to_cpu_16(ptp_hdr->seq_id); + ptp_data->seqID_SYNC = rte_be_to_cpu_16(ptp_hdr->sequence_id); if (ptp_data->ptpset == 0) { - ptp_data->transmitter_clock_id = ptp_hdr->source_port_id.clock_id; + memcpy(ptp_data->transmitter_clock_id, + ptp_hdr->source_port_id.clock_id, 8); ptp_data->ptpset = 1; } - if (memcmp(&ptp_data->transmitter_clock_id, - &ptp_hdr->source_port_id.clock_id, - sizeof(struct clock_id)) == 0) { + if (memcmp(ptp_data->transmitter_clock_id, + ptp_hdr->source_port_id.clock_id, 8) == 0) { if (ptp_data->ptpset == 1) rte_eth_timesync_read_rx_timestamp(ptp_data->portid, @@ -386,12 +344,12 @@ parse_fup(struct ptpv2_time_receiver_ordinary *ptp_data) { struct rte_ether_hdr *eth_hdr; struct rte_ether_addr eth_addr; - struct ptp_header *ptp_hdr; - struct clock_id *client_clkid; + struct rte_ptp_hdr *ptp_hdr; + uint8_t *client_clkid; struct ptp_message *ptp_msg; struct delay_req_msg *req_msg; struct rte_mbuf *created_pkt; - struct tstamp *origin_tstamp; + struct rte_ptp_timestamp *origin_tstamp; struct rte_ether_addr eth_multicast = ether_multicast; size_t pkt_size; int wait_us; @@ -399,22 +357,21 @@ parse_fup(struct ptpv2_time_receiver_ordinary *ptp_data) int ret; eth_hdr = rte_pktmbuf_mtod(m, struct rte_ether_hdr *); - ptp_hdr = rte_pktmbuf_mtod_offset(m, struct ptp_header *, + ptp_hdr = rte_pktmbuf_mtod_offset(m, struct rte_ptp_hdr *, sizeof(struct rte_ether_hdr)); - if (memcmp(&ptp_data->transmitter_clock_id, - &ptp_hdr->source_port_id.clock_id, - sizeof(struct clock_id)) != 0) + if (memcmp(ptp_data->transmitter_clock_id, + ptp_hdr->source_port_id.clock_id, 8) != 0) return; - ptp_data->seqID_FOLLOWUP = rte_be_to_cpu_16(ptp_hdr->seq_id); + ptp_data->seqID_FOLLOWUP = rte_be_to_cpu_16(ptp_hdr->sequence_id); ptp_msg = rte_pktmbuf_mtod_offset(m, struct ptp_message *, sizeof(struct rte_ether_hdr)); origin_tstamp = &ptp_msg->follow_up.precise_origin_tstamp; - ptp_data->tstamp1.tv_nsec = ntohl(origin_tstamp->ns); + ptp_data->tstamp1.tv_nsec = ntohl(origin_tstamp->nanoseconds); ptp_data->tstamp1.tv_sec = - ((uint64_t)ntohl(origin_tstamp->sec_lsb)) | - (((uint64_t)ntohs(origin_tstamp->sec_msb)) << 32); + ((uint64_t)ntohl(origin_tstamp->seconds_lo)) | + (((uint64_t)ntohs(origin_tstamp->seconds_hi)) << 32); if (ptp_data->seqID_FOLLOWUP == ptp_data->seqID_SYNC) { ret = rte_eth_macaddr_get(ptp_data->portid, ð_addr); @@ -441,34 +398,34 @@ parse_fup(struct ptpv2_time_receiver_ordinary *ptp_data) /* Set multicast address 01-1B-19-00-00-00. */ rte_ether_addr_copy(ð_multicast, ð_hdr->dst_addr); - eth_hdr->ether_type = htons(PTP_PROTOCOL); + eth_hdr->ether_type = htons(RTE_PTP_ETHERTYPE); req_msg = rte_pktmbuf_mtod_offset(created_pkt, struct delay_req_msg *, sizeof(struct rte_ether_hdr)); - req_msg->hdr.seq_id = htons(ptp_data->seqID_SYNC); - req_msg->hdr.msg_type = DELAY_REQ; - req_msg->hdr.ver = 2; + req_msg->hdr.sequence_id = htons(ptp_data->seqID_SYNC); + req_msg->hdr.msg_type = RTE_PTP_MSGTYPE_DELAY_REQ; + req_msg->hdr.version = 2; req_msg->hdr.control = 1; - req_msg->hdr.log_message_interval = 127; - req_msg->hdr.message_length = + req_msg->hdr.log_msg_interval = 127; + req_msg->hdr.msg_length = htons(sizeof(struct delay_req_msg)); req_msg->hdr.domain_number = ptp_hdr->domain_number; /* Set up clock id. */ client_clkid = - &req_msg->hdr.source_port_id.clock_id; + req_msg->hdr.source_port_id.clock_id; - client_clkid->id[0] = eth_hdr->src_addr.addr_bytes[0]; - client_clkid->id[1] = eth_hdr->src_addr.addr_bytes[1]; - client_clkid->id[2] = eth_hdr->src_addr.addr_bytes[2]; - client_clkid->id[3] = 0xFF; - client_clkid->id[4] = 0xFE; - client_clkid->id[5] = eth_hdr->src_addr.addr_bytes[3]; - client_clkid->id[6] = eth_hdr->src_addr.addr_bytes[4]; - client_clkid->id[7] = eth_hdr->src_addr.addr_bytes[5]; + client_clkid[0] = eth_hdr->src_addr.addr_bytes[0]; + client_clkid[1] = eth_hdr->src_addr.addr_bytes[1]; + client_clkid[2] = eth_hdr->src_addr.addr_bytes[2]; + client_clkid[3] = 0xFF; + client_clkid[4] = 0xFE; + client_clkid[5] = eth_hdr->src_addr.addr_bytes[3]; + client_clkid[6] = eth_hdr->src_addr.addr_bytes[4]; + client_clkid[7] = eth_hdr->src_addr.addr_bytes[5]; - ptp_data->client_clock_id = *client_clkid; + memcpy(ptp_data->client_clock_id, client_clkid, 8); /* Enable flag for hardware timestamping. */ created_pkt->ol_flags |= RTE_MBUF_F_TX_IEEE1588_TMST; @@ -534,21 +491,20 @@ parse_drsp(struct ptpv2_time_receiver_ordinary *ptp_data) { struct rte_mbuf *m = ptp_data->m; struct ptp_message *ptp_msg; - struct tstamp *rx_tstamp; + struct rte_ptp_timestamp *rx_tstamp; uint16_t seq_id; ptp_msg = rte_pktmbuf_mtod_offset(m, struct ptp_message *, sizeof(struct rte_ether_hdr)); - seq_id = rte_be_to_cpu_16(ptp_msg->delay_resp.hdr.seq_id); - if (memcmp(&ptp_data->client_clock_id, - &ptp_msg->delay_resp.req_port_id.clock_id, - sizeof(struct clock_id)) == 0) { + seq_id = rte_be_to_cpu_16(ptp_msg->delay_resp.hdr.sequence_id); + if (memcmp(ptp_data->client_clock_id, + ptp_msg->delay_resp.req_port_id.clock_id, 8) == 0) { if (seq_id == ptp_data->seqID_FOLLOWUP) { rx_tstamp = &ptp_msg->delay_resp.rx_tstamp; - ptp_data->tstamp4.tv_nsec = ntohl(rx_tstamp->ns); + ptp_data->tstamp4.tv_nsec = ntohl(rx_tstamp->nanoseconds); ptp_data->tstamp4.tv_sec = - ((uint64_t)ntohl(rx_tstamp->sec_lsb)) | - (((uint64_t)ntohs(rx_tstamp->sec_msb)) << 32); + ((uint64_t)ntohl(rx_tstamp->seconds_lo)) | + (((uint64_t)ntohs(rx_tstamp->seconds_hi)) << 32); /* Evaluate the delta for adjustment. */ ptp_data->delta = delta_eval(ptp_data); @@ -575,27 +531,27 @@ parse_drsp(struct ptpv2_time_receiver_ordinary *ptp_data) /* Parse ptp frames. 8< */ static void parse_ptp_frames(uint16_t portid, struct rte_mbuf *m) { - struct ptp_header *ptp_hdr; + struct rte_ptp_hdr *ptp_hdr; struct rte_ether_hdr *eth_hdr; uint16_t eth_type; eth_hdr = rte_pktmbuf_mtod(m, struct rte_ether_hdr *); eth_type = rte_be_to_cpu_16(eth_hdr->ether_type); - if (eth_type == PTP_PROTOCOL) { + if (eth_type == RTE_PTP_ETHERTYPE) { ptp_data.m = m; ptp_data.portid = portid; - ptp_hdr = rte_pktmbuf_mtod_offset(m, struct ptp_header *, + ptp_hdr = rte_pktmbuf_mtod_offset(m, struct rte_ptp_hdr *, sizeof(struct rte_ether_hdr)); - switch (ptp_hdr->msg_type) { - case SYNC: + switch (rte_ptp_msg_type(ptp_hdr)) { + case RTE_PTP_MSGTYPE_SYNC: parse_sync(&ptp_data, m->timesync); break; - case FOLLOW_UP: + case RTE_PTP_MSGTYPE_FOLLOW_UP: parse_fup(&ptp_data); break; - case DELAY_RESP: + case RTE_PTP_MSGTYPE_DELAY_RESP: parse_drsp(&ptp_data); print_clock_info(&ptp_data); break; -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* Re: [RFC v3 0/6] introduce PTP protocol library and software relay 2026-05-04 9:17 ` [RFC v3 " Rajesh Kumar ` (5 preceding siblings ...) 2026-05-04 9:17 ` [RFC v3 6/6] examples/ptpclient: use shared PTP library definitions Rajesh Kumar @ 2026-05-04 17:56 ` Stephen Hemminger 6 siblings, 0 replies; 60+ messages in thread From: Stephen Hemminger @ 2026-05-04 17:56 UTC (permalink / raw) To: Rajesh Kumar; +Cc: dev, bruce.richardson, aman.deep.singh On Mon, 4 May 2026 14:47:16 +0530 Rajesh Kumar <rajesh3.kumar@intel.com> wrote: > This series introduces a new DPDK library (lib/ptp) for IEEE 1588-2019 > PTP protocol packet processing and a companion example application > (ptp_tap_relay_sw) that demonstrates its usage. > > Motivation > ---------- > Several DPDK applications need to classify and manipulate PTP packets > (e.g. ptpclient, ptp_tap_relay, custom Transparent Clocks). Today each > application re-implements its own PTP header parsing and correctionField > handling. A shared library avoids duplication and provides a tested, > standards-compliant foundation. > > Library: lib/ptp > ---------------- > The library provides: > - PTP header structures (IEEE 1588-2019 common header, timestamp, > port identity) > - Packet classification: rte_ptp_classify() detects PTP over L2 > (EtherType 0x88F7), VLAN-tagged L2 (TPIDs 0x8100/0x88A8, single > or double), UDP/IPv4, UDP/IPv6 (ports 319/320), and VLAN-tagged > UDP variants > - Header access: rte_ptp_hdr_get() returns a pointer to the PTP > header inside an mbuf > - Inline helpers: correctionField manipulation (48.16 fixed-point), > message type extraction, two-step flag check, timestamp conversion > - Debug: rte_ptp_msg_type_str() for human-readable message names > > Example: ptp_tap_relay_sw > ------------------------- > A minimal PTP Transparent Clock relay between a DPDK-bound physical NIC > and a kernel TAP interface using software timestamps only. No patched > kernel modules, custom TAP PMD, or hardware timestamp support is > required. > > The relay: > 1. Receives packets on the physical NIC via DPDK > 2. Parses packets using rte_ptp_hdr_get() > 3. For event messages, records software timestamps > (clock_gettime(CLOCK_MONOTONIC)) at ingress and egress > 4. Adds residence time to correctionField via rte_ptp_add_correction() > (IEEE 1588-2019 10.2 Transparent Clock) > 5. Forwards bidirectionally: PHY <-> TAP > > Unit Tests: app/test > -------------------- > A comprehensive test suite (ptp_autotest) covers all library APIs, > including VLAN/QinQ/IPv4/IPv6 transports, correctionField helpers, > flags, and negative tests. This v3 also adds coverage for IPv4 options > (IHL > 5). > > v3: > - Reused RTE_ETHER_TYPE_1588 via RTE_PTP_ETHERTYPE alias > - Updated version field comment to minorVersionPTP|versionPTP > - Fixed prog guide rte_ptp_hdr_get() signature (non-const mbuf) > - Added ALLOW_EXPERIMENTAL_API to ptp_tap_relay_sw Makefile > - Updated relay example to parse once (rte_ptp_hdr_get + msg_type) > - Updated relay sample app limitations wording to match capabilities > - Added hdr_get NULL checks in correction tests > - Added IPv4 options classification test (IHL > 5) > - Made correction counter update style consistent via pointer arg > > v2: > - Fixed flag bit positions for host-order representation after > rte_be_to_cpu_16(): TWO_STEP (1<<9), UNICAST (1<<10), > LI_61 (1<<0), LI_59 (1<<1) > - Fixed QinQ classification: outer 0x88A8 now enters VLAN parser > - Fixed signed left-shift UB in rte_ptp_add_correction() > - Fixed promiscuous enable failure handling in relay port_init() > - Replaced deprecated master/slave terminology with IEEE terms > - Added and documented IHL validation for IPv4 PTP-over-UDP > - Changed rte_ptp_is_event() parameter to int with negative guard > - Changed rte_ptp_hdr_get() parameter to non-const mbuf > - Added programmer's guide note about avoiding double-parse > > Rajesh Kumar (6): > ptp: introduce PTP protocol library > doc: add PTP library programmer's guide > examples/ptp_tap_relay_sw: add software PTP relay example > doc: add PTP software relay sample app guide > app/test: add PTP library unit tests > examples/ptpclient: use shared PTP library definitions > > MAINTAINERS | 8 + > app/test/meson.build | 1 + > app/test/test_ptp.c | 1106 +++++++++++++++++ > doc/api/doxy-api-index.md | 1 + > doc/api/doxy-api.conf.in | 1 + > doc/guides/prog_guide/index.rst | 1 + > doc/guides/prog_guide/ptp_lib.rst | 205 +++ > doc/guides/rel_notes/release_26_07.rst | 13 + > doc/guides/sample_app_ug/index.rst | 1 + > doc/guides/sample_app_ug/ptp_tap_relay_sw.rst | 212 ++++ > examples/meson.build | 1 + > examples/ptp_tap_relay_sw/Makefile | 43 + > examples/ptp_tap_relay_sw/meson.build | 14 + > examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c | 433 +++++++ > examples/ptpclient/meson.build | 1 + > examples/ptpclient/ptpclient.c | 188 ++- > lib/meson.build | 1 + > lib/ptp/meson.build | 6 + > lib/ptp/rte_ptp.c | 185 +++ > lib/ptp/rte_ptp.h | 339 +++++ > 20 files changed, 2644 insertions(+), 116 deletions(-) > create mode 100644 app/test/test_ptp.c > create mode 100644 doc/guides/prog_guide/ptp_lib.rst > create mode 100644 doc/guides/sample_app_ug/ptp_tap_relay_sw.rst > create mode 100644 examples/ptp_tap_relay_sw/Makefile > create mode 100644 examples/ptp_tap_relay_sw/meson.build > create mode 100644 examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c > create mode 100644 lib/ptp/meson.build > create mode 100644 lib/ptp/rte_ptp.c > create mode 100644 lib/ptp/rte_ptp.h > > > base-commit: 7baf81674a011ab8a2fe329566b6d43d7377244c Lots of build failures in CI. Looks like alot around c++ build of header etc. g++ -Ibuildtools/chkincs/chkincs-cpp-exp.p -Ibuildtools/chkincs -I../buildtools/chkincs -I. -I.. -Iconfig -I../config -Ibuildtools/chkincs/staging -I../buildtools/chkincs/staging -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -Wnon-virtual-dtor -Wextra -Werror -g -include rte_config.h -march=corei7 -mrtm -Wno-missing-field-initializers -Wno-vla -DALLOW_EXPERIMENTAL_API -MD -MQ buildtools/chkincs/chkincs-cpp-exp.p/meson-generated_rte_ptp.cpp.o -MF buildtools/chkincs/chkincs-cpp-exp.p/meson-generated_rte_ptp.cpp.o.d -o buildtools/chkincs/chkincs-cpp-exp.p/meson-generated_rte_ptp.cpp.o -c buildtools/chkincs/chkincs-cpp-exp.p/rte_ptp.cpp In file included from buildtools/chkincs/staging/rte_memory.h:18, from buildtools/chkincs/staging/rte_ring_core.h:29, from buildtools/chkincs/staging/rte_ring.h:38, from buildtools/chkincs/staging/rte_mempool.h:49, from buildtools/chkincs/staging/rte_mbuf.h:39, from buildtools/chkincs/staging/rte_ether.h:20, from /home/runner/work/dpdk/dpdk/lib/ptp/rte_ptp.h:32, from buildtools/chkincs/chkincs-cpp-exp.p/rte_ptp.cpp:1: buildtools/chkincs/staging/rte_bitops.h:1468:1: error: conflicting declaration of C function ‘bool rte_bit_test(const volatile uint32_t*, unsigned int)’ 1468 | rte_bit_ ## family ## fun(qualifier uint ## size ## _t *addr, arg1_type arg1_name) \ | ^~~~~~~~ buildtools/chkincs/staging/rte_bitops.h:1475:9: note: in expansion of macro ‘__RTE_BIT_OVERLOAD_V_2R’ 1475 | __RTE_BIT_OVERLOAD_V_2R(family, v_, fun, qualifier volatile, size, ret_type, arg1_type, \ | ^~~~~~~~~~~~~~~~~~~~~~~ buildtools/chkincs/staging/rte_bitops.h:1479:9: note: in expansion of macro ‘__RTE_BIT_OVERLOAD_SZ_2R’ 1479 | __RTE_BIT_OVERLOAD_SZ_2R(family, fun, qualifier, 32, ret_type, arg1_type, arg1_name) \ | ^~~~~~~~~~~~~~~~~~~~~~~~ buildtools/chkincs/staging/rte_bitops.h:1575:1: note: in expansion of macro ‘__RTE_BIT_OVERLOAD_2R’ 1575 | __RTE_BIT_OVERLOAD_2R(, test, const, bool, unsigned int, nr) | ^~~~~~~~~~~~~~~~~~~~~ buildtools/chkincs/staging/rte_bitops.h:1468:1: note: previous declaration ‘bool rte_bit_test(const uint32_t*, unsigned int)’ 1468 | rte_bit_ ## family ## fun(qualifier uint ## size ## _t *addr, arg1_type arg1_name) \ | ^~~~~~~~ buildtools/chkincs/staging/rte_bitops.h:1474:9: note: in expansion of macro ‘__RTE_BIT_OVERLOAD_V_2R’ 1474 | __RTE_BIT_OVERLOAD_V_2R(family,, fun, qualifier, size, ret_type, arg1_type, arg1_name) \ | ^~~~~~~~~~~~~~~~~~~~~~~ buildtools/chkincs/staging/rte_bitops.h:1479:9: note: in expansion of macro ‘__RTE_BIT_OVERLOAD_SZ_2R’ 1479 | __RTE_BIT_OVERLOAD_SZ_2R(family, fun, qualifier, 32, ret_type, arg1_type, arg1_name) \ | ^~~~~~~~~~~~~~~~~~~~~~~~ buildtools/chkincs/staging/rte_bitops.h:1575:1: note: in expansion of macro ‘__RTE_BIT_OVERLOAD_2R’ 1575 | __RTE_BIT_OVERLOAD_2R(, test, const, bool, unsigned int, nr) ^ permalink raw reply [flat|nested] 60+ messages in thread
* [RFC v4 0/6] introduce PTP protocol library and software relay 2026-04-28 1:01 [RFC v1 0/4] introduce PTP protocol library and software relay example Rajesh Kumar ` (6 preceding siblings ...) 2026-05-04 9:17 ` [RFC v3 " Rajesh Kumar @ 2026-05-05 16:38 ` Rajesh Kumar 2026-05-05 16:38 ` [RFC v4 1/6] ptp: introduce PTP protocol library Rajesh Kumar ` (5 more replies) 2026-05-06 15:41 ` [RFC v5 0/6] introduce PTP protocol library and software relay Rajesh Kumar ` (3 subsequent siblings) 11 siblings, 6 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-05 16:38 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, Rajesh Kumar This series introduces a new DPDK library (lib/ptp) for IEEE 1588-2019 PTP protocol packet processing and a companion example application (ptp_tap_relay_sw) that demonstrates its usage. Motivation ---------- Several DPDK applications need to classify and manipulate PTP packets (e.g. ptpclient, ptp_tap_relay, custom Transparent Clocks). Today each application re-implements its own PTP header parsing and correctionField handling. A shared library avoids duplication and provides a tested, standards-compliant foundation. Library: lib/ptp ---------------- The library provides: - PTP header structures (IEEE 1588-2019 common header, timestamp, port identity) - Packet classification: rte_ptp_classify() detects PTP over L2 (EtherType 0x88F7), VLAN-tagged L2 (TPIDs 0x8100/0x88A8, single or double), UDP/IPv4, UDP/IPv6 (ports 319/320), and VLAN-tagged UDP variants - Header access: rte_ptp_hdr_get() returns a pointer to the PTP header inside an mbuf - Inline helpers: correctionField manipulation (48.16 fixed-point), message type extraction, two-step flag check, timestamp conversion - Debug: rte_ptp_msg_type_str() for human-readable message names Example: ptp_tap_relay_sw ------------------------- A minimal PTP Transparent Clock relay between a DPDK-bound physical NIC and a kernel TAP interface using software timestamps only. No patched kernel modules, custom TAP PMD, or hardware timestamp support is required. The relay: 1. Receives packets on the physical NIC via DPDK 2. Parses packets using rte_ptp_hdr_get() 3. For event messages, records software timestamps (clock_gettime(CLOCK_MONOTONIC)) at ingress and egress 4. Adds residence time to correctionField via rte_ptp_add_correction() (IEEE 1588-2019 10.2 Transparent Clock) 5. Forwards bidirectionally: PHY <-> TAP Unit Tests: app/test -------------------- A comprehensive test suite (ptp_autotest) covers all library APIs, including VLAN/QinQ/IPv4/IPv6 transports, correctionField helpers, flags, and negative tests. Also covers IPv4 options (IHL > 5). v4: - Fixed C++ header check (chkincs) failure: moved #include directives above extern "C" guard in rte_ptp.h to prevent rte_bitops.h C++ overloads from being forced into C linkage v3: - Reused RTE_ETHER_TYPE_1588 via RTE_PTP_ETHERTYPE alias - Updated version field comment to minorVersionPTP|versionPTP - Fixed prog guide rte_ptp_hdr_get() signature (non-const mbuf) - Added ALLOW_EXPERIMENTAL_API to ptp_tap_relay_sw Makefile - Updated relay example to parse once (rte_ptp_hdr_get + msg_type) - Updated relay sample app limitations wording to match capabilities - Added hdr_get NULL checks in correction tests - Added IPv4 options classification test (IHL > 5) - Made correction counter update style consistent via pointer arg v2: - Fixed flag bit positions for host-order representation after rte_be_to_cpu_16(): TWO_STEP (1<<9), UNICAST (1<<10), LI_61 (1<<0), LI_59 (1<<1) Rajesh Kumar (6): ptp: introduce PTP protocol library doc: add PTP library programmer's guide examples/ptp_tap_relay_sw: add software PTP relay example doc: add PTP software relay sample app guide app/test: add PTP library unit tests examples/ptpclient: use shared PTP library definitions MAINTAINERS | 8 + app/test/meson.build | 1 + app/test/test_ptp.c | 1106 +++++++++++++++++ doc/api/doxy-api-index.md | 1 + doc/api/doxy-api.conf.in | 1 + doc/guides/prog_guide/index.rst | 1 + doc/guides/prog_guide/ptp_lib.rst | 205 +++ doc/guides/rel_notes/release_26_07.rst | 13 + doc/guides/sample_app_ug/index.rst | 1 + doc/guides/sample_app_ug/ptp_tap_relay_sw.rst | 212 ++++ examples/meson.build | 1 + examples/ptp_tap_relay_sw/Makefile | 43 + examples/ptp_tap_relay_sw/meson.build | 14 + examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c | 433 +++++++ examples/ptpclient/meson.build | 1 + examples/ptpclient/ptpclient.c | 188 ++- lib/meson.build | 1 + lib/ptp/meson.build | 6 + lib/ptp/rte_ptp.c | 185 +++ lib/ptp/rte_ptp.h | 339 +++++ 20 files changed, 2644 insertions(+), 116 deletions(-) create mode 100644 app/test/test_ptp.c create mode 100644 doc/guides/prog_guide/ptp_lib.rst create mode 100644 doc/guides/sample_app_ug/ptp_tap_relay_sw.rst create mode 100644 examples/ptp_tap_relay_sw/Makefile create mode 100644 examples/ptp_tap_relay_sw/meson.build create mode 100644 examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c create mode 100644 lib/ptp/meson.build create mode 100644 lib/ptp/rte_ptp.c create mode 100644 lib/ptp/rte_ptp.h base-commit: 7baf81674a011ab8a2fe329566b6d43d7377244c -- 2.53.0 ^ permalink raw reply [flat|nested] 60+ messages in thread
* [RFC v4 1/6] ptp: introduce PTP protocol library 2026-05-05 16:38 ` [RFC v4 " Rajesh Kumar @ 2026-05-05 16:38 ` Rajesh Kumar 2026-05-05 16:38 ` [RFC v4 2/6] doc: add PTP library programmer's guide Rajesh Kumar ` (4 subsequent siblings) 5 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-05 16:38 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, Rajesh Kumar [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #1: Type: text/plain; charset=y, Size: 19055 bytes --] Add IEEE 1588-2019 Precision Time Protocol (PTP) processing library. The library provides packet classification, header structures, and helper functions for PTP packet processing in DPDK. This avoids duplicate PTP header definitions across multiple applications and drivers. Supported transports: 1. L2 PTP (EtherType 0x88F7) 2. VLAN-tagged L2 PTP (single and QinQ) 3. PTP over UDP/IPv4 (ports 319/320) 4. PTP over UDP/IPv6 (ports 319/320) Public APIs: 1. rte_ptp_classify() 2. rte_ptp_hdr_get() 3. rte_ptp_msg_type_str() Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- MAINTAINERS | 7 + doc/guides/rel_notes/release_26_07.rst | 7 + lib/meson.build | 1 + lib/ptp/meson.build | 6 + lib/ptp/rte_ptp.c | 185 ++++++++++++++ lib/ptp/rte_ptp.h | 339 +++++++++++++++++++++++++ 6 files changed, 545 insertions(+) create mode 100644 lib/ptp/meson.build create mode 100644 lib/ptp/rte_ptp.c create mode 100644 lib/ptp/rte_ptp.h diff --git a/MAINTAINERS b/MAINTAINERS index 0f5539f851..665e08dc90 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1665,6 +1665,13 @@ F: doc/guides/prog_guide/ipsec_lib.rst M: Vladimir Medvedkin <vladimir.medvedkin@intel.com> F: app/test-sad/ +PTP - EXPERIMENTAL +M: Rajesh Kumar <rajesh3.kumar@intel.com> +F: lib/ptp/ +F: doc/guides/prog_guide/ptp_lib.rst +F: examples/ptp_tap_relay_sw/ +F: doc/guides/sample_app_ug/ptp_tap_relay_sw.rst + PDCP - EXPERIMENTAL M: Anoob Joseph <anoobj@marvell.com> M: Volodymyr Fialko <vfialko@marvell.com> diff --git a/doc/guides/rel_notes/release_26_07.rst b/doc/guides/rel_notes/release_26_07.rst index f012d47a4b..b2208d6fb3 100644 --- a/doc/guides/rel_notes/release_26_07.rst +++ b/doc/guides/rel_notes/release_26_07.rst @@ -63,6 +63,13 @@ New Features ``rte_eal_init`` and the application is responsible for probing each device, * ``--auto-probing`` enables the initial bus probing, which is the current default behavior. +* **Added PTP protocol library.** + + Added a new library ``rte_ptp`` for IEEE 1588 Precision Time Protocol + packet processing. The library provides packet classification, header + parsing, and correctionField manipulation across L2, VLAN-tagged, + UDP/IPv4, and UDP/IPv6 transports. + Removed Items ------------- diff --git a/lib/meson.build b/lib/meson.build index 8f5cfd28a5..78d694f951 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -50,6 +50,7 @@ libraries = [ 'member', 'pcapng', 'power', + 'ptp', 'rawdev', 'regexdev', 'mldev', diff --git a/lib/ptp/meson.build b/lib/ptp/meson.build new file mode 100644 index 0000000000..05f9d87cbe --- /dev/null +++ b/lib/ptp/meson.build @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2026 Intel Corporation + +sources = files('rte_ptp.c') +headers = files('rte_ptp.h') +deps += ['mbuf', 'net'] diff --git a/lib/ptp/rte_ptp.c b/lib/ptp/rte_ptp.c new file mode 100644 index 0000000000..5e4fc666fb --- /dev/null +++ b/lib/ptp/rte_ptp.c @@ -0,0 +1,185 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2026 Intel Corporation + * + * IEEE 1588 / PTP Protocol Library — Implementation + */ + +#include <eal_export.h> +#include <rte_ether.h> +#include <rte_ip.h> +#include <rte_udp.h> + +#include "rte_ptp.h" + +/* + * Internal: find PTP header offset within a packet. + * Returns pointer to PTP header or NULL. + */ +static struct rte_ptp_hdr * +ptp_hdr_find(const struct rte_mbuf *m) +{ + const struct rte_ether_hdr *eth; + uint16_t ether_type; + uint32_t offset; + + if (rte_pktmbuf_data_len(m) < sizeof(struct rte_ether_hdr)) + return NULL; + + eth = rte_pktmbuf_mtod(m, const struct rte_ether_hdr *); + ether_type = rte_be_to_cpu_16(eth->ether_type); + offset = sizeof(struct rte_ether_hdr); + + /* Strip VLAN / QinQ tags to reach the inner EtherType */ + if (ether_type == RTE_ETHER_TYPE_VLAN || + ether_type == RTE_ETHER_TYPE_QINQ) { + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_vlan_hdr)) + return NULL; + const struct rte_vlan_hdr *vlan = + rte_pktmbuf_mtod_offset(m, + const struct rte_vlan_hdr *, offset); + ether_type = rte_be_to_cpu_16(vlan->eth_proto); + offset += sizeof(struct rte_vlan_hdr); + + /* Second tag (QinQ inner or stacked VLAN) */ + if (ether_type == RTE_ETHER_TYPE_VLAN || + ether_type == RTE_ETHER_TYPE_QINQ) { + if (rte_pktmbuf_data_len(m) < + offset + sizeof(struct rte_vlan_hdr)) + return NULL; + vlan = rte_pktmbuf_mtod_offset(m, + const struct rte_vlan_hdr *, offset); + ether_type = rte_be_to_cpu_16(vlan->eth_proto); + offset += sizeof(struct rte_vlan_hdr); + } + } + + /* L2 PTP: EtherType 0x88F7 (plain, VLAN, or QinQ) */ + if (ether_type == RTE_PTP_ETHERTYPE) { + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ptp_hdr)) + return NULL; + return rte_pktmbuf_mtod_offset(m, + struct rte_ptp_hdr *, offset); + } + + /* PTP over UDP/IPv4 (plain or VLAN-tagged) */ + if (ether_type == RTE_ETHER_TYPE_IPV4) { + const struct rte_ipv4_hdr *iph; + uint16_t ihl; + + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ipv4_hdr)) + return NULL; + + iph = rte_pktmbuf_mtod_offset(m, + const struct rte_ipv4_hdr *, offset); + if (iph->next_proto_id != IPPROTO_UDP) + return NULL; + + ihl = (iph->version_ihl & 0x0F) * 4; + if (ihl < 20) + return NULL; + offset += ihl; + + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_udp_hdr)) + return NULL; + + const struct rte_udp_hdr *udp = + rte_pktmbuf_mtod_offset(m, + const struct rte_udp_hdr *, offset); + uint16_t dst_port = rte_be_to_cpu_16(udp->dst_port); + + if (dst_port != RTE_PTP_EVENT_PORT && + dst_port != RTE_PTP_GENERAL_PORT) + return NULL; + + offset += sizeof(struct rte_udp_hdr); + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ptp_hdr)) + return NULL; + + return rte_pktmbuf_mtod_offset(m, + struct rte_ptp_hdr *, offset); + } + + /* PTP over UDP/IPv6 (plain or VLAN-tagged) */ + if (ether_type == RTE_ETHER_TYPE_IPV6) { + const struct rte_ipv6_hdr *ip6h; + + if (rte_pktmbuf_data_len(m) < + offset + sizeof(struct rte_ipv6_hdr)) + return NULL; + + ip6h = rte_pktmbuf_mtod_offset(m, + const struct rte_ipv6_hdr *, offset); + if (ip6h->proto != IPPROTO_UDP) + return NULL; + + offset += sizeof(struct rte_ipv6_hdr); + + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_udp_hdr)) + return NULL; + + const struct rte_udp_hdr *udp = + rte_pktmbuf_mtod_offset(m, + const struct rte_udp_hdr *, offset); + uint16_t dst_port = rte_be_to_cpu_16(udp->dst_port); + + if (dst_port != RTE_PTP_EVENT_PORT && + dst_port != RTE_PTP_GENERAL_PORT) + return NULL; + + offset += sizeof(struct rte_udp_hdr); + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ptp_hdr)) + return NULL; + + return rte_pktmbuf_mtod_offset(m, + struct rte_ptp_hdr *, offset); + } + + return NULL; +} + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_ptp_classify, 26.07) +int +rte_ptp_classify(const struct rte_mbuf *m) +{ + struct rte_ptp_hdr *hdr = ptp_hdr_find(m); + + if (hdr == NULL) + return RTE_PTP_MSGTYPE_INVALID; + + return rte_ptp_msg_type(hdr); +} + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_ptp_hdr_get, 26.07) +struct rte_ptp_hdr * +rte_ptp_hdr_get(struct rte_mbuf *m) +{ + return ptp_hdr_find(m); +} + +static const char * const ptp_msg_names[] = { + [RTE_PTP_MSGTYPE_SYNC] = "Sync", + [RTE_PTP_MSGTYPE_DELAY_REQ] = "Delay_Req", + [RTE_PTP_MSGTYPE_PDELAY_REQ] = "PDelay_Req", + [RTE_PTP_MSGTYPE_PDELAY_RESP] = "PDelay_Resp", + [0x4] = "Reserved_4", + [0x5] = "Reserved_5", + [0x6] = "Reserved_6", + [0x7] = "Reserved_7", + [RTE_PTP_MSGTYPE_FOLLOW_UP] = "Follow_Up", + [RTE_PTP_MSGTYPE_DELAY_RESP] = "Delay_Resp", + [RTE_PTP_MSGTYPE_PDELAY_RESP_FU] = "PDelay_Resp_Follow_Up", + [RTE_PTP_MSGTYPE_ANNOUNCE] = "Announce", + [RTE_PTP_MSGTYPE_SIGNALING] = "Signaling", + [RTE_PTP_MSGTYPE_MANAGEMENT] = "Management", + [0xE] = "Reserved_E", + [0xF] = "Reserved_F", +}; + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_ptp_msg_type_str, 26.07) +const char * +rte_ptp_msg_type_str(int msg_type) +{ + if (msg_type < 0 || msg_type > 0xF) + return "Not_PTP"; + return ptp_msg_names[msg_type]; +} diff --git a/lib/ptp/rte_ptp.h b/lib/ptp/rte_ptp.h new file mode 100644 index 0000000000..d9f1cce638 --- /dev/null +++ b/lib/ptp/rte_ptp.h @@ -0,0 +1,339 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2026 Intel Corporation + * + * IEEE 1588 / PTP Protocol Library + * + * Provides header structures, packet classification, and helper functions + * for Precision Time Protocol (IEEE 1588-2019) packet processing in DPDK. + * + * Supports: + * - L2 PTP (EtherType 0x88F7) + * - VLAN-tagged L2 PTP (single or double VLAN, TPIDs 0x8100/0x88A8) + * - PTP over UDP/IPv4 (ports 319/320) + * - PTP over UDP/IPv6 (ports 319/320) + * - VLAN-tagged PTP over UDP/IPv4 or UDP/IPv6 + * - Two-step and one-step message identification + * - correctionField manipulation (scaled nanoseconds, 48.16 fixed-point) + * + * Limitations: + * - IPv6 extension headers are not traversed. + */ + +#ifndef _RTE_PTP_H_ +#define _RTE_PTP_H_ + +#include <stdint.h> +#include <stdbool.h> +#include <rte_byteorder.h> +#include <rte_ether.h> +#include <rte_mbuf.h> +#include <rte_common.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @file + * IEEE 1588 PTP protocol definitions and helpers. + */ + +/* ============================================================ + * PTP Constants + * ============================================================ + */ + +/** PTP EtherType (IEEE 802.1AS / IEEE 1588) */ +#define RTE_PTP_ETHERTYPE RTE_ETHER_TYPE_1588 + +/** PTP over UDP event port (Sync, Delay_Req, PDelay_Req, PDelay_Resp) */ +#define RTE_PTP_EVENT_PORT 319 + +/** PTP over UDP general port (Follow_Up, Delay_Resp, Announce, etc.) */ +#define RTE_PTP_GENERAL_PORT 320 + +/** PTP multicast MAC address: 01:1B:19:00:00:00 */ +#define RTE_PTP_MULTICAST_MAC { 0x01, 0x1B, 0x19, 0x00, 0x00, 0x00 } + +/** PTP peer delay multicast MAC: 01:80:C2:00:00:0E */ +#define RTE_PTP_PDELAY_MULTICAST_MAC { 0x01, 0x80, 0xC2, 0x00, 0x00, 0x0E } + +/* ============================================================ + * PTP Message Types (IEEE 1588-2019 Table 36) + * ============================================================ + */ + +#define RTE_PTP_MSGTYPE_SYNC 0x0 /**< Sync (event) */ +#define RTE_PTP_MSGTYPE_DELAY_REQ 0x1 /**< Delay_Req (event) */ +#define RTE_PTP_MSGTYPE_PDELAY_REQ 0x2 /**< Peer_Delay_Req (event) */ +#define RTE_PTP_MSGTYPE_PDELAY_RESP 0x3 /**< Peer_Delay_Resp (event) */ +#define RTE_PTP_MSGTYPE_FOLLOW_UP 0x8 /**< Follow_Up (general) */ +#define RTE_PTP_MSGTYPE_DELAY_RESP 0x9 /**< Delay_Resp (general) */ +#define RTE_PTP_MSGTYPE_PDELAY_RESP_FU 0xA /**< Peer_Delay_Resp_Follow_Up */ +#define RTE_PTP_MSGTYPE_ANNOUNCE 0xB /**< Announce (general) */ +#define RTE_PTP_MSGTYPE_SIGNALING 0xC /**< Signaling (general) */ +#define RTE_PTP_MSGTYPE_MANAGEMENT 0xD /**< Management (general) */ + +/** Invalid / not a PTP packet */ +#define RTE_PTP_MSGTYPE_INVALID (-1) + +/* ============================================================ + * PTP Flag Field Bits (IEEE 1588-2019 Table 37) + * + * These constants are for use after rte_be_to_cpu_16(hdr->flags). + * flagField[0] (octet 6) maps to host bits 8-15. + * flagField[1] (octet 7) maps to host bits 0-7. + * ============================================================ + */ + +#define RTE_PTP_FLAG_TWO_STEP (1 << 9) /**< Two-step flag (flagField[0] bit 1) */ +#define RTE_PTP_FLAG_UNICAST (1 << 10) /**< Unicast flag (flagField[0] bit 2) */ +#define RTE_PTP_FLAG_LI_61 (1 << 0) /**< Leap indicator 61 (flagField[1] bit 0) */ +#define RTE_PTP_FLAG_LI_59 (1 << 1) /**< Leap indicator 59 (flagField[1] bit 1) */ + +/* ============================================================ + * PTP Header Structures (IEEE 1588-2019) + * ============================================================ + */ + +/** + * PTP Port Identity (10 bytes). + */ +struct __rte_packed_begin rte_ptp_port_id { + uint8_t clock_id[8]; /**< clockIdentity (EUI-64) */ + uint16_t port_number; /**< portNumber */ +} __rte_packed_end; + +/** + * PTP Common Message Header (34 bytes). + * All PTP messages begin with this header. + */ +struct __rte_packed_begin rte_ptp_hdr { + uint8_t msg_type; /**< transportSpecific (4) | messageType (4) */ + uint8_t version; /**< minorVersionPTP (4) | versionPTP (4) */ + uint16_t msg_length; /**< Total message length in bytes */ + uint8_t domain_number; /**< PTP domain (0-255) */ + uint8_t minor_sdo_id; /**< minorSdoId (IEEE 1588-2019) */ + uint16_t flags; /**< Flag field (see RTE_PTP_FLAG_*) */ + int64_t correction; /**< correctionField (scaled ns, 48.16 fixed) */ + uint32_t msg_type_specific; /**< messageTypeSpecific */ + struct rte_ptp_port_id source_port_id; /**< sourcePortIdentity */ + uint16_t sequence_id; /**< sequenceId */ + uint8_t control; /**< controlField (deprecated in 1588-2019) */ + int8_t log_msg_interval; /**< logMessageInterval */ +} __rte_packed_end; + +/** + * PTP Timestamp (10 bytes, used in Sync/Delay_Req/Follow_Up bodies). + */ +struct __rte_packed_begin rte_ptp_timestamp { + uint16_t seconds_hi; /**< Upper 16 bits of seconds */ + uint32_t seconds_lo; /**< Lower 32 bits of seconds */ + uint32_t nanoseconds; /**< Nanoseconds (0-999999999) */ +} __rte_packed_end; + +/* ============================================================ + * Inline Helpers + * ============================================================ + */ + +/** + * Extract PTP message type from header. + * + * @param hdr + * Pointer to PTP header. + * @return + * Message type (0x0-0xF). + */ +static inline uint8_t +rte_ptp_msg_type(const struct rte_ptp_hdr *hdr) +{ + return hdr->msg_type & 0x0F; +} + +/** + * Extract transport-specific field from header. + * + * @param hdr + * Pointer to PTP header. + * @return + * Transport-specific value (upper nibble, 0x0-0xF). + */ +static inline uint8_t +rte_ptp_transport_specific(const struct rte_ptp_hdr *hdr) +{ + return (hdr->msg_type >> 4) & 0x0F; +} + +/** + * Extract PTP version from header. + * + * @param hdr + * Pointer to PTP header. + * @return + * PTP version number (typically 2). + */ +static inline uint8_t +rte_ptp_version(const struct rte_ptp_hdr *hdr) +{ + return hdr->version & 0x0F; +} + +/** + * Get sequence ID from PTP header (host byte order). + * + * @param hdr + * Pointer to PTP header. + * @return + * Sequence ID in host byte order. + */ +static inline uint16_t +rte_ptp_seq_id(const struct rte_ptp_hdr *hdr) +{ + return rte_be_to_cpu_16(hdr->sequence_id); +} + +/** + * Get PTP domain number. + * + * @param hdr + * Pointer to PTP header. + * @return + * Domain number (0-255). + */ +static inline uint8_t +rte_ptp_domain(const struct rte_ptp_hdr *hdr) +{ + return hdr->domain_number; +} + +/** + * Check if PTP message type is an event message. + * Event messages (msg_type 0x0-0x3) require hardware timestamps. + * + * @param msg_type + * PTP message type value (0x0-0xF). + * @return + * true if event message, false otherwise. + */ +static inline bool +rte_ptp_is_event(int msg_type) +{ + return msg_type >= 0 && msg_type <= RTE_PTP_MSGTYPE_PDELAY_RESP; +} + +/** + * Check if the two-step flag is set in a PTP header. + * + * @param hdr + * Pointer to PTP header. + * @return + * true if two-step flag is set. + */ +static inline bool +rte_ptp_is_two_step(const struct rte_ptp_hdr *hdr) +{ + return (rte_be_to_cpu_16(hdr->flags) & RTE_PTP_FLAG_TWO_STEP) != 0; +} + +/** + * Get correctionField value in nanoseconds (from 48.16 fixed-point). + * + * @param hdr + * Pointer to PTP header. + * @return + * Correction value in nanoseconds. + */ +static inline int64_t +rte_ptp_correction_ns(const struct rte_ptp_hdr *hdr) +{ + return (int64_t)rte_be_to_cpu_64(hdr->correction) >> 16; +} + +/** + * Add a residence time (in nanoseconds) to the correctionField. + * Used by Transparent Clocks to account for relay transit delay. + * The correctionField uses IEEE 1588 scaled nanoseconds (48.16 fixed-point). + * + * @param hdr + * Pointer to PTP header (will be modified in-place). + * @param residence_ns + * Residence time in nanoseconds to add. + */ +static inline void +rte_ptp_add_correction(struct rte_ptp_hdr *hdr, int64_t residence_ns) +{ + int64_t cf = (int64_t)rte_be_to_cpu_64(hdr->correction); + cf += (int64_t)((uint64_t)residence_ns << 16); + hdr->correction = rte_cpu_to_be_64(cf); +} + +/** + * Convert a PTP timestamp structure to nanoseconds since epoch. + * + * @param ts + * Pointer to PTP timestamp. + * @return + * Time in nanoseconds since epoch. + */ +static inline uint64_t +rte_ptp_timestamp_to_ns(const struct rte_ptp_timestamp *ts) +{ + uint64_t sec = ((uint64_t)rte_be_to_cpu_16(ts->seconds_hi) << 32) | + rte_be_to_cpu_32(ts->seconds_lo); + return sec * 1000000000ULL + rte_be_to_cpu_32(ts->nanoseconds); +} + +/* ============================================================ + * Packet Classification Functions (implemented in rte_ptp.c) + * ============================================================ + */ + +/** + * Classify a packet as PTP and return the message type. + * + * Examines the mbuf to determine if it contains a PTP message. + * Supports L2 (EtherType 0x88F7), VLAN-tagged L2 (single/double, + * TPIDs 0x8100/0x88A8), PTP over UDP/IPv4, PTP over UDP/IPv6, + * and VLAN-tagged UDP/IPv4 or UDP/IPv6 (ports 319/320). + * + * @param m + * Pointer to the mbuf to classify. + * @return + * PTP message type (0x0-0xF) on success, RTE_PTP_MSGTYPE_INVALID (-1) + * if the packet is not PTP. + */ +__rte_experimental +int rte_ptp_classify(const struct rte_mbuf *m); + +/** + * Get a pointer to the PTP header inside an mbuf. + * + * Locates the PTP header within the packet, handling L2, VLAN-tagged L2 + * (single/double, TPIDs 0x8100/0x88A8), PTP over UDP/IPv4, + * PTP over UDP/IPv6, and VLAN-tagged UDP variants. + * + * @param m + * Pointer to the mbuf. + * @return + * Pointer to the PTP header, or NULL if not a PTP packet. + */ +__rte_experimental +struct rte_ptp_hdr *rte_ptp_hdr_get(struct rte_mbuf *m); + +/** + * Get a human-readable name for a PTP message type. + * + * @param msg_type + * PTP message type (0x0-0xF or RTE_PTP_MSGTYPE_INVALID). + * @return + * Static string with the message type name (e.g., "Sync", "Follow_Up"). + */ +__rte_experimental +const char *rte_ptp_msg_type_str(int msg_type); + +#ifdef __cplusplus +} +#endif + +#endif /* _RTE_PTP_H_ */ -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [RFC v4 2/6] doc: add PTP library programmer's guide 2026-05-05 16:38 ` [RFC v4 " Rajesh Kumar 2026-05-05 16:38 ` [RFC v4 1/6] ptp: introduce PTP protocol library Rajesh Kumar @ 2026-05-05 16:38 ` Rajesh Kumar 2026-05-05 16:38 ` [RFC v4 3/6] examples/ptp_tap_relay_sw: add software PTP relay example Rajesh Kumar ` (3 subsequent siblings) 5 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-05 16:38 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, Rajesh Kumar [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #1: Type: text/plain; charset=y, Size: 9526 bytes --] Add programmer's guide for the PTP protocol library covering message types, header structures, packet classification API, inline helpers, and usage examples. Add PTP header to Doxygen API index under layers section. Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- doc/api/doxy-api-index.md | 1 + doc/api/doxy-api.conf.in | 1 + doc/guides/prog_guide/index.rst | 1 + doc/guides/prog_guide/ptp_lib.rst | 205 ++++++++++++++++++++++++++++++ 4 files changed, 208 insertions(+) create mode 100644 doc/guides/prog_guide/ptp_lib.rst diff --git a/doc/api/doxy-api-index.md b/doc/api/doxy-api-index.md index 9296042119..bbc79168bb 100644 --- a/doc/api/doxy-api-index.md +++ b/doc/api/doxy-api-index.md @@ -137,6 +137,7 @@ The public API headers are grouped by topics: [eCPRI](@ref rte_ecpri.h), [PDCP hdr](@ref rte_pdcp_hdr.h), [PDCP](@ref rte_pdcp.h), + [PTP](@ref rte_ptp.h), [L2TPv2](@ref rte_l2tpv2.h), [PPP](@ref rte_ppp.h), [IB](@ref rte_ib.h) diff --git a/doc/api/doxy-api.conf.in b/doc/api/doxy-api.conf.in index bedd944681..f15d962733 100644 --- a/doc/api/doxy-api.conf.in +++ b/doc/api/doxy-api.conf.in @@ -72,6 +72,7 @@ INPUT = @TOPDIR@/doc/api/doxy-api-index.md \ @TOPDIR@/lib/pmu \ @TOPDIR@/lib/port \ @TOPDIR@/lib/power \ + @TOPDIR@/lib/ptp \ @TOPDIR@/lib/ptr_compress \ @TOPDIR@/lib/rawdev \ @TOPDIR@/lib/rcu \ diff --git a/doc/guides/prog_guide/index.rst b/doc/guides/prog_guide/index.rst index e6f24945b0..60dad4475c 100644 --- a/doc/guides/prog_guide/index.rst +++ b/doc/guides/prog_guide/index.rst @@ -97,6 +97,7 @@ Protocol Processing Libraries :maxdepth: 1 :numbered: + ptp_lib pdcp_lib ipsec_lib diff --git a/doc/guides/prog_guide/ptp_lib.rst b/doc/guides/prog_guide/ptp_lib.rst new file mode 100644 index 0000000000..fb6794781a --- /dev/null +++ b/doc/guides/prog_guide/ptp_lib.rst @@ -0,0 +1,205 @@ +.. SPDX-License-Identifier: BSD-3-Clause + Copyright(c) 2026 Intel Corporation. + +PTP Protocol Library +==================== + +The DPDK PTP library provides IEEE 1588 / Precision Time Protocol (PTP) +packet structures, constants, and helper functions for PTP packet processing. + +The library supports classification and header parsing of PTP messages +across multiple transport encapsulations: + +- L2 PTP (EtherType 0x88F7) +- VLAN-tagged L2 PTP (single or double VLAN, TPIDs 0x8100 and 0x88A8) +- PTP over UDP/IPv4 (destination ports 319 and 320) +- PTP over UDP/IPv6 (destination ports 319 and 320) +- VLAN-tagged PTP over UDP/IPv4 or UDP/IPv6 + +The library conforms to +`IEEE 1588-2019 <https://standards.ieee.org/ieee/1588/6825/>`_ +(Precision Time Protocol). + +Overview +-------- + +PTP is the foundation of time synchronization in networking. +DPDK applications that relay, classify, or timestamp PTP packets +currently duplicate header definitions and parsing logic. +This library provides a shared, tested implementation. + +The library provides: + +#. Packed header structures matching the IEEE 1588-2019 wire format +#. Constants for message types, flags, ports, and multicast addresses +#. Inline helpers for common field extraction and manipulation +#. Packet classification across L2, VLAN, UDP/IPv4, and UDP/IPv6 transports +#. Correction field manipulation for Transparent Clock residence time + +PTP Message Types +----------------- + +IEEE 1588-2019 defines the following message types, all supported by the library: + +.. csv-table:: PTP Message Types + :header: "Type", "Name", "Category", "Macro" + :widths: 5, 20, 10, 30 + + "0x0", "Sync", "Event", "``RTE_PTP_MSGTYPE_SYNC``" + "0x1", "Delay_Req", "Event", "``RTE_PTP_MSGTYPE_DELAY_REQ``" + "0x2", "Peer_Delay_Req", "Event", "``RTE_PTP_MSGTYPE_PDELAY_REQ``" + "0x3", "Peer_Delay_Resp", "Event", "``RTE_PTP_MSGTYPE_PDELAY_RESP``" + "0x8", "Follow_Up", "General", "``RTE_PTP_MSGTYPE_FOLLOW_UP``" + "0x9", "Delay_Resp", "General", "``RTE_PTP_MSGTYPE_DELAY_RESP``" + "0xA", "PDelay_Resp_Follow_Up", "General", "``RTE_PTP_MSGTYPE_PDELAY_RESP_FU``" + "0xB", "Announce", "General", "``RTE_PTP_MSGTYPE_ANNOUNCE``" + "0xC", "Signaling", "General", "``RTE_PTP_MSGTYPE_SIGNALING``" + "0xD", "Management", "General", "``RTE_PTP_MSGTYPE_MANAGEMENT``" + +Event messages (types 0x0–0x3) require hardware timestamps for accurate +time transfer. + +Header Structures +----------------- + +The library defines the following packed structures that map directly to +the IEEE 1588-2019 wire format: + +``struct rte_ptp_hdr`` + Common PTP message header (34 bytes). All PTP messages begin with this header. + Contains message type, version, flags, correction field, source port identity, + sequence ID, and log message interval. + +``struct rte_ptp_timestamp`` + PTP timestamp (10 bytes). Used in Sync, Delay_Req, and Follow_Up message bodies. + Contains seconds (48-bit) and nanoseconds (32-bit). + +``struct rte_ptp_port_id`` + PTP port identity (10 bytes). Contains an EUI-64 clock identity and a + 16-bit port number. + +Packet Classification API +-------------------------- + +``rte_ptp_classify()`` +~~~~~~~~~~~~~~~~~~~~~~ + +Classify a packet and return the PTP message type. + +.. code-block:: C + + int rte_ptp_classify(const struct rte_mbuf *m); + +Examines the mbuf to determine if it contains a PTP message. +Returns the PTP message type (0x0–0xF) on success, +or ``RTE_PTP_MSGTYPE_INVALID`` (-1) if the packet is not PTP. + +Supported encapsulations (VLAN TPIDs recognised: 0x8100 and 0x88A8): + +- EtherType 0x88F7 (L2 PTP) +- Single VLAN (0x8100 or 0x88A8) + EtherType 0x88F7 +- Double VLAN (any combination of 0x8100 / 0x88A8) + EtherType 0x88F7 +- IPv4 + UDP destination port 319 or 320 +- IPv6 + UDP destination port 319 or 320 +- Single or double VLAN + IPv4/IPv6 + UDP destination port 319 or 320 + +``rte_ptp_hdr_get()`` +~~~~~~~~~~~~~~~~~~~~~ + +Get a pointer to the PTP header inside a packet. + +.. code-block:: C + + struct rte_ptp_hdr *rte_ptp_hdr_get(struct rte_mbuf *m); + +Returns a pointer to the PTP header, or NULL if the packet is not PTP. +Handles the same set of encapsulations as ``rte_ptp_classify()``. + +``rte_ptp_msg_type_str()`` +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Convert a PTP message type to a human-readable string. + +.. code-block:: C + + const char *rte_ptp_msg_type_str(int msg_type); + +Returns a string such as ``"Sync"``, ``"Delay_Req"``, ``"Follow_Up"``, etc. +Returns ``"Not_PTP"`` for invalid message types. + +Inline Helpers +-------------- + +The following inline functions operate on ``struct rte_ptp_hdr`` and require +no function call overhead: + +.. csv-table:: Inline Helper Functions + :header: "Function", "Returns", "Description" + :widths: 30, 15, 40 + + "``rte_ptp_msg_type()``", "``uint8_t``", "Extract message type (lower nibble)" + "``rte_ptp_transport_specific()``", "``uint8_t``", "Extract transport-specific field (upper nibble)" + "``rte_ptp_version()``", "``uint8_t``", "Extract PTP version number" + "``rte_ptp_seq_id()``", "``uint16_t``", "Get sequence ID in host byte order" + "``rte_ptp_domain()``", "``uint8_t``", "Get PTP domain number" + "``rte_ptp_is_event()``", "``bool``", "Check if message type is an event (0x0–0x3)" + "``rte_ptp_is_two_step()``", "``bool``", "Check if two-step flag is set" + "``rte_ptp_correction_ns()``", "``int64_t``", "Get correctionField in nanoseconds (from 48.16 fixed-point)" + "``rte_ptp_add_correction()``", "``void``", "Add residence time to correctionField (for Transparent Clocks)" + "``rte_ptp_timestamp_to_ns()``", "``uint64_t``", "Convert PTP timestamp struct to nanoseconds" + +Usage Example +------------- + +Classifying and processing PTP packets: + +.. code-block:: C + + #include <rte_ptp.h> + + void process_packets(struct rte_mbuf **pkts, uint16_t nb_pkts) + { + for (uint16_t i = 0; i < nb_pkts; i++) { + int ptp_type = rte_ptp_classify(pkts[i]); + if (ptp_type == RTE_PTP_MSGTYPE_INVALID) + continue; + + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(pkts[i]); + + printf("PTP %s seq=%u domain=%u\n", + rte_ptp_msg_type_str(ptp_type), + rte_ptp_seq_id(hdr), + rte_ptp_domain(hdr)); + + if (rte_ptp_is_event(ptp_type)) { + /* Event message — needs hardware timestamp */ + } + } + } + +.. note:: + + ``rte_ptp_classify()`` and ``rte_ptp_hdr_get()`` both parse the packet + internally. When the caller needs both the message type and a header + pointer, calling ``rte_ptp_hdr_get()`` alone and then using + ``rte_ptp_msg_type()`` on the returned header avoids parsing the + packet twice. + +Adding residence time for a Transparent Clock: + +.. code-block:: C + + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(pkt); + if (hdr != NULL) { + int64_t residence_ns = egress_ts - ingress_ts; + rte_ptp_add_correction(hdr, residence_ns); + } + +Limitations +----------- + +- IPv6 extension headers are not traversed. Only the base IPv6 header + with ``next_header == UDP`` is handled. +- Multi-segment mbufs are not supported. PTP event messages are + typically less than 128 bytes and fit in a single segment. +- PTP over MPLS or other tunneling protocols is not supported. -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [RFC v4 3/6] examples/ptp_tap_relay_sw: add software PTP relay example 2026-05-05 16:38 ` [RFC v4 " Rajesh Kumar 2026-05-05 16:38 ` [RFC v4 1/6] ptp: introduce PTP protocol library Rajesh Kumar 2026-05-05 16:38 ` [RFC v4 2/6] doc: add PTP library programmer's guide Rajesh Kumar @ 2026-05-05 16:38 ` Rajesh Kumar 2026-05-05 16:38 ` [RFC v4 4/6] doc: add PTP software relay sample app guide Rajesh Kumar ` (2 subsequent siblings) 5 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-05 16:38 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, Rajesh Kumar [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #1: Type: text/plain; charset=y, Size: 16475 bytes --] Add a minimal PTP Transparent Clock relay that bridges PTP packets between a DPDK-bound physical NIC and a kernel TAP interface using software timestamps only. The relay uses the rte_ptp library for packet classification and correctionField manipulation. For each PTP event message, it records software timestamps (CLOCK_MONOTONIC_RAW) at ingress and egress, then adds the residence time to the correctionField per IEEE 1588-2019 section 10.2. A two-pass design is used: first all packets are classified and PTP header pointers saved, then a single TX timestamp is taken immediately before applying corrections and calling rte_eth_tx_burst(). This minimises the gap between the measured timestamp and actual wire egress. Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- doc/guides/rel_notes/release_26_07.rst | 6 + examples/meson.build | 1 + examples/ptp_tap_relay_sw/Makefile | 43 ++ examples/ptp_tap_relay_sw/meson.build | 14 + examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c | 433 +++++++++++++++++++ 5 files changed, 497 insertions(+) create mode 100644 examples/ptp_tap_relay_sw/Makefile create mode 100644 examples/ptp_tap_relay_sw/meson.build create mode 100644 examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c diff --git a/doc/guides/rel_notes/release_26_07.rst b/doc/guides/rel_notes/release_26_07.rst index b2208d6fb3..5c2329d867 100644 --- a/doc/guides/rel_notes/release_26_07.rst +++ b/doc/guides/rel_notes/release_26_07.rst @@ -70,6 +70,12 @@ New Features parsing, and correctionField manipulation across L2, VLAN-tagged, UDP/IPv4, and UDP/IPv6 transports. +* **Added PTP software relay example application.** + + Added a new example application ``ptp_tap_relay_sw`` demonstrating a + software PTP Transparent Clock relay between a DPDK port and a kernel + TAP interface. + Removed Items ------------- diff --git a/examples/meson.build b/examples/meson.build index 25d9c88457..ab82cf5fbd 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -43,6 +43,7 @@ all_examples = [ 'packet_ordering', 'pipeline', 'ptpclient', + 'ptp_tap_relay_sw', 'qos_meter', 'qos_sched', 'rxtx_callbacks', diff --git a/examples/ptp_tap_relay_sw/Makefile b/examples/ptp_tap_relay_sw/Makefile new file mode 100644 index 0000000000..259e53bd18 --- /dev/null +++ b/examples/ptp_tap_relay_sw/Makefile @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2026 Intel Corporation + +# binary name +APP = dpdk-ptp_tap_relay_sw + +# all source are stored in SRCS-y +SRCS-y := ptp_tap_relay_sw.c + +PKGCONF ?= pkg-config + +# Build using pkg-config variables if possible +ifneq ($(shell $(PKGCONF) --exists libdpdk && echo 0),0) +$(error "no installation of DPDK found") +endif + +all: shared +.PHONY: shared static +shared: build/$(APP)-shared + ln -sf $(APP)-shared build/$(APP) +static: build/$(APP)-static + ln -sf $(APP)-static build/$(APP) + +PC_FILE := $(shell $(PKGCONF) --path libdpdk 2>/dev/null) +CFLAGS += -O3 $(shell $(PKGCONF) --cflags libdpdk) +LDFLAGS_SHARED = $(shell $(PKGCONF) --libs libdpdk) +LDFLAGS_STATIC = $(shell $(PKGCONF) --static --libs libdpdk) + +CFLAGS += -DALLOW_EXPERIMENTAL_API + +build/$(APP)-shared: $(SRCS-y) Makefile $(PC_FILE) | build + $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_SHARED) + +build/$(APP)-static: $(SRCS-y) Makefile $(PC_FILE) | build + $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_STATIC) + +build: + @mkdir -p $@ + +.PHONY: clean +clean: + rm -f build/$(APP) build/$(APP)-static build/$(APP)-shared + test -d build && rmdir -p build || true diff --git a/examples/ptp_tap_relay_sw/meson.build b/examples/ptp_tap_relay_sw/meson.build new file mode 100644 index 0000000000..56abad02ed --- /dev/null +++ b/examples/ptp_tap_relay_sw/meson.build @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2026 Intel Corporation + +# meson file, for building this example as part of a main DPDK build. +# +# To build this example as a standalone application with an already-installed +# DPDK instance, use 'make' + +allow_experimental_apis = true +sources = files( + 'ptp_tap_relay_sw.c', +) +deps += ['net', 'ptp'] +cflags += no_shadow_cflag diff --git a/examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c b/examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c new file mode 100644 index 0000000000..591007b1d5 --- /dev/null +++ b/examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c @@ -0,0 +1,433 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2026 Intel Corporation + */ + +/* + * PTP Software Relay — lib/ptp usage example + * + * A minimal PTP relay between a DPDK-bound physical NIC and a kernel + * TAP interface using software timestamps only. No patched TUN driver + * or ptp_proxy kernel module is required. + * + * How it works: + * 1. Physical NIC receives PTP (and non-PTP) packets via DPDK RX. + * 2. For PTP event messages (Sync, Delay_Req, PDelay_Req, PDelay_Resp) + * the relay records an RX software timestamp (clock_gettime). + * 3. Just before TX on the other side it records a TX software timestamp. + * 4. The relay residence time (tx_ts − rx_ts) is added to the PTP + * correctionField via rte_ptp_add_correction() — standard + * Transparent Clock behaviour (IEEE 1588-2019 §10.2). + * 5. Packets are forwarded bi-directionally: + * PHY → TAP (network → ptp4l) + * TAP → PHY (ptp4l → network) + * + * ptp4l runs in software-timestamping mode on the TAP interface: + * + * ptp4l -i dtap0 -m -s -S # -S = software timestamps + * + * Topology: + * + * Time Transmitter (remote) ──L2── Physical NIC (DPDK) + * │ + * PTP SW Relay ← correctionField update + * │ + * TAP (kernel) ── ptp4l -S (time receiver) + * + * Usage: + * dpdk-ptp_tap_relay_sw -l 0-1 --vdev=net_tap0,iface=dtap0 -- \ + * -p 0 -t 1 + * + * Parameters: + * -p PORT Physical NIC port ID (default: 0) + * -t PORT TAP port ID (default: 1) + * -T SECS Stats print interval in seconds (default: 10) + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <stdbool.h> +#include <signal.h> +#include <getopt.h> +#include <time.h> + +#include <rte_eal.h> +#include <rte_ethdev.h> +#include <rte_mbuf.h> +#include <rte_cycles.h> +#include <rte_lcore.h> +#include <rte_ptp.h> + +/* Ring sizes */ +#define RX_RING_SIZE 1024 +#define TX_RING_SIZE 1024 + +/* Mempool */ +#define NUM_MBUFS 8191 +#define MBUF_CACHE 250 +#define BURST_SIZE 32 + +#define NSEC_PER_SEC 1000000000ULL + +/* Logging helpers */ +#define LOG_INFO(fmt, ...) \ + fprintf(stdout, "[PTP-SW] " fmt "\n", ##__VA_ARGS__) +#define LOG_ERR(fmt, ...) \ + fprintf(stderr, "[PTP-SW ERROR] " fmt "\n", ##__VA_ARGS__) + +static volatile bool force_quit; + +/* Port IDs */ +static uint16_t phy_port; +static uint16_t tap_port = 1; +static unsigned int stats_interval = 10; /* seconds */ + +/* Statistics */ +static struct { + uint64_t phy_rx; /* total packets from PHY */ + uint64_t phy_rx_ptp; /* PTP packets from PHY */ + uint64_t tap_tx; /* packets forwarded to TAP */ + uint64_t tap_rx; /* total packets from TAP */ + uint64_t tap_rx_ptp; /* PTP packets from TAP */ + uint64_t phy_tx; /* packets forwarded to PHY */ + uint64_t corrections; /* correctionField updates */ +} stats; + +static void +signal_handler(int signum) +{ + if (signum == SIGINT || signum == SIGTERM) { + LOG_INFO("Signal %d received, shutting down...", signum); + force_quit = true; + } +} + +/* Helpers */ + +/* Read monotonic clock in nanoseconds (for residence time). */ +static inline uint64_t +sw_timestamp_ns(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * NSEC_PER_SEC + (uint64_t)ts.tv_nsec; +} + +/* Port Init */ + +static int +port_init(uint16_t port, struct rte_mempool *mp) +{ + struct rte_eth_conf port_conf; + struct rte_eth_dev_info dev_info; + uint16_t nb_rxd = RX_RING_SIZE; + uint16_t nb_txd = TX_RING_SIZE; + int ret; + + memset(&port_conf, 0, sizeof(port_conf)); + + ret = rte_eth_dev_info_get(port, &dev_info); + if (ret != 0) { + LOG_ERR("rte_eth_dev_info_get(port %u) failed: %d", port, ret); + return ret; + } + + if (dev_info.tx_offload_capa & RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE) + port_conf.txmode.offloads |= + RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE; + + ret = rte_eth_dev_configure(port, 1, 1, &port_conf); + if (ret != 0) + return ret; + + ret = rte_eth_dev_adjust_nb_rx_tx_desc(port, &nb_rxd, &nb_txd); + if (ret != 0) + return ret; + + ret = rte_eth_rx_queue_setup(port, 0, nb_rxd, + rte_eth_dev_socket_id(port), NULL, mp); + if (ret < 0) + return ret; + + ret = rte_eth_tx_queue_setup(port, 0, nb_txd, + rte_eth_dev_socket_id(port), NULL); + if (ret < 0) + return ret; + + ret = rte_eth_dev_start(port); + if (ret < 0) + return ret; + + ret = rte_eth_promiscuous_enable(port); + if (ret != 0) { + LOG_ERR("Failed to enable promiscuous on port %u: %s", + port, rte_strerror(-ret)); + return ret; + } + + return 0; +} + +/* Relay one direction */ + +/* + * Forward packets from src_port to dst_port. + * For PTP event messages, record SW timestamps around the + * relay path and add the residence time to the correctionField. + * + * This implements a Transparent Clock (IEEE 1588-2019 §10.2): + * correctionField += (t_egress − t_ingress) + * + * Note: a single rx_ts / tx_ts pair is used for the entire burst. + * At typical PTP rates (logSyncInterval >= -4, i.e. <= 16 pkt/s) + * bursts contain at most one packet, so this is exact. At higher + * rates, early packets in a burst are slightly under-corrected and + * late ones over-corrected by up to one poll-loop iteration. + */ +static void +relay_burst(uint16_t src_port, uint16_t dst_port, + uint64_t *rx_cnt, uint64_t *rx_ptp_cnt, + uint64_t *tx_cnt, uint64_t *corr_cnt) +{ + struct rte_mbuf *bufs[BURST_SIZE]; + struct rte_ptp_hdr *ptp_hdrs[BURST_SIZE]; + uint64_t rx_ts; + uint16_t nb_rx, nb_tx, i; + + nb_rx = rte_eth_rx_burst(src_port, 0, bufs, BURST_SIZE); + if (nb_rx == 0) + return; + + /* Record a single RX software timestamp for the whole burst. + * All packets in one burst arrived at essentially the same instant + * from rte_eth_rx_burst()'s perspective. + */ + rx_ts = sw_timestamp_ns(); + + *rx_cnt += nb_rx; + + /* + * Pass 1: Parse each packet once and remember PTP event headers. + * This avoids taking the TX timestamp too early — we want it as + * close to the actual rte_eth_tx_burst() call as possible. + */ + memset(ptp_hdrs, 0, sizeof(ptp_hdrs[0]) * nb_rx); + for (i = 0; i < nb_rx; i++) { + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(bufs[i]); + + if (hdr == NULL) + continue; + + (*rx_ptp_cnt)++; + + /* Only event messages carry timestamps that need correction */ + if (!rte_ptp_is_event(rte_ptp_msg_type(hdr))) + continue; + + ptp_hdrs[i] = hdr; + } + + /* + * Pass 2: Take a single TX timestamp right before transmission. + * This minimises the gap between the measured tx_ts and the + * actual kernel write inside rte_eth_tx_burst(), giving the + * most accurate residence time we can achieve with SW timestamps. + * + * residence_time = tx_ts − rx_ts + * + * Remaining untracked delays: + * - Pre-RX: NIC DMA → rx_burst return (~1-5 µs, unavoidable) + * - Post-TX: tx_ts → kernel TAP write (~1-2 µs) + * Both are symmetric for Sync and Delay_Req so they largely + * cancel in the ptp4l offset calculation. + */ + uint64_t tx_ts = sw_timestamp_ns(); + int64_t residence_ns = (int64_t)(tx_ts - rx_ts); + + for (i = 0; i < nb_rx; i++) { + if (ptp_hdrs[i] == NULL) + continue; + rte_ptp_add_correction(ptp_hdrs[i], residence_ns); + (*corr_cnt)++; + } + + /* Forward the burst */ + nb_tx = rte_eth_tx_burst(dst_port, 0, bufs, nb_rx); + *tx_cnt += nb_tx; + + /* Free any unsent packets */ + for (i = nb_tx; i < nb_rx; i++) + rte_pktmbuf_free(bufs[i]); +} + +/* Print statistics */ + +static void +print_stats(void) +{ + LOG_INFO("=== Statistics ==="); + LOG_INFO(" PHY RX total: %"PRIu64, stats.phy_rx); + LOG_INFO(" PHY RX PTP: %"PRIu64, stats.phy_rx_ptp); + LOG_INFO(" TAP TX: %"PRIu64, stats.tap_tx); + LOG_INFO(" TAP RX total: %"PRIu64, stats.tap_rx); + LOG_INFO(" TAP RX PTP: %"PRIu64, stats.tap_rx_ptp); + LOG_INFO(" PHY TX: %"PRIu64, stats.phy_tx); + LOG_INFO(" Corrections: %"PRIu64, stats.corrections); +} + +/* Main relay loop */ + +static int +relay_loop(__rte_unused void *arg) +{ + uint64_t last_stats = rte_rdtsc(); + uint64_t stats_tsc = rte_get_tsc_hz() * stats_interval; + + LOG_INFO("Relay loop started on lcore %u", rte_lcore_id()); + LOG_INFO(" PHY port %u <--> TAP port %u", phy_port, tap_port); + LOG_INFO(" Correction field updates: enabled for event messages"); + + while (!force_quit) { + /* PHY → TAP */ + relay_burst(phy_port, tap_port, + &stats.phy_rx, &stats.phy_rx_ptp, + &stats.tap_tx, &stats.corrections); + + /* TAP → PHY */ + relay_burst(tap_port, phy_port, + &stats.tap_rx, &stats.tap_rx_ptp, + &stats.phy_tx, &stats.corrections); + + /* Periodic stats */ + if (rte_rdtsc() - last_stats > stats_tsc) { + print_stats(); + last_stats = rte_rdtsc(); + } + } + + print_stats(); + return 0; +} + +/* Argument parsing */ + +static void +usage(const char *prog) +{ + fprintf(stderr, + "Usage: %s [EAL options] -- [options]\n" + " -p PORT Physical NIC port ID (default: 0)\n" + " -t PORT TAP port ID (default: 1)\n" + " -T SECS Stats interval in seconds (default: 10)\n" + "\n" + "Example:\n" + " %s -l 0-1 --vdev=net_tap0,iface=dtap0 -- -p 0 -t 1\n" + "\n" + "Then run ptp4l with software timestamps:\n" + " ptp4l -i dtap0 -m -s -S\n", + prog, prog); +} + +static int +parse_args(int argc, char **argv) +{ + int opt; + + while ((opt = getopt(argc, argv, "p:t:T:h")) != -1) { + switch (opt) { + case 'p': + phy_port = (uint16_t)atoi(optarg); + break; + case 't': + tap_port = (uint16_t)atoi(optarg); + break; + case 'T': + stats_interval = (unsigned int)atoi(optarg); + break; + case 'h': + default: + usage(argv[0]); + return -1; + } + } + + return 0; +} + +/* Main */ + +int +main(int argc, char **argv) +{ + struct rte_mempool *mp; + uint16_t nb_ports; + int ret; + + /* EAL init */ + ret = rte_eal_init(argc, argv); + if (ret < 0) + rte_exit(EXIT_FAILURE, "EAL init failed\n"); + argc -= ret; + argv += ret; + + /* App args */ + ret = parse_args(argc, argv); + if (ret < 0) + rte_exit(EXIT_FAILURE, "Invalid arguments\n"); + + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + + nb_ports = rte_eth_dev_count_avail(); + if (nb_ports < 2) + rte_exit(EXIT_FAILURE, + "Need at least 2 ports (PHY + TAP).\n" + "Use --vdev=net_tap0,iface=dtap0\n"); + + if (!rte_eth_dev_is_valid_port(phy_port)) + rte_exit(EXIT_FAILURE, "Invalid PHY port %u\n", phy_port); + if (!rte_eth_dev_is_valid_port(tap_port)) + rte_exit(EXIT_FAILURE, "Invalid TAP port %u\n", tap_port); + + mp = rte_pktmbuf_pool_create("MBUF_POOL", NUM_MBUFS * nb_ports, + MBUF_CACHE, 0, + RTE_MBUF_DEFAULT_BUF_SIZE, + rte_socket_id()); + if (mp == NULL) + rte_exit(EXIT_FAILURE, "Cannot create mbuf pool\n"); + + LOG_INFO("Initializing PHY port %u...", phy_port); + ret = port_init(phy_port, mp); + if (ret != 0) + rte_exit(EXIT_FAILURE, "Cannot init PHY port %u (%d)\n", + phy_port, ret); + + LOG_INFO("Initializing TAP port %u...", tap_port); + ret = port_init(tap_port, mp); + if (ret != 0) + rte_exit(EXIT_FAILURE, "Cannot init TAP port %u (%d)\n", + tap_port, ret); + + LOG_INFO("PTP Software Relay ready"); + LOG_INFO(" PHY port: %u", phy_port); + LOG_INFO(" TAP port: %u", tap_port); + LOG_INFO(" Stats every: %u seconds", stats_interval); + LOG_INFO(" Correction: Transparent Clock (SW timestamps)"); + LOG_INFO(""); + LOG_INFO("Run ptp4l: ptp4l -i dtap0 -m -s -S"); + + /* Run relay on main lcore */ + relay_loop(NULL); + + /* Cleanup */ + LOG_INFO("Stopping ports..."); + rte_eth_dev_stop(phy_port); + rte_eth_dev_stop(tap_port); + rte_eth_dev_close(phy_port); + rte_eth_dev_close(tap_port); + rte_eal_cleanup(); + + return 0; +} -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [RFC v4 4/6] doc: add PTP software relay sample app guide 2026-05-05 16:38 ` [RFC v4 " Rajesh Kumar ` (2 preceding siblings ...) 2026-05-05 16:38 ` [RFC v4 3/6] examples/ptp_tap_relay_sw: add software PTP relay example Rajesh Kumar @ 2026-05-05 16:38 ` Rajesh Kumar 2026-05-05 16:38 ` [RFC v4 5/6] app/test: add PTP library unit tests Rajesh Kumar 2026-05-05 16:38 ` [RFC v4 6/6] examples/ptpclient: use shared PTP library definitions Rajesh Kumar 5 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-05 16:38 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, Rajesh Kumar [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #1: Type: text/plain; charset=y, Size: 8125 bytes --] Add a sample application user guide for the ptp_tap_relay_sw example. The guide covers the application topology, packet flow, compilation, command-line options, and a code walkthrough explaining the two-pass relay burst design and CLOCK_MONOTONIC_RAW timestamp source selection. Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- doc/guides/sample_app_ug/index.rst | 1 + doc/guides/sample_app_ug/ptp_tap_relay_sw.rst | 212 ++++++++++++++++++ 2 files changed, 213 insertions(+) create mode 100644 doc/guides/sample_app_ug/ptp_tap_relay_sw.rst diff --git a/doc/guides/sample_app_ug/index.rst b/doc/guides/sample_app_ug/index.rst index e895f692f9..f12623bb66 100644 --- a/doc/guides/sample_app_ug/index.rst +++ b/doc/guides/sample_app_ug/index.rst @@ -51,6 +51,7 @@ Sample Applications User Guides dist_app vm_power_management ptpclient + ptp_tap_relay_sw fips_validation ipsec_secgw bbdev_app diff --git a/doc/guides/sample_app_ug/ptp_tap_relay_sw.rst b/doc/guides/sample_app_ug/ptp_tap_relay_sw.rst new file mode 100644 index 0000000000..1af2601fdb --- /dev/null +++ b/doc/guides/sample_app_ug/ptp_tap_relay_sw.rst @@ -0,0 +1,212 @@ +.. SPDX-License-Identifier: BSD-3-Clause + Copyright(c) 2026 Intel Corporation. + +PTP Software Relay Sample Application +====================================== + +The PTP Software Relay sample application demonstrates how to use the +DPDK ``lib/ptp`` library to build a minimal PTP Transparent Clock relay +between a DPDK-bound physical NIC and a kernel TAP interface using +**software timestamps only**. + +No patched kernel modules or custom TAP PMD changes are required. +The application works with an unmodified Linux kernel and stock DPDK. + +For background on PTP see: +`Precision Time Protocol +<https://en.wikipedia.org/wiki/Precision_Time_Protocol>`_. + + +Limitations +----------- + +* Tested with L2 PTP (EtherType 0x88F7) on the wire. + The underlying PTP library also classifies VLAN/QinQ and UDP/IPv4/IPv6. +* Only PTP v2 messages are processed. +* Software timestamps have microsecond-class jitter; sub-microsecond + precision depends on system load and NIC-to-TAP forwarding latency. +* The PTP time transmitter must be reachable on the physical NIC's L2 network. +* Only one physical port and one TAP port are supported. + + +How the Application Works +------------------------- + +Topology +~~~~~~~~ + +:: + + PTP Time Transmitter Physical NIC TAP (kernel) + (ptp4l -H) ──L2── (DPDK vfio-pci) ────── dtap0 + │ │ + ptp_tap_relay_sw ptp4l -S + (correctionField += (SW timestamps, + residence time) adjusts CLOCK_REALTIME) + +The relay sits between a DPDK-owned physical NIC and a kernel TAP +virtual interface. ``ptp4l`` runs on the TAP interface in software +timestamp mode (``-S``) as a PTP time receiver. + +Packet Flow +~~~~~~~~~~~ + +1. The physical NIC receives PTP (and non-PTP) packets via DPDK RX. +2. A software RX timestamp is recorded using + ``clock_gettime(CLOCK_MONOTONIC)``. +3. Each packet is classified with ``rte_ptp_classify()``. +4. For PTP **event** messages (Sync, Delay_Req, PDelay_Req, PDelay_Resp), + a TX software timestamp is taken just before transmission. +5. The residence time (``tx_ts − rx_ts``) is added to the PTP + ``correctionField`` via ``rte_ptp_add_correction()`` — standard + IEEE 1588-2019 Transparent Clock behaviour (§10.2). +6. Packets are forwarded bidirectionally: + + * PHY → TAP (network → ptp4l) + * TAP → PHY (ptp4l → network) + +A two-pass design is used: first all packets are classified and PTP +header pointers saved, then a single TX timestamp is taken immediately +before applying corrections and calling ``rte_eth_tx_burst()``. +This minimises the gap between the measured timestamp and the actual +wire egress. + + +Compiling the Application +------------------------- + +To compile the sample application see :doc:`compiling`. + +The application is located in the ``ptp_tap_relay_sw`` sub-directory. + +.. note:: + + The application depends on the ``ptp`` library. + Ensure that ``lib/ptp`` is built (it is built by default). + + +Running the Application +----------------------- + +Prerequisites +~~~~~~~~~~~~~ + +* A PTP-capable physical NIC bound to DPDK (e.g. via ``vfio-pci``). +* ``linuxptp`` (``ptp4l``) installed on the system. +* A PTP time transmitter reachable on the same L2 network. + +Start the relay +~~~~~~~~~~~~~~~~ + +.. code-block:: console + + ./<build_dir>/examples/dpdk-ptp_tap_relay_sw \ + -l 18-19 -a 0000:cc:00.1 --vdev=net_tap0,iface=dtap0 -- \ + -p 0 -t 1 -T 10 + +Command-line Options +~~~~~~~~~~~~~~~~~~~~ + +* ``-p PORT`` — Physical NIC port ID (default: 0). +* ``-t PORT`` — TAP port ID (default: 1). +* ``-T SECS`` — Statistics print interval in seconds (default: 10). + +Start PTP time transmitter +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +On a separate terminal or remote host, start ``ptp4l`` as time +transmitter with hardware timestamps on the physical NIC: + +.. code-block:: console + + ptp4l -i <iface> -m -2 -H --serverOnly=1 \ + --logSyncInterval=-4 --logMinDelayReqInterval=-4 + +Start PTP time receiver +~~~~~~~~~~~~~~~~~~~~~~~ + +On the TAP interface, start ``ptp4l`` in software timestamp mode: + +.. code-block:: console + + ptp4l -i dtap0 -m -2 -s -S \ + --delay_filter=moving_median --delay_filter_length=10 + +The time receiver will enter UNCALIBRATED state for approximately 60 +seconds while the PI servo estimates the frequency offset, then step +the clock and enter time-receiver (synchronized) state. +Steady-state RMS offset of 500–1000 ns is typical on a lightly loaded +system with a hardware-timestamped time transmitter. + +Example Output +~~~~~~~~~~~~~~ + +Relay statistics printed every ``-T`` seconds: + +:: + + [PTP-SW] === Statistics === + [PTP-SW] PHY RX total: 5646 + [PTP-SW] PHY RX PTP: 5598 + [PTP-SW] TAP TX: 5646 + [PTP-SW] TAP RX total: 1800 + [PTP-SW] TAP RX PTP: 1788 + [PTP-SW] PHY TX: 1800 + [PTP-SW] Corrections: 3635 + +Time receiver ``ptp4l`` output after convergence: + +:: + + ptp4l[451534.520]: rms 630 max 1166 freq -44365 +/- 100 delay 37668 +/- 71 + ptp4l[451539.525]: rms 602 max 1177 freq -44339 +/- 119 delay 37517 +/- 43 + ptp4l[451544.530]: rms 535 max 1194 freq -44345 +/- 103 delay 37410 +/- 81 + + +Code Explanation +---------------- + +The following sections explain the main components of the application. + +Relay Burst Function +~~~~~~~~~~~~~~~~~~~~ + +The core relay logic is in ``relay_burst()``, which handles one direction +(PHY→TAP or TAP→PHY) per call: + +**Pass 1 — Classify:** + +For each received packet, ``rte_ptp_classify()`` determines if it is a +PTP message. For event messages, ``rte_ptp_hdr_get()`` retrieves a +pointer to the PTP header, which is saved for the second pass. + +**Pass 2 — Timestamp and correct:** + +A single software TX timestamp is taken via +``clock_gettime(CLOCK_MONOTONIC)``. The residence time +(``tx_ts − rx_ts``) is added to each saved PTP header's +``correctionField`` using ``rte_ptp_add_correction()``. +The burst is then transmitted with ``rte_eth_tx_burst()``. + +Main Loop +~~~~~~~~~ + +The ``relay_loop()`` function polls both directions in a tight loop: + +.. code-block:: c + + while (!force_quit) { + relay_burst(phy_port, tap_port, ...); /* PHY → TAP */ + relay_burst(tap_port, phy_port, ...); /* TAP → PHY */ + } + +Statistics are printed at the interval specified by ``-T``. + +Timestamp Source +~~~~~~~~~~~~~~~~ + +``CLOCK_MONOTONIC`` is used rather than ``CLOCK_REALTIME`` because +the PTP time receiver's servo continuously adjusts ``CLOCK_REALTIME``. +Using ``CLOCK_REALTIME`` would corrupt residence time measurements +during clock stepping or frequency slewing. ``CLOCK_MONOTONIC`` is +portable across Linux and FreeBSD. -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [RFC v4 5/6] app/test: add PTP library unit tests 2026-05-05 16:38 ` [RFC v4 " Rajesh Kumar ` (3 preceding siblings ...) 2026-05-05 16:38 ` [RFC v4 4/6] doc: add PTP software relay sample app guide Rajesh Kumar @ 2026-05-05 16:38 ` Rajesh Kumar 2026-05-05 16:38 ` [RFC v4 6/6] examples/ptpclient: use shared PTP library definitions Rajesh Kumar 5 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-05 16:38 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, Rajesh Kumar Add autotest for the PTP library covering: - Transport classification (L2, VLAN, QinQ, UDP/IPv4, UDP/IPv6) - Message type parsing and string conversion - Inline helpers (is_event, two_step, seq_id, version, domain) - Correction field arithmetic (add, accumulate, negative values) - Timestamp nanosecond conversion - Flag field extraction (TWO_STEP, UNICAST, LI_61, LI_59) Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- MAINTAINERS | 1 + app/test/meson.build | 1 + app/test/test_ptp.c | 1106 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1108 insertions(+) create mode 100644 app/test/test_ptp.c diff --git a/MAINTAINERS b/MAINTAINERS index 665e08dc90..8d509cbe8e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1668,6 +1668,7 @@ F: app/test-sad/ PTP - EXPERIMENTAL M: Rajesh Kumar <rajesh3.kumar@intel.com> F: lib/ptp/ +F: app/test/test_ptp.c F: doc/guides/prog_guide/ptp_lib.rst F: examples/ptp_tap_relay_sw/ F: doc/guides/sample_app_ug/ptp_tap_relay_sw.rst diff --git a/app/test/meson.build b/app/test/meson.build index 7d458f9c07..ecaa8d0839 100644 --- a/app/test/meson.build +++ b/app/test/meson.build @@ -154,6 +154,7 @@ source_file_deps = { 'test_power_kvm_vm.c': ['power', 'power_kvm_vm'], 'test_prefetch.c': [], 'test_ptr_compress.c': ['ptr_compress'], + 'test_ptp.c': ['ptp'], 'test_rand_perf.c': [], 'test_rawdev.c': ['rawdev', 'bus_vdev', 'raw_skeleton'], 'test_rcu_qsbr.c': ['rcu', 'hash'], diff --git a/app/test/test_ptp.c b/app/test/test_ptp.c new file mode 100644 index 0000000000..803951b1fa --- /dev/null +++ b/app/test/test_ptp.c @@ -0,0 +1,1106 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2026 Intel Corporation + */ + +#include "test.h" + +#include <string.h> +#include <stdio.h> +#include <stdint.h> +#include <inttypes.h> + +#include <rte_mbuf.h> +#include <rte_mempool.h> +#include <rte_memory.h> +#include <rte_ether.h> +#include <rte_ip.h> +#include <rte_udp.h> +#include <rte_byteorder.h> +#include <rte_ptp.h> + +#define PTP_TEST_MP_NAME "test_ptp_pool" +#define PTP_TEST_MP_SIZE 63 +#define PTP_TEST_BUF_SIZE RTE_MBUF_DEFAULT_BUF_SIZE + +static struct rte_mempool *ptp_mp; + +/* Helper: fill a minimal PTP header */ +static void +fill_ptp_hdr(struct rte_ptp_hdr *ptp, uint8_t msg_type, uint16_t flags_host, + int64_t correction_scaled_ns, uint16_t seq_id) +{ + memset(ptp, 0, sizeof(*ptp)); + ptp->msg_type = msg_type; + ptp->version = 0x02; + ptp->msg_length = rte_cpu_to_be_16(34); + ptp->flags = rte_cpu_to_be_16(flags_host); + ptp->correction = rte_cpu_to_be_64(correction_scaled_ns); + ptp->sequence_id = rte_cpu_to_be_16(seq_id); +} + +/* ================================================================ + * Packet builders + * ================================================================ + */ + +static struct rte_mbuf * +build_l2_ptp(uint8_t msg_type) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *) + rte_pktmbuf_append(m, sizeof(*eth) + sizeof(struct rte_ptp_hdr)); + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(0x88F7); + + struct rte_ptp_hdr *ptp = (struct rte_ptp_hdr *)((uint8_t *)eth + sizeof(*eth)); + fill_ptp_hdr(ptp, msg_type, RTE_PTP_FLAG_TWO_STEP, 0, 100); + return m; +} + +/* Build L2 PTP with no TWO_STEP flag */ +static struct rte_mbuf * +build_l2_ptp_noflags(uint8_t msg_type) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *) + rte_pktmbuf_append(m, sizeof(*eth) + sizeof(struct rte_ptp_hdr)); + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(0x88F7); + + struct rte_ptp_hdr *ptp = (struct rte_ptp_hdr *)((uint8_t *)eth + sizeof(*eth)); + fill_ptp_hdr(ptp, msg_type, 0, 0, 200); + return m; +} + +static struct rte_mbuf * +build_vlan_l2_ptp(uint8_t msg_type, uint16_t tpid) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + sizeof(struct rte_vlan_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(tpid); + + struct rte_vlan_hdr *vlan = (struct rte_vlan_hdr *)(data + sizeof(*eth)); + vlan->vlan_tci = rte_cpu_to_be_16(100); + vlan->eth_proto = rte_cpu_to_be_16(0x88F7); + + struct rte_ptp_hdr *ptp = (struct rte_ptp_hdr *) + (data + sizeof(*eth) + sizeof(*vlan)); + fill_ptp_hdr(ptp, msg_type, 0, 0, 200); + return m; +} + +static struct rte_mbuf * +build_qinq_l2_ptp(uint8_t msg_type, uint16_t outer_tpid, uint16_t inner_tpid) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + 2 * sizeof(struct rte_vlan_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(outer_tpid); + + uint32_t off = sizeof(*eth); + struct rte_vlan_hdr *vo = (struct rte_vlan_hdr *)(data + off); + vo->vlan_tci = rte_cpu_to_be_16(200); + vo->eth_proto = rte_cpu_to_be_16(inner_tpid); + off += sizeof(*vo); + + struct rte_vlan_hdr *vi = (struct rte_vlan_hdr *)(data + off); + vi->vlan_tci = rte_cpu_to_be_16(300); + vi->eth_proto = rte_cpu_to_be_16(0x88F7); + off += sizeof(*vi); + + struct rte_ptp_hdr *ptp = (struct rte_ptp_hdr *)(data + off); + fill_ptp_hdr(ptp, msg_type, 0, 0, 300); + return m; +} + +/* Helper: append IPv4 + UDP + PTP after offset */ +static void +fill_ipv4_udp_ptp(uint8_t *data, uint32_t off, uint8_t msg_type, + uint16_t dst_port, uint16_t seq_id) +{ + struct rte_ipv4_hdr *iph = (struct rte_ipv4_hdr *)(data + off); + memset(iph, 0, sizeof(*iph)); + iph->version_ihl = 0x45; + iph->next_proto_id = IPPROTO_UDP; + iph->total_length = rte_cpu_to_be_16( + sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr)); + iph->src_addr = rte_cpu_to_be_32(0x0A000001); + iph->dst_addr = rte_cpu_to_be_32(0xE0000181); + off += sizeof(*iph); + + struct rte_udp_hdr *udp = (struct rte_udp_hdr *)(data + off); + memset(udp, 0, sizeof(*udp)); + udp->src_port = rte_cpu_to_be_16(12345); + udp->dst_port = rte_cpu_to_be_16(dst_port); + udp->dgram_len = rte_cpu_to_be_16(sizeof(*udp) + + sizeof(struct rte_ptp_hdr)); + off += sizeof(*udp); + + struct rte_ptp_hdr *ptp = (struct rte_ptp_hdr *)(data + off); + fill_ptp_hdr(ptp, msg_type, 0, 0, seq_id); +} + +static struct rte_mbuf * +build_ipv4_udp_ptp(uint8_t msg_type, uint16_t dst_port) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + sizeof(struct rte_ipv4_hdr) + + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4); + + fill_ipv4_udp_ptp(data, sizeof(*eth), msg_type, dst_port, 400); + return m; +} + +static struct rte_mbuf * +build_vlan_ipv4_udp_ptp(uint8_t msg_type, uint16_t dst_port) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + sizeof(struct rte_vlan_hdr) + + sizeof(struct rte_ipv4_hdr) + + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN); + + uint32_t off = sizeof(*eth); + struct rte_vlan_hdr *vlan = (struct rte_vlan_hdr *)(data + off); + vlan->vlan_tci = rte_cpu_to_be_16(100); + vlan->eth_proto = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4); + off += sizeof(*vlan); + + fill_ipv4_udp_ptp(data, off, msg_type, dst_port, 500); + return m; +} + +static struct rte_mbuf * +build_qinq_ipv4_udp_ptp(uint8_t msg_type, uint16_t dst_port) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + 2 * sizeof(struct rte_vlan_hdr) + + sizeof(struct rte_ipv4_hdr) + + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_QINQ); + + uint32_t off = sizeof(*eth); + struct rte_vlan_hdr *vo = (struct rte_vlan_hdr *)(data + off); + vo->vlan_tci = rte_cpu_to_be_16(200); + vo->eth_proto = rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN); + off += sizeof(*vo); + + struct rte_vlan_hdr *vi = (struct rte_vlan_hdr *)(data + off); + vi->vlan_tci = rte_cpu_to_be_16(300); + vi->eth_proto = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4); + off += sizeof(*vi); + + fill_ipv4_udp_ptp(data, off, msg_type, dst_port, 600); + return m; +} + +/* Helper: append IPv6 + UDP + PTP */ +static void +fill_ipv6_udp_ptp(uint8_t *data, uint32_t off, uint8_t msg_type, + uint16_t dst_port, uint16_t seq_id) +{ + struct rte_ipv6_hdr *ip6 = (struct rte_ipv6_hdr *)(data + off); + memset(ip6, 0, sizeof(*ip6)); + ip6->vtc_flow = rte_cpu_to_be_32(0x60000000); + ip6->payload_len = rte_cpu_to_be_16( + sizeof(struct rte_udp_hdr) + sizeof(struct rte_ptp_hdr)); + ip6->proto = IPPROTO_UDP; + ip6->hop_limits = 64; + off += sizeof(*ip6); + + struct rte_udp_hdr *udp = (struct rte_udp_hdr *)(data + off); + memset(udp, 0, sizeof(*udp)); + udp->src_port = rte_cpu_to_be_16(12345); + udp->dst_port = rte_cpu_to_be_16(dst_port); + udp->dgram_len = rte_cpu_to_be_16(sizeof(*udp) + + sizeof(struct rte_ptp_hdr)); + off += sizeof(*udp); + + struct rte_ptp_hdr *ptp = (struct rte_ptp_hdr *)(data + off); + fill_ptp_hdr(ptp, msg_type, 0, 0, seq_id); +} + +static struct rte_mbuf * +build_ipv6_udp_ptp(uint8_t msg_type, uint16_t dst_port) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + sizeof(struct rte_ipv6_hdr) + + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV6); + + fill_ipv6_udp_ptp(data, sizeof(*eth), msg_type, dst_port, 700); + return m; +} + +static struct rte_mbuf * +build_vlan_ipv6_udp_ptp(uint8_t msg_type, uint16_t dst_port) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + sizeof(struct rte_vlan_hdr) + + sizeof(struct rte_ipv6_hdr) + + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN); + + uint32_t off = sizeof(*eth); + struct rte_vlan_hdr *vlan = (struct rte_vlan_hdr *)(data + off); + vlan->vlan_tci = rte_cpu_to_be_16(100); + vlan->eth_proto = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV6); + off += sizeof(*vlan); + + fill_ipv6_udp_ptp(data, off, msg_type, dst_port, 800); + return m; +} + +static struct rte_mbuf * +build_qinq_ipv6_udp_ptp(uint8_t msg_type, uint16_t dst_port) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + 2 * sizeof(struct rte_vlan_hdr) + + sizeof(struct rte_ipv6_hdr) + + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_QINQ); + + uint32_t off = sizeof(*eth); + struct rte_vlan_hdr *vo = (struct rte_vlan_hdr *)(data + off); + vo->vlan_tci = rte_cpu_to_be_16(200); + vo->eth_proto = rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN); + off += sizeof(*vo); + + struct rte_vlan_hdr *vi = (struct rte_vlan_hdr *)(data + off); + vi->vlan_tci = rte_cpu_to_be_16(300); + vi->eth_proto = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV6); + off += sizeof(*vi); + + fill_ipv6_udp_ptp(data, off, msg_type, dst_port, 900); + return m; +} + +static struct rte_mbuf * +build_non_ptp(void) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + 28; + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(data, 0, pkt_len); + eth->ether_type = rte_cpu_to_be_16(0x0806); + return m; +} + +static struct rte_mbuf * +build_ipv4_udp_non_ptp(void) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + sizeof(struct rte_ipv4_hdr) + + sizeof(struct rte_udp_hdr) + 20; + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(data, 0, pkt_len); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4); + + struct rte_ipv4_hdr *iph = (struct rte_ipv4_hdr *)(data + sizeof(*eth)); + iph->version_ihl = 0x45; + iph->next_proto_id = IPPROTO_UDP; + iph->total_length = rte_cpu_to_be_16( + sizeof(*iph) + sizeof(struct rte_udp_hdr) + 20); + + struct rte_udp_hdr *udp = (struct rte_udp_hdr *) + (data + sizeof(*eth) + sizeof(*iph)); + udp->dst_port = rte_cpu_to_be_16(53); + return m; +} + +/* ================================================================ + * Individual test cases + * ================================================================ + */ + +/* Helper: classify + hdr_get for a given mbuf */ +static int +check_classify_and_hdr_get(struct rte_mbuf *m, int expected_type) +{ + int ret; + + TEST_ASSERT_NOT_NULL(m, "mbuf allocation failed"); + + ret = rte_ptp_classify(m); + TEST_ASSERT_EQUAL(ret, expected_type, + "classify: expected %d, got %d", expected_type, ret); + + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + if (expected_type == RTE_PTP_MSGTYPE_INVALID) { + TEST_ASSERT_NULL(hdr, + "hdr_get: expected NULL for non-PTP packet"); + } else { + TEST_ASSERT_NOT_NULL(hdr, + "hdr_get: expected non-NULL for PTP packet"); + TEST_ASSERT_EQUAL(rte_ptp_msg_type(hdr), + (uint8_t)expected_type, + "hdr_get: msg_type mismatch: expected %d, got %d", + expected_type, rte_ptp_msg_type(hdr)); + } + + rte_pktmbuf_free(m); + return TEST_SUCCESS; +} + +/* Section 1: Transport classification */ +static int +test_ptp_classify_l2(void) +{ + return check_classify_and_hdr_get( + build_l2_ptp(RTE_PTP_MSGTYPE_SYNC), RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_l2_delay_req(void) +{ + return check_classify_and_hdr_get( + build_l2_ptp(RTE_PTP_MSGTYPE_DELAY_REQ), + RTE_PTP_MSGTYPE_DELAY_REQ); +} + +static int +test_ptp_classify_vlan_8100(void) +{ + return check_classify_and_hdr_get( + build_vlan_l2_ptp(RTE_PTP_MSGTYPE_SYNC, RTE_ETHER_TYPE_VLAN), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_vlan_88a8(void) +{ + return check_classify_and_hdr_get( + build_vlan_l2_ptp(RTE_PTP_MSGTYPE_SYNC, RTE_ETHER_TYPE_QINQ), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_qinq(void) +{ + return check_classify_and_hdr_get( + build_qinq_l2_ptp(RTE_PTP_MSGTYPE_SYNC, + RTE_ETHER_TYPE_QINQ, RTE_ETHER_TYPE_VLAN), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_double_8100(void) +{ + return check_classify_and_hdr_get( + build_qinq_l2_ptp(RTE_PTP_MSGTYPE_SYNC, + RTE_ETHER_TYPE_VLAN, RTE_ETHER_TYPE_VLAN), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_ipv4_udp_319(void) +{ + return check_classify_and_hdr_get( + build_ipv4_udp_ptp(RTE_PTP_MSGTYPE_SYNC, 319), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_ipv4_udp_320(void) +{ + return check_classify_and_hdr_get( + build_ipv4_udp_ptp(RTE_PTP_MSGTYPE_FOLLOW_UP, 320), + RTE_PTP_MSGTYPE_FOLLOW_UP); +} + +static int +test_ptp_classify_vlan_ipv4_udp(void) +{ + return check_classify_and_hdr_get( + build_vlan_ipv4_udp_ptp(RTE_PTP_MSGTYPE_SYNC, 319), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_qinq_ipv4_udp(void) +{ + return check_classify_and_hdr_get( + build_qinq_ipv4_udp_ptp(RTE_PTP_MSGTYPE_SYNC, 319), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_ipv6_udp_319(void) +{ + return check_classify_and_hdr_get( + build_ipv6_udp_ptp(RTE_PTP_MSGTYPE_SYNC, 319), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_ipv6_udp_320(void) +{ + return check_classify_and_hdr_get( + build_ipv6_udp_ptp(RTE_PTP_MSGTYPE_FOLLOW_UP, 320), + RTE_PTP_MSGTYPE_FOLLOW_UP); +} + +static int +test_ptp_classify_vlan_ipv6_udp(void) +{ + return check_classify_and_hdr_get( + build_vlan_ipv6_udp_ptp(RTE_PTP_MSGTYPE_SYNC, 319), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_qinq_ipv6_udp(void) +{ + return check_classify_and_hdr_get( + build_qinq_ipv6_udp_ptp(RTE_PTP_MSGTYPE_SYNC, 319), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_non_ptp_arp(void) +{ + return check_classify_and_hdr_get( + build_non_ptp(), RTE_PTP_MSGTYPE_INVALID); +} + +static int +test_ptp_classify_non_ptp_udp(void) +{ + return check_classify_and_hdr_get( + build_ipv4_udp_non_ptp(), RTE_PTP_MSGTYPE_INVALID); +} + +/* IPv4 with invalid IHL (< 5) should be rejected */ +static int +test_ptp_classify_ipv4_bad_ihl(void) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + sizeof(struct rte_ipv4_hdr) + + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4); + + fill_ipv4_udp_ptp(data, sizeof(*eth), RTE_PTP_MSGTYPE_SYNC, 319, 999); + + /* Corrupt the IHL to 3 (< minimum 5) */ + struct rte_ipv4_hdr *iph = (struct rte_ipv4_hdr *) + (data + sizeof(*eth)); + iph->version_ihl = 0x43; + + return check_classify_and_hdr_get(m, RTE_PTP_MSGTYPE_INVALID); +} + +/* IPv4 with options (IHL > 5) should still be parsed correctly */ +static int +test_ptp_classify_ipv4_options(void) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + uint32_t pkt_len; + uint32_t off; + uint8_t *data; + struct rte_ether_hdr *eth; + struct rte_ipv4_hdr *iph; + struct rte_udp_hdr *udp; + struct rte_ptp_hdr *ptp; + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + + pkt_len = sizeof(struct rte_ether_hdr) + + sizeof(struct rte_ipv4_hdr) + 4 + + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr); + data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + TEST_ASSERT_NOT_NULL(data, "append failed"); + + eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4); + + off = sizeof(*eth); + iph = (struct rte_ipv4_hdr *)(data + off); + memset(iph, 0, sizeof(*iph)); + iph->version_ihl = 0x46; /* IHL = 6 words = 24 bytes */ + iph->next_proto_id = IPPROTO_UDP; + iph->total_length = rte_cpu_to_be_16(sizeof(struct rte_ipv4_hdr) + 4 + + sizeof(struct rte_udp_hdr) + sizeof(struct rte_ptp_hdr)); + iph->src_addr = rte_cpu_to_be_32(0x0A000001); + iph->dst_addr = rte_cpu_to_be_32(0xE0000181); + off += sizeof(*iph); + + memset(data + off, 0, 4); /* IPv4 options bytes */ + off += 4; + + udp = (struct rte_udp_hdr *)(data + off); + memset(udp, 0, sizeof(*udp)); + udp->src_port = rte_cpu_to_be_16(12345); + udp->dst_port = rte_cpu_to_be_16(RTE_PTP_EVENT_PORT); + udp->dgram_len = rte_cpu_to_be_16(sizeof(*udp) + sizeof(struct rte_ptp_hdr)); + off += sizeof(*udp); + + ptp = (struct rte_ptp_hdr *)(data + off); + fill_ptp_hdr(ptp, RTE_PTP_MSGTYPE_SYNC, 0, 0, 1001); + + return check_classify_and_hdr_get(m, RTE_PTP_MSGTYPE_SYNC); +} + +/* Truncated packet: Ethernet header only, no payload */ +static int +test_ptp_classify_truncated(void) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, + sizeof(struct rte_ether_hdr)); + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(0x88F7); + + return check_classify_and_hdr_get(m, RTE_PTP_MSGTYPE_INVALID); +} + +/* Section 2: All 10 message types via L2 */ +static int +test_ptp_all_msg_types(void) +{ + static const uint8_t types[] = { + RTE_PTP_MSGTYPE_SYNC, + RTE_PTP_MSGTYPE_DELAY_REQ, + RTE_PTP_MSGTYPE_PDELAY_REQ, + RTE_PTP_MSGTYPE_PDELAY_RESP, + RTE_PTP_MSGTYPE_FOLLOW_UP, + RTE_PTP_MSGTYPE_DELAY_RESP, + RTE_PTP_MSGTYPE_PDELAY_RESP_FU, + RTE_PTP_MSGTYPE_ANNOUNCE, + RTE_PTP_MSGTYPE_SIGNALING, + RTE_PTP_MSGTYPE_MANAGEMENT, + }; + unsigned int i; + + for (i = 0; i < RTE_DIM(types); i++) { + int ret = check_classify_and_hdr_get( + build_l2_ptp(types[i]), types[i]); + if (ret != TEST_SUCCESS) + return ret; + } + + return TEST_SUCCESS; +} + +/* Section 3: Inline helpers */ +static int +test_ptp_is_event(void) +{ + TEST_ASSERT(rte_ptp_is_event(RTE_PTP_MSGTYPE_SYNC), + "Sync should be event"); + TEST_ASSERT(rte_ptp_is_event(RTE_PTP_MSGTYPE_DELAY_REQ), + "Delay_Req should be event"); + TEST_ASSERT(rte_ptp_is_event(RTE_PTP_MSGTYPE_PDELAY_REQ), + "Pdelay_Req should be event"); + TEST_ASSERT(rte_ptp_is_event(RTE_PTP_MSGTYPE_PDELAY_RESP), + "Pdelay_Resp should be event"); + TEST_ASSERT(!rte_ptp_is_event(RTE_PTP_MSGTYPE_FOLLOW_UP), + "Follow_Up should not be event"); + TEST_ASSERT(!rte_ptp_is_event(RTE_PTP_MSGTYPE_ANNOUNCE), + "Announce should not be event"); + TEST_ASSERT(!rte_ptp_is_event(RTE_PTP_MSGTYPE_INVALID), + "INVALID (-1) should not be event"); + + return TEST_SUCCESS; +} + +static int +test_ptp_two_step(void) +{ + struct rte_mbuf *m; + struct rte_ptp_hdr *hdr; + + m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + TEST_ASSERT(rte_ptp_is_two_step(hdr), + "TWO_STEP flag should be set"); + rte_pktmbuf_free(m); + + m = build_l2_ptp_noflags(RTE_PTP_MSGTYPE_SYNC); + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + TEST_ASSERT(!rte_ptp_is_two_step(hdr), + "TWO_STEP flag should not be set"); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_seq_id(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + TEST_ASSERT_EQUAL(rte_ptp_seq_id(hdr), 100, + "seq_id: expected 100, got %u", rte_ptp_seq_id(hdr)); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_version(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + TEST_ASSERT_EQUAL(rte_ptp_version(hdr), 2, + "version: expected 2, got %u", rte_ptp_version(hdr)); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_domain(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + TEST_ASSERT_EQUAL(rte_ptp_domain(hdr), 0, + "domain: expected 0, got %u", rte_ptp_domain(hdr)); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +/* Section 4: correctionField */ +static int +test_ptp_correction_zero(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + int64_t ns = rte_ptp_correction_ns(hdr); + TEST_ASSERT_EQUAL(ns, 0, + "correction_ns: expected 0, got %" PRId64, ns); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_correction_known(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *ptp = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(ptp, "hdr_get failed"); + int64_t scaled_1000 = (int64_t)1000 << 16; + fill_ptp_hdr(ptp, RTE_PTP_MSGTYPE_SYNC, 0, scaled_1000, 0); + + int64_t ns = rte_ptp_correction_ns(ptp); + TEST_ASSERT_EQUAL(ns, 1000, + "correction_ns: expected 1000, got %" PRId64, ns); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_add_correction(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + + rte_ptp_add_correction(hdr, 500); + int64_t ns = rte_ptp_correction_ns(hdr); + TEST_ASSERT_EQUAL(ns, 500, + "add 500: expected 500, got %" PRId64, ns); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_add_correction_accumulate(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + + rte_ptp_add_correction(hdr, 300); + rte_ptp_add_correction(hdr, 700); + int64_t ns = rte_ptp_correction_ns(hdr); + TEST_ASSERT_EQUAL(ns, 1000, + "accumulate: expected 1000, got %" PRId64, ns); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_add_correction_large(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + + rte_ptp_add_correction(hdr, 1000000000LL); + int64_t ns = rte_ptp_correction_ns(hdr); + TEST_ASSERT_EQUAL(ns, 1000000000LL, + "1s: expected 1000000000, got %" PRId64, ns); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_add_correction_negative(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + + rte_ptp_add_correction(hdr, -100LL); + int64_t ns = rte_ptp_correction_ns(hdr); + TEST_ASSERT_EQUAL(ns, -100LL, + "negative: expected -100, got %" PRId64, ns); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +/* Section 5: Timestamp conversion */ +static int +test_ptp_timestamp_to_ns(void) +{ + struct rte_ptp_timestamp ts; + uint64_t ns; + + /* Zero */ + memset(&ts, 0, sizeof(ts)); + ns = rte_ptp_timestamp_to_ns(&ts); + TEST_ASSERT_EQUAL(ns, 0ULL, + "zero: expected 0, got %" PRIu64, ns); + + /* 1 second */ + ts.seconds_hi = 0; + ts.seconds_lo = rte_cpu_to_be_32(1); + ts.nanoseconds = 0; + ns = rte_ptp_timestamp_to_ns(&ts); + TEST_ASSERT_EQUAL(ns, 1000000000ULL, + "1s: expected 1000000000, got %" PRIu64, ns); + + /* 1.5 seconds */ + ts.seconds_lo = rte_cpu_to_be_32(1); + ts.nanoseconds = rte_cpu_to_be_32(500000000); + ns = rte_ptp_timestamp_to_ns(&ts); + TEST_ASSERT_EQUAL(ns, 1500000000ULL, + "1.5s: expected 1500000000, got %" PRIu64, ns); + + /* Large value with seconds_hi */ + ts.seconds_hi = rte_cpu_to_be_16(1); + ts.seconds_lo = 0; + ts.nanoseconds = 0; + ns = rte_ptp_timestamp_to_ns(&ts); + uint64_t expected = ((uint64_t)1 << 32) * 1000000000ULL; + TEST_ASSERT_EQUAL(ns, expected, + "2^32s: expected %" PRIu64 ", got %" PRIu64, expected, ns); + + return TEST_SUCCESS; +} + +/* Section 6: msg_type_str */ +static int +test_ptp_msg_type_str(void) +{ + static const struct { + int type; + const char *expected; + } cases[] = { + { RTE_PTP_MSGTYPE_SYNC, "Sync" }, + { RTE_PTP_MSGTYPE_DELAY_REQ, "Delay_Req" }, + { RTE_PTP_MSGTYPE_PDELAY_REQ, "PDelay_Req" }, + { RTE_PTP_MSGTYPE_PDELAY_RESP, "PDelay_Resp" }, + { RTE_PTP_MSGTYPE_FOLLOW_UP, "Follow_Up" }, + { RTE_PTP_MSGTYPE_DELAY_RESP, "Delay_Resp" }, + { RTE_PTP_MSGTYPE_PDELAY_RESP_FU, "PDelay_Resp_Follow_Up" }, + { RTE_PTP_MSGTYPE_ANNOUNCE, "Announce" }, + { RTE_PTP_MSGTYPE_SIGNALING, "Signaling" }, + { RTE_PTP_MSGTYPE_MANAGEMENT, "Management" }, + }; + unsigned int i; + + for (i = 0; i < RTE_DIM(cases); i++) { + const char *str = rte_ptp_msg_type_str(cases[i].type); + TEST_ASSERT_NOT_NULL(str, + "msg_type_str(%d) returned NULL", cases[i].type); + TEST_ASSERT(strcmp(str, cases[i].expected) == 0, + "msg_type_str(%d): expected \"%s\", got \"%s\"", + cases[i].type, cases[i].expected, str); + } + + /* Invalid type should still return non-NULL */ + const char *inv = rte_ptp_msg_type_str(RTE_PTP_MSGTYPE_INVALID); + TEST_ASSERT_NOT_NULL(inv, + "msg_type_str(INVALID) returned NULL"); + + return TEST_SUCCESS; +} + +/* Section 7: Flag bit positions */ +static int +test_ptp_flags(void) +{ + struct rte_ptp_hdr hdr; + uint16_t f; + + /* TWO_STEP */ + memset(&hdr, 0, sizeof(hdr)); + hdr.flags = rte_cpu_to_be_16(RTE_PTP_FLAG_TWO_STEP); + f = rte_be_to_cpu_16(hdr.flags); + TEST_ASSERT(f & RTE_PTP_FLAG_TWO_STEP, + "TWO_STEP bit not set: 0x%04x", f); + TEST_ASSERT(rte_ptp_is_two_step(&hdr), + "is_two_step() should return true"); + + /* UNICAST */ + memset(&hdr, 0, sizeof(hdr)); + hdr.flags = rte_cpu_to_be_16(RTE_PTP_FLAG_UNICAST); + f = rte_be_to_cpu_16(hdr.flags); + TEST_ASSERT(f & RTE_PTP_FLAG_UNICAST, + "UNICAST bit not set: 0x%04x", f); + + /* LI_61 */ + memset(&hdr, 0, sizeof(hdr)); + hdr.flags = rte_cpu_to_be_16(RTE_PTP_FLAG_LI_61); + f = rte_be_to_cpu_16(hdr.flags); + TEST_ASSERT(f & RTE_PTP_FLAG_LI_61, + "LI_61 bit not set: 0x%04x", f); + + /* LI_59 */ + memset(&hdr, 0, sizeof(hdr)); + hdr.flags = rte_cpu_to_be_16(RTE_PTP_FLAG_LI_59); + f = rte_be_to_cpu_16(hdr.flags); + TEST_ASSERT(f & RTE_PTP_FLAG_LI_59, + "LI_59 bit not set: 0x%04x", f); + + /* Combined TWO_STEP + UNICAST */ + memset(&hdr, 0, sizeof(hdr)); + hdr.flags = rte_cpu_to_be_16( + RTE_PTP_FLAG_TWO_STEP | RTE_PTP_FLAG_UNICAST); + f = rte_be_to_cpu_16(hdr.flags); + TEST_ASSERT((f & RTE_PTP_FLAG_TWO_STEP) && + (f & RTE_PTP_FLAG_UNICAST) && + !(f & RTE_PTP_FLAG_LI_61) && + !(f & RTE_PTP_FLAG_LI_59), + "combined flags incorrect: 0x%04x", f); + + return TEST_SUCCESS; +} + +/* ================================================================ + * Suite setup / teardown + * ================================================================ + */ + +static int +test_ptp_setup(void) +{ + ptp_mp = rte_pktmbuf_pool_create(PTP_TEST_MP_NAME, PTP_TEST_MP_SIZE, + 0, 0, PTP_TEST_BUF_SIZE, SOCKET_ID_ANY); + if (ptp_mp == NULL) { + printf("Cannot create ptp test mempool\n"); + return TEST_FAILED; + } + return TEST_SUCCESS; +} + +static void +test_ptp_teardown(void) +{ + rte_mempool_free(ptp_mp); + ptp_mp = NULL; +} + +static struct unit_test_suite ptp_test_suite = { + .suite_name = "PTP Library Unit Tests", + .setup = test_ptp_setup, + .teardown = test_ptp_teardown, + .unit_test_cases = { + /* Transport classification */ + TEST_CASE(test_ptp_classify_l2), + TEST_CASE(test_ptp_classify_l2_delay_req), + TEST_CASE(test_ptp_classify_vlan_8100), + TEST_CASE(test_ptp_classify_vlan_88a8), + TEST_CASE(test_ptp_classify_qinq), + TEST_CASE(test_ptp_classify_double_8100), + TEST_CASE(test_ptp_classify_ipv4_udp_319), + TEST_CASE(test_ptp_classify_ipv4_udp_320), + TEST_CASE(test_ptp_classify_vlan_ipv4_udp), + TEST_CASE(test_ptp_classify_qinq_ipv4_udp), + TEST_CASE(test_ptp_classify_ipv6_udp_319), + TEST_CASE(test_ptp_classify_ipv6_udp_320), + TEST_CASE(test_ptp_classify_vlan_ipv6_udp), + TEST_CASE(test_ptp_classify_qinq_ipv6_udp), + TEST_CASE(test_ptp_classify_non_ptp_arp), + TEST_CASE(test_ptp_classify_non_ptp_udp), + TEST_CASE(test_ptp_classify_ipv4_bad_ihl), + TEST_CASE(test_ptp_classify_ipv4_options), + TEST_CASE(test_ptp_classify_truncated), + + /* All message types */ + TEST_CASE(test_ptp_all_msg_types), + + /* Inline helpers */ + TEST_CASE(test_ptp_is_event), + TEST_CASE(test_ptp_two_step), + TEST_CASE(test_ptp_seq_id), + TEST_CASE(test_ptp_version), + TEST_CASE(test_ptp_domain), + + /* correctionField */ + TEST_CASE(test_ptp_correction_zero), + TEST_CASE(test_ptp_correction_known), + TEST_CASE(test_ptp_add_correction), + TEST_CASE(test_ptp_add_correction_accumulate), + TEST_CASE(test_ptp_add_correction_large), + TEST_CASE(test_ptp_add_correction_negative), + + /* Timestamp conversion */ + TEST_CASE(test_ptp_timestamp_to_ns), + + /* msg_type_str */ + TEST_CASE(test_ptp_msg_type_str), + + /* Flag field bit positions */ + TEST_CASE(test_ptp_flags), + + TEST_CASES_END() + }, +}; + +static int +test_ptp(void) +{ + return unit_test_suite_runner(&ptp_test_suite); +} + +REGISTER_FAST_TEST(ptp_autotest, NOHUGE_SKIP, ASAN_OK, test_ptp); -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [RFC v4 6/6] examples/ptpclient: use shared PTP library definitions 2026-05-05 16:38 ` [RFC v4 " Rajesh Kumar ` (4 preceding siblings ...) 2026-05-05 16:38 ` [RFC v4 5/6] app/test: add PTP library unit tests Rajesh Kumar @ 2026-05-05 16:38 ` Rajesh Kumar 5 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-05 16:38 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, Rajesh Kumar Replace local PTP struct and constant definitions with the shared rte_ptp.h library types introduced earlier in this series: - struct ptp_header -> struct rte_ptp_hdr - struct tstamp -> struct rte_ptp_timestamp - struct clock_id -> uint8_t clock_id[8] (from rte_ptp_port_id) - SYNC/FOLLOW_UP/DELAY_* defines -> RTE_PTP_MSGTYPE_* constants - PTP_PROTOCOL -> RTE_PTP_ETHERTYPE - hardcoded multicast -> RTE_PTP_MULTICAST_MAC - ptp_hdr->msg_type switch -> rte_ptp_msg_type() accessor No functional change. Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- examples/ptpclient/meson.build | 1 + examples/ptpclient/ptpclient.c | 188 +++++++++++++-------------------- 2 files changed, 73 insertions(+), 116 deletions(-) diff --git a/examples/ptpclient/meson.build b/examples/ptpclient/meson.build index 2e9b7625fc..ab074c9cb1 100644 --- a/examples/ptpclient/meson.build +++ b/examples/ptpclient/meson.build @@ -7,6 +7,7 @@ # DPDK instance, use 'make' allow_experimental_apis = true +deps += ['ptp'] sources = files( 'ptpclient.c', ) diff --git a/examples/ptpclient/ptpclient.c b/examples/ptpclient/ptpclient.c index 174ca5dd70..ee93f511d7 100644 --- a/examples/ptpclient/ptpclient.c +++ b/examples/ptpclient/ptpclient.c @@ -17,6 +17,7 @@ #include <rte_lcore.h> #include <rte_mbuf.h> #include <rte_ip.h> +#include <rte_ptp.h> #include <limits.h> #include <sys/time.h> #include <getopt.h> @@ -30,21 +31,8 @@ static volatile bool force_quit; #define NUM_MBUFS 8191 #define MBUF_CACHE_SIZE 250 -/* Values for the PTP messageType field. */ -#define SYNC 0x0 -#define DELAY_REQ 0x1 -#define PDELAY_REQ 0x2 -#define PDELAY_RESP 0x3 -#define FOLLOW_UP 0x8 -#define DELAY_RESP 0x9 -#define PDELAY_RESP_FOLLOW_UP 0xA -#define ANNOUNCE 0xB -#define SIGNALING 0xC -#define MANAGEMENT 0xD - #define NSEC_PER_SEC 1000000000L #define KERNEL_TIME_ADJUST_LIMIT 20000 -#define PTP_PROTOCOL 0x88F7 struct rte_mempool *mbuf_pool; uint32_t ptp_enabled_port_mask; @@ -52,66 +40,36 @@ uint8_t ptp_enabled_port_nb; static uint8_t ptp_enabled_ports[RTE_MAX_ETHPORTS]; static const struct rte_ether_addr ether_multicast = { - .addr_bytes = {0x01, 0x1b, 0x19, 0x0, 0x0, 0x0} -}; - -/* Structs used for PTP handling. */ -struct __rte_packed_begin tstamp { - uint16_t sec_msb; - uint32_t sec_lsb; - uint32_t ns; -} __rte_packed_end; - -struct clock_id { - uint8_t id[8]; + .addr_bytes = RTE_PTP_MULTICAST_MAC }; -struct __rte_packed_begin port_id { - struct clock_id clock_id; - uint16_t port_number; -} __rte_packed_end; - -struct __rte_packed_begin ptp_header { - uint8_t msg_type; - uint8_t ver; - uint16_t message_length; - uint8_t domain_number; - uint8_t reserved1; - uint8_t flag_field[2]; - int64_t correction; - uint32_t reserved2; - struct port_id source_port_id; - uint16_t seq_id; - uint8_t control; - int8_t log_message_interval; -} __rte_packed_end; - +/* Message body structs using the library PTP header and timestamp. */ struct __rte_packed_begin sync_msg { - struct ptp_header hdr; - struct tstamp origin_tstamp; + struct rte_ptp_hdr hdr; + struct rte_ptp_timestamp origin_tstamp; } __rte_packed_end; struct __rte_packed_begin follow_up_msg { - struct ptp_header hdr; - struct tstamp precise_origin_tstamp; - uint8_t suffix[]; + struct rte_ptp_hdr hdr; + struct rte_ptp_timestamp precise_origin_tstamp; + uint8_t suffix[]; } __rte_packed_end; struct __rte_packed_begin delay_req_msg { - struct ptp_header hdr; - struct tstamp origin_tstamp; + struct rte_ptp_hdr hdr; + struct rte_ptp_timestamp origin_tstamp; } __rte_packed_end; struct __rte_packed_begin delay_resp_msg { - struct ptp_header hdr; - struct tstamp rx_tstamp; - struct port_id req_port_id; - uint8_t suffix[]; + struct rte_ptp_hdr hdr; + struct rte_ptp_timestamp rx_tstamp; + struct rte_ptp_port_id req_port_id; + uint8_t suffix[]; } __rte_packed_end; struct ptp_message { union __rte_packed_begin { - struct ptp_header header; + struct rte_ptp_hdr header; struct sync_msg sync; struct delay_req_msg delay_req; struct follow_up_msg follow_up; @@ -125,8 +83,8 @@ struct ptpv2_time_receiver_ordinary { struct timespec tstamp2; struct timespec tstamp3; struct timespec tstamp4; - struct clock_id client_clock_id; - struct clock_id transmitter_clock_id; + uint8_t client_clock_id[8]; + uint8_t transmitter_clock_id[8]; struct timeval new_adj; int64_t delta; uint16_t portid; @@ -272,14 +230,14 @@ print_clock_info(struct ptpv2_time_receiver_ordinary *ptp_data) struct timespec net_time, sys_time; printf("time transmitter clock id: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", - ptp_data->transmitter_clock_id.id[0], - ptp_data->transmitter_clock_id.id[1], - ptp_data->transmitter_clock_id.id[2], - ptp_data->transmitter_clock_id.id[3], - ptp_data->transmitter_clock_id.id[4], - ptp_data->transmitter_clock_id.id[5], - ptp_data->transmitter_clock_id.id[6], - ptp_data->transmitter_clock_id.id[7]); + ptp_data->transmitter_clock_id[0], + ptp_data->transmitter_clock_id[1], + ptp_data->transmitter_clock_id[2], + ptp_data->transmitter_clock_id[3], + ptp_data->transmitter_clock_id[4], + ptp_data->transmitter_clock_id[5], + ptp_data->transmitter_clock_id[6], + ptp_data->transmitter_clock_id[7]); printf("\nT2 - time receiver clock. %lds %ldns", (ptp_data->tstamp2.tv_sec), @@ -356,20 +314,20 @@ delta_eval(struct ptpv2_time_receiver_ordinary *ptp_data) static void parse_sync(struct ptpv2_time_receiver_ordinary *ptp_data, uint16_t rx_tstamp_idx) { - struct ptp_header *ptp_hdr; + struct rte_ptp_hdr *ptp_hdr; - ptp_hdr = rte_pktmbuf_mtod_offset(ptp_data->m, struct ptp_header *, + ptp_hdr = rte_pktmbuf_mtod_offset(ptp_data->m, struct rte_ptp_hdr *, sizeof(struct rte_ether_hdr)); - ptp_data->seqID_SYNC = rte_be_to_cpu_16(ptp_hdr->seq_id); + ptp_data->seqID_SYNC = rte_be_to_cpu_16(ptp_hdr->sequence_id); if (ptp_data->ptpset == 0) { - ptp_data->transmitter_clock_id = ptp_hdr->source_port_id.clock_id; + memcpy(ptp_data->transmitter_clock_id, + ptp_hdr->source_port_id.clock_id, 8); ptp_data->ptpset = 1; } - if (memcmp(&ptp_data->transmitter_clock_id, - &ptp_hdr->source_port_id.clock_id, - sizeof(struct clock_id)) == 0) { + if (memcmp(ptp_data->transmitter_clock_id, + ptp_hdr->source_port_id.clock_id, 8) == 0) { if (ptp_data->ptpset == 1) rte_eth_timesync_read_rx_timestamp(ptp_data->portid, @@ -386,12 +344,12 @@ parse_fup(struct ptpv2_time_receiver_ordinary *ptp_data) { struct rte_ether_hdr *eth_hdr; struct rte_ether_addr eth_addr; - struct ptp_header *ptp_hdr; - struct clock_id *client_clkid; + struct rte_ptp_hdr *ptp_hdr; + uint8_t *client_clkid; struct ptp_message *ptp_msg; struct delay_req_msg *req_msg; struct rte_mbuf *created_pkt; - struct tstamp *origin_tstamp; + struct rte_ptp_timestamp *origin_tstamp; struct rte_ether_addr eth_multicast = ether_multicast; size_t pkt_size; int wait_us; @@ -399,22 +357,21 @@ parse_fup(struct ptpv2_time_receiver_ordinary *ptp_data) int ret; eth_hdr = rte_pktmbuf_mtod(m, struct rte_ether_hdr *); - ptp_hdr = rte_pktmbuf_mtod_offset(m, struct ptp_header *, + ptp_hdr = rte_pktmbuf_mtod_offset(m, struct rte_ptp_hdr *, sizeof(struct rte_ether_hdr)); - if (memcmp(&ptp_data->transmitter_clock_id, - &ptp_hdr->source_port_id.clock_id, - sizeof(struct clock_id)) != 0) + if (memcmp(ptp_data->transmitter_clock_id, + ptp_hdr->source_port_id.clock_id, 8) != 0) return; - ptp_data->seqID_FOLLOWUP = rte_be_to_cpu_16(ptp_hdr->seq_id); + ptp_data->seqID_FOLLOWUP = rte_be_to_cpu_16(ptp_hdr->sequence_id); ptp_msg = rte_pktmbuf_mtod_offset(m, struct ptp_message *, sizeof(struct rte_ether_hdr)); origin_tstamp = &ptp_msg->follow_up.precise_origin_tstamp; - ptp_data->tstamp1.tv_nsec = ntohl(origin_tstamp->ns); + ptp_data->tstamp1.tv_nsec = ntohl(origin_tstamp->nanoseconds); ptp_data->tstamp1.tv_sec = - ((uint64_t)ntohl(origin_tstamp->sec_lsb)) | - (((uint64_t)ntohs(origin_tstamp->sec_msb)) << 32); + ((uint64_t)ntohl(origin_tstamp->seconds_lo)) | + (((uint64_t)ntohs(origin_tstamp->seconds_hi)) << 32); if (ptp_data->seqID_FOLLOWUP == ptp_data->seqID_SYNC) { ret = rte_eth_macaddr_get(ptp_data->portid, ð_addr); @@ -441,34 +398,34 @@ parse_fup(struct ptpv2_time_receiver_ordinary *ptp_data) /* Set multicast address 01-1B-19-00-00-00. */ rte_ether_addr_copy(ð_multicast, ð_hdr->dst_addr); - eth_hdr->ether_type = htons(PTP_PROTOCOL); + eth_hdr->ether_type = htons(RTE_PTP_ETHERTYPE); req_msg = rte_pktmbuf_mtod_offset(created_pkt, struct delay_req_msg *, sizeof(struct rte_ether_hdr)); - req_msg->hdr.seq_id = htons(ptp_data->seqID_SYNC); - req_msg->hdr.msg_type = DELAY_REQ; - req_msg->hdr.ver = 2; + req_msg->hdr.sequence_id = htons(ptp_data->seqID_SYNC); + req_msg->hdr.msg_type = RTE_PTP_MSGTYPE_DELAY_REQ; + req_msg->hdr.version = 2; req_msg->hdr.control = 1; - req_msg->hdr.log_message_interval = 127; - req_msg->hdr.message_length = + req_msg->hdr.log_msg_interval = 127; + req_msg->hdr.msg_length = htons(sizeof(struct delay_req_msg)); req_msg->hdr.domain_number = ptp_hdr->domain_number; /* Set up clock id. */ client_clkid = - &req_msg->hdr.source_port_id.clock_id; + req_msg->hdr.source_port_id.clock_id; - client_clkid->id[0] = eth_hdr->src_addr.addr_bytes[0]; - client_clkid->id[1] = eth_hdr->src_addr.addr_bytes[1]; - client_clkid->id[2] = eth_hdr->src_addr.addr_bytes[2]; - client_clkid->id[3] = 0xFF; - client_clkid->id[4] = 0xFE; - client_clkid->id[5] = eth_hdr->src_addr.addr_bytes[3]; - client_clkid->id[6] = eth_hdr->src_addr.addr_bytes[4]; - client_clkid->id[7] = eth_hdr->src_addr.addr_bytes[5]; + client_clkid[0] = eth_hdr->src_addr.addr_bytes[0]; + client_clkid[1] = eth_hdr->src_addr.addr_bytes[1]; + client_clkid[2] = eth_hdr->src_addr.addr_bytes[2]; + client_clkid[3] = 0xFF; + client_clkid[4] = 0xFE; + client_clkid[5] = eth_hdr->src_addr.addr_bytes[3]; + client_clkid[6] = eth_hdr->src_addr.addr_bytes[4]; + client_clkid[7] = eth_hdr->src_addr.addr_bytes[5]; - ptp_data->client_clock_id = *client_clkid; + memcpy(ptp_data->client_clock_id, client_clkid, 8); /* Enable flag for hardware timestamping. */ created_pkt->ol_flags |= RTE_MBUF_F_TX_IEEE1588_TMST; @@ -534,21 +491,20 @@ parse_drsp(struct ptpv2_time_receiver_ordinary *ptp_data) { struct rte_mbuf *m = ptp_data->m; struct ptp_message *ptp_msg; - struct tstamp *rx_tstamp; + struct rte_ptp_timestamp *rx_tstamp; uint16_t seq_id; ptp_msg = rte_pktmbuf_mtod_offset(m, struct ptp_message *, sizeof(struct rte_ether_hdr)); - seq_id = rte_be_to_cpu_16(ptp_msg->delay_resp.hdr.seq_id); - if (memcmp(&ptp_data->client_clock_id, - &ptp_msg->delay_resp.req_port_id.clock_id, - sizeof(struct clock_id)) == 0) { + seq_id = rte_be_to_cpu_16(ptp_msg->delay_resp.hdr.sequence_id); + if (memcmp(ptp_data->client_clock_id, + ptp_msg->delay_resp.req_port_id.clock_id, 8) == 0) { if (seq_id == ptp_data->seqID_FOLLOWUP) { rx_tstamp = &ptp_msg->delay_resp.rx_tstamp; - ptp_data->tstamp4.tv_nsec = ntohl(rx_tstamp->ns); + ptp_data->tstamp4.tv_nsec = ntohl(rx_tstamp->nanoseconds); ptp_data->tstamp4.tv_sec = - ((uint64_t)ntohl(rx_tstamp->sec_lsb)) | - (((uint64_t)ntohs(rx_tstamp->sec_msb)) << 32); + ((uint64_t)ntohl(rx_tstamp->seconds_lo)) | + (((uint64_t)ntohs(rx_tstamp->seconds_hi)) << 32); /* Evaluate the delta for adjustment. */ ptp_data->delta = delta_eval(ptp_data); @@ -575,27 +531,27 @@ parse_drsp(struct ptpv2_time_receiver_ordinary *ptp_data) /* Parse ptp frames. 8< */ static void parse_ptp_frames(uint16_t portid, struct rte_mbuf *m) { - struct ptp_header *ptp_hdr; + struct rte_ptp_hdr *ptp_hdr; struct rte_ether_hdr *eth_hdr; uint16_t eth_type; eth_hdr = rte_pktmbuf_mtod(m, struct rte_ether_hdr *); eth_type = rte_be_to_cpu_16(eth_hdr->ether_type); - if (eth_type == PTP_PROTOCOL) { + if (eth_type == RTE_PTP_ETHERTYPE) { ptp_data.m = m; ptp_data.portid = portid; - ptp_hdr = rte_pktmbuf_mtod_offset(m, struct ptp_header *, + ptp_hdr = rte_pktmbuf_mtod_offset(m, struct rte_ptp_hdr *, sizeof(struct rte_ether_hdr)); - switch (ptp_hdr->msg_type) { - case SYNC: + switch (rte_ptp_msg_type(ptp_hdr)) { + case RTE_PTP_MSGTYPE_SYNC: parse_sync(&ptp_data, m->timesync); break; - case FOLLOW_UP: + case RTE_PTP_MSGTYPE_FOLLOW_UP: parse_fup(&ptp_data); break; - case DELAY_RESP: + case RTE_PTP_MSGTYPE_DELAY_RESP: parse_drsp(&ptp_data); print_clock_info(&ptp_data); break; -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [RFC v5 0/6] introduce PTP protocol library and software relay 2026-04-28 1:01 [RFC v1 0/4] introduce PTP protocol library and software relay example Rajesh Kumar ` (7 preceding siblings ...) 2026-05-05 16:38 ` [RFC v4 " Rajesh Kumar @ 2026-05-06 15:41 ` Rajesh Kumar 2026-05-06 15:41 ` [RFC v5 1/6] ptp: introduce PTP protocol library Rajesh Kumar ` (6 more replies) 2026-05-07 10:13 ` [PATCH v6 0/4] PTP protocol support in lib/net Rajesh Kumar ` (2 subsequent siblings) 11 siblings, 7 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-06 15:41 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, Rajesh Kumar This series introduces a new DPDK library (lib/ptp) for IEEE 1588-2019 PTP protocol packet processing and a companion example application (ptp_tap_relay_sw) that demonstrates its usage. Motivation ---------- Several DPDK applications need to classify and manipulate PTP packets (e.g. ptpclient, ptp_tap_relay, custom Transparent Clocks). Today each application re-implements its own PTP header parsing and correctionField handling. A shared library avoids duplication and provides a tested, standards-compliant foundation. Library: lib/ptp ---------------- The library provides: - PTP header structures (IEEE 1588-2019 common header, timestamp, port identity) - Packet classification: rte_ptp_classify() detects PTP over L2 (EtherType 0x88F7), VLAN-tagged L2 (TPIDs 0x8100/0x88A8, single or double), UDP/IPv4, UDP/IPv6 (ports 319/320), and VLAN-tagged UDP variants - Header access: rte_ptp_hdr_get() returns a pointer to the PTP header inside an mbuf - Inline helpers: correctionField manipulation (48.16 fixed-point), message type extraction, two-step flag check, timestamp conversion - Debug: rte_ptp_msg_type_str() for human-readable message names Example: ptp_tap_relay_sw ------------------------- A minimal PTP Transparent Clock relay between a DPDK-bound physical NIC and a kernel TAP interface using software timestamps only. No patched kernel modules, custom TAP PMD, or hardware timestamp support is required. The relay: 1. Receives packets on the physical NIC via DPDK 2. Parses packets using rte_ptp_hdr_get() 3. For event messages, records software timestamps (clock_gettime(CLOCK_MONOTONIC)) at ingress and egress 4. Adds residence time to correctionField via rte_ptp_add_correction() (IEEE 1588-2019 10.2 Transparent Clock) 5. Forwards bidirectionally: PHY <-> TAP Unit Tests: app/test -------------------- A comprehensive test suite (ptp_autotest) covers all library APIs, including VLAN/QinQ/IPv4/IPv6 transports, correctionField helpers, flags, and negative tests. Also covers IPv4 options (IHL > 5). v5: - Fixed checkpatch warnings: removed unnecessary #include <stdio.h> from example and test files v4: - Fixed C++ header check (chkincs) failure: moved #include directives above extern "C" guard in rte_ptp.h to prevent rte_bitops.h C++ overloads from being forced into C linkage v3: - Reused RTE_ETHER_TYPE_1588 via RTE_PTP_ETHERTYPE alias - Updated version field comment to minorVersionPTP|versionPTP - Fixed prog guide rte_ptp_hdr_get() signature (non-const mbuf) - Added ALLOW_EXPERIMENTAL_API to ptp_tap_relay_sw Makefile - Updated relay example to parse once (rte_ptp_hdr_get + msg_type) - Updated relay sample app limitations wording to match capabilities - Added hdr_get NULL checks in correction tests - Added IPv4 options classification test (IHL > 5) - Made correction counter update style consistent via pointer arg v2: - Fixed flag bit positions for host-order representation after rte_be_to_cpu_16(): TWO_STEP (1<<9), UNICAST (1<<10), LI_61 (1<<0), LI_59 (1<<1) Rajesh Kumar (6): ptp: introduce PTP protocol library doc: add PTP library programmer's guide examples/ptp_tap_relay_sw: add software PTP relay example doc: add PTP software relay sample app guide app/test: add PTP library unit tests examples/ptpclient: use shared PTP library definitions MAINTAINERS | 8 + app/test/meson.build | 1 + app/test/test_ptp.c | 1105 +++++++++++++++++ doc/api/doxy-api-index.md | 1 + doc/api/doxy-api.conf.in | 1 + doc/guides/prog_guide/index.rst | 1 + doc/guides/prog_guide/ptp_lib.rst | 205 +++ doc/guides/rel_notes/release_26_07.rst | 13 + doc/guides/sample_app_ug/index.rst | 1 + doc/guides/sample_app_ug/ptp_tap_relay_sw.rst | 212 ++++ examples/meson.build | 1 + examples/ptp_tap_relay_sw/Makefile | 43 + examples/ptp_tap_relay_sw/meson.build | 14 + examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c | 432 +++++++ examples/ptpclient/meson.build | 1 + examples/ptpclient/ptpclient.c | 189 ++- lib/meson.build | 1 + lib/ptp/meson.build | 6 + lib/ptp/rte_ptp.c | 185 +++ lib/ptp/rte_ptp.h | 339 +++++ 20 files changed, 2643 insertions(+), 116 deletions(-) create mode 100644 app/test/test_ptp.c create mode 100644 doc/guides/prog_guide/ptp_lib.rst create mode 100644 doc/guides/sample_app_ug/ptp_tap_relay_sw.rst create mode 100644 examples/ptp_tap_relay_sw/Makefile create mode 100644 examples/ptp_tap_relay_sw/meson.build create mode 100644 examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c create mode 100644 lib/ptp/meson.build create mode 100644 lib/ptp/rte_ptp.c create mode 100644 lib/ptp/rte_ptp.h base-commit: 7baf81674a011ab8a2fe329566b6d43d7377244c -- 2.53.0 ^ permalink raw reply [flat|nested] 60+ messages in thread
* [RFC v5 1/6] ptp: introduce PTP protocol library 2026-05-06 15:41 ` [RFC v5 0/6] introduce PTP protocol library and software relay Rajesh Kumar @ 2026-05-06 15:41 ` Rajesh Kumar 2026-05-06 11:02 ` Morten Brørup 2026-05-06 15:41 ` [RFC v5 2/6] doc: add PTP library programmer's guide Rajesh Kumar ` (5 subsequent siblings) 6 siblings, 1 reply; 60+ messages in thread From: Rajesh Kumar @ 2026-05-06 15:41 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, Rajesh Kumar [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #1: Type: text/plain; charset=y, Size: 19055 bytes --] Add IEEE 1588-2019 Precision Time Protocol (PTP) processing library. The library provides packet classification, header structures, and helper functions for PTP packet processing in DPDK. This avoids duplicate PTP header definitions across multiple applications and drivers. Supported transports: 1. L2 PTP (EtherType 0x88F7) 2. VLAN-tagged L2 PTP (single and QinQ) 3. PTP over UDP/IPv4 (ports 319/320) 4. PTP over UDP/IPv6 (ports 319/320) Public APIs: 1. rte_ptp_classify() 2. rte_ptp_hdr_get() 3. rte_ptp_msg_type_str() Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- MAINTAINERS | 7 + doc/guides/rel_notes/release_26_07.rst | 7 + lib/meson.build | 1 + lib/ptp/meson.build | 6 + lib/ptp/rte_ptp.c | 185 ++++++++++++++ lib/ptp/rte_ptp.h | 339 +++++++++++++++++++++++++ 6 files changed, 545 insertions(+) create mode 100644 lib/ptp/meson.build create mode 100644 lib/ptp/rte_ptp.c create mode 100644 lib/ptp/rte_ptp.h diff --git a/MAINTAINERS b/MAINTAINERS index 0f5539f851..665e08dc90 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1665,6 +1665,13 @@ F: doc/guides/prog_guide/ipsec_lib.rst M: Vladimir Medvedkin <vladimir.medvedkin@intel.com> F: app/test-sad/ +PTP - EXPERIMENTAL +M: Rajesh Kumar <rajesh3.kumar@intel.com> +F: lib/ptp/ +F: doc/guides/prog_guide/ptp_lib.rst +F: examples/ptp_tap_relay_sw/ +F: doc/guides/sample_app_ug/ptp_tap_relay_sw.rst + PDCP - EXPERIMENTAL M: Anoob Joseph <anoobj@marvell.com> M: Volodymyr Fialko <vfialko@marvell.com> diff --git a/doc/guides/rel_notes/release_26_07.rst b/doc/guides/rel_notes/release_26_07.rst index f012d47a4b..b2208d6fb3 100644 --- a/doc/guides/rel_notes/release_26_07.rst +++ b/doc/guides/rel_notes/release_26_07.rst @@ -63,6 +63,13 @@ New Features ``rte_eal_init`` and the application is responsible for probing each device, * ``--auto-probing`` enables the initial bus probing, which is the current default behavior. +* **Added PTP protocol library.** + + Added a new library ``rte_ptp`` for IEEE 1588 Precision Time Protocol + packet processing. The library provides packet classification, header + parsing, and correctionField manipulation across L2, VLAN-tagged, + UDP/IPv4, and UDP/IPv6 transports. + Removed Items ------------- diff --git a/lib/meson.build b/lib/meson.build index 8f5cfd28a5..78d694f951 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -50,6 +50,7 @@ libraries = [ 'member', 'pcapng', 'power', + 'ptp', 'rawdev', 'regexdev', 'mldev', diff --git a/lib/ptp/meson.build b/lib/ptp/meson.build new file mode 100644 index 0000000000..05f9d87cbe --- /dev/null +++ b/lib/ptp/meson.build @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2026 Intel Corporation + +sources = files('rte_ptp.c') +headers = files('rte_ptp.h') +deps += ['mbuf', 'net'] diff --git a/lib/ptp/rte_ptp.c b/lib/ptp/rte_ptp.c new file mode 100644 index 0000000000..5e4fc666fb --- /dev/null +++ b/lib/ptp/rte_ptp.c @@ -0,0 +1,185 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2026 Intel Corporation + * + * IEEE 1588 / PTP Protocol Library — Implementation + */ + +#include <eal_export.h> +#include <rte_ether.h> +#include <rte_ip.h> +#include <rte_udp.h> + +#include "rte_ptp.h" + +/* + * Internal: find PTP header offset within a packet. + * Returns pointer to PTP header or NULL. + */ +static struct rte_ptp_hdr * +ptp_hdr_find(const struct rte_mbuf *m) +{ + const struct rte_ether_hdr *eth; + uint16_t ether_type; + uint32_t offset; + + if (rte_pktmbuf_data_len(m) < sizeof(struct rte_ether_hdr)) + return NULL; + + eth = rte_pktmbuf_mtod(m, const struct rte_ether_hdr *); + ether_type = rte_be_to_cpu_16(eth->ether_type); + offset = sizeof(struct rte_ether_hdr); + + /* Strip VLAN / QinQ tags to reach the inner EtherType */ + if (ether_type == RTE_ETHER_TYPE_VLAN || + ether_type == RTE_ETHER_TYPE_QINQ) { + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_vlan_hdr)) + return NULL; + const struct rte_vlan_hdr *vlan = + rte_pktmbuf_mtod_offset(m, + const struct rte_vlan_hdr *, offset); + ether_type = rte_be_to_cpu_16(vlan->eth_proto); + offset += sizeof(struct rte_vlan_hdr); + + /* Second tag (QinQ inner or stacked VLAN) */ + if (ether_type == RTE_ETHER_TYPE_VLAN || + ether_type == RTE_ETHER_TYPE_QINQ) { + if (rte_pktmbuf_data_len(m) < + offset + sizeof(struct rte_vlan_hdr)) + return NULL; + vlan = rte_pktmbuf_mtod_offset(m, + const struct rte_vlan_hdr *, offset); + ether_type = rte_be_to_cpu_16(vlan->eth_proto); + offset += sizeof(struct rte_vlan_hdr); + } + } + + /* L2 PTP: EtherType 0x88F7 (plain, VLAN, or QinQ) */ + if (ether_type == RTE_PTP_ETHERTYPE) { + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ptp_hdr)) + return NULL; + return rte_pktmbuf_mtod_offset(m, + struct rte_ptp_hdr *, offset); + } + + /* PTP over UDP/IPv4 (plain or VLAN-tagged) */ + if (ether_type == RTE_ETHER_TYPE_IPV4) { + const struct rte_ipv4_hdr *iph; + uint16_t ihl; + + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ipv4_hdr)) + return NULL; + + iph = rte_pktmbuf_mtod_offset(m, + const struct rte_ipv4_hdr *, offset); + if (iph->next_proto_id != IPPROTO_UDP) + return NULL; + + ihl = (iph->version_ihl & 0x0F) * 4; + if (ihl < 20) + return NULL; + offset += ihl; + + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_udp_hdr)) + return NULL; + + const struct rte_udp_hdr *udp = + rte_pktmbuf_mtod_offset(m, + const struct rte_udp_hdr *, offset); + uint16_t dst_port = rte_be_to_cpu_16(udp->dst_port); + + if (dst_port != RTE_PTP_EVENT_PORT && + dst_port != RTE_PTP_GENERAL_PORT) + return NULL; + + offset += sizeof(struct rte_udp_hdr); + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ptp_hdr)) + return NULL; + + return rte_pktmbuf_mtod_offset(m, + struct rte_ptp_hdr *, offset); + } + + /* PTP over UDP/IPv6 (plain or VLAN-tagged) */ + if (ether_type == RTE_ETHER_TYPE_IPV6) { + const struct rte_ipv6_hdr *ip6h; + + if (rte_pktmbuf_data_len(m) < + offset + sizeof(struct rte_ipv6_hdr)) + return NULL; + + ip6h = rte_pktmbuf_mtod_offset(m, + const struct rte_ipv6_hdr *, offset); + if (ip6h->proto != IPPROTO_UDP) + return NULL; + + offset += sizeof(struct rte_ipv6_hdr); + + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_udp_hdr)) + return NULL; + + const struct rte_udp_hdr *udp = + rte_pktmbuf_mtod_offset(m, + const struct rte_udp_hdr *, offset); + uint16_t dst_port = rte_be_to_cpu_16(udp->dst_port); + + if (dst_port != RTE_PTP_EVENT_PORT && + dst_port != RTE_PTP_GENERAL_PORT) + return NULL; + + offset += sizeof(struct rte_udp_hdr); + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ptp_hdr)) + return NULL; + + return rte_pktmbuf_mtod_offset(m, + struct rte_ptp_hdr *, offset); + } + + return NULL; +} + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_ptp_classify, 26.07) +int +rte_ptp_classify(const struct rte_mbuf *m) +{ + struct rte_ptp_hdr *hdr = ptp_hdr_find(m); + + if (hdr == NULL) + return RTE_PTP_MSGTYPE_INVALID; + + return rte_ptp_msg_type(hdr); +} + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_ptp_hdr_get, 26.07) +struct rte_ptp_hdr * +rte_ptp_hdr_get(struct rte_mbuf *m) +{ + return ptp_hdr_find(m); +} + +static const char * const ptp_msg_names[] = { + [RTE_PTP_MSGTYPE_SYNC] = "Sync", + [RTE_PTP_MSGTYPE_DELAY_REQ] = "Delay_Req", + [RTE_PTP_MSGTYPE_PDELAY_REQ] = "PDelay_Req", + [RTE_PTP_MSGTYPE_PDELAY_RESP] = "PDelay_Resp", + [0x4] = "Reserved_4", + [0x5] = "Reserved_5", + [0x6] = "Reserved_6", + [0x7] = "Reserved_7", + [RTE_PTP_MSGTYPE_FOLLOW_UP] = "Follow_Up", + [RTE_PTP_MSGTYPE_DELAY_RESP] = "Delay_Resp", + [RTE_PTP_MSGTYPE_PDELAY_RESP_FU] = "PDelay_Resp_Follow_Up", + [RTE_PTP_MSGTYPE_ANNOUNCE] = "Announce", + [RTE_PTP_MSGTYPE_SIGNALING] = "Signaling", + [RTE_PTP_MSGTYPE_MANAGEMENT] = "Management", + [0xE] = "Reserved_E", + [0xF] = "Reserved_F", +}; + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_ptp_msg_type_str, 26.07) +const char * +rte_ptp_msg_type_str(int msg_type) +{ + if (msg_type < 0 || msg_type > 0xF) + return "Not_PTP"; + return ptp_msg_names[msg_type]; +} diff --git a/lib/ptp/rte_ptp.h b/lib/ptp/rte_ptp.h new file mode 100644 index 0000000000..d9f1cce638 --- /dev/null +++ b/lib/ptp/rte_ptp.h @@ -0,0 +1,339 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2026 Intel Corporation + * + * IEEE 1588 / PTP Protocol Library + * + * Provides header structures, packet classification, and helper functions + * for Precision Time Protocol (IEEE 1588-2019) packet processing in DPDK. + * + * Supports: + * - L2 PTP (EtherType 0x88F7) + * - VLAN-tagged L2 PTP (single or double VLAN, TPIDs 0x8100/0x88A8) + * - PTP over UDP/IPv4 (ports 319/320) + * - PTP over UDP/IPv6 (ports 319/320) + * - VLAN-tagged PTP over UDP/IPv4 or UDP/IPv6 + * - Two-step and one-step message identification + * - correctionField manipulation (scaled nanoseconds, 48.16 fixed-point) + * + * Limitations: + * - IPv6 extension headers are not traversed. + */ + +#ifndef _RTE_PTP_H_ +#define _RTE_PTP_H_ + +#include <stdint.h> +#include <stdbool.h> +#include <rte_byteorder.h> +#include <rte_ether.h> +#include <rte_mbuf.h> +#include <rte_common.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @file + * IEEE 1588 PTP protocol definitions and helpers. + */ + +/* ============================================================ + * PTP Constants + * ============================================================ + */ + +/** PTP EtherType (IEEE 802.1AS / IEEE 1588) */ +#define RTE_PTP_ETHERTYPE RTE_ETHER_TYPE_1588 + +/** PTP over UDP event port (Sync, Delay_Req, PDelay_Req, PDelay_Resp) */ +#define RTE_PTP_EVENT_PORT 319 + +/** PTP over UDP general port (Follow_Up, Delay_Resp, Announce, etc.) */ +#define RTE_PTP_GENERAL_PORT 320 + +/** PTP multicast MAC address: 01:1B:19:00:00:00 */ +#define RTE_PTP_MULTICAST_MAC { 0x01, 0x1B, 0x19, 0x00, 0x00, 0x00 } + +/** PTP peer delay multicast MAC: 01:80:C2:00:00:0E */ +#define RTE_PTP_PDELAY_MULTICAST_MAC { 0x01, 0x80, 0xC2, 0x00, 0x00, 0x0E } + +/* ============================================================ + * PTP Message Types (IEEE 1588-2019 Table 36) + * ============================================================ + */ + +#define RTE_PTP_MSGTYPE_SYNC 0x0 /**< Sync (event) */ +#define RTE_PTP_MSGTYPE_DELAY_REQ 0x1 /**< Delay_Req (event) */ +#define RTE_PTP_MSGTYPE_PDELAY_REQ 0x2 /**< Peer_Delay_Req (event) */ +#define RTE_PTP_MSGTYPE_PDELAY_RESP 0x3 /**< Peer_Delay_Resp (event) */ +#define RTE_PTP_MSGTYPE_FOLLOW_UP 0x8 /**< Follow_Up (general) */ +#define RTE_PTP_MSGTYPE_DELAY_RESP 0x9 /**< Delay_Resp (general) */ +#define RTE_PTP_MSGTYPE_PDELAY_RESP_FU 0xA /**< Peer_Delay_Resp_Follow_Up */ +#define RTE_PTP_MSGTYPE_ANNOUNCE 0xB /**< Announce (general) */ +#define RTE_PTP_MSGTYPE_SIGNALING 0xC /**< Signaling (general) */ +#define RTE_PTP_MSGTYPE_MANAGEMENT 0xD /**< Management (general) */ + +/** Invalid / not a PTP packet */ +#define RTE_PTP_MSGTYPE_INVALID (-1) + +/* ============================================================ + * PTP Flag Field Bits (IEEE 1588-2019 Table 37) + * + * These constants are for use after rte_be_to_cpu_16(hdr->flags). + * flagField[0] (octet 6) maps to host bits 8-15. + * flagField[1] (octet 7) maps to host bits 0-7. + * ============================================================ + */ + +#define RTE_PTP_FLAG_TWO_STEP (1 << 9) /**< Two-step flag (flagField[0] bit 1) */ +#define RTE_PTP_FLAG_UNICAST (1 << 10) /**< Unicast flag (flagField[0] bit 2) */ +#define RTE_PTP_FLAG_LI_61 (1 << 0) /**< Leap indicator 61 (flagField[1] bit 0) */ +#define RTE_PTP_FLAG_LI_59 (1 << 1) /**< Leap indicator 59 (flagField[1] bit 1) */ + +/* ============================================================ + * PTP Header Structures (IEEE 1588-2019) + * ============================================================ + */ + +/** + * PTP Port Identity (10 bytes). + */ +struct __rte_packed_begin rte_ptp_port_id { + uint8_t clock_id[8]; /**< clockIdentity (EUI-64) */ + uint16_t port_number; /**< portNumber */ +} __rte_packed_end; + +/** + * PTP Common Message Header (34 bytes). + * All PTP messages begin with this header. + */ +struct __rte_packed_begin rte_ptp_hdr { + uint8_t msg_type; /**< transportSpecific (4) | messageType (4) */ + uint8_t version; /**< minorVersionPTP (4) | versionPTP (4) */ + uint16_t msg_length; /**< Total message length in bytes */ + uint8_t domain_number; /**< PTP domain (0-255) */ + uint8_t minor_sdo_id; /**< minorSdoId (IEEE 1588-2019) */ + uint16_t flags; /**< Flag field (see RTE_PTP_FLAG_*) */ + int64_t correction; /**< correctionField (scaled ns, 48.16 fixed) */ + uint32_t msg_type_specific; /**< messageTypeSpecific */ + struct rte_ptp_port_id source_port_id; /**< sourcePortIdentity */ + uint16_t sequence_id; /**< sequenceId */ + uint8_t control; /**< controlField (deprecated in 1588-2019) */ + int8_t log_msg_interval; /**< logMessageInterval */ +} __rte_packed_end; + +/** + * PTP Timestamp (10 bytes, used in Sync/Delay_Req/Follow_Up bodies). + */ +struct __rte_packed_begin rte_ptp_timestamp { + uint16_t seconds_hi; /**< Upper 16 bits of seconds */ + uint32_t seconds_lo; /**< Lower 32 bits of seconds */ + uint32_t nanoseconds; /**< Nanoseconds (0-999999999) */ +} __rte_packed_end; + +/* ============================================================ + * Inline Helpers + * ============================================================ + */ + +/** + * Extract PTP message type from header. + * + * @param hdr + * Pointer to PTP header. + * @return + * Message type (0x0-0xF). + */ +static inline uint8_t +rte_ptp_msg_type(const struct rte_ptp_hdr *hdr) +{ + return hdr->msg_type & 0x0F; +} + +/** + * Extract transport-specific field from header. + * + * @param hdr + * Pointer to PTP header. + * @return + * Transport-specific value (upper nibble, 0x0-0xF). + */ +static inline uint8_t +rte_ptp_transport_specific(const struct rte_ptp_hdr *hdr) +{ + return (hdr->msg_type >> 4) & 0x0F; +} + +/** + * Extract PTP version from header. + * + * @param hdr + * Pointer to PTP header. + * @return + * PTP version number (typically 2). + */ +static inline uint8_t +rte_ptp_version(const struct rte_ptp_hdr *hdr) +{ + return hdr->version & 0x0F; +} + +/** + * Get sequence ID from PTP header (host byte order). + * + * @param hdr + * Pointer to PTP header. + * @return + * Sequence ID in host byte order. + */ +static inline uint16_t +rte_ptp_seq_id(const struct rte_ptp_hdr *hdr) +{ + return rte_be_to_cpu_16(hdr->sequence_id); +} + +/** + * Get PTP domain number. + * + * @param hdr + * Pointer to PTP header. + * @return + * Domain number (0-255). + */ +static inline uint8_t +rte_ptp_domain(const struct rte_ptp_hdr *hdr) +{ + return hdr->domain_number; +} + +/** + * Check if PTP message type is an event message. + * Event messages (msg_type 0x0-0x3) require hardware timestamps. + * + * @param msg_type + * PTP message type value (0x0-0xF). + * @return + * true if event message, false otherwise. + */ +static inline bool +rte_ptp_is_event(int msg_type) +{ + return msg_type >= 0 && msg_type <= RTE_PTP_MSGTYPE_PDELAY_RESP; +} + +/** + * Check if the two-step flag is set in a PTP header. + * + * @param hdr + * Pointer to PTP header. + * @return + * true if two-step flag is set. + */ +static inline bool +rte_ptp_is_two_step(const struct rte_ptp_hdr *hdr) +{ + return (rte_be_to_cpu_16(hdr->flags) & RTE_PTP_FLAG_TWO_STEP) != 0; +} + +/** + * Get correctionField value in nanoseconds (from 48.16 fixed-point). + * + * @param hdr + * Pointer to PTP header. + * @return + * Correction value in nanoseconds. + */ +static inline int64_t +rte_ptp_correction_ns(const struct rte_ptp_hdr *hdr) +{ + return (int64_t)rte_be_to_cpu_64(hdr->correction) >> 16; +} + +/** + * Add a residence time (in nanoseconds) to the correctionField. + * Used by Transparent Clocks to account for relay transit delay. + * The correctionField uses IEEE 1588 scaled nanoseconds (48.16 fixed-point). + * + * @param hdr + * Pointer to PTP header (will be modified in-place). + * @param residence_ns + * Residence time in nanoseconds to add. + */ +static inline void +rte_ptp_add_correction(struct rte_ptp_hdr *hdr, int64_t residence_ns) +{ + int64_t cf = (int64_t)rte_be_to_cpu_64(hdr->correction); + cf += (int64_t)((uint64_t)residence_ns << 16); + hdr->correction = rte_cpu_to_be_64(cf); +} + +/** + * Convert a PTP timestamp structure to nanoseconds since epoch. + * + * @param ts + * Pointer to PTP timestamp. + * @return + * Time in nanoseconds since epoch. + */ +static inline uint64_t +rte_ptp_timestamp_to_ns(const struct rte_ptp_timestamp *ts) +{ + uint64_t sec = ((uint64_t)rte_be_to_cpu_16(ts->seconds_hi) << 32) | + rte_be_to_cpu_32(ts->seconds_lo); + return sec * 1000000000ULL + rte_be_to_cpu_32(ts->nanoseconds); +} + +/* ============================================================ + * Packet Classification Functions (implemented in rte_ptp.c) + * ============================================================ + */ + +/** + * Classify a packet as PTP and return the message type. + * + * Examines the mbuf to determine if it contains a PTP message. + * Supports L2 (EtherType 0x88F7), VLAN-tagged L2 (single/double, + * TPIDs 0x8100/0x88A8), PTP over UDP/IPv4, PTP over UDP/IPv6, + * and VLAN-tagged UDP/IPv4 or UDP/IPv6 (ports 319/320). + * + * @param m + * Pointer to the mbuf to classify. + * @return + * PTP message type (0x0-0xF) on success, RTE_PTP_MSGTYPE_INVALID (-1) + * if the packet is not PTP. + */ +__rte_experimental +int rte_ptp_classify(const struct rte_mbuf *m); + +/** + * Get a pointer to the PTP header inside an mbuf. + * + * Locates the PTP header within the packet, handling L2, VLAN-tagged L2 + * (single/double, TPIDs 0x8100/0x88A8), PTP over UDP/IPv4, + * PTP over UDP/IPv6, and VLAN-tagged UDP variants. + * + * @param m + * Pointer to the mbuf. + * @return + * Pointer to the PTP header, or NULL if not a PTP packet. + */ +__rte_experimental +struct rte_ptp_hdr *rte_ptp_hdr_get(struct rte_mbuf *m); + +/** + * Get a human-readable name for a PTP message type. + * + * @param msg_type + * PTP message type (0x0-0xF or RTE_PTP_MSGTYPE_INVALID). + * @return + * Static string with the message type name (e.g., "Sync", "Follow_Up"). + */ +__rte_experimental +const char *rte_ptp_msg_type_str(int msg_type); + +#ifdef __cplusplus +} +#endif + +#endif /* _RTE_PTP_H_ */ -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* RE: [RFC v5 1/6] ptp: introduce PTP protocol library 2026-05-06 15:41 ` [RFC v5 1/6] ptp: introduce PTP protocol library Rajesh Kumar @ 2026-05-06 11:02 ` Morten Brørup 2026-05-07 4:45 ` Kumar, Rajesh 0 siblings, 1 reply; 60+ messages in thread From: Morten Brørup @ 2026-05-06 11:02 UTC (permalink / raw) To: Rajesh Kumar, dev; +Cc: bruce.richardson, aman.deep.singh, stephen > From: Rajesh Kumar [mailto:rajesh3.kumar@intel.com] > Sent: Wednesday, 6 May 2026 17.41 > > Add IEEE 1588-2019 Precision Time Protocol (PTP) processing library. > > The library provides packet classification, header structures, and > helper functions for PTP packet processing in DPDK. This avoids > duplicate PTP header definitions across multiple applications and > drivers. > > Supported transports: > 1. L2 PTP (EtherType 0x88F7) > 2. VLAN-tagged L2 PTP (single and QinQ) > 3. PTP over UDP/IPv4 (ports 319/320) > 4. PTP over UDP/IPv6 (ports 319/320) > > Public APIs: > 1. rte_ptp_classify() > 2. rte_ptp_hdr_get() > 3. rte_ptp_msg_type_str() > > Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> > --- Improved PTP support seems like a good addition to DPDK. Here's some high level feedback to the library: I think much of this belongs into /lib/net/, which holds similar protocol structure definitions and helper functions for many other protocols. Please familiarize yourself with the code conventions in that directory, and move the relevant parts of the PTP library there (following the code conventions there). Remember to define the packet structures with endianness in mind; e.g. use rte_be32_t instead of uint32_t where appropriate. Don't define simple aliases, just use RTE_ETHER_TYPE_1588 directly. Your DPI (deep packet inspection) parser function that can parse all kinds of PTP encapsulation does not belong in the library. Applications would rely on a layered packet parser, such as rte_net_get_ptype(). If you need a DPI parser for the examples, feel free to add it there. ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [RFC v5 1/6] ptp: introduce PTP protocol library 2026-05-06 11:02 ` Morten Brørup @ 2026-05-07 4:45 ` Kumar, Rajesh 0 siblings, 0 replies; 60+ messages in thread From: Kumar, Rajesh @ 2026-05-07 4:45 UTC (permalink / raw) To: Morten Brørup, dev; +Cc: bruce.richardson, aman.deep.singh, stephen On 06-05-2026 04:32 pm, Morten Brørup wrote: >> From: Rajesh Kumar [mailto:rajesh3.kumar@intel.com] >> Sent: Wednesday, 6 May 2026 17.41 >> >> Add IEEE 1588-2019 Precision Time Protocol (PTP) processing library. >> >> The library provides packet classification, header structures, and >> helper functions for PTP packet processing in DPDK. This avoids >> duplicate PTP header definitions across multiple applications and >> drivers. >> >> Supported transports: >> 1. L2 PTP (EtherType 0x88F7) >> 2. VLAN-tagged L2 PTP (single and QinQ) >> 3. PTP over UDP/IPv4 (ports 319/320) >> 4. PTP over UDP/IPv6 (ports 319/320) >> >> Public APIs: >> 1. rte_ptp_classify() >> 2. rte_ptp_hdr_get() >> 3. rte_ptp_msg_type_str() >> >> Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> >> --- > Improved PTP support seems like a good addition to DPDK. > > Here's some high level feedback to the library: > > I think much of this belongs into /lib/net/, which holds similar protocol structure definitions and helper functions for many other protocols. > Please familiarize yourself with the code conventions in that directory, and move the relevant parts of the PTP library there (following the code conventions there). > > Remember to define the packet structures with endianness in mind; e.g. use rte_be32_t instead of uint32_t where appropriate. > > Don't define simple aliases, just use RTE_ETHER_TYPE_1588 directly. > > Your DPI (deep packet inspection) parser function that can parse all kinds of PTP encapsulation does not belong in the library. > Applications would rely on a layered packet parser, such as rte_net_get_ptype(). > > If you need a DPI parser for the examples, feel free to add it there. Thank you for the feedback, Morten! All comments addressed in v6: - Moved to lib/net/ as a header-only library (following rte_tcp.h conventions) - All multi-byte struct fields now use rte_beXX_t types - Removed RTE_ETHER_TYPE_1588 alias; using constant directly - DPI parser moved to example-local ptp_parse.h (not library API) ^ permalink raw reply [flat|nested] 60+ messages in thread
* [RFC v5 2/6] doc: add PTP library programmer's guide 2026-05-06 15:41 ` [RFC v5 0/6] introduce PTP protocol library and software relay Rajesh Kumar 2026-05-06 15:41 ` [RFC v5 1/6] ptp: introduce PTP protocol library Rajesh Kumar @ 2026-05-06 15:41 ` Rajesh Kumar 2026-05-06 15:41 ` [RFC v5 3/6] examples/ptp_tap_relay_sw: add software PTP relay example Rajesh Kumar ` (4 subsequent siblings) 6 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-06 15:41 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, Rajesh Kumar [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #1: Type: text/plain; charset=y, Size: 9526 bytes --] Add programmer's guide for the PTP protocol library covering message types, header structures, packet classification API, inline helpers, and usage examples. Add PTP header to Doxygen API index under layers section. Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- doc/api/doxy-api-index.md | 1 + doc/api/doxy-api.conf.in | 1 + doc/guides/prog_guide/index.rst | 1 + doc/guides/prog_guide/ptp_lib.rst | 205 ++++++++++++++++++++++++++++++ 4 files changed, 208 insertions(+) create mode 100644 doc/guides/prog_guide/ptp_lib.rst diff --git a/doc/api/doxy-api-index.md b/doc/api/doxy-api-index.md index 9296042119..bbc79168bb 100644 --- a/doc/api/doxy-api-index.md +++ b/doc/api/doxy-api-index.md @@ -137,6 +137,7 @@ The public API headers are grouped by topics: [eCPRI](@ref rte_ecpri.h), [PDCP hdr](@ref rte_pdcp_hdr.h), [PDCP](@ref rte_pdcp.h), + [PTP](@ref rte_ptp.h), [L2TPv2](@ref rte_l2tpv2.h), [PPP](@ref rte_ppp.h), [IB](@ref rte_ib.h) diff --git a/doc/api/doxy-api.conf.in b/doc/api/doxy-api.conf.in index bedd944681..f15d962733 100644 --- a/doc/api/doxy-api.conf.in +++ b/doc/api/doxy-api.conf.in @@ -72,6 +72,7 @@ INPUT = @TOPDIR@/doc/api/doxy-api-index.md \ @TOPDIR@/lib/pmu \ @TOPDIR@/lib/port \ @TOPDIR@/lib/power \ + @TOPDIR@/lib/ptp \ @TOPDIR@/lib/ptr_compress \ @TOPDIR@/lib/rawdev \ @TOPDIR@/lib/rcu \ diff --git a/doc/guides/prog_guide/index.rst b/doc/guides/prog_guide/index.rst index e6f24945b0..60dad4475c 100644 --- a/doc/guides/prog_guide/index.rst +++ b/doc/guides/prog_guide/index.rst @@ -97,6 +97,7 @@ Protocol Processing Libraries :maxdepth: 1 :numbered: + ptp_lib pdcp_lib ipsec_lib diff --git a/doc/guides/prog_guide/ptp_lib.rst b/doc/guides/prog_guide/ptp_lib.rst new file mode 100644 index 0000000000..fb6794781a --- /dev/null +++ b/doc/guides/prog_guide/ptp_lib.rst @@ -0,0 +1,205 @@ +.. SPDX-License-Identifier: BSD-3-Clause + Copyright(c) 2026 Intel Corporation. + +PTP Protocol Library +==================== + +The DPDK PTP library provides IEEE 1588 / Precision Time Protocol (PTP) +packet structures, constants, and helper functions for PTP packet processing. + +The library supports classification and header parsing of PTP messages +across multiple transport encapsulations: + +- L2 PTP (EtherType 0x88F7) +- VLAN-tagged L2 PTP (single or double VLAN, TPIDs 0x8100 and 0x88A8) +- PTP over UDP/IPv4 (destination ports 319 and 320) +- PTP over UDP/IPv6 (destination ports 319 and 320) +- VLAN-tagged PTP over UDP/IPv4 or UDP/IPv6 + +The library conforms to +`IEEE 1588-2019 <https://standards.ieee.org/ieee/1588/6825/>`_ +(Precision Time Protocol). + +Overview +-------- + +PTP is the foundation of time synchronization in networking. +DPDK applications that relay, classify, or timestamp PTP packets +currently duplicate header definitions and parsing logic. +This library provides a shared, tested implementation. + +The library provides: + +#. Packed header structures matching the IEEE 1588-2019 wire format +#. Constants for message types, flags, ports, and multicast addresses +#. Inline helpers for common field extraction and manipulation +#. Packet classification across L2, VLAN, UDP/IPv4, and UDP/IPv6 transports +#. Correction field manipulation for Transparent Clock residence time + +PTP Message Types +----------------- + +IEEE 1588-2019 defines the following message types, all supported by the library: + +.. csv-table:: PTP Message Types + :header: "Type", "Name", "Category", "Macro" + :widths: 5, 20, 10, 30 + + "0x0", "Sync", "Event", "``RTE_PTP_MSGTYPE_SYNC``" + "0x1", "Delay_Req", "Event", "``RTE_PTP_MSGTYPE_DELAY_REQ``" + "0x2", "Peer_Delay_Req", "Event", "``RTE_PTP_MSGTYPE_PDELAY_REQ``" + "0x3", "Peer_Delay_Resp", "Event", "``RTE_PTP_MSGTYPE_PDELAY_RESP``" + "0x8", "Follow_Up", "General", "``RTE_PTP_MSGTYPE_FOLLOW_UP``" + "0x9", "Delay_Resp", "General", "``RTE_PTP_MSGTYPE_DELAY_RESP``" + "0xA", "PDelay_Resp_Follow_Up", "General", "``RTE_PTP_MSGTYPE_PDELAY_RESP_FU``" + "0xB", "Announce", "General", "``RTE_PTP_MSGTYPE_ANNOUNCE``" + "0xC", "Signaling", "General", "``RTE_PTP_MSGTYPE_SIGNALING``" + "0xD", "Management", "General", "``RTE_PTP_MSGTYPE_MANAGEMENT``" + +Event messages (types 0x0–0x3) require hardware timestamps for accurate +time transfer. + +Header Structures +----------------- + +The library defines the following packed structures that map directly to +the IEEE 1588-2019 wire format: + +``struct rte_ptp_hdr`` + Common PTP message header (34 bytes). All PTP messages begin with this header. + Contains message type, version, flags, correction field, source port identity, + sequence ID, and log message interval. + +``struct rte_ptp_timestamp`` + PTP timestamp (10 bytes). Used in Sync, Delay_Req, and Follow_Up message bodies. + Contains seconds (48-bit) and nanoseconds (32-bit). + +``struct rte_ptp_port_id`` + PTP port identity (10 bytes). Contains an EUI-64 clock identity and a + 16-bit port number. + +Packet Classification API +-------------------------- + +``rte_ptp_classify()`` +~~~~~~~~~~~~~~~~~~~~~~ + +Classify a packet and return the PTP message type. + +.. code-block:: C + + int rte_ptp_classify(const struct rte_mbuf *m); + +Examines the mbuf to determine if it contains a PTP message. +Returns the PTP message type (0x0–0xF) on success, +or ``RTE_PTP_MSGTYPE_INVALID`` (-1) if the packet is not PTP. + +Supported encapsulations (VLAN TPIDs recognised: 0x8100 and 0x88A8): + +- EtherType 0x88F7 (L2 PTP) +- Single VLAN (0x8100 or 0x88A8) + EtherType 0x88F7 +- Double VLAN (any combination of 0x8100 / 0x88A8) + EtherType 0x88F7 +- IPv4 + UDP destination port 319 or 320 +- IPv6 + UDP destination port 319 or 320 +- Single or double VLAN + IPv4/IPv6 + UDP destination port 319 or 320 + +``rte_ptp_hdr_get()`` +~~~~~~~~~~~~~~~~~~~~~ + +Get a pointer to the PTP header inside a packet. + +.. code-block:: C + + struct rte_ptp_hdr *rte_ptp_hdr_get(struct rte_mbuf *m); + +Returns a pointer to the PTP header, or NULL if the packet is not PTP. +Handles the same set of encapsulations as ``rte_ptp_classify()``. + +``rte_ptp_msg_type_str()`` +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Convert a PTP message type to a human-readable string. + +.. code-block:: C + + const char *rte_ptp_msg_type_str(int msg_type); + +Returns a string such as ``"Sync"``, ``"Delay_Req"``, ``"Follow_Up"``, etc. +Returns ``"Not_PTP"`` for invalid message types. + +Inline Helpers +-------------- + +The following inline functions operate on ``struct rte_ptp_hdr`` and require +no function call overhead: + +.. csv-table:: Inline Helper Functions + :header: "Function", "Returns", "Description" + :widths: 30, 15, 40 + + "``rte_ptp_msg_type()``", "``uint8_t``", "Extract message type (lower nibble)" + "``rte_ptp_transport_specific()``", "``uint8_t``", "Extract transport-specific field (upper nibble)" + "``rte_ptp_version()``", "``uint8_t``", "Extract PTP version number" + "``rte_ptp_seq_id()``", "``uint16_t``", "Get sequence ID in host byte order" + "``rte_ptp_domain()``", "``uint8_t``", "Get PTP domain number" + "``rte_ptp_is_event()``", "``bool``", "Check if message type is an event (0x0–0x3)" + "``rte_ptp_is_two_step()``", "``bool``", "Check if two-step flag is set" + "``rte_ptp_correction_ns()``", "``int64_t``", "Get correctionField in nanoseconds (from 48.16 fixed-point)" + "``rte_ptp_add_correction()``", "``void``", "Add residence time to correctionField (for Transparent Clocks)" + "``rte_ptp_timestamp_to_ns()``", "``uint64_t``", "Convert PTP timestamp struct to nanoseconds" + +Usage Example +------------- + +Classifying and processing PTP packets: + +.. code-block:: C + + #include <rte_ptp.h> + + void process_packets(struct rte_mbuf **pkts, uint16_t nb_pkts) + { + for (uint16_t i = 0; i < nb_pkts; i++) { + int ptp_type = rte_ptp_classify(pkts[i]); + if (ptp_type == RTE_PTP_MSGTYPE_INVALID) + continue; + + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(pkts[i]); + + printf("PTP %s seq=%u domain=%u\n", + rte_ptp_msg_type_str(ptp_type), + rte_ptp_seq_id(hdr), + rte_ptp_domain(hdr)); + + if (rte_ptp_is_event(ptp_type)) { + /* Event message — needs hardware timestamp */ + } + } + } + +.. note:: + + ``rte_ptp_classify()`` and ``rte_ptp_hdr_get()`` both parse the packet + internally. When the caller needs both the message type and a header + pointer, calling ``rte_ptp_hdr_get()`` alone and then using + ``rte_ptp_msg_type()`` on the returned header avoids parsing the + packet twice. + +Adding residence time for a Transparent Clock: + +.. code-block:: C + + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(pkt); + if (hdr != NULL) { + int64_t residence_ns = egress_ts - ingress_ts; + rte_ptp_add_correction(hdr, residence_ns); + } + +Limitations +----------- + +- IPv6 extension headers are not traversed. Only the base IPv6 header + with ``next_header == UDP`` is handled. +- Multi-segment mbufs are not supported. PTP event messages are + typically less than 128 bytes and fit in a single segment. +- PTP over MPLS or other tunneling protocols is not supported. -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [RFC v5 3/6] examples/ptp_tap_relay_sw: add software PTP relay example 2026-05-06 15:41 ` [RFC v5 0/6] introduce PTP protocol library and software relay Rajesh Kumar 2026-05-06 15:41 ` [RFC v5 1/6] ptp: introduce PTP protocol library Rajesh Kumar 2026-05-06 15:41 ` [RFC v5 2/6] doc: add PTP library programmer's guide Rajesh Kumar @ 2026-05-06 15:41 ` Rajesh Kumar 2026-05-06 15:41 ` [RFC v5 4/6] doc: add PTP software relay sample app guide Rajesh Kumar ` (3 subsequent siblings) 6 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-06 15:41 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, Rajesh Kumar [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #1: Type: text/plain; charset=y, Size: 16455 bytes --] Add a minimal PTP Transparent Clock relay that bridges PTP packets between a DPDK-bound physical NIC and a kernel TAP interface using software timestamps only. The relay uses the rte_ptp library for packet classification and correctionField manipulation. For each PTP event message, it records software timestamps (CLOCK_MONOTONIC_RAW) at ingress and egress, then adds the residence time to the correctionField per IEEE 1588-2019 section 10.2. A two-pass design is used: first all packets are classified and PTP header pointers saved, then a single TX timestamp is taken immediately before applying corrections and calling rte_eth_tx_burst(). This minimises the gap between the measured timestamp and actual wire egress. Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- doc/guides/rel_notes/release_26_07.rst | 6 + examples/meson.build | 1 + examples/ptp_tap_relay_sw/Makefile | 43 ++ examples/ptp_tap_relay_sw/meson.build | 14 + examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c | 432 +++++++++++++++++++ 5 files changed, 496 insertions(+) create mode 100644 examples/ptp_tap_relay_sw/Makefile create mode 100644 examples/ptp_tap_relay_sw/meson.build create mode 100644 examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c diff --git a/doc/guides/rel_notes/release_26_07.rst b/doc/guides/rel_notes/release_26_07.rst index b2208d6fb3..5c2329d867 100644 --- a/doc/guides/rel_notes/release_26_07.rst +++ b/doc/guides/rel_notes/release_26_07.rst @@ -70,6 +70,12 @@ New Features parsing, and correctionField manipulation across L2, VLAN-tagged, UDP/IPv4, and UDP/IPv6 transports. +* **Added PTP software relay example application.** + + Added a new example application ``ptp_tap_relay_sw`` demonstrating a + software PTP Transparent Clock relay between a DPDK port and a kernel + TAP interface. + Removed Items ------------- diff --git a/examples/meson.build b/examples/meson.build index 25d9c88457..ab82cf5fbd 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -43,6 +43,7 @@ all_examples = [ 'packet_ordering', 'pipeline', 'ptpclient', + 'ptp_tap_relay_sw', 'qos_meter', 'qos_sched', 'rxtx_callbacks', diff --git a/examples/ptp_tap_relay_sw/Makefile b/examples/ptp_tap_relay_sw/Makefile new file mode 100644 index 0000000000..259e53bd18 --- /dev/null +++ b/examples/ptp_tap_relay_sw/Makefile @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2026 Intel Corporation + +# binary name +APP = dpdk-ptp_tap_relay_sw + +# all source are stored in SRCS-y +SRCS-y := ptp_tap_relay_sw.c + +PKGCONF ?= pkg-config + +# Build using pkg-config variables if possible +ifneq ($(shell $(PKGCONF) --exists libdpdk && echo 0),0) +$(error "no installation of DPDK found") +endif + +all: shared +.PHONY: shared static +shared: build/$(APP)-shared + ln -sf $(APP)-shared build/$(APP) +static: build/$(APP)-static + ln -sf $(APP)-static build/$(APP) + +PC_FILE := $(shell $(PKGCONF) --path libdpdk 2>/dev/null) +CFLAGS += -O3 $(shell $(PKGCONF) --cflags libdpdk) +LDFLAGS_SHARED = $(shell $(PKGCONF) --libs libdpdk) +LDFLAGS_STATIC = $(shell $(PKGCONF) --static --libs libdpdk) + +CFLAGS += -DALLOW_EXPERIMENTAL_API + +build/$(APP)-shared: $(SRCS-y) Makefile $(PC_FILE) | build + $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_SHARED) + +build/$(APP)-static: $(SRCS-y) Makefile $(PC_FILE) | build + $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_STATIC) + +build: + @mkdir -p $@ + +.PHONY: clean +clean: + rm -f build/$(APP) build/$(APP)-static build/$(APP)-shared + test -d build && rmdir -p build || true diff --git a/examples/ptp_tap_relay_sw/meson.build b/examples/ptp_tap_relay_sw/meson.build new file mode 100644 index 0000000000..56abad02ed --- /dev/null +++ b/examples/ptp_tap_relay_sw/meson.build @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2026 Intel Corporation + +# meson file, for building this example as part of a main DPDK build. +# +# To build this example as a standalone application with an already-installed +# DPDK instance, use 'make' + +allow_experimental_apis = true +sources = files( + 'ptp_tap_relay_sw.c', +) +deps += ['net', 'ptp'] +cflags += no_shadow_cflag diff --git a/examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c b/examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c new file mode 100644 index 0000000000..ba2c6bf843 --- /dev/null +++ b/examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c @@ -0,0 +1,432 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2026 Intel Corporation + */ + +/* + * PTP Software Relay — lib/ptp usage example + * + * A minimal PTP relay between a DPDK-bound physical NIC and a kernel + * TAP interface using software timestamps only. No patched TUN driver + * or ptp_proxy kernel module is required. + * + * How it works: + * 1. Physical NIC receives PTP (and non-PTP) packets via DPDK RX. + * 2. For PTP event messages (Sync, Delay_Req, PDelay_Req, PDelay_Resp) + * the relay records an RX software timestamp (clock_gettime). + * 3. Just before TX on the other side it records a TX software timestamp. + * 4. The relay residence time (tx_ts − rx_ts) is added to the PTP + * correctionField via rte_ptp_add_correction() — standard + * Transparent Clock behaviour (IEEE 1588-2019 §10.2). + * 5. Packets are forwarded bi-directionally: + * PHY → TAP (network → ptp4l) + * TAP → PHY (ptp4l → network) + * + * ptp4l runs in software-timestamping mode on the TAP interface: + * + * ptp4l -i dtap0 -m -s -S # -S = software timestamps + * + * Topology: + * + * Time Transmitter (remote) ──L2── Physical NIC (DPDK) + * │ + * PTP SW Relay ← correctionField update + * │ + * TAP (kernel) ── ptp4l -S (time receiver) + * + * Usage: + * dpdk-ptp_tap_relay_sw -l 0-1 --vdev=net_tap0,iface=dtap0 -- \ + * -p 0 -t 1 + * + * Parameters: + * -p PORT Physical NIC port ID (default: 0) + * -t PORT TAP port ID (default: 1) + * -T SECS Stats print interval in seconds (default: 10) + */ + +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <stdbool.h> +#include <signal.h> +#include <getopt.h> +#include <time.h> + +#include <rte_eal.h> +#include <rte_ethdev.h> +#include <rte_mbuf.h> +#include <rte_cycles.h> +#include <rte_lcore.h> +#include <rte_ptp.h> + +/* Ring sizes */ +#define RX_RING_SIZE 1024 +#define TX_RING_SIZE 1024 + +/* Mempool */ +#define NUM_MBUFS 8191 +#define MBUF_CACHE 250 +#define BURST_SIZE 32 + +#define NSEC_PER_SEC 1000000000ULL + +/* Logging helpers */ +#define LOG_INFO(fmt, ...) \ + fprintf(stdout, "[PTP-SW] " fmt "\n", ##__VA_ARGS__) +#define LOG_ERR(fmt, ...) \ + fprintf(stderr, "[PTP-SW ERROR] " fmt "\n", ##__VA_ARGS__) + +static volatile bool force_quit; + +/* Port IDs */ +static uint16_t phy_port; +static uint16_t tap_port = 1; +static unsigned int stats_interval = 10; /* seconds */ + +/* Statistics */ +static struct { + uint64_t phy_rx; /* total packets from PHY */ + uint64_t phy_rx_ptp; /* PTP packets from PHY */ + uint64_t tap_tx; /* packets forwarded to TAP */ + uint64_t tap_rx; /* total packets from TAP */ + uint64_t tap_rx_ptp; /* PTP packets from TAP */ + uint64_t phy_tx; /* packets forwarded to PHY */ + uint64_t corrections; /* correctionField updates */ +} stats; + +static void +signal_handler(int signum) +{ + if (signum == SIGINT || signum == SIGTERM) { + LOG_INFO("Signal %d received, shutting down...", signum); + force_quit = true; + } +} + +/* Helpers */ + +/* Read monotonic clock in nanoseconds (for residence time). */ +static inline uint64_t +sw_timestamp_ns(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * NSEC_PER_SEC + (uint64_t)ts.tv_nsec; +} + +/* Port Init */ + +static int +port_init(uint16_t port, struct rte_mempool *mp) +{ + struct rte_eth_conf port_conf; + struct rte_eth_dev_info dev_info; + uint16_t nb_rxd = RX_RING_SIZE; + uint16_t nb_txd = TX_RING_SIZE; + int ret; + + memset(&port_conf, 0, sizeof(port_conf)); + + ret = rte_eth_dev_info_get(port, &dev_info); + if (ret != 0) { + LOG_ERR("rte_eth_dev_info_get(port %u) failed: %d", port, ret); + return ret; + } + + if (dev_info.tx_offload_capa & RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE) + port_conf.txmode.offloads |= + RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE; + + ret = rte_eth_dev_configure(port, 1, 1, &port_conf); + if (ret != 0) + return ret; + + ret = rte_eth_dev_adjust_nb_rx_tx_desc(port, &nb_rxd, &nb_txd); + if (ret != 0) + return ret; + + ret = rte_eth_rx_queue_setup(port, 0, nb_rxd, + rte_eth_dev_socket_id(port), NULL, mp); + if (ret < 0) + return ret; + + ret = rte_eth_tx_queue_setup(port, 0, nb_txd, + rte_eth_dev_socket_id(port), NULL); + if (ret < 0) + return ret; + + ret = rte_eth_dev_start(port); + if (ret < 0) + return ret; + + ret = rte_eth_promiscuous_enable(port); + if (ret != 0) { + LOG_ERR("Failed to enable promiscuous on port %u: %s", + port, rte_strerror(-ret)); + return ret; + } + + return 0; +} + +/* Relay one direction */ + +/* + * Forward packets from src_port to dst_port. + * For PTP event messages, record SW timestamps around the + * relay path and add the residence time to the correctionField. + * + * This implements a Transparent Clock (IEEE 1588-2019 §10.2): + * correctionField += (t_egress − t_ingress) + * + * Note: a single rx_ts / tx_ts pair is used for the entire burst. + * At typical PTP rates (logSyncInterval >= -4, i.e. <= 16 pkt/s) + * bursts contain at most one packet, so this is exact. At higher + * rates, early packets in a burst are slightly under-corrected and + * late ones over-corrected by up to one poll-loop iteration. + */ +static void +relay_burst(uint16_t src_port, uint16_t dst_port, + uint64_t *rx_cnt, uint64_t *rx_ptp_cnt, + uint64_t *tx_cnt, uint64_t *corr_cnt) +{ + struct rte_mbuf *bufs[BURST_SIZE]; + struct rte_ptp_hdr *ptp_hdrs[BURST_SIZE]; + uint64_t rx_ts; + uint16_t nb_rx, nb_tx, i; + + nb_rx = rte_eth_rx_burst(src_port, 0, bufs, BURST_SIZE); + if (nb_rx == 0) + return; + + /* Record a single RX software timestamp for the whole burst. + * All packets in one burst arrived at essentially the same instant + * from rte_eth_rx_burst()'s perspective. + */ + rx_ts = sw_timestamp_ns(); + + *rx_cnt += nb_rx; + + /* + * Pass 1: Parse each packet once and remember PTP event headers. + * This avoids taking the TX timestamp too early — we want it as + * close to the actual rte_eth_tx_burst() call as possible. + */ + memset(ptp_hdrs, 0, sizeof(ptp_hdrs[0]) * nb_rx); + for (i = 0; i < nb_rx; i++) { + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(bufs[i]); + + if (hdr == NULL) + continue; + + (*rx_ptp_cnt)++; + + /* Only event messages carry timestamps that need correction */ + if (!rte_ptp_is_event(rte_ptp_msg_type(hdr))) + continue; + + ptp_hdrs[i] = hdr; + } + + /* + * Pass 2: Take a single TX timestamp right before transmission. + * This minimises the gap between the measured tx_ts and the + * actual kernel write inside rte_eth_tx_burst(), giving the + * most accurate residence time we can achieve with SW timestamps. + * + * residence_time = tx_ts − rx_ts + * + * Remaining untracked delays: + * - Pre-RX: NIC DMA → rx_burst return (~1-5 µs, unavoidable) + * - Post-TX: tx_ts → kernel TAP write (~1-2 µs) + * Both are symmetric for Sync and Delay_Req so they largely + * cancel in the ptp4l offset calculation. + */ + uint64_t tx_ts = sw_timestamp_ns(); + int64_t residence_ns = (int64_t)(tx_ts - rx_ts); + + for (i = 0; i < nb_rx; i++) { + if (ptp_hdrs[i] == NULL) + continue; + rte_ptp_add_correction(ptp_hdrs[i], residence_ns); + (*corr_cnt)++; + } + + /* Forward the burst */ + nb_tx = rte_eth_tx_burst(dst_port, 0, bufs, nb_rx); + *tx_cnt += nb_tx; + + /* Free any unsent packets */ + for (i = nb_tx; i < nb_rx; i++) + rte_pktmbuf_free(bufs[i]); +} + +/* Print statistics */ + +static void +print_stats(void) +{ + LOG_INFO("=== Statistics ==="); + LOG_INFO(" PHY RX total: %"PRIu64, stats.phy_rx); + LOG_INFO(" PHY RX PTP: %"PRIu64, stats.phy_rx_ptp); + LOG_INFO(" TAP TX: %"PRIu64, stats.tap_tx); + LOG_INFO(" TAP RX total: %"PRIu64, stats.tap_rx); + LOG_INFO(" TAP RX PTP: %"PRIu64, stats.tap_rx_ptp); + LOG_INFO(" PHY TX: %"PRIu64, stats.phy_tx); + LOG_INFO(" Corrections: %"PRIu64, stats.corrections); +} + +/* Main relay loop */ + +static int +relay_loop(__rte_unused void *arg) +{ + uint64_t last_stats = rte_rdtsc(); + uint64_t stats_tsc = rte_get_tsc_hz() * stats_interval; + + LOG_INFO("Relay loop started on lcore %u", rte_lcore_id()); + LOG_INFO(" PHY port %u <--> TAP port %u", phy_port, tap_port); + LOG_INFO(" Correction field updates: enabled for event messages"); + + while (!force_quit) { + /* PHY → TAP */ + relay_burst(phy_port, tap_port, + &stats.phy_rx, &stats.phy_rx_ptp, + &stats.tap_tx, &stats.corrections); + + /* TAP → PHY */ + relay_burst(tap_port, phy_port, + &stats.tap_rx, &stats.tap_rx_ptp, + &stats.phy_tx, &stats.corrections); + + /* Periodic stats */ + if (rte_rdtsc() - last_stats > stats_tsc) { + print_stats(); + last_stats = rte_rdtsc(); + } + } + + print_stats(); + return 0; +} + +/* Argument parsing */ + +static void +usage(const char *prog) +{ + fprintf(stderr, + "Usage: %s [EAL options] -- [options]\n" + " -p PORT Physical NIC port ID (default: 0)\n" + " -t PORT TAP port ID (default: 1)\n" + " -T SECS Stats interval in seconds (default: 10)\n" + "\n" + "Example:\n" + " %s -l 0-1 --vdev=net_tap0,iface=dtap0 -- -p 0 -t 1\n" + "\n" + "Then run ptp4l with software timestamps:\n" + " ptp4l -i dtap0 -m -s -S\n", + prog, prog); +} + +static int +parse_args(int argc, char **argv) +{ + int opt; + + while ((opt = getopt(argc, argv, "p:t:T:h")) != -1) { + switch (opt) { + case 'p': + phy_port = (uint16_t)atoi(optarg); + break; + case 't': + tap_port = (uint16_t)atoi(optarg); + break; + case 'T': + stats_interval = (unsigned int)atoi(optarg); + break; + case 'h': + default: + usage(argv[0]); + return -1; + } + } + + return 0; +} + +/* Main */ + +int +main(int argc, char **argv) +{ + struct rte_mempool *mp; + uint16_t nb_ports; + int ret; + + /* EAL init */ + ret = rte_eal_init(argc, argv); + if (ret < 0) + rte_exit(EXIT_FAILURE, "EAL init failed\n"); + argc -= ret; + argv += ret; + + /* App args */ + ret = parse_args(argc, argv); + if (ret < 0) + rte_exit(EXIT_FAILURE, "Invalid arguments\n"); + + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + + nb_ports = rte_eth_dev_count_avail(); + if (nb_ports < 2) + rte_exit(EXIT_FAILURE, + "Need at least 2 ports (PHY + TAP).\n" + "Use --vdev=net_tap0,iface=dtap0\n"); + + if (!rte_eth_dev_is_valid_port(phy_port)) + rte_exit(EXIT_FAILURE, "Invalid PHY port %u\n", phy_port); + if (!rte_eth_dev_is_valid_port(tap_port)) + rte_exit(EXIT_FAILURE, "Invalid TAP port %u\n", tap_port); + + mp = rte_pktmbuf_pool_create("MBUF_POOL", NUM_MBUFS * nb_ports, + MBUF_CACHE, 0, + RTE_MBUF_DEFAULT_BUF_SIZE, + rte_socket_id()); + if (mp == NULL) + rte_exit(EXIT_FAILURE, "Cannot create mbuf pool\n"); + + LOG_INFO("Initializing PHY port %u...", phy_port); + ret = port_init(phy_port, mp); + if (ret != 0) + rte_exit(EXIT_FAILURE, "Cannot init PHY port %u (%d)\n", + phy_port, ret); + + LOG_INFO("Initializing TAP port %u...", tap_port); + ret = port_init(tap_port, mp); + if (ret != 0) + rte_exit(EXIT_FAILURE, "Cannot init TAP port %u (%d)\n", + tap_port, ret); + + LOG_INFO("PTP Software Relay ready"); + LOG_INFO(" PHY port: %u", phy_port); + LOG_INFO(" TAP port: %u", tap_port); + LOG_INFO(" Stats every: %u seconds", stats_interval); + LOG_INFO(" Correction: Transparent Clock (SW timestamps)"); + LOG_INFO(""); + LOG_INFO("Run ptp4l: ptp4l -i dtap0 -m -s -S"); + + /* Run relay on main lcore */ + relay_loop(NULL); + + /* Cleanup */ + LOG_INFO("Stopping ports..."); + rte_eth_dev_stop(phy_port); + rte_eth_dev_stop(tap_port); + rte_eth_dev_close(phy_port); + rte_eth_dev_close(tap_port); + rte_eal_cleanup(); + + return 0; +} -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [RFC v5 4/6] doc: add PTP software relay sample app guide 2026-05-06 15:41 ` [RFC v5 0/6] introduce PTP protocol library and software relay Rajesh Kumar ` (2 preceding siblings ...) 2026-05-06 15:41 ` [RFC v5 3/6] examples/ptp_tap_relay_sw: add software PTP relay example Rajesh Kumar @ 2026-05-06 15:41 ` Rajesh Kumar 2026-05-06 15:41 ` [RFC v5 5/6] app/test: add PTP library unit tests Rajesh Kumar ` (2 subsequent siblings) 6 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-06 15:41 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, Rajesh Kumar [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #1: Type: text/plain; charset=y, Size: 8125 bytes --] Add a sample application user guide for the ptp_tap_relay_sw example. The guide covers the application topology, packet flow, compilation, command-line options, and a code walkthrough explaining the two-pass relay burst design and CLOCK_MONOTONIC_RAW timestamp source selection. Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- doc/guides/sample_app_ug/index.rst | 1 + doc/guides/sample_app_ug/ptp_tap_relay_sw.rst | 212 ++++++++++++++++++ 2 files changed, 213 insertions(+) create mode 100644 doc/guides/sample_app_ug/ptp_tap_relay_sw.rst diff --git a/doc/guides/sample_app_ug/index.rst b/doc/guides/sample_app_ug/index.rst index e895f692f9..f12623bb66 100644 --- a/doc/guides/sample_app_ug/index.rst +++ b/doc/guides/sample_app_ug/index.rst @@ -51,6 +51,7 @@ Sample Applications User Guides dist_app vm_power_management ptpclient + ptp_tap_relay_sw fips_validation ipsec_secgw bbdev_app diff --git a/doc/guides/sample_app_ug/ptp_tap_relay_sw.rst b/doc/guides/sample_app_ug/ptp_tap_relay_sw.rst new file mode 100644 index 0000000000..1af2601fdb --- /dev/null +++ b/doc/guides/sample_app_ug/ptp_tap_relay_sw.rst @@ -0,0 +1,212 @@ +.. SPDX-License-Identifier: BSD-3-Clause + Copyright(c) 2026 Intel Corporation. + +PTP Software Relay Sample Application +====================================== + +The PTP Software Relay sample application demonstrates how to use the +DPDK ``lib/ptp`` library to build a minimal PTP Transparent Clock relay +between a DPDK-bound physical NIC and a kernel TAP interface using +**software timestamps only**. + +No patched kernel modules or custom TAP PMD changes are required. +The application works with an unmodified Linux kernel and stock DPDK. + +For background on PTP see: +`Precision Time Protocol +<https://en.wikipedia.org/wiki/Precision_Time_Protocol>`_. + + +Limitations +----------- + +* Tested with L2 PTP (EtherType 0x88F7) on the wire. + The underlying PTP library also classifies VLAN/QinQ and UDP/IPv4/IPv6. +* Only PTP v2 messages are processed. +* Software timestamps have microsecond-class jitter; sub-microsecond + precision depends on system load and NIC-to-TAP forwarding latency. +* The PTP time transmitter must be reachable on the physical NIC's L2 network. +* Only one physical port and one TAP port are supported. + + +How the Application Works +------------------------- + +Topology +~~~~~~~~ + +:: + + PTP Time Transmitter Physical NIC TAP (kernel) + (ptp4l -H) ──L2── (DPDK vfio-pci) ────── dtap0 + │ │ + ptp_tap_relay_sw ptp4l -S + (correctionField += (SW timestamps, + residence time) adjusts CLOCK_REALTIME) + +The relay sits between a DPDK-owned physical NIC and a kernel TAP +virtual interface. ``ptp4l`` runs on the TAP interface in software +timestamp mode (``-S``) as a PTP time receiver. + +Packet Flow +~~~~~~~~~~~ + +1. The physical NIC receives PTP (and non-PTP) packets via DPDK RX. +2. A software RX timestamp is recorded using + ``clock_gettime(CLOCK_MONOTONIC)``. +3. Each packet is classified with ``rte_ptp_classify()``. +4. For PTP **event** messages (Sync, Delay_Req, PDelay_Req, PDelay_Resp), + a TX software timestamp is taken just before transmission. +5. The residence time (``tx_ts − rx_ts``) is added to the PTP + ``correctionField`` via ``rte_ptp_add_correction()`` — standard + IEEE 1588-2019 Transparent Clock behaviour (§10.2). +6. Packets are forwarded bidirectionally: + + * PHY → TAP (network → ptp4l) + * TAP → PHY (ptp4l → network) + +A two-pass design is used: first all packets are classified and PTP +header pointers saved, then a single TX timestamp is taken immediately +before applying corrections and calling ``rte_eth_tx_burst()``. +This minimises the gap between the measured timestamp and the actual +wire egress. + + +Compiling the Application +------------------------- + +To compile the sample application see :doc:`compiling`. + +The application is located in the ``ptp_tap_relay_sw`` sub-directory. + +.. note:: + + The application depends on the ``ptp`` library. + Ensure that ``lib/ptp`` is built (it is built by default). + + +Running the Application +----------------------- + +Prerequisites +~~~~~~~~~~~~~ + +* A PTP-capable physical NIC bound to DPDK (e.g. via ``vfio-pci``). +* ``linuxptp`` (``ptp4l``) installed on the system. +* A PTP time transmitter reachable on the same L2 network. + +Start the relay +~~~~~~~~~~~~~~~~ + +.. code-block:: console + + ./<build_dir>/examples/dpdk-ptp_tap_relay_sw \ + -l 18-19 -a 0000:cc:00.1 --vdev=net_tap0,iface=dtap0 -- \ + -p 0 -t 1 -T 10 + +Command-line Options +~~~~~~~~~~~~~~~~~~~~ + +* ``-p PORT`` — Physical NIC port ID (default: 0). +* ``-t PORT`` — TAP port ID (default: 1). +* ``-T SECS`` — Statistics print interval in seconds (default: 10). + +Start PTP time transmitter +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +On a separate terminal or remote host, start ``ptp4l`` as time +transmitter with hardware timestamps on the physical NIC: + +.. code-block:: console + + ptp4l -i <iface> -m -2 -H --serverOnly=1 \ + --logSyncInterval=-4 --logMinDelayReqInterval=-4 + +Start PTP time receiver +~~~~~~~~~~~~~~~~~~~~~~~ + +On the TAP interface, start ``ptp4l`` in software timestamp mode: + +.. code-block:: console + + ptp4l -i dtap0 -m -2 -s -S \ + --delay_filter=moving_median --delay_filter_length=10 + +The time receiver will enter UNCALIBRATED state for approximately 60 +seconds while the PI servo estimates the frequency offset, then step +the clock and enter time-receiver (synchronized) state. +Steady-state RMS offset of 500–1000 ns is typical on a lightly loaded +system with a hardware-timestamped time transmitter. + +Example Output +~~~~~~~~~~~~~~ + +Relay statistics printed every ``-T`` seconds: + +:: + + [PTP-SW] === Statistics === + [PTP-SW] PHY RX total: 5646 + [PTP-SW] PHY RX PTP: 5598 + [PTP-SW] TAP TX: 5646 + [PTP-SW] TAP RX total: 1800 + [PTP-SW] TAP RX PTP: 1788 + [PTP-SW] PHY TX: 1800 + [PTP-SW] Corrections: 3635 + +Time receiver ``ptp4l`` output after convergence: + +:: + + ptp4l[451534.520]: rms 630 max 1166 freq -44365 +/- 100 delay 37668 +/- 71 + ptp4l[451539.525]: rms 602 max 1177 freq -44339 +/- 119 delay 37517 +/- 43 + ptp4l[451544.530]: rms 535 max 1194 freq -44345 +/- 103 delay 37410 +/- 81 + + +Code Explanation +---------------- + +The following sections explain the main components of the application. + +Relay Burst Function +~~~~~~~~~~~~~~~~~~~~ + +The core relay logic is in ``relay_burst()``, which handles one direction +(PHY→TAP or TAP→PHY) per call: + +**Pass 1 — Classify:** + +For each received packet, ``rte_ptp_classify()`` determines if it is a +PTP message. For event messages, ``rte_ptp_hdr_get()`` retrieves a +pointer to the PTP header, which is saved for the second pass. + +**Pass 2 — Timestamp and correct:** + +A single software TX timestamp is taken via +``clock_gettime(CLOCK_MONOTONIC)``. The residence time +(``tx_ts − rx_ts``) is added to each saved PTP header's +``correctionField`` using ``rte_ptp_add_correction()``. +The burst is then transmitted with ``rte_eth_tx_burst()``. + +Main Loop +~~~~~~~~~ + +The ``relay_loop()`` function polls both directions in a tight loop: + +.. code-block:: c + + while (!force_quit) { + relay_burst(phy_port, tap_port, ...); /* PHY → TAP */ + relay_burst(tap_port, phy_port, ...); /* TAP → PHY */ + } + +Statistics are printed at the interval specified by ``-T``. + +Timestamp Source +~~~~~~~~~~~~~~~~ + +``CLOCK_MONOTONIC`` is used rather than ``CLOCK_REALTIME`` because +the PTP time receiver's servo continuously adjusts ``CLOCK_REALTIME``. +Using ``CLOCK_REALTIME`` would corrupt residence time measurements +during clock stepping or frequency slewing. ``CLOCK_MONOTONIC`` is +portable across Linux and FreeBSD. -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [RFC v5 5/6] app/test: add PTP library unit tests 2026-05-06 15:41 ` [RFC v5 0/6] introduce PTP protocol library and software relay Rajesh Kumar ` (3 preceding siblings ...) 2026-05-06 15:41 ` [RFC v5 4/6] doc: add PTP software relay sample app guide Rajesh Kumar @ 2026-05-06 15:41 ` Rajesh Kumar 2026-05-06 15:41 ` [RFC v5 6/6] examples/ptpclient: use shared PTP library definitions Rajesh Kumar 2026-05-06 15:45 ` [RFC v5 0/6] introduce PTP protocol library and software relay Stephen Hemminger 6 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-06 15:41 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, Rajesh Kumar Add autotest for the PTP library covering: - Transport classification (L2, VLAN, QinQ, UDP/IPv4, UDP/IPv6) - Message type parsing and string conversion - Inline helpers (is_event, two_step, seq_id, version, domain) - Correction field arithmetic (add, accumulate, negative values) - Timestamp nanosecond conversion - Flag field extraction (TWO_STEP, UNICAST, LI_61, LI_59) Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- MAINTAINERS | 1 + app/test/meson.build | 1 + app/test/test_ptp.c | 1105 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1107 insertions(+) create mode 100644 app/test/test_ptp.c diff --git a/MAINTAINERS b/MAINTAINERS index 665e08dc90..8d509cbe8e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1668,6 +1668,7 @@ F: app/test-sad/ PTP - EXPERIMENTAL M: Rajesh Kumar <rajesh3.kumar@intel.com> F: lib/ptp/ +F: app/test/test_ptp.c F: doc/guides/prog_guide/ptp_lib.rst F: examples/ptp_tap_relay_sw/ F: doc/guides/sample_app_ug/ptp_tap_relay_sw.rst diff --git a/app/test/meson.build b/app/test/meson.build index 7d458f9c07..ecaa8d0839 100644 --- a/app/test/meson.build +++ b/app/test/meson.build @@ -154,6 +154,7 @@ source_file_deps = { 'test_power_kvm_vm.c': ['power', 'power_kvm_vm'], 'test_prefetch.c': [], 'test_ptr_compress.c': ['ptr_compress'], + 'test_ptp.c': ['ptp'], 'test_rand_perf.c': [], 'test_rawdev.c': ['rawdev', 'bus_vdev', 'raw_skeleton'], 'test_rcu_qsbr.c': ['rcu', 'hash'], diff --git a/app/test/test_ptp.c b/app/test/test_ptp.c new file mode 100644 index 0000000000..a30b92cbc1 --- /dev/null +++ b/app/test/test_ptp.c @@ -0,0 +1,1105 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2026 Intel Corporation + */ + +#include "test.h" + +#include <string.h> +#include <stdint.h> +#include <inttypes.h> + +#include <rte_mbuf.h> +#include <rte_mempool.h> +#include <rte_memory.h> +#include <rte_ether.h> +#include <rte_ip.h> +#include <rte_udp.h> +#include <rte_byteorder.h> +#include <rte_ptp.h> + +#define PTP_TEST_MP_NAME "test_ptp_pool" +#define PTP_TEST_MP_SIZE 63 +#define PTP_TEST_BUF_SIZE RTE_MBUF_DEFAULT_BUF_SIZE + +static struct rte_mempool *ptp_mp; + +/* Helper: fill a minimal PTP header */ +static void +fill_ptp_hdr(struct rte_ptp_hdr *ptp, uint8_t msg_type, uint16_t flags_host, + int64_t correction_scaled_ns, uint16_t seq_id) +{ + memset(ptp, 0, sizeof(*ptp)); + ptp->msg_type = msg_type; + ptp->version = 0x02; + ptp->msg_length = rte_cpu_to_be_16(34); + ptp->flags = rte_cpu_to_be_16(flags_host); + ptp->correction = rte_cpu_to_be_64(correction_scaled_ns); + ptp->sequence_id = rte_cpu_to_be_16(seq_id); +} + +/* ================================================================ + * Packet builders + * ================================================================ + */ + +static struct rte_mbuf * +build_l2_ptp(uint8_t msg_type) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *) + rte_pktmbuf_append(m, sizeof(*eth) + sizeof(struct rte_ptp_hdr)); + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(0x88F7); + + struct rte_ptp_hdr *ptp = (struct rte_ptp_hdr *)((uint8_t *)eth + sizeof(*eth)); + fill_ptp_hdr(ptp, msg_type, RTE_PTP_FLAG_TWO_STEP, 0, 100); + return m; +} + +/* Build L2 PTP with no TWO_STEP flag */ +static struct rte_mbuf * +build_l2_ptp_noflags(uint8_t msg_type) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *) + rte_pktmbuf_append(m, sizeof(*eth) + sizeof(struct rte_ptp_hdr)); + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(0x88F7); + + struct rte_ptp_hdr *ptp = (struct rte_ptp_hdr *)((uint8_t *)eth + sizeof(*eth)); + fill_ptp_hdr(ptp, msg_type, 0, 0, 200); + return m; +} + +static struct rte_mbuf * +build_vlan_l2_ptp(uint8_t msg_type, uint16_t tpid) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + sizeof(struct rte_vlan_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(tpid); + + struct rte_vlan_hdr *vlan = (struct rte_vlan_hdr *)(data + sizeof(*eth)); + vlan->vlan_tci = rte_cpu_to_be_16(100); + vlan->eth_proto = rte_cpu_to_be_16(0x88F7); + + struct rte_ptp_hdr *ptp = (struct rte_ptp_hdr *) + (data + sizeof(*eth) + sizeof(*vlan)); + fill_ptp_hdr(ptp, msg_type, 0, 0, 200); + return m; +} + +static struct rte_mbuf * +build_qinq_l2_ptp(uint8_t msg_type, uint16_t outer_tpid, uint16_t inner_tpid) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + 2 * sizeof(struct rte_vlan_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(outer_tpid); + + uint32_t off = sizeof(*eth); + struct rte_vlan_hdr *vo = (struct rte_vlan_hdr *)(data + off); + vo->vlan_tci = rte_cpu_to_be_16(200); + vo->eth_proto = rte_cpu_to_be_16(inner_tpid); + off += sizeof(*vo); + + struct rte_vlan_hdr *vi = (struct rte_vlan_hdr *)(data + off); + vi->vlan_tci = rte_cpu_to_be_16(300); + vi->eth_proto = rte_cpu_to_be_16(0x88F7); + off += sizeof(*vi); + + struct rte_ptp_hdr *ptp = (struct rte_ptp_hdr *)(data + off); + fill_ptp_hdr(ptp, msg_type, 0, 0, 300); + return m; +} + +/* Helper: append IPv4 + UDP + PTP after offset */ +static void +fill_ipv4_udp_ptp(uint8_t *data, uint32_t off, uint8_t msg_type, + uint16_t dst_port, uint16_t seq_id) +{ + struct rte_ipv4_hdr *iph = (struct rte_ipv4_hdr *)(data + off); + memset(iph, 0, sizeof(*iph)); + iph->version_ihl = 0x45; + iph->next_proto_id = IPPROTO_UDP; + iph->total_length = rte_cpu_to_be_16( + sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr)); + iph->src_addr = rte_cpu_to_be_32(0x0A000001); + iph->dst_addr = rte_cpu_to_be_32(0xE0000181); + off += sizeof(*iph); + + struct rte_udp_hdr *udp = (struct rte_udp_hdr *)(data + off); + memset(udp, 0, sizeof(*udp)); + udp->src_port = rte_cpu_to_be_16(12345); + udp->dst_port = rte_cpu_to_be_16(dst_port); + udp->dgram_len = rte_cpu_to_be_16(sizeof(*udp) + + sizeof(struct rte_ptp_hdr)); + off += sizeof(*udp); + + struct rte_ptp_hdr *ptp = (struct rte_ptp_hdr *)(data + off); + fill_ptp_hdr(ptp, msg_type, 0, 0, seq_id); +} + +static struct rte_mbuf * +build_ipv4_udp_ptp(uint8_t msg_type, uint16_t dst_port) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + sizeof(struct rte_ipv4_hdr) + + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4); + + fill_ipv4_udp_ptp(data, sizeof(*eth), msg_type, dst_port, 400); + return m; +} + +static struct rte_mbuf * +build_vlan_ipv4_udp_ptp(uint8_t msg_type, uint16_t dst_port) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + sizeof(struct rte_vlan_hdr) + + sizeof(struct rte_ipv4_hdr) + + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN); + + uint32_t off = sizeof(*eth); + struct rte_vlan_hdr *vlan = (struct rte_vlan_hdr *)(data + off); + vlan->vlan_tci = rte_cpu_to_be_16(100); + vlan->eth_proto = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4); + off += sizeof(*vlan); + + fill_ipv4_udp_ptp(data, off, msg_type, dst_port, 500); + return m; +} + +static struct rte_mbuf * +build_qinq_ipv4_udp_ptp(uint8_t msg_type, uint16_t dst_port) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + 2 * sizeof(struct rte_vlan_hdr) + + sizeof(struct rte_ipv4_hdr) + + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_QINQ); + + uint32_t off = sizeof(*eth); + struct rte_vlan_hdr *vo = (struct rte_vlan_hdr *)(data + off); + vo->vlan_tci = rte_cpu_to_be_16(200); + vo->eth_proto = rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN); + off += sizeof(*vo); + + struct rte_vlan_hdr *vi = (struct rte_vlan_hdr *)(data + off); + vi->vlan_tci = rte_cpu_to_be_16(300); + vi->eth_proto = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4); + off += sizeof(*vi); + + fill_ipv4_udp_ptp(data, off, msg_type, dst_port, 600); + return m; +} + +/* Helper: append IPv6 + UDP + PTP */ +static void +fill_ipv6_udp_ptp(uint8_t *data, uint32_t off, uint8_t msg_type, + uint16_t dst_port, uint16_t seq_id) +{ + struct rte_ipv6_hdr *ip6 = (struct rte_ipv6_hdr *)(data + off); + memset(ip6, 0, sizeof(*ip6)); + ip6->vtc_flow = rte_cpu_to_be_32(0x60000000); + ip6->payload_len = rte_cpu_to_be_16( + sizeof(struct rte_udp_hdr) + sizeof(struct rte_ptp_hdr)); + ip6->proto = IPPROTO_UDP; + ip6->hop_limits = 64; + off += sizeof(*ip6); + + struct rte_udp_hdr *udp = (struct rte_udp_hdr *)(data + off); + memset(udp, 0, sizeof(*udp)); + udp->src_port = rte_cpu_to_be_16(12345); + udp->dst_port = rte_cpu_to_be_16(dst_port); + udp->dgram_len = rte_cpu_to_be_16(sizeof(*udp) + + sizeof(struct rte_ptp_hdr)); + off += sizeof(*udp); + + struct rte_ptp_hdr *ptp = (struct rte_ptp_hdr *)(data + off); + fill_ptp_hdr(ptp, msg_type, 0, 0, seq_id); +} + +static struct rte_mbuf * +build_ipv6_udp_ptp(uint8_t msg_type, uint16_t dst_port) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + sizeof(struct rte_ipv6_hdr) + + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV6); + + fill_ipv6_udp_ptp(data, sizeof(*eth), msg_type, dst_port, 700); + return m; +} + +static struct rte_mbuf * +build_vlan_ipv6_udp_ptp(uint8_t msg_type, uint16_t dst_port) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + sizeof(struct rte_vlan_hdr) + + sizeof(struct rte_ipv6_hdr) + + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN); + + uint32_t off = sizeof(*eth); + struct rte_vlan_hdr *vlan = (struct rte_vlan_hdr *)(data + off); + vlan->vlan_tci = rte_cpu_to_be_16(100); + vlan->eth_proto = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV6); + off += sizeof(*vlan); + + fill_ipv6_udp_ptp(data, off, msg_type, dst_port, 800); + return m; +} + +static struct rte_mbuf * +build_qinq_ipv6_udp_ptp(uint8_t msg_type, uint16_t dst_port) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + 2 * sizeof(struct rte_vlan_hdr) + + sizeof(struct rte_ipv6_hdr) + + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_QINQ); + + uint32_t off = sizeof(*eth); + struct rte_vlan_hdr *vo = (struct rte_vlan_hdr *)(data + off); + vo->vlan_tci = rte_cpu_to_be_16(200); + vo->eth_proto = rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN); + off += sizeof(*vo); + + struct rte_vlan_hdr *vi = (struct rte_vlan_hdr *)(data + off); + vi->vlan_tci = rte_cpu_to_be_16(300); + vi->eth_proto = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV6); + off += sizeof(*vi); + + fill_ipv6_udp_ptp(data, off, msg_type, dst_port, 900); + return m; +} + +static struct rte_mbuf * +build_non_ptp(void) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + 28; + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(data, 0, pkt_len); + eth->ether_type = rte_cpu_to_be_16(0x0806); + return m; +} + +static struct rte_mbuf * +build_ipv4_udp_non_ptp(void) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + if (!m) + return NULL; + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + sizeof(struct rte_ipv4_hdr) + + sizeof(struct rte_udp_hdr) + 20; + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(data, 0, pkt_len); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4); + + struct rte_ipv4_hdr *iph = (struct rte_ipv4_hdr *)(data + sizeof(*eth)); + iph->version_ihl = 0x45; + iph->next_proto_id = IPPROTO_UDP; + iph->total_length = rte_cpu_to_be_16( + sizeof(*iph) + sizeof(struct rte_udp_hdr) + 20); + + struct rte_udp_hdr *udp = (struct rte_udp_hdr *) + (data + sizeof(*eth) + sizeof(*iph)); + udp->dst_port = rte_cpu_to_be_16(53); + return m; +} + +/* ================================================================ + * Individual test cases + * ================================================================ + */ + +/* Helper: classify + hdr_get for a given mbuf */ +static int +check_classify_and_hdr_get(struct rte_mbuf *m, int expected_type) +{ + int ret; + + TEST_ASSERT_NOT_NULL(m, "mbuf allocation failed"); + + ret = rte_ptp_classify(m); + TEST_ASSERT_EQUAL(ret, expected_type, + "classify: expected %d, got %d", expected_type, ret); + + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + if (expected_type == RTE_PTP_MSGTYPE_INVALID) { + TEST_ASSERT_NULL(hdr, + "hdr_get: expected NULL for non-PTP packet"); + } else { + TEST_ASSERT_NOT_NULL(hdr, + "hdr_get: expected non-NULL for PTP packet"); + TEST_ASSERT_EQUAL(rte_ptp_msg_type(hdr), + (uint8_t)expected_type, + "hdr_get: msg_type mismatch: expected %d, got %d", + expected_type, rte_ptp_msg_type(hdr)); + } + + rte_pktmbuf_free(m); + return TEST_SUCCESS; +} + +/* Section 1: Transport classification */ +static int +test_ptp_classify_l2(void) +{ + return check_classify_and_hdr_get( + build_l2_ptp(RTE_PTP_MSGTYPE_SYNC), RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_l2_delay_req(void) +{ + return check_classify_and_hdr_get( + build_l2_ptp(RTE_PTP_MSGTYPE_DELAY_REQ), + RTE_PTP_MSGTYPE_DELAY_REQ); +} + +static int +test_ptp_classify_vlan_8100(void) +{ + return check_classify_and_hdr_get( + build_vlan_l2_ptp(RTE_PTP_MSGTYPE_SYNC, RTE_ETHER_TYPE_VLAN), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_vlan_88a8(void) +{ + return check_classify_and_hdr_get( + build_vlan_l2_ptp(RTE_PTP_MSGTYPE_SYNC, RTE_ETHER_TYPE_QINQ), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_qinq(void) +{ + return check_classify_and_hdr_get( + build_qinq_l2_ptp(RTE_PTP_MSGTYPE_SYNC, + RTE_ETHER_TYPE_QINQ, RTE_ETHER_TYPE_VLAN), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_double_8100(void) +{ + return check_classify_and_hdr_get( + build_qinq_l2_ptp(RTE_PTP_MSGTYPE_SYNC, + RTE_ETHER_TYPE_VLAN, RTE_ETHER_TYPE_VLAN), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_ipv4_udp_319(void) +{ + return check_classify_and_hdr_get( + build_ipv4_udp_ptp(RTE_PTP_MSGTYPE_SYNC, 319), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_ipv4_udp_320(void) +{ + return check_classify_and_hdr_get( + build_ipv4_udp_ptp(RTE_PTP_MSGTYPE_FOLLOW_UP, 320), + RTE_PTP_MSGTYPE_FOLLOW_UP); +} + +static int +test_ptp_classify_vlan_ipv4_udp(void) +{ + return check_classify_and_hdr_get( + build_vlan_ipv4_udp_ptp(RTE_PTP_MSGTYPE_SYNC, 319), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_qinq_ipv4_udp(void) +{ + return check_classify_and_hdr_get( + build_qinq_ipv4_udp_ptp(RTE_PTP_MSGTYPE_SYNC, 319), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_ipv6_udp_319(void) +{ + return check_classify_and_hdr_get( + build_ipv6_udp_ptp(RTE_PTP_MSGTYPE_SYNC, 319), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_ipv6_udp_320(void) +{ + return check_classify_and_hdr_get( + build_ipv6_udp_ptp(RTE_PTP_MSGTYPE_FOLLOW_UP, 320), + RTE_PTP_MSGTYPE_FOLLOW_UP); +} + +static int +test_ptp_classify_vlan_ipv6_udp(void) +{ + return check_classify_and_hdr_get( + build_vlan_ipv6_udp_ptp(RTE_PTP_MSGTYPE_SYNC, 319), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_qinq_ipv6_udp(void) +{ + return check_classify_and_hdr_get( + build_qinq_ipv6_udp_ptp(RTE_PTP_MSGTYPE_SYNC, 319), + RTE_PTP_MSGTYPE_SYNC); +} + +static int +test_ptp_classify_non_ptp_arp(void) +{ + return check_classify_and_hdr_get( + build_non_ptp(), RTE_PTP_MSGTYPE_INVALID); +} + +static int +test_ptp_classify_non_ptp_udp(void) +{ + return check_classify_and_hdr_get( + build_ipv4_udp_non_ptp(), RTE_PTP_MSGTYPE_INVALID); +} + +/* IPv4 with invalid IHL (< 5) should be rejected */ +static int +test_ptp_classify_ipv4_bad_ihl(void) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + + uint32_t pkt_len = sizeof(struct rte_ether_hdr) + + sizeof(struct rte_ipv4_hdr) + + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr); + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4); + + fill_ipv4_udp_ptp(data, sizeof(*eth), RTE_PTP_MSGTYPE_SYNC, 319, 999); + + /* Corrupt the IHL to 3 (< minimum 5) */ + struct rte_ipv4_hdr *iph = (struct rte_ipv4_hdr *) + (data + sizeof(*eth)); + iph->version_ihl = 0x43; + + return check_classify_and_hdr_get(m, RTE_PTP_MSGTYPE_INVALID); +} + +/* IPv4 with options (IHL > 5) should still be parsed correctly */ +static int +test_ptp_classify_ipv4_options(void) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + uint32_t pkt_len; + uint32_t off; + uint8_t *data; + struct rte_ether_hdr *eth; + struct rte_ipv4_hdr *iph; + struct rte_udp_hdr *udp; + struct rte_ptp_hdr *ptp; + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + + pkt_len = sizeof(struct rte_ether_hdr) + + sizeof(struct rte_ipv4_hdr) + 4 + + sizeof(struct rte_udp_hdr) + + sizeof(struct rte_ptp_hdr); + data = (uint8_t *)rte_pktmbuf_append(m, pkt_len); + TEST_ASSERT_NOT_NULL(data, "append failed"); + + eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4); + + off = sizeof(*eth); + iph = (struct rte_ipv4_hdr *)(data + off); + memset(iph, 0, sizeof(*iph)); + iph->version_ihl = 0x46; /* IHL = 6 words = 24 bytes */ + iph->next_proto_id = IPPROTO_UDP; + iph->total_length = rte_cpu_to_be_16(sizeof(struct rte_ipv4_hdr) + 4 + + sizeof(struct rte_udp_hdr) + sizeof(struct rte_ptp_hdr)); + iph->src_addr = rte_cpu_to_be_32(0x0A000001); + iph->dst_addr = rte_cpu_to_be_32(0xE0000181); + off += sizeof(*iph); + + memset(data + off, 0, 4); /* IPv4 options bytes */ + off += 4; + + udp = (struct rte_udp_hdr *)(data + off); + memset(udp, 0, sizeof(*udp)); + udp->src_port = rte_cpu_to_be_16(12345); + udp->dst_port = rte_cpu_to_be_16(RTE_PTP_EVENT_PORT); + udp->dgram_len = rte_cpu_to_be_16(sizeof(*udp) + sizeof(struct rte_ptp_hdr)); + off += sizeof(*udp); + + ptp = (struct rte_ptp_hdr *)(data + off); + fill_ptp_hdr(ptp, RTE_PTP_MSGTYPE_SYNC, 0, 0, 1001); + + return check_classify_and_hdr_get(m, RTE_PTP_MSGTYPE_SYNC); +} + +/* Truncated packet: Ethernet header only, no payload */ +static int +test_ptp_classify_truncated(void) +{ + struct rte_mbuf *m = rte_pktmbuf_alloc(ptp_mp); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + + uint8_t *data = (uint8_t *)rte_pktmbuf_append(m, + sizeof(struct rte_ether_hdr)); + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)data; + memset(eth, 0, sizeof(*eth)); + eth->ether_type = rte_cpu_to_be_16(0x88F7); + + return check_classify_and_hdr_get(m, RTE_PTP_MSGTYPE_INVALID); +} + +/* Section 2: All 10 message types via L2 */ +static int +test_ptp_all_msg_types(void) +{ + static const uint8_t types[] = { + RTE_PTP_MSGTYPE_SYNC, + RTE_PTP_MSGTYPE_DELAY_REQ, + RTE_PTP_MSGTYPE_PDELAY_REQ, + RTE_PTP_MSGTYPE_PDELAY_RESP, + RTE_PTP_MSGTYPE_FOLLOW_UP, + RTE_PTP_MSGTYPE_DELAY_RESP, + RTE_PTP_MSGTYPE_PDELAY_RESP_FU, + RTE_PTP_MSGTYPE_ANNOUNCE, + RTE_PTP_MSGTYPE_SIGNALING, + RTE_PTP_MSGTYPE_MANAGEMENT, + }; + unsigned int i; + + for (i = 0; i < RTE_DIM(types); i++) { + int ret = check_classify_and_hdr_get( + build_l2_ptp(types[i]), types[i]); + if (ret != TEST_SUCCESS) + return ret; + } + + return TEST_SUCCESS; +} + +/* Section 3: Inline helpers */ +static int +test_ptp_is_event(void) +{ + TEST_ASSERT(rte_ptp_is_event(RTE_PTP_MSGTYPE_SYNC), + "Sync should be event"); + TEST_ASSERT(rte_ptp_is_event(RTE_PTP_MSGTYPE_DELAY_REQ), + "Delay_Req should be event"); + TEST_ASSERT(rte_ptp_is_event(RTE_PTP_MSGTYPE_PDELAY_REQ), + "Pdelay_Req should be event"); + TEST_ASSERT(rte_ptp_is_event(RTE_PTP_MSGTYPE_PDELAY_RESP), + "Pdelay_Resp should be event"); + TEST_ASSERT(!rte_ptp_is_event(RTE_PTP_MSGTYPE_FOLLOW_UP), + "Follow_Up should not be event"); + TEST_ASSERT(!rte_ptp_is_event(RTE_PTP_MSGTYPE_ANNOUNCE), + "Announce should not be event"); + TEST_ASSERT(!rte_ptp_is_event(RTE_PTP_MSGTYPE_INVALID), + "INVALID (-1) should not be event"); + + return TEST_SUCCESS; +} + +static int +test_ptp_two_step(void) +{ + struct rte_mbuf *m; + struct rte_ptp_hdr *hdr; + + m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + TEST_ASSERT(rte_ptp_is_two_step(hdr), + "TWO_STEP flag should be set"); + rte_pktmbuf_free(m); + + m = build_l2_ptp_noflags(RTE_PTP_MSGTYPE_SYNC); + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + TEST_ASSERT(!rte_ptp_is_two_step(hdr), + "TWO_STEP flag should not be set"); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_seq_id(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + TEST_ASSERT_EQUAL(rte_ptp_seq_id(hdr), 100, + "seq_id: expected 100, got %u", rte_ptp_seq_id(hdr)); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_version(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + TEST_ASSERT_EQUAL(rte_ptp_version(hdr), 2, + "version: expected 2, got %u", rte_ptp_version(hdr)); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_domain(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + TEST_ASSERT_EQUAL(rte_ptp_domain(hdr), 0, + "domain: expected 0, got %u", rte_ptp_domain(hdr)); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +/* Section 4: correctionField */ +static int +test_ptp_correction_zero(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + int64_t ns = rte_ptp_correction_ns(hdr); + TEST_ASSERT_EQUAL(ns, 0, + "correction_ns: expected 0, got %" PRId64, ns); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_correction_known(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *ptp = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(ptp, "hdr_get failed"); + int64_t scaled_1000 = (int64_t)1000 << 16; + fill_ptp_hdr(ptp, RTE_PTP_MSGTYPE_SYNC, 0, scaled_1000, 0); + + int64_t ns = rte_ptp_correction_ns(ptp); + TEST_ASSERT_EQUAL(ns, 1000, + "correction_ns: expected 1000, got %" PRId64, ns); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_add_correction(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + + rte_ptp_add_correction(hdr, 500); + int64_t ns = rte_ptp_correction_ns(hdr); + TEST_ASSERT_EQUAL(ns, 500, + "add 500: expected 500, got %" PRId64, ns); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_add_correction_accumulate(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + + rte_ptp_add_correction(hdr, 300); + rte_ptp_add_correction(hdr, 700); + int64_t ns = rte_ptp_correction_ns(hdr); + TEST_ASSERT_EQUAL(ns, 1000, + "accumulate: expected 1000, got %" PRId64, ns); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_add_correction_large(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + + rte_ptp_add_correction(hdr, 1000000000LL); + int64_t ns = rte_ptp_correction_ns(hdr); + TEST_ASSERT_EQUAL(ns, 1000000000LL, + "1s: expected 1000000000, got %" PRId64, ns); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +static int +test_ptp_add_correction_negative(void) +{ + struct rte_mbuf *m = build_l2_ptp(RTE_PTP_MSGTYPE_SYNC); + + TEST_ASSERT_NOT_NULL(m, "alloc failed"); + struct rte_ptp_hdr *hdr = rte_ptp_hdr_get(m); + TEST_ASSERT_NOT_NULL(hdr, "hdr_get failed"); + + rte_ptp_add_correction(hdr, -100LL); + int64_t ns = rte_ptp_correction_ns(hdr); + TEST_ASSERT_EQUAL(ns, -100LL, + "negative: expected -100, got %" PRId64, ns); + rte_pktmbuf_free(m); + + return TEST_SUCCESS; +} + +/* Section 5: Timestamp conversion */ +static int +test_ptp_timestamp_to_ns(void) +{ + struct rte_ptp_timestamp ts; + uint64_t ns; + + /* Zero */ + memset(&ts, 0, sizeof(ts)); + ns = rte_ptp_timestamp_to_ns(&ts); + TEST_ASSERT_EQUAL(ns, 0ULL, + "zero: expected 0, got %" PRIu64, ns); + + /* 1 second */ + ts.seconds_hi = 0; + ts.seconds_lo = rte_cpu_to_be_32(1); + ts.nanoseconds = 0; + ns = rte_ptp_timestamp_to_ns(&ts); + TEST_ASSERT_EQUAL(ns, 1000000000ULL, + "1s: expected 1000000000, got %" PRIu64, ns); + + /* 1.5 seconds */ + ts.seconds_lo = rte_cpu_to_be_32(1); + ts.nanoseconds = rte_cpu_to_be_32(500000000); + ns = rte_ptp_timestamp_to_ns(&ts); + TEST_ASSERT_EQUAL(ns, 1500000000ULL, + "1.5s: expected 1500000000, got %" PRIu64, ns); + + /* Large value with seconds_hi */ + ts.seconds_hi = rte_cpu_to_be_16(1); + ts.seconds_lo = 0; + ts.nanoseconds = 0; + ns = rte_ptp_timestamp_to_ns(&ts); + uint64_t expected = ((uint64_t)1 << 32) * 1000000000ULL; + TEST_ASSERT_EQUAL(ns, expected, + "2^32s: expected %" PRIu64 ", got %" PRIu64, expected, ns); + + return TEST_SUCCESS; +} + +/* Section 6: msg_type_str */ +static int +test_ptp_msg_type_str(void) +{ + static const struct { + int type; + const char *expected; + } cases[] = { + { RTE_PTP_MSGTYPE_SYNC, "Sync" }, + { RTE_PTP_MSGTYPE_DELAY_REQ, "Delay_Req" }, + { RTE_PTP_MSGTYPE_PDELAY_REQ, "PDelay_Req" }, + { RTE_PTP_MSGTYPE_PDELAY_RESP, "PDelay_Resp" }, + { RTE_PTP_MSGTYPE_FOLLOW_UP, "Follow_Up" }, + { RTE_PTP_MSGTYPE_DELAY_RESP, "Delay_Resp" }, + { RTE_PTP_MSGTYPE_PDELAY_RESP_FU, "PDelay_Resp_Follow_Up" }, + { RTE_PTP_MSGTYPE_ANNOUNCE, "Announce" }, + { RTE_PTP_MSGTYPE_SIGNALING, "Signaling" }, + { RTE_PTP_MSGTYPE_MANAGEMENT, "Management" }, + }; + unsigned int i; + + for (i = 0; i < RTE_DIM(cases); i++) { + const char *str = rte_ptp_msg_type_str(cases[i].type); + TEST_ASSERT_NOT_NULL(str, + "msg_type_str(%d) returned NULL", cases[i].type); + TEST_ASSERT(strcmp(str, cases[i].expected) == 0, + "msg_type_str(%d): expected \"%s\", got \"%s\"", + cases[i].type, cases[i].expected, str); + } + + /* Invalid type should still return non-NULL */ + const char *inv = rte_ptp_msg_type_str(RTE_PTP_MSGTYPE_INVALID); + TEST_ASSERT_NOT_NULL(inv, + "msg_type_str(INVALID) returned NULL"); + + return TEST_SUCCESS; +} + +/* Section 7: Flag bit positions */ +static int +test_ptp_flags(void) +{ + struct rte_ptp_hdr hdr; + uint16_t f; + + /* TWO_STEP */ + memset(&hdr, 0, sizeof(hdr)); + hdr.flags = rte_cpu_to_be_16(RTE_PTP_FLAG_TWO_STEP); + f = rte_be_to_cpu_16(hdr.flags); + TEST_ASSERT(f & RTE_PTP_FLAG_TWO_STEP, + "TWO_STEP bit not set: 0x%04x", f); + TEST_ASSERT(rte_ptp_is_two_step(&hdr), + "is_two_step() should return true"); + + /* UNICAST */ + memset(&hdr, 0, sizeof(hdr)); + hdr.flags = rte_cpu_to_be_16(RTE_PTP_FLAG_UNICAST); + f = rte_be_to_cpu_16(hdr.flags); + TEST_ASSERT(f & RTE_PTP_FLAG_UNICAST, + "UNICAST bit not set: 0x%04x", f); + + /* LI_61 */ + memset(&hdr, 0, sizeof(hdr)); + hdr.flags = rte_cpu_to_be_16(RTE_PTP_FLAG_LI_61); + f = rte_be_to_cpu_16(hdr.flags); + TEST_ASSERT(f & RTE_PTP_FLAG_LI_61, + "LI_61 bit not set: 0x%04x", f); + + /* LI_59 */ + memset(&hdr, 0, sizeof(hdr)); + hdr.flags = rte_cpu_to_be_16(RTE_PTP_FLAG_LI_59); + f = rte_be_to_cpu_16(hdr.flags); + TEST_ASSERT(f & RTE_PTP_FLAG_LI_59, + "LI_59 bit not set: 0x%04x", f); + + /* Combined TWO_STEP + UNICAST */ + memset(&hdr, 0, sizeof(hdr)); + hdr.flags = rte_cpu_to_be_16( + RTE_PTP_FLAG_TWO_STEP | RTE_PTP_FLAG_UNICAST); + f = rte_be_to_cpu_16(hdr.flags); + TEST_ASSERT((f & RTE_PTP_FLAG_TWO_STEP) && + (f & RTE_PTP_FLAG_UNICAST) && + !(f & RTE_PTP_FLAG_LI_61) && + !(f & RTE_PTP_FLAG_LI_59), + "combined flags incorrect: 0x%04x", f); + + return TEST_SUCCESS; +} + +/* ================================================================ + * Suite setup / teardown + * ================================================================ + */ + +static int +test_ptp_setup(void) +{ + ptp_mp = rte_pktmbuf_pool_create(PTP_TEST_MP_NAME, PTP_TEST_MP_SIZE, + 0, 0, PTP_TEST_BUF_SIZE, SOCKET_ID_ANY); + if (ptp_mp == NULL) { + printf("Cannot create ptp test mempool\n"); + return TEST_FAILED; + } + return TEST_SUCCESS; +} + +static void +test_ptp_teardown(void) +{ + rte_mempool_free(ptp_mp); + ptp_mp = NULL; +} + +static struct unit_test_suite ptp_test_suite = { + .suite_name = "PTP Library Unit Tests", + .setup = test_ptp_setup, + .teardown = test_ptp_teardown, + .unit_test_cases = { + /* Transport classification */ + TEST_CASE(test_ptp_classify_l2), + TEST_CASE(test_ptp_classify_l2_delay_req), + TEST_CASE(test_ptp_classify_vlan_8100), + TEST_CASE(test_ptp_classify_vlan_88a8), + TEST_CASE(test_ptp_classify_qinq), + TEST_CASE(test_ptp_classify_double_8100), + TEST_CASE(test_ptp_classify_ipv4_udp_319), + TEST_CASE(test_ptp_classify_ipv4_udp_320), + TEST_CASE(test_ptp_classify_vlan_ipv4_udp), + TEST_CASE(test_ptp_classify_qinq_ipv4_udp), + TEST_CASE(test_ptp_classify_ipv6_udp_319), + TEST_CASE(test_ptp_classify_ipv6_udp_320), + TEST_CASE(test_ptp_classify_vlan_ipv6_udp), + TEST_CASE(test_ptp_classify_qinq_ipv6_udp), + TEST_CASE(test_ptp_classify_non_ptp_arp), + TEST_CASE(test_ptp_classify_non_ptp_udp), + TEST_CASE(test_ptp_classify_ipv4_bad_ihl), + TEST_CASE(test_ptp_classify_ipv4_options), + TEST_CASE(test_ptp_classify_truncated), + + /* All message types */ + TEST_CASE(test_ptp_all_msg_types), + + /* Inline helpers */ + TEST_CASE(test_ptp_is_event), + TEST_CASE(test_ptp_two_step), + TEST_CASE(test_ptp_seq_id), + TEST_CASE(test_ptp_version), + TEST_CASE(test_ptp_domain), + + /* correctionField */ + TEST_CASE(test_ptp_correction_zero), + TEST_CASE(test_ptp_correction_known), + TEST_CASE(test_ptp_add_correction), + TEST_CASE(test_ptp_add_correction_accumulate), + TEST_CASE(test_ptp_add_correction_large), + TEST_CASE(test_ptp_add_correction_negative), + + /* Timestamp conversion */ + TEST_CASE(test_ptp_timestamp_to_ns), + + /* msg_type_str */ + TEST_CASE(test_ptp_msg_type_str), + + /* Flag field bit positions */ + TEST_CASE(test_ptp_flags), + + TEST_CASES_END() + }, +}; + +static int +test_ptp(void) +{ + return unit_test_suite_runner(&ptp_test_suite); +} + +REGISTER_FAST_TEST(ptp_autotest, NOHUGE_SKIP, ASAN_OK, test_ptp); -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [RFC v5 6/6] examples/ptpclient: use shared PTP library definitions 2026-05-06 15:41 ` [RFC v5 0/6] introduce PTP protocol library and software relay Rajesh Kumar ` (4 preceding siblings ...) 2026-05-06 15:41 ` [RFC v5 5/6] app/test: add PTP library unit tests Rajesh Kumar @ 2026-05-06 15:41 ` Rajesh Kumar 2026-05-06 15:45 ` [RFC v5 0/6] introduce PTP protocol library and software relay Stephen Hemminger 6 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-06 15:41 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, Rajesh Kumar Replace local PTP struct and constant definitions with the shared rte_ptp.h library types introduced earlier in this series: - struct ptp_header -> struct rte_ptp_hdr - struct tstamp -> struct rte_ptp_timestamp - struct clock_id -> uint8_t clock_id[8] (from rte_ptp_port_id) - SYNC/FOLLOW_UP/DELAY_* defines -> RTE_PTP_MSGTYPE_* constants - PTP_PROTOCOL -> RTE_PTP_ETHERTYPE - hardcoded multicast -> RTE_PTP_MULTICAST_MAC - ptp_hdr->msg_type switch -> rte_ptp_msg_type() accessor No functional change. Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- examples/ptpclient/meson.build | 1 + examples/ptpclient/ptpclient.c | 189 +++++++++++++-------------------- 2 files changed, 74 insertions(+), 116 deletions(-) diff --git a/examples/ptpclient/meson.build b/examples/ptpclient/meson.build index 2e9b7625fc..ab074c9cb1 100644 --- a/examples/ptpclient/meson.build +++ b/examples/ptpclient/meson.build @@ -7,6 +7,7 @@ # DPDK instance, use 'make' allow_experimental_apis = true +deps += ['ptp'] sources = files( 'ptpclient.c', ) diff --git a/examples/ptpclient/ptpclient.c b/examples/ptpclient/ptpclient.c index 174ca5dd70..bb9370081a 100644 --- a/examples/ptpclient/ptpclient.c +++ b/examples/ptpclient/ptpclient.c @@ -17,6 +17,7 @@ #include <rte_lcore.h> #include <rte_mbuf.h> #include <rte_ip.h> +#include <rte_ptp.h> #include <limits.h> #include <sys/time.h> #include <getopt.h> @@ -30,21 +31,8 @@ static volatile bool force_quit; #define NUM_MBUFS 8191 #define MBUF_CACHE_SIZE 250 -/* Values for the PTP messageType field. */ -#define SYNC 0x0 -#define DELAY_REQ 0x1 -#define PDELAY_REQ 0x2 -#define PDELAY_RESP 0x3 -#define FOLLOW_UP 0x8 -#define DELAY_RESP 0x9 -#define PDELAY_RESP_FOLLOW_UP 0xA -#define ANNOUNCE 0xB -#define SIGNALING 0xC -#define MANAGEMENT 0xD - #define NSEC_PER_SEC 1000000000L #define KERNEL_TIME_ADJUST_LIMIT 20000 -#define PTP_PROTOCOL 0x88F7 struct rte_mempool *mbuf_pool; uint32_t ptp_enabled_port_mask; @@ -52,66 +40,36 @@ uint8_t ptp_enabled_port_nb; static uint8_t ptp_enabled_ports[RTE_MAX_ETHPORTS]; static const struct rte_ether_addr ether_multicast = { - .addr_bytes = {0x01, 0x1b, 0x19, 0x0, 0x0, 0x0} -}; - -/* Structs used for PTP handling. */ -struct __rte_packed_begin tstamp { - uint16_t sec_msb; - uint32_t sec_lsb; - uint32_t ns; -} __rte_packed_end; - -struct clock_id { - uint8_t id[8]; + .addr_bytes = RTE_PTP_MULTICAST_MAC }; -struct __rte_packed_begin port_id { - struct clock_id clock_id; - uint16_t port_number; -} __rte_packed_end; - -struct __rte_packed_begin ptp_header { - uint8_t msg_type; - uint8_t ver; - uint16_t message_length; - uint8_t domain_number; - uint8_t reserved1; - uint8_t flag_field[2]; - int64_t correction; - uint32_t reserved2; - struct port_id source_port_id; - uint16_t seq_id; - uint8_t control; - int8_t log_message_interval; -} __rte_packed_end; - +/* Message body structs using the library PTP header and timestamp. */ struct __rte_packed_begin sync_msg { - struct ptp_header hdr; - struct tstamp origin_tstamp; + struct rte_ptp_hdr hdr; + struct rte_ptp_timestamp origin_tstamp; } __rte_packed_end; struct __rte_packed_begin follow_up_msg { - struct ptp_header hdr; - struct tstamp precise_origin_tstamp; - uint8_t suffix[]; + struct rte_ptp_hdr hdr; + struct rte_ptp_timestamp precise_origin_tstamp; + uint8_t suffix[]; } __rte_packed_end; struct __rte_packed_begin delay_req_msg { - struct ptp_header hdr; - struct tstamp origin_tstamp; + struct rte_ptp_hdr hdr; + struct rte_ptp_timestamp origin_tstamp; } __rte_packed_end; struct __rte_packed_begin delay_resp_msg { - struct ptp_header hdr; - struct tstamp rx_tstamp; - struct port_id req_port_id; - uint8_t suffix[]; + struct rte_ptp_hdr hdr; + struct rte_ptp_timestamp rx_tstamp; + struct rte_ptp_port_id req_port_id; + uint8_t suffix[]; } __rte_packed_end; struct ptp_message { union __rte_packed_begin { - struct ptp_header header; + struct rte_ptp_hdr header; struct sync_msg sync; struct delay_req_msg delay_req; struct follow_up_msg follow_up; @@ -119,14 +77,15 @@ struct ptp_message { } __rte_packed_end; }; + struct ptpv2_time_receiver_ordinary { struct rte_mbuf *m; struct timespec tstamp1; struct timespec tstamp2; struct timespec tstamp3; struct timespec tstamp4; - struct clock_id client_clock_id; - struct clock_id transmitter_clock_id; + uint8_t client_clock_id[8]; + uint8_t transmitter_clock_id[8]; struct timeval new_adj; int64_t delta; uint16_t portid; @@ -272,14 +231,14 @@ print_clock_info(struct ptpv2_time_receiver_ordinary *ptp_data) struct timespec net_time, sys_time; printf("time transmitter clock id: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", - ptp_data->transmitter_clock_id.id[0], - ptp_data->transmitter_clock_id.id[1], - ptp_data->transmitter_clock_id.id[2], - ptp_data->transmitter_clock_id.id[3], - ptp_data->transmitter_clock_id.id[4], - ptp_data->transmitter_clock_id.id[5], - ptp_data->transmitter_clock_id.id[6], - ptp_data->transmitter_clock_id.id[7]); + ptp_data->transmitter_clock_id[0], + ptp_data->transmitter_clock_id[1], + ptp_data->transmitter_clock_id[2], + ptp_data->transmitter_clock_id[3], + ptp_data->transmitter_clock_id[4], + ptp_data->transmitter_clock_id[5], + ptp_data->transmitter_clock_id[6], + ptp_data->transmitter_clock_id[7]); printf("\nT2 - time receiver clock. %lds %ldns", (ptp_data->tstamp2.tv_sec), @@ -356,20 +315,20 @@ delta_eval(struct ptpv2_time_receiver_ordinary *ptp_data) static void parse_sync(struct ptpv2_time_receiver_ordinary *ptp_data, uint16_t rx_tstamp_idx) { - struct ptp_header *ptp_hdr; + struct rte_ptp_hdr *ptp_hdr; - ptp_hdr = rte_pktmbuf_mtod_offset(ptp_data->m, struct ptp_header *, + ptp_hdr = rte_pktmbuf_mtod_offset(ptp_data->m, struct rte_ptp_hdr *, sizeof(struct rte_ether_hdr)); - ptp_data->seqID_SYNC = rte_be_to_cpu_16(ptp_hdr->seq_id); + ptp_data->seqID_SYNC = rte_be_to_cpu_16(ptp_hdr->sequence_id); if (ptp_data->ptpset == 0) { - ptp_data->transmitter_clock_id = ptp_hdr->source_port_id.clock_id; + memcpy(ptp_data->transmitter_clock_id, + ptp_hdr->source_port_id.clock_id, 8); ptp_data->ptpset = 1; } - if (memcmp(&ptp_data->transmitter_clock_id, - &ptp_hdr->source_port_id.clock_id, - sizeof(struct clock_id)) == 0) { + if (memcmp(ptp_data->transmitter_clock_id, + ptp_hdr->source_port_id.clock_id, 8) == 0) { if (ptp_data->ptpset == 1) rte_eth_timesync_read_rx_timestamp(ptp_data->portid, @@ -386,12 +345,12 @@ parse_fup(struct ptpv2_time_receiver_ordinary *ptp_data) { struct rte_ether_hdr *eth_hdr; struct rte_ether_addr eth_addr; - struct ptp_header *ptp_hdr; - struct clock_id *client_clkid; + struct rte_ptp_hdr *ptp_hdr; + uint8_t *client_clkid; struct ptp_message *ptp_msg; struct delay_req_msg *req_msg; struct rte_mbuf *created_pkt; - struct tstamp *origin_tstamp; + struct rte_ptp_timestamp *origin_tstamp; struct rte_ether_addr eth_multicast = ether_multicast; size_t pkt_size; int wait_us; @@ -399,22 +358,21 @@ parse_fup(struct ptpv2_time_receiver_ordinary *ptp_data) int ret; eth_hdr = rte_pktmbuf_mtod(m, struct rte_ether_hdr *); - ptp_hdr = rte_pktmbuf_mtod_offset(m, struct ptp_header *, + ptp_hdr = rte_pktmbuf_mtod_offset(m, struct rte_ptp_hdr *, sizeof(struct rte_ether_hdr)); - if (memcmp(&ptp_data->transmitter_clock_id, - &ptp_hdr->source_port_id.clock_id, - sizeof(struct clock_id)) != 0) + if (memcmp(ptp_data->transmitter_clock_id, + ptp_hdr->source_port_id.clock_id, 8) != 0) return; - ptp_data->seqID_FOLLOWUP = rte_be_to_cpu_16(ptp_hdr->seq_id); + ptp_data->seqID_FOLLOWUP = rte_be_to_cpu_16(ptp_hdr->sequence_id); ptp_msg = rte_pktmbuf_mtod_offset(m, struct ptp_message *, sizeof(struct rte_ether_hdr)); origin_tstamp = &ptp_msg->follow_up.precise_origin_tstamp; - ptp_data->tstamp1.tv_nsec = ntohl(origin_tstamp->ns); + ptp_data->tstamp1.tv_nsec = ntohl(origin_tstamp->nanoseconds); ptp_data->tstamp1.tv_sec = - ((uint64_t)ntohl(origin_tstamp->sec_lsb)) | - (((uint64_t)ntohs(origin_tstamp->sec_msb)) << 32); + ((uint64_t)ntohl(origin_tstamp->seconds_lo)) | + (((uint64_t)ntohs(origin_tstamp->seconds_hi)) << 32); if (ptp_data->seqID_FOLLOWUP == ptp_data->seqID_SYNC) { ret = rte_eth_macaddr_get(ptp_data->portid, ð_addr); @@ -441,34 +399,34 @@ parse_fup(struct ptpv2_time_receiver_ordinary *ptp_data) /* Set multicast address 01-1B-19-00-00-00. */ rte_ether_addr_copy(ð_multicast, ð_hdr->dst_addr); - eth_hdr->ether_type = htons(PTP_PROTOCOL); + eth_hdr->ether_type = htons(RTE_PTP_ETHERTYPE); req_msg = rte_pktmbuf_mtod_offset(created_pkt, struct delay_req_msg *, sizeof(struct rte_ether_hdr)); - req_msg->hdr.seq_id = htons(ptp_data->seqID_SYNC); - req_msg->hdr.msg_type = DELAY_REQ; - req_msg->hdr.ver = 2; + req_msg->hdr.sequence_id = htons(ptp_data->seqID_SYNC); + req_msg->hdr.msg_type = RTE_PTP_MSGTYPE_DELAY_REQ; + req_msg->hdr.version = 2; req_msg->hdr.control = 1; - req_msg->hdr.log_message_interval = 127; - req_msg->hdr.message_length = + req_msg->hdr.log_msg_interval = 127; + req_msg->hdr.msg_length = htons(sizeof(struct delay_req_msg)); req_msg->hdr.domain_number = ptp_hdr->domain_number; /* Set up clock id. */ client_clkid = - &req_msg->hdr.source_port_id.clock_id; + req_msg->hdr.source_port_id.clock_id; - client_clkid->id[0] = eth_hdr->src_addr.addr_bytes[0]; - client_clkid->id[1] = eth_hdr->src_addr.addr_bytes[1]; - client_clkid->id[2] = eth_hdr->src_addr.addr_bytes[2]; - client_clkid->id[3] = 0xFF; - client_clkid->id[4] = 0xFE; - client_clkid->id[5] = eth_hdr->src_addr.addr_bytes[3]; - client_clkid->id[6] = eth_hdr->src_addr.addr_bytes[4]; - client_clkid->id[7] = eth_hdr->src_addr.addr_bytes[5]; + client_clkid[0] = eth_hdr->src_addr.addr_bytes[0]; + client_clkid[1] = eth_hdr->src_addr.addr_bytes[1]; + client_clkid[2] = eth_hdr->src_addr.addr_bytes[2]; + client_clkid[3] = 0xFF; + client_clkid[4] = 0xFE; + client_clkid[5] = eth_hdr->src_addr.addr_bytes[3]; + client_clkid[6] = eth_hdr->src_addr.addr_bytes[4]; + client_clkid[7] = eth_hdr->src_addr.addr_bytes[5]; - ptp_data->client_clock_id = *client_clkid; + memcpy(ptp_data->client_clock_id, client_clkid, 8); /* Enable flag for hardware timestamping. */ created_pkt->ol_flags |= RTE_MBUF_F_TX_IEEE1588_TMST; @@ -534,21 +492,20 @@ parse_drsp(struct ptpv2_time_receiver_ordinary *ptp_data) { struct rte_mbuf *m = ptp_data->m; struct ptp_message *ptp_msg; - struct tstamp *rx_tstamp; + struct rte_ptp_timestamp *rx_tstamp; uint16_t seq_id; ptp_msg = rte_pktmbuf_mtod_offset(m, struct ptp_message *, sizeof(struct rte_ether_hdr)); - seq_id = rte_be_to_cpu_16(ptp_msg->delay_resp.hdr.seq_id); - if (memcmp(&ptp_data->client_clock_id, - &ptp_msg->delay_resp.req_port_id.clock_id, - sizeof(struct clock_id)) == 0) { + seq_id = rte_be_to_cpu_16(ptp_msg->delay_resp.hdr.sequence_id); + if (memcmp(ptp_data->client_clock_id, + ptp_msg->delay_resp.req_port_id.clock_id, 8) == 0) { if (seq_id == ptp_data->seqID_FOLLOWUP) { rx_tstamp = &ptp_msg->delay_resp.rx_tstamp; - ptp_data->tstamp4.tv_nsec = ntohl(rx_tstamp->ns); + ptp_data->tstamp4.tv_nsec = ntohl(rx_tstamp->nanoseconds); ptp_data->tstamp4.tv_sec = - ((uint64_t)ntohl(rx_tstamp->sec_lsb)) | - (((uint64_t)ntohs(rx_tstamp->sec_msb)) << 32); + ((uint64_t)ntohl(rx_tstamp->seconds_lo)) | + (((uint64_t)ntohs(rx_tstamp->seconds_hi)) << 32); /* Evaluate the delta for adjustment. */ ptp_data->delta = delta_eval(ptp_data); @@ -575,27 +532,27 @@ parse_drsp(struct ptpv2_time_receiver_ordinary *ptp_data) /* Parse ptp frames. 8< */ static void parse_ptp_frames(uint16_t portid, struct rte_mbuf *m) { - struct ptp_header *ptp_hdr; + struct rte_ptp_hdr *ptp_hdr; struct rte_ether_hdr *eth_hdr; uint16_t eth_type; eth_hdr = rte_pktmbuf_mtod(m, struct rte_ether_hdr *); eth_type = rte_be_to_cpu_16(eth_hdr->ether_type); - if (eth_type == PTP_PROTOCOL) { + if (eth_type == RTE_PTP_ETHERTYPE) { ptp_data.m = m; ptp_data.portid = portid; - ptp_hdr = rte_pktmbuf_mtod_offset(m, struct ptp_header *, + ptp_hdr = rte_pktmbuf_mtod_offset(m, struct rte_ptp_hdr *, sizeof(struct rte_ether_hdr)); - switch (ptp_hdr->msg_type) { - case SYNC: + switch (rte_ptp_msg_type(ptp_hdr)) { + case RTE_PTP_MSGTYPE_SYNC: parse_sync(&ptp_data, m->timesync); break; - case FOLLOW_UP: + case RTE_PTP_MSGTYPE_FOLLOW_UP: parse_fup(&ptp_data); break; - case DELAY_RESP: + case RTE_PTP_MSGTYPE_DELAY_RESP: parse_drsp(&ptp_data); print_clock_info(&ptp_data); break; -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* Re: [RFC v5 0/6] introduce PTP protocol library and software relay 2026-05-06 15:41 ` [RFC v5 0/6] introduce PTP protocol library and software relay Rajesh Kumar ` (5 preceding siblings ...) 2026-05-06 15:41 ` [RFC v5 6/6] examples/ptpclient: use shared PTP library definitions Rajesh Kumar @ 2026-05-06 15:45 ` Stephen Hemminger 6 siblings, 0 replies; 60+ messages in thread From: Stephen Hemminger @ 2026-05-06 15:45 UTC (permalink / raw) To: Rajesh Kumar; +Cc: dev, bruce.richardson, aman.deep.singh On Wed, 6 May 2026 21:11:22 +0530 Rajesh Kumar <rajesh3.kumar@intel.com> wrote: > - Fixed checkpatch warnings: removed unnecessary #include <stdio.h> PS. spelling error complaints about stdio.h are bogus ^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v6 0/4] PTP protocol support in lib/net 2026-04-28 1:01 [RFC v1 0/4] introduce PTP protocol library and software relay example Rajesh Kumar ` (8 preceding siblings ...) 2026-05-06 15:41 ` [RFC v5 0/6] introduce PTP protocol library and software relay Rajesh Kumar @ 2026-05-07 10:13 ` Rajesh Kumar 2026-05-07 10:13 ` [PATCH v6 1/4] lib/net: add IEEE 1588 PTP v2 protocol header definitions Rajesh Kumar ` (3 more replies) 2026-05-07 13:45 ` [PATCH v7 0/4] IEEE 1588 PTP v2 protocol support in lib/net Rajesh Kumar 2026-05-09 23:25 ` [PATCH v8 0/4] IEEE 1588 PTP v2 protocol support in lib/net Rajesh Kumar 11 siblings, 4 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-07 10:13 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, mb, Rajesh Kumar Add IEEE 1588-2019 Precision Time Protocol (PTP) support to DPDK via a new header library in lib/net/. This patchset provides wire-format packet structure definitions and inline helpers for Transparent Clock and ordinary clock applications. ## Design Rationale Following DPDK conventions for protocol libraries (rte_tcp.h, rte_ip.h), PTP definitions are provided as a header-only library with inline functions for zero-overhead packet processing. All functions are suitable for real-time, performance-critical code paths. ## Contents **Patch 1:** lib/net/rte_ptp.h - Header structures and inline helpers - rte_ptp_port_id: 10-byte port identity with EUI-64 clock ID - rte_ptp_hdr: 34-byte PTP v2 common message header - rte_ptp_timestamp: 10-byte nanosecond-precision timestamp - Message type constants (Sync, Delay_Req, Announce, etc.) - Flag field bits (two-step, unicast, leap indicator) - Inline helpers: rte_ptp_msg_type(), rte_ptp_seq_id(), rte_ptp_is_event(), rte_ptp_correction_ns(), rte_ptp_add_correction(), rte_ptp_timestamp_to_ns() **Patch 2:** examples/ptp_tap_relay_sw - Software PTP Transparent Clock relay - Relays PTP packets between a DPDK-bound physical NIC and Linux TAP interface - Software timestamp-based residence time measurement (CLOCK_MONOTONIC) - Accumulates residence time to PTP correctionField per IEEE 1588-2019 §10.2 - Handles L2, VLAN/QinQ, UDP/IPv4, UDP/IPv6 PTP encapsulations - Works with unmodified Linux kernel and stock DPDK (no patches required) - Compatible with standard linuxptp (ptp4l) tooling **Patch 3:** doc/guides/rel_notes/release_26_07.rst - Release notes update **Patch 4:** examples/ptpclient - Update to use lib/net PTP definitions - Uses RTE_ETHER_TYPE_1588 constant - Adds lib/net dependency ## Testing - Build: `ninja -C build` — clean compilation - Examples: Both ptpclient and ptp_tap_relay_sw compile and link correctly - No stale references to old patterns or removed functions ## Changelog v6 changes: - Restructured to lib/net (header-only, following lib/net conventions) - Removed separate DPI library functions (moved to example-local ptp_parse.h) - Removed app/test unit tests (library now header-only, example-driven testing) - Removed programmer's guide doc (lib/net headers use Doxygen API docs only) Rajesh Kumar (4): lib/net: add IEEE 1588 PTP v2 protocol header definitions examples/ptp_tap_relay_sw: add PTP software transparent clock relay doc: update release notes for PTP protocol library examples/ptpclient: use shared PTP library definitions MAINTAINERS | 6 + doc/guides/rel_notes/release_26_07.rst | 12 + doc/guides/sample_app_ug/ptp_tap_relay_sw.rst | 212 +++++++++ examples/ptp_tap_relay_sw/Makefile | 41 ++ examples/ptp_tap_relay_sw/meson.build | 13 + examples/ptp_tap_relay_sw/ptp_parse.h | 211 +++++++++ examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c | 432 ++++++++++++++++++ examples/ptpclient/meson.build | 1 + examples/ptpclient/ptpclient.c | 4 +- lib/net/meson.build | 1 + lib/net/rte_ptp.h | 264 +++++++++++ 11 files changed, 1195 insertions(+), 2 deletions(-) create mode 100644 doc/guides/sample_app_ug/ptp_tap_relay_sw.rst create mode 100644 examples/ptp_tap_relay_sw/Makefile create mode 100644 examples/ptp_tap_relay_sw/meson.build create mode 100644 examples/ptp_tap_relay_sw/ptp_parse.h create mode 100644 examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c create mode 100644 lib/net/rte_ptp.h -- 2.53.0 ^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v6 1/4] lib/net: add IEEE 1588 PTP v2 protocol header definitions 2026-05-07 10:13 ` [PATCH v6 0/4] PTP protocol support in lib/net Rajesh Kumar @ 2026-05-07 10:13 ` Rajesh Kumar 2026-05-07 10:13 ` [PATCH v6 2/4] examples/ptp_tap_relay_sw: add PTP software transparent clock relay Rajesh Kumar ` (2 subsequent siblings) 3 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-07 10:13 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, mb, Rajesh Kumar Add PTP (Precision Time Protocol) header structures and inline helper functions to lib/net following DPDK conventions for protocol libraries (similar to rte_tcp.h, rte_ip.h). Provides wire-format structures with endian-annotated types: - rte_ptp_port_id: 10-byte port identity with EUI-64 clock ID - rte_ptp_hdr: 34-byte common message header with correctionField - rte_ptp_timestamp: 10-byte nanosecond-precision timestamp PTP message type constants for all IEEE 1588-2019 message types (Sync, Delay_Req, Announce, Management, etc.) and flag field bits (two-step, unicast, leap indicator). Inline accessor and utility functions for performance: - rte_ptp_msg_type(), rte_ptp_version(), rte_ptp_domain() - rte_ptp_seq_id(), rte_ptp_is_event(), rte_ptp_is_two_step() - rte_ptp_correction_ns(), rte_ptp_add_correction() - rte_ptp_timestamp_to_ns() for timestamp conversion Supports all PTP encapsulations: L2 (EtherType 0x88F7), VLAN/QinQ, UDP/IPv4, and UDP/IPv6 (ports 319/320). All inline functions — zero function call overhead, suitable for real-time packet processing. Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- MAINTAINERS | 6 + lib/net/meson.build | 1 + lib/net/rte_ptp.h | 264 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 271 insertions(+) create mode 100644 lib/net/rte_ptp.h diff --git a/MAINTAINERS b/MAINTAINERS index 0f5539f851..da31ada871 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1665,6 +1665,12 @@ F: doc/guides/prog_guide/ipsec_lib.rst M: Vladimir Medvedkin <vladimir.medvedkin@intel.com> F: app/test-sad/ +PTP - lib/net +M: Rajesh Kumar <rajesh3.kumar@intel.com> +F: lib/net/rte_ptp.h +F: examples/ptp_tap_relay_sw/ +F: doc/guides/sample_app_ug/ptp_tap_relay_sw.rst + PDCP - EXPERIMENTAL M: Anoob Joseph <anoobj@marvell.com> M: Volodymyr Fialko <vfialko@marvell.com> diff --git a/lib/net/meson.build b/lib/net/meson.build index 3fad5edc5b..63d13719f3 100644 --- a/lib/net/meson.build +++ b/lib/net/meson.build @@ -28,6 +28,7 @@ headers = files( 'rte_geneve.h', 'rte_l2tpv2.h', 'rte_ppp.h', + 'rte_ptp.h', 'rte_ib.h', ) diff --git a/lib/net/rte_ptp.h b/lib/net/rte_ptp.h new file mode 100644 index 0000000000..da7b29ab0e --- /dev/null +++ b/lib/net/rte_ptp.h @@ -0,0 +1,264 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2026 Intel Corporation + */ + +#ifndef _RTE_PTP_H_ +#define _RTE_PTP_H_ + +/** + * @file + * + * PTP (IEEE 1588) protocol definitions + */ + +#include <stdint.h> +#include <stdbool.h> + +#include <rte_byteorder.h> +#include <rte_common.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* ============================================================ + * PTP Constants + * ============================================================ */ + +/** PTP over UDP event port (Sync, Delay_Req, PDelay_Req, PDelay_Resp). */ +#define RTE_PTP_EVENT_PORT 319 + +/** PTP over UDP general port (Follow_Up, Delay_Resp, Announce, etc.). */ +#define RTE_PTP_GENERAL_PORT 320 + +/** PTP multicast MAC address: 01:1B:19:00:00:00. */ +#define RTE_PTP_MULTICAST_MAC { 0x01, 0x1B, 0x19, 0x00, 0x00, 0x00 } + +/** PTP peer delay multicast MAC: 01:80:C2:00:00:0E. */ +#define RTE_PTP_PDELAY_MULTICAST_MAC { 0x01, 0x80, 0xC2, 0x00, 0x00, 0x0E } + +/* ============================================================ + * PTP Message Types (IEEE 1588-2019 Table 36) + * ============================================================ */ + +#define RTE_PTP_MSGTYPE_SYNC 0x0 /**< Sync (event). */ +#define RTE_PTP_MSGTYPE_DELAY_REQ 0x1 /**< Delay_Req (event). */ +#define RTE_PTP_MSGTYPE_PDELAY_REQ 0x2 /**< Peer_Delay_Req (event). */ +#define RTE_PTP_MSGTYPE_PDELAY_RESP 0x3 /**< Peer_Delay_Resp (event). */ +#define RTE_PTP_MSGTYPE_FOLLOW_UP 0x8 /**< Follow_Up (general). */ +#define RTE_PTP_MSGTYPE_DELAY_RESP 0x9 /**< Delay_Resp (general). */ +#define RTE_PTP_MSGTYPE_PDELAY_RESP_FU 0xA /**< Peer_Delay_Resp_Follow_Up. */ +#define RTE_PTP_MSGTYPE_ANNOUNCE 0xB /**< Announce (general). */ +#define RTE_PTP_MSGTYPE_SIGNALING 0xC /**< Signaling (general). */ +#define RTE_PTP_MSGTYPE_MANAGEMENT 0xD /**< Management (general). */ + +/* ============================================================ + * PTP Flag Field Bits (IEEE 1588-2019 Table 37) + * + * These constants are for use after rte_be_to_cpu_16(hdr->flags). + * flagField[0] (octet 6) maps to host bits 8-15. + * flagField[1] (octet 7) maps to host bits 0-7. + * ============================================================ */ + +#define RTE_PTP_FLAG_TWO_STEP (1 << 9) /**< Two-step flag. */ +#define RTE_PTP_FLAG_UNICAST (1 << 10) /**< Unicast flag. */ +#define RTE_PTP_FLAG_LI_61 (1 << 0) /**< Leap indicator 61. */ +#define RTE_PTP_FLAG_LI_59 (1 << 1) /**< Leap indicator 59. */ + +/* ============================================================ + * PTP Header Structures (IEEE 1588-2019) + * ============================================================ */ + +/** + * PTP Port Identity (10 bytes). + */ +struct __rte_packed_begin rte_ptp_port_id { + uint8_t clock_id[8]; /**< clockIdentity (EUI-64). */ + rte_be16_t port_number; /**< portNumber. */ +} __rte_packed_end; + +/** + * PTP Common Message Header (34 bytes). + */ +struct __rte_packed_begin rte_ptp_hdr { + uint8_t msg_type; /**< transportSpecific (4) | messageType (4). */ + uint8_t version; /**< minorVersionPTP (4) | versionPTP (4). */ + rte_be16_t msg_length; /**< Total message length in bytes. */ + uint8_t domain_number; /**< PTP domain (0-255). */ + uint8_t minor_sdo_id; /**< minorSdoId (IEEE 1588-2019). */ + rte_be16_t flags; /**< Flag field (see RTE_PTP_FLAG_*). */ + rte_be64_t correction; /**< correctionField (scaled ns, 48.16 fixed). */ + rte_be32_t msg_type_specific; /**< messageTypeSpecific. */ + struct rte_ptp_port_id source_port_id; /**< sourcePortIdentity. */ + rte_be16_t sequence_id; /**< sequenceId. */ + uint8_t control; /**< controlField (deprecated in 1588-2019). */ + int8_t log_msg_interval; /**< logMessageInterval. */ +} __rte_packed_end; + +/** + * PTP Timestamp (10 bytes, used in Sync/Delay_Req/Follow_Up bodies). + */ +struct __rte_packed_begin rte_ptp_timestamp { + rte_be16_t seconds_hi; /**< Upper 16 bits of seconds. */ + rte_be32_t seconds_lo; /**< Lower 32 bits of seconds. */ + rte_be32_t nanoseconds; /**< Nanoseconds (0-999999999). */ +} __rte_packed_end; + +/* ============================================================ + * Inline Helpers + * ============================================================ */ + +/** + * Extract PTP message type from header. + * + * @param hdr + * Pointer to PTP header. + * @return + * Message type (0x0-0xF). + */ +static inline uint8_t +rte_ptp_msg_type(const struct rte_ptp_hdr *hdr) +{ + return hdr->msg_type & 0x0F; +} + +/** + * Extract transport-specific field from header. + * + * @param hdr + * Pointer to PTP header. + * @return + * Transport-specific value (upper nibble, 0x0-0xF). + */ +static inline uint8_t +rte_ptp_transport_specific(const struct rte_ptp_hdr *hdr) +{ + return (hdr->msg_type >> 4) & 0x0F; +} + +/** + * Extract PTP version from header. + * + * @param hdr + * Pointer to PTP header. + * @return + * PTP version number (typically 2). + */ +static inline uint8_t +rte_ptp_version(const struct rte_ptp_hdr *hdr) +{ + return hdr->version & 0x0F; +} + +/** + * Get sequence ID from PTP header (host byte order). + * + * @param hdr + * Pointer to PTP header. + * @return + * Sequence ID in host byte order. + */ +static inline uint16_t +rte_ptp_seq_id(const struct rte_ptp_hdr *hdr) +{ + return rte_be_to_cpu_16(hdr->sequence_id); +} + +/** + * Get PTP domain number. + * + * @param hdr + * Pointer to PTP header. + * @return + * Domain number (0-255). + */ +static inline uint8_t +rte_ptp_domain(const struct rte_ptp_hdr *hdr) +{ + return hdr->domain_number; +} + +/** + * Check if PTP message type is an event message. + * Event messages (msg_type 0x0-0x3) require timestamps. + * + * @param msg_type + * PTP message type value (0x0-0xF). + * @return + * true if event message, false otherwise. + */ +static inline bool +rte_ptp_is_event(int msg_type) +{ + return msg_type >= 0 && msg_type <= RTE_PTP_MSGTYPE_PDELAY_RESP; +} + +/** + * Check if the two-step flag is set in a PTP header. + * + * @param hdr + * Pointer to PTP header. + * @return + * true if two-step flag is set. + */ +static inline bool +rte_ptp_is_two_step(const struct rte_ptp_hdr *hdr) +{ + return (rte_be_to_cpu_16(hdr->flags) & RTE_PTP_FLAG_TWO_STEP) != 0; +} + +/** + * Get correctionField value in nanoseconds (from 48.16 fixed-point). + * + * @param hdr + * Pointer to PTP header. + * @return + * Correction value in nanoseconds. + */ +static inline int64_t +rte_ptp_correction_ns(const struct rte_ptp_hdr *hdr) +{ + return (int64_t)rte_be_to_cpu_64(hdr->correction) >> 16; +} + +/** + * Add a residence time (in nanoseconds) to the correctionField. + * Used by Transparent Clocks to account for relay transit delay. + * The correctionField uses IEEE 1588 scaled nanoseconds (48.16 fixed-point). + * + * @param hdr + * Pointer to PTP header (will be modified in-place). + * @param residence_ns + * Residence time in nanoseconds to add. + */ +static inline void +rte_ptp_add_correction(struct rte_ptp_hdr *hdr, int64_t residence_ns) +{ + int64_t cf = (int64_t)rte_be_to_cpu_64(hdr->correction); + + cf += (int64_t)((uint64_t)residence_ns << 16); + hdr->correction = rte_cpu_to_be_64(cf); +} + +/** + * Convert a PTP timestamp structure to nanoseconds since epoch. + * + * @param ts + * Pointer to PTP timestamp. + * @return + * Time in nanoseconds since epoch. + */ +static inline uint64_t +rte_ptp_timestamp_to_ns(const struct rte_ptp_timestamp *ts) +{ + uint64_t sec = ((uint64_t)rte_be_to_cpu_16(ts->seconds_hi) << 32) | + rte_be_to_cpu_32(ts->seconds_lo); + + return sec * 1000000000ULL + rte_be_to_cpu_32(ts->nanoseconds); +} + +#ifdef __cplusplus +} +#endif + +#endif /* _RTE_PTP_H_ */ -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [PATCH v6 2/4] examples/ptp_tap_relay_sw: add PTP software transparent clock relay 2026-05-07 10:13 ` [PATCH v6 0/4] PTP protocol support in lib/net Rajesh Kumar 2026-05-07 10:13 ` [PATCH v6 1/4] lib/net: add IEEE 1588 PTP v2 protocol header definitions Rajesh Kumar @ 2026-05-07 10:13 ` Rajesh Kumar 2026-05-07 10:13 ` [PATCH v6 3/4] doc: update release notes for PTP protocol library Rajesh Kumar 2026-05-07 10:13 ` [PATCH v6 4/4] examples/ptpclient: use shared PTP library definitions Rajesh Kumar 3 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-07 10:13 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, mb, Rajesh Kumar Add a new example application demonstrating a software PTP Transparent Clock relay between a DPDK-bound physical NIC and a Linux kernel TAP virtual interface. The relay uses software timestamps (CLOCK_MONOTONIC) to measure residence time and accumulates it into the PTP correctionField per IEEE 1588-2019 §10.2, enabling synchronized time distribution via standard linuxptp (ptp4l) on both sides. Features: - Handles L2, VLAN/QinQ, and UDP/IPv4/IPv6 PTP encapsulations - Supports PTP v2 event messages (Sync, Delay_Req, PDelay_Req, PDelay_Resp) - Two-pass burst processing: classify then timestamp immediately before TX - Unmodified Linux kernel and stock DPDK (no kernel patches required) - Bidirectional relay: PHY ↔ TAP Includes: - ptp_tap_relay_sw.c: Main relay logic with burst processing - ptp_parse.h: Local DPI parser for PTP classification (not a library API) - Sample app guide with topology, command-line options, and example output Uses lib/net/rte_ptp.h inline helpers for correctionField manipulation and header parsing. Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- doc/guides/sample_app_ug/ptp_tap_relay_sw.rst | 212 +++++++++ examples/ptp_tap_relay_sw/Makefile | 41 ++ examples/ptp_tap_relay_sw/meson.build | 13 + examples/ptp_tap_relay_sw/ptp_parse.h | 211 +++++++++ examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c | 432 ++++++++++++++++++ 5 files changed, 909 insertions(+) create mode 100644 doc/guides/sample_app_ug/ptp_tap_relay_sw.rst create mode 100644 examples/ptp_tap_relay_sw/Makefile create mode 100644 examples/ptp_tap_relay_sw/meson.build create mode 100644 examples/ptp_tap_relay_sw/ptp_parse.h create mode 100644 examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c diff --git a/doc/guides/sample_app_ug/ptp_tap_relay_sw.rst b/doc/guides/sample_app_ug/ptp_tap_relay_sw.rst new file mode 100644 index 0000000000..15727383c1 --- /dev/null +++ b/doc/guides/sample_app_ug/ptp_tap_relay_sw.rst @@ -0,0 +1,212 @@ +.. SPDX-License-Identifier: BSD-3-Clause + Copyright(c) 2026 Intel Corporation. + +PTP Software Relay Sample Application +====================================== + +The PTP Software Relay sample application demonstrates how to build a +minimal PTP Transparent Clock relay between a DPDK-bound physical NIC +and a kernel TAP interface using **software timestamps only**. It uses +the PTP definitions from ``rte_ptp.h`` (in ``lib/net/``) together with a +local packet parser. + +The application works with an unmodified Linux kernel and stock DPDK. + +For background on PTP see: +`Precision Time Protocol +<https://en.wikipedia.org/wiki/Precision_Time_Protocol>`_. + + +Limitations +----------- + +* Tested with L2 PTP (EtherType 0x88F7) on the wire. + The local parser also classifies VLAN/QinQ and UDP/IPv4/IPv6. +* Only PTP v2 messages are processed. +* Software timestamps have microsecond-class jitter; sub-microsecond + precision depends on system load and NIC-to-TAP forwarding latency. +* The PTP time transmitter must be reachable on the physical NIC's L2 network. +* Only one physical port and one TAP port are supported. + + +How the Application Works +------------------------- + +Topology +~~~~~~~~ + +:: + + PTP Time Transmitter Physical NIC TAP (kernel) + (ptp4l -H) ──L2── (DPDK vfio-pci) ────── dtap0 + │ │ + ptp_tap_relay_sw ptp4l -S + (correctionField += (SW timestamps, + residence time) adjusts CLOCK_REALTIME) + +The relay sits between a DPDK-owned physical NIC and a kernel TAP +virtual interface. ``ptp4l`` runs on the TAP interface in software +timestamp mode (``-S``) as a PTP time receiver. + +Packet Flow +~~~~~~~~~~~ + +1. The physical NIC receives PTP (and non-PTP) packets via DPDK RX. +2. A software RX timestamp is recorded using + ``clock_gettime(CLOCK_MONOTONIC)``. +3. Each packet is parsed to locate the PTP header. +4. For PTP **event** messages (Sync, Delay_Req, PDelay_Req, PDelay_Resp), + a TX software timestamp is taken just before transmission. +5. The residence time (``tx_ts − rx_ts``) is added to the PTP + ``correctionField`` via ``rte_ptp_add_correction()`` — standard + IEEE 1588-2019 Transparent Clock behaviour (§10.2). +6. Packets are forwarded bidirectionally: + + * PHY → TAP (network → ptp4l) + * TAP → PHY (ptp4l → network) + +A two-pass design is used: first all packets are classified and PTP +header pointers saved, then a single TX timestamp is taken immediately +before applying corrections and calling ``rte_eth_tx_burst()``. +This minimises the gap between the measured timestamp and the actual +wire egress. + + +Compiling the Application +------------------------- + +To compile the sample application see :doc:`compiling`. + +The application is located in the ``ptp_tap_relay_sw`` sub-directory. + +.. note:: + + The application uses ``rte_ptp.h`` from ``lib/net/`` (built by default) + and a local ``ptp_parse.h`` header for packet classification. + + +Running the Application +----------------------- + +Prerequisites +~~~~~~~~~~~~~ + +* A PTP-capable physical NIC bound to DPDK (e.g. via ``vfio-pci``). +* ``linuxptp`` (``ptp4l``) installed on the system. +* A PTP time transmitter reachable on the same L2 network. + +Start the relay +~~~~~~~~~~~~~~~~ + +.. code-block:: console + + ./<build_dir>/examples/dpdk-ptp_tap_relay_sw \ + -l 18-19 -a 0000:cc:00.1 --vdev=net_tap0,iface=dtap0 -- \ + -p 0 -t 1 -T 10 + +Command-line Options +~~~~~~~~~~~~~~~~~~~~ + +* ``-p PORT`` — Physical NIC port ID (default: 0). +* ``-t PORT`` — TAP port ID (default: 1). +* ``-T SECS`` — Statistics print interval in seconds (default: 10). + +Start PTP time transmitter +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +On a separate terminal or remote host, start ``ptp4l`` as time +transmitter with hardware timestamps on the physical NIC: + +.. code-block:: console + + ptp4l -i <iface> -m -2 -H --serverOnly=1 \ + --logSyncInterval=-4 --logMinDelayReqInterval=-4 + +Start PTP time receiver +~~~~~~~~~~~~~~~~~~~~~~~ + +On the TAP interface, start ``ptp4l`` in software timestamp mode: + +.. code-block:: console + + ptp4l -i dtap0 -m -2 -s -S \ + --delay_filter=moving_median --delay_filter_length=10 + +The time receiver will enter UNCALIBRATED state for approximately 60 +seconds while the PI servo estimates the frequency offset, then step +the clock and enter time-receiver (synchronized) state. +Steady-state RMS offset of 500–1000 ns is typical on a lightly loaded +system with a hardware-timestamped time transmitter. + +Example Output +~~~~~~~~~~~~~~ + +Relay statistics printed every ``-T`` seconds: + +:: + + [PTP-SW] === Statistics === + [PTP-SW] PHY RX total: 5646 + [PTP-SW] PHY RX PTP: 5598 + [PTP-SW] TAP TX: 5646 + [PTP-SW] TAP RX total: 1800 + [PTP-SW] TAP RX PTP: 1788 + [PTP-SW] PHY TX: 1800 + [PTP-SW] Corrections: 3635 + +Time receiver ``ptp4l`` output after convergence: + +:: + + ptp4l[451534.520]: rms 630 max 1166 freq -44365 +/- 100 delay 37668 +/- 71 + ptp4l[451539.525]: rms 602 max 1177 freq -44339 +/- 119 delay 37517 +/- 43 + ptp4l[451544.530]: rms 535 max 1194 freq -44345 +/- 103 delay 37410 +/- 81 + + +Code Explanation +---------------- + +The following sections explain the main components of the application. + +Relay Burst Function +~~~~~~~~~~~~~~~~~~~~ + +The core relay logic is in ``relay_burst()``, which handles one direction +(PHY→TAP or TAP→PHY) per call: + +**Pass 1 — Classify:** + +For each received packet, ``ptp_hdr_find()`` locates the PTP header +(if present). For event messages, the header pointer is saved for the +second pass. + +**Pass 2 — Timestamp and correct:** + +A single software TX timestamp is taken via +``clock_gettime(CLOCK_MONOTONIC)``. The residence time +(``tx_ts − rx_ts``) is added to each saved PTP header's +``correctionField`` using ``rte_ptp_add_correction()``. +The burst is then transmitted with ``rte_eth_tx_burst()``. + +Main Loop +~~~~~~~~~ + +The ``relay_loop()`` function polls both directions in a tight loop: + +.. code-block:: c + + while (!force_quit) { + relay_burst(phy_port, tap_port, ...); /* PHY → TAP */ + relay_burst(tap_port, phy_port, ...); /* TAP → PHY */ + } + +Statistics are printed at the interval specified by ``-T``. + +Timestamp Source +~~~~~~~~~~~~~~~~ + +``CLOCK_MONOTONIC`` is used rather than ``CLOCK_REALTIME`` because +the PTP time receiver's servo continuously adjusts ``CLOCK_REALTIME``. +Using ``CLOCK_REALTIME`` would corrupt residence time measurements +during clock stepping or frequency slewing. ``CLOCK_MONOTONIC`` is +portable across Linux and FreeBSD. diff --git a/examples/ptp_tap_relay_sw/Makefile b/examples/ptp_tap_relay_sw/Makefile new file mode 100644 index 0000000000..fd178f46ae --- /dev/null +++ b/examples/ptp_tap_relay_sw/Makefile @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2026 Intel Corporation + +# binary name +APP = dpdk-ptp_tap_relay_sw + +# all source are stored in SRCS-y +SRCS-y := ptp_tap_relay_sw.c + +PKGCONF ?= pkg-config + +# Build using pkg-config variables if possible +ifneq ($(shell $(PKGCONF) --exists libdpdk && echo 0),0) +$(error "no installation of DPDK found") +endif + +all: shared +.PHONY: shared static +shared: build/$(APP)-shared + ln -sf $(APP)-shared build/$(APP) +static: build/$(APP)-static + ln -sf $(APP)-static build/$(APP) + +PC_FILE := $(shell $(PKGCONF) --path libdpdk 2>/dev/null) +CFLAGS += -O3 $(shell $(PKGCONF) --cflags libdpdk) +LDFLAGS_SHARED = $(shell $(PKGCONF) --libs libdpdk) +LDFLAGS_STATIC = $(shell $(PKGCONF) --static --libs libdpdk) + +build/$(APP)-shared: $(SRCS-y) Makefile $(PC_FILE) | build + $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_SHARED) + +build/$(APP)-static: $(SRCS-y) Makefile $(PC_FILE) | build + $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_STATIC) + +build: + @mkdir -p $@ + +.PHONY: clean +clean: + rm -f build/$(APP) build/$(APP)-static build/$(APP)-shared + test -d build && rmdir -p build || true diff --git a/examples/ptp_tap_relay_sw/meson.build b/examples/ptp_tap_relay_sw/meson.build new file mode 100644 index 0000000000..34a4d86439 --- /dev/null +++ b/examples/ptp_tap_relay_sw/meson.build @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2026 Intel Corporation + +# meson file, for building this example as part of a main DPDK build. +# +# To build this example as a standalone application with an already-installed +# DPDK instance, use 'make' + +sources = files( + 'ptp_tap_relay_sw.c', +) +deps += ['net'] +cflags += no_shadow_cflag diff --git a/examples/ptp_tap_relay_sw/ptp_parse.h b/examples/ptp_tap_relay_sw/ptp_parse.h new file mode 100644 index 0000000000..db0dcfe5c1 --- /dev/null +++ b/examples/ptp_tap_relay_sw/ptp_parse.h @@ -0,0 +1,211 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2026 Intel Corporation + * + * PTP packet parser — locates PTP headers through L2, VLAN, and UDP + * encapsulations. This is a DPI helper for use within example + * applications; it does not belong in the core library. + */ + +#ifndef _PTP_PARSE_H_ +#define _PTP_PARSE_H_ + +#include <rte_mbuf.h> +#include <rte_ether.h> +#include <rte_ip.h> +#include <rte_udp.h> +#include <rte_ptp.h> + +/** Not a PTP packet. */ +#define PTP_MSGTYPE_INVALID (-1) + +/** + * Locate the PTP header within a packet. + * + * Handles L2 (EtherType 0x88F7), VLAN-tagged L2 (single/double, + * TPIDs 0x8100/0x88A8), PTP over UDP/IPv4, PTP over UDP/IPv6, + * and VLAN-tagged UDP variants. + * + * @param m + * Pointer to the mbuf. + * @return + * Pointer to the PTP header, or NULL if not a PTP packet. + */ +static inline struct rte_ptp_hdr * +ptp_hdr_find(const struct rte_mbuf *m) +{ + const struct rte_ether_hdr *eth; + uint16_t ether_type; + uint32_t offset; + + if (rte_pktmbuf_data_len(m) < sizeof(struct rte_ether_hdr)) + return NULL; + + eth = rte_pktmbuf_mtod(m, const struct rte_ether_hdr *); + ether_type = rte_be_to_cpu_16(eth->ether_type); + offset = sizeof(struct rte_ether_hdr); + + /* Strip VLAN / QinQ tags */ + if (ether_type == RTE_ETHER_TYPE_VLAN || + ether_type == RTE_ETHER_TYPE_QINQ) { + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_vlan_hdr)) + return NULL; + const struct rte_vlan_hdr *vlan = + rte_pktmbuf_mtod_offset(m, + const struct rte_vlan_hdr *, offset); + ether_type = rte_be_to_cpu_16(vlan->eth_proto); + offset += sizeof(struct rte_vlan_hdr); + + /* Second tag (QinQ inner or stacked VLAN) */ + if (ether_type == RTE_ETHER_TYPE_VLAN || + ether_type == RTE_ETHER_TYPE_QINQ) { + if (rte_pktmbuf_data_len(m) < + offset + sizeof(struct rte_vlan_hdr)) + return NULL; + vlan = rte_pktmbuf_mtod_offset(m, + const struct rte_vlan_hdr *, offset); + ether_type = rte_be_to_cpu_16(vlan->eth_proto); + offset += sizeof(struct rte_vlan_hdr); + } + } + + /* L2 PTP: EtherType 0x88F7 */ + if (ether_type == RTE_ETHER_TYPE_1588) { + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ptp_hdr)) + return NULL; + return rte_pktmbuf_mtod_offset(m, + struct rte_ptp_hdr *, offset); + } + + /* PTP over UDP/IPv4 */ + if (ether_type == RTE_ETHER_TYPE_IPV4) { + const struct rte_ipv4_hdr *iph; + uint16_t ihl; + + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ipv4_hdr)) + return NULL; + + iph = rte_pktmbuf_mtod_offset(m, + const struct rte_ipv4_hdr *, offset); + if (iph->next_proto_id != IPPROTO_UDP) + return NULL; + + ihl = (iph->version_ihl & 0x0F) * 4; + if (ihl < 20) + return NULL; + offset += ihl; + + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_udp_hdr)) + return NULL; + + const struct rte_udp_hdr *udp = + rte_pktmbuf_mtod_offset(m, + const struct rte_udp_hdr *, offset); + uint16_t dst_port = rte_be_to_cpu_16(udp->dst_port); + + if (dst_port != RTE_PTP_EVENT_PORT && + dst_port != RTE_PTP_GENERAL_PORT) + return NULL; + + offset += sizeof(struct rte_udp_hdr); + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ptp_hdr)) + return NULL; + + return rte_pktmbuf_mtod_offset(m, + struct rte_ptp_hdr *, offset); + } + + /* PTP over UDP/IPv6 */ + if (ether_type == RTE_ETHER_TYPE_IPV6) { + const struct rte_ipv6_hdr *ip6h; + + if (rte_pktmbuf_data_len(m) < + offset + sizeof(struct rte_ipv6_hdr)) + return NULL; + + ip6h = rte_pktmbuf_mtod_offset(m, + const struct rte_ipv6_hdr *, offset); + if (ip6h->proto != IPPROTO_UDP) + return NULL; + + offset += sizeof(struct rte_ipv6_hdr); + + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_udp_hdr)) + return NULL; + + const struct rte_udp_hdr *udp = + rte_pktmbuf_mtod_offset(m, + const struct rte_udp_hdr *, offset); + uint16_t dst_port = rte_be_to_cpu_16(udp->dst_port); + + if (dst_port != RTE_PTP_EVENT_PORT && + dst_port != RTE_PTP_GENERAL_PORT) + return NULL; + + offset += sizeof(struct rte_udp_hdr); + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ptp_hdr)) + return NULL; + + return rte_pktmbuf_mtod_offset(m, + struct rte_ptp_hdr *, offset); + } + + return NULL; +} + +/** + * Classify a packet as PTP and return the message type. + * + * @param m + * Pointer to the mbuf to classify. + * @return + * PTP message type (0x0-0xF) on success, PTP_MSGTYPE_INVALID (-1) + * if the packet is not PTP. + */ +static inline int +ptp_classify(const struct rte_mbuf *m) +{ + struct rte_ptp_hdr *hdr = ptp_hdr_find(m); + + if (hdr == NULL) + return PTP_MSGTYPE_INVALID; + + return rte_ptp_msg_type(hdr); +} + +/** PTP message type name table. */ +static const char * const ptp_msg_names[] = { + [RTE_PTP_MSGTYPE_SYNC] = "Sync", + [RTE_PTP_MSGTYPE_DELAY_REQ] = "Delay_Req", + [RTE_PTP_MSGTYPE_PDELAY_REQ] = "PDelay_Req", + [RTE_PTP_MSGTYPE_PDELAY_RESP] = "PDelay_Resp", + [0x4] = "Reserved_4", + [0x5] = "Reserved_5", + [0x6] = "Reserved_6", + [0x7] = "Reserved_7", + [RTE_PTP_MSGTYPE_FOLLOW_UP] = "Follow_Up", + [RTE_PTP_MSGTYPE_DELAY_RESP] = "Delay_Resp", + [RTE_PTP_MSGTYPE_PDELAY_RESP_FU] = "PDelay_Resp_Follow_Up", + [RTE_PTP_MSGTYPE_ANNOUNCE] = "Announce", + [RTE_PTP_MSGTYPE_SIGNALING] = "Signaling", + [RTE_PTP_MSGTYPE_MANAGEMENT] = "Management", + [0xE] = "Reserved_E", + [0xF] = "Reserved_F", +}; + +/** + * Get a human-readable name for a PTP message type. + * + * @param msg_type + * PTP message type (0x0-0xF or PTP_MSGTYPE_INVALID). + * @return + * Static string with the message type name. + */ +static inline const char * +ptp_msg_type_str(int msg_type) +{ + if (msg_type < 0 || msg_type > 0xF) + return "Not_PTP"; + return ptp_msg_names[msg_type]; +} + +#endif /* _PTP_PARSE_H_ */ diff --git a/examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c b/examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c new file mode 100644 index 0000000000..998df2ac3b --- /dev/null +++ b/examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c @@ -0,0 +1,432 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2026 Intel Corporation + */ + +/* + * PTP Software Relay + * + * A minimal PTP relay between a DPDK-bound physical NIC and a kernel + * TAP interface using software timestamps only. + * + * How it works: + * 1. Physical NIC receives PTP (and non-PTP) packets via DPDK RX. + * 2. For PTP event messages (Sync, Delay_Req, PDelay_Req, PDelay_Resp) + * the relay records an RX software timestamp (clock_gettime). + * 3. Just before TX on the other side it records a TX software timestamp. + * 4. The relay residence time (tx_ts − rx_ts) is added to the PTP + * correctionField via rte_ptp_add_correction() — standard + * Transparent Clock behaviour (IEEE 1588-2019 §10.2). + * 5. Packets are forwarded bi-directionally: + * PHY → TAP (network → ptp4l) + * TAP → PHY (ptp4l → network) + * + * ptp4l runs in software-timestamping mode on the TAP interface: + * + * ptp4l -i dtap0 -m -s -S # -S = software timestamps + * + * Topology: + * + * Time Transmitter (remote) ──L2── Physical NIC (DPDK) + * │ + * PTP SW Relay ← correctionField update + * │ + * TAP (kernel) ── ptp4l -S (time receiver) + * + * Usage: + * dpdk-ptp_tap_relay_sw -l 0-1 --vdev=net_tap0,iface=dtap0 -- \ + * -p 0 -t 1 + * + * Parameters: + * -p PORT Physical NIC port ID (default: 0) + * -t PORT TAP port ID (default: 1) + * -T SECS Stats print interval in seconds (default: 10) + */ + +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <stdbool.h> +#include <signal.h> +#include <getopt.h> +#include <time.h> + +#include <rte_eal.h> +#include <rte_ethdev.h> +#include <rte_mbuf.h> +#include <rte_cycles.h> +#include <rte_lcore.h> + +#include "ptp_parse.h" + +/* Ring sizes */ +#define RX_RING_SIZE 1024 +#define TX_RING_SIZE 1024 + +/* Mempool */ +#define NUM_MBUFS 8191 +#define MBUF_CACHE 250 +#define BURST_SIZE 32 + +#define NSEC_PER_SEC 1000000000ULL + +/* Logging helpers */ +#define LOG_INFO(fmt, ...) \ + fprintf(stdout, "[PTP-SW] " fmt "\n", ##__VA_ARGS__) +#define LOG_ERR(fmt, ...) \ + fprintf(stderr, "[PTP-SW ERROR] " fmt "\n", ##__VA_ARGS__) + +static volatile bool force_quit; + +/* Port IDs */ +static uint16_t phy_port; +static uint16_t tap_port = 1; +static unsigned int stats_interval = 10; /* seconds */ + +/* Statistics */ +static struct { + uint64_t phy_rx; /* total packets from PHY */ + uint64_t phy_rx_ptp; /* PTP packets from PHY */ + uint64_t tap_tx; /* packets forwarded to TAP */ + uint64_t tap_rx; /* total packets from TAP */ + uint64_t tap_rx_ptp; /* PTP packets from TAP */ + uint64_t phy_tx; /* packets forwarded to PHY */ + uint64_t corrections; /* correctionField updates */ +} stats; + +static void +signal_handler(int signum) +{ + if (signum == SIGINT || signum == SIGTERM) { + LOG_INFO("Signal %d received, shutting down...", signum); + force_quit = true; + } +} + +/* Helpers */ + +/* Read monotonic clock in nanoseconds (for residence time). */ +static inline uint64_t +sw_timestamp_ns(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * NSEC_PER_SEC + (uint64_t)ts.tv_nsec; +} + +/* Port Init */ + +static int +port_init(uint16_t port, struct rte_mempool *mp) +{ + struct rte_eth_conf port_conf; + struct rte_eth_dev_info dev_info; + uint16_t nb_rxd = RX_RING_SIZE; + uint16_t nb_txd = TX_RING_SIZE; + int ret; + + memset(&port_conf, 0, sizeof(port_conf)); + + ret = rte_eth_dev_info_get(port, &dev_info); + if (ret != 0) { + LOG_ERR("rte_eth_dev_info_get(port %u) failed: %d", port, ret); + return ret; + } + + if (dev_info.tx_offload_capa & RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE) + port_conf.txmode.offloads |= + RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE; + + ret = rte_eth_dev_configure(port, 1, 1, &port_conf); + if (ret != 0) + return ret; + + ret = rte_eth_dev_adjust_nb_rx_tx_desc(port, &nb_rxd, &nb_txd); + if (ret != 0) + return ret; + + ret = rte_eth_rx_queue_setup(port, 0, nb_rxd, + rte_eth_dev_socket_id(port), NULL, mp); + if (ret < 0) + return ret; + + ret = rte_eth_tx_queue_setup(port, 0, nb_txd, + rte_eth_dev_socket_id(port), NULL); + if (ret < 0) + return ret; + + ret = rte_eth_dev_start(port); + if (ret < 0) + return ret; + + ret = rte_eth_promiscuous_enable(port); + if (ret != 0) { + LOG_ERR("Failed to enable promiscuous on port %u: %s", + port, rte_strerror(-ret)); + return ret; + } + + return 0; +} + +/* Relay one direction */ + +/* + * Forward packets from src_port to dst_port. + * For PTP event messages, record SW timestamps around the + * relay path and add the residence time to the correctionField. + * + * This implements a Transparent Clock (IEEE 1588-2019 §10.2): + * correctionField += (t_egress − t_ingress) + * + * Note: a single rx_ts / tx_ts pair is used for the entire burst. + * At typical PTP rates (logSyncInterval >= -4, i.e. <= 16 pkt/s) + * bursts contain at most one packet, so this is exact. At higher + * rates, early packets in a burst are slightly under-corrected and + * late ones over-corrected by up to one poll-loop iteration. + */ +static void +relay_burst(uint16_t src_port, uint16_t dst_port, + uint64_t *rx_cnt, uint64_t *rx_ptp_cnt, + uint64_t *tx_cnt, uint64_t *corr_cnt) +{ + struct rte_mbuf *bufs[BURST_SIZE]; + struct rte_ptp_hdr *ptp_hdrs[BURST_SIZE]; + uint64_t rx_ts; + uint16_t nb_rx, nb_tx, i; + + nb_rx = rte_eth_rx_burst(src_port, 0, bufs, BURST_SIZE); + if (nb_rx == 0) + return; + + /* Record a single RX software timestamp for the whole burst. + * All packets in one burst arrived at essentially the same instant + * from rte_eth_rx_burst()'s perspective. + */ + rx_ts = sw_timestamp_ns(); + + *rx_cnt += nb_rx; + + /* + * Pass 1: Parse each packet once and remember PTP event headers. + * This avoids taking the TX timestamp too early — we want it as + * close to the actual rte_eth_tx_burst() call as possible. + */ + memset(ptp_hdrs, 0, sizeof(ptp_hdrs[0]) * nb_rx); + for (i = 0; i < nb_rx; i++) { + struct rte_ptp_hdr *hdr = ptp_hdr_find(bufs[i]); + + if (hdr == NULL) + continue; + + (*rx_ptp_cnt)++; + + /* Only event messages carry timestamps that need correction */ + if (!rte_ptp_is_event(rte_ptp_msg_type(hdr))) + continue; + + ptp_hdrs[i] = hdr; + } + + /* + * Pass 2: Take a single TX timestamp right before transmission. + * This minimises the gap between the measured tx_ts and the + * actual kernel write inside rte_eth_tx_burst(), giving the + * most accurate residence time we can achieve with SW timestamps. + * + * residence_time = tx_ts − rx_ts + * + * Remaining untracked delays: + * - Pre-RX: NIC DMA → rx_burst return (~1-5 µs, unavoidable) + * - Post-TX: tx_ts → kernel TAP write (~1-2 µs) + * Both are symmetric for Sync and Delay_Req so they largely + * cancel in the ptp4l offset calculation. + */ + uint64_t tx_ts = sw_timestamp_ns(); + int64_t residence_ns = (int64_t)(tx_ts - rx_ts); + + for (i = 0; i < nb_rx; i++) { + if (ptp_hdrs[i] == NULL) + continue; + rte_ptp_add_correction(ptp_hdrs[i], residence_ns); + (*corr_cnt)++; + } + + /* Forward the burst */ + nb_tx = rte_eth_tx_burst(dst_port, 0, bufs, nb_rx); + *tx_cnt += nb_tx; + + /* Free any unsent packets */ + for (i = nb_tx; i < nb_rx; i++) + rte_pktmbuf_free(bufs[i]); +} + +/* Print statistics */ + +static void +print_stats(void) +{ + LOG_INFO("=== Statistics ==="); + LOG_INFO(" PHY RX total: %"PRIu64, stats.phy_rx); + LOG_INFO(" PHY RX PTP: %"PRIu64, stats.phy_rx_ptp); + LOG_INFO(" TAP TX: %"PRIu64, stats.tap_tx); + LOG_INFO(" TAP RX total: %"PRIu64, stats.tap_rx); + LOG_INFO(" TAP RX PTP: %"PRIu64, stats.tap_rx_ptp); + LOG_INFO(" PHY TX: %"PRIu64, stats.phy_tx); + LOG_INFO(" Corrections: %"PRIu64, stats.corrections); +} + +/* Main relay loop */ + +static int +relay_loop(__rte_unused void *arg) +{ + uint64_t last_stats = rte_rdtsc(); + uint64_t stats_tsc = rte_get_tsc_hz() * stats_interval; + + LOG_INFO("Relay loop started on lcore %u", rte_lcore_id()); + LOG_INFO(" PHY port %u <--> TAP port %u", phy_port, tap_port); + LOG_INFO(" Correction field updates: enabled for event messages"); + + while (!force_quit) { + /* PHY → TAP */ + relay_burst(phy_port, tap_port, + &stats.phy_rx, &stats.phy_rx_ptp, + &stats.tap_tx, &stats.corrections); + + /* TAP → PHY */ + relay_burst(tap_port, phy_port, + &stats.tap_rx, &stats.tap_rx_ptp, + &stats.phy_tx, &stats.corrections); + + /* Periodic stats */ + if (rte_rdtsc() - last_stats > stats_tsc) { + print_stats(); + last_stats = rte_rdtsc(); + } + } + + print_stats(); + return 0; +} + +/* Argument parsing */ + +static void +usage(const char *prog) +{ + fprintf(stderr, + "Usage: %s [EAL options] -- [options]\n" + " -p PORT Physical NIC port ID (default: 0)\n" + " -t PORT TAP port ID (default: 1)\n" + " -T SECS Stats interval in seconds (default: 10)\n" + "\n" + "Example:\n" + " %s -l 0-1 --vdev=net_tap0,iface=dtap0 -- -p 0 -t 1\n" + "\n" + "Then run ptp4l with software timestamps:\n" + " ptp4l -i dtap0 -m -s -S\n", + prog, prog); +} + +static int +parse_args(int argc, char **argv) +{ + int opt; + + while ((opt = getopt(argc, argv, "p:t:T:h")) != -1) { + switch (opt) { + case 'p': + phy_port = (uint16_t)atoi(optarg); + break; + case 't': + tap_port = (uint16_t)atoi(optarg); + break; + case 'T': + stats_interval = (unsigned int)atoi(optarg); + break; + case 'h': + default: + usage(argv[0]); + return -1; + } + } + + return 0; +} + +/* Main */ + +int +main(int argc, char **argv) +{ + struct rte_mempool *mp; + uint16_t nb_ports; + int ret; + + /* EAL init */ + ret = rte_eal_init(argc, argv); + if (ret < 0) + rte_exit(EXIT_FAILURE, "EAL init failed\n"); + argc -= ret; + argv += ret; + + /* App args */ + ret = parse_args(argc, argv); + if (ret < 0) + rte_exit(EXIT_FAILURE, "Invalid arguments\n"); + + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + + nb_ports = rte_eth_dev_count_avail(); + if (nb_ports < 2) + rte_exit(EXIT_FAILURE, + "Need at least 2 ports (PHY + TAP).\n" + "Use --vdev=net_tap0,iface=dtap0\n"); + + if (!rte_eth_dev_is_valid_port(phy_port)) + rte_exit(EXIT_FAILURE, "Invalid PHY port %u\n", phy_port); + if (!rte_eth_dev_is_valid_port(tap_port)) + rte_exit(EXIT_FAILURE, "Invalid TAP port %u\n", tap_port); + + mp = rte_pktmbuf_pool_create("MBUF_POOL", NUM_MBUFS * nb_ports, + MBUF_CACHE, 0, + RTE_MBUF_DEFAULT_BUF_SIZE, + rte_socket_id()); + if (mp == NULL) + rte_exit(EXIT_FAILURE, "Cannot create mbuf pool\n"); + + LOG_INFO("Initializing PHY port %u...", phy_port); + ret = port_init(phy_port, mp); + if (ret != 0) + rte_exit(EXIT_FAILURE, "Cannot init PHY port %u (%d)\n", + phy_port, ret); + + LOG_INFO("Initializing TAP port %u...", tap_port); + ret = port_init(tap_port, mp); + if (ret != 0) + rte_exit(EXIT_FAILURE, "Cannot init TAP port %u (%d)\n", + tap_port, ret); + + LOG_INFO("PTP Software Relay ready"); + LOG_INFO(" PHY port: %u", phy_port); + LOG_INFO(" TAP port: %u", tap_port); + LOG_INFO(" Stats every: %u seconds", stats_interval); + LOG_INFO(" Correction: Transparent Clock (SW timestamps)"); + LOG_INFO(""); + LOG_INFO("Run ptp4l: ptp4l -i dtap0 -m -s -S"); + + /* Run relay on main lcore */ + relay_loop(NULL); + + /* Cleanup */ + LOG_INFO("Stopping ports..."); + rte_eth_dev_stop(phy_port); + rte_eth_dev_stop(tap_port); + rte_eth_dev_close(phy_port); + rte_eth_dev_close(tap_port); + rte_eal_cleanup(); + + return 0; +} -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [PATCH v6 3/4] doc: update release notes for PTP protocol library 2026-05-07 10:13 ` [PATCH v6 0/4] PTP protocol support in lib/net Rajesh Kumar 2026-05-07 10:13 ` [PATCH v6 1/4] lib/net: add IEEE 1588 PTP v2 protocol header definitions Rajesh Kumar 2026-05-07 10:13 ` [PATCH v6 2/4] examples/ptp_tap_relay_sw: add PTP software transparent clock relay Rajesh Kumar @ 2026-05-07 10:13 ` Rajesh Kumar 2026-05-07 10:13 ` [PATCH v6 4/4] examples/ptpclient: use shared PTP library definitions Rajesh Kumar 3 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-07 10:13 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, mb, Rajesh Kumar Update release notes with IEEE 1588 PTP additions: - PTP protocol definitions in lib/net/rte_ptp.h (header-only library with inline helpers and wire-format structures) - PTP software relay example application (ptp_tap_relay_sw) Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- doc/guides/rel_notes/release_26_07.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/doc/guides/rel_notes/release_26_07.rst b/doc/guides/rel_notes/release_26_07.rst index f012d47a4b..c64e6e141b 100644 --- a/doc/guides/rel_notes/release_26_07.rst +++ b/doc/guides/rel_notes/release_26_07.rst @@ -63,6 +63,18 @@ New Features ``rte_eal_init`` and the application is responsible for probing each device, * ``--auto-probing`` enables the initial bus probing, which is the current default behavior. +* **Added PTP protocol definitions (rte_ptp.h).** + + Added IEEE 1588 Precision Time Protocol header structures, constants, + and inline helpers to ``lib/net/rte_ptp.h``. Provides wire-format + structures with endian-annotated types and correctionField manipulation + for Transparent Clock implementations. + +* **Added PTP software relay example application.** + + Added a new example application ``ptp_tap_relay_sw`` demonstrating a + software PTP Transparent Clock relay between a DPDK port and a kernel + TAP interface. Removed Items ------------- -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [PATCH v6 4/4] examples/ptpclient: use shared PTP library definitions 2026-05-07 10:13 ` [PATCH v6 0/4] PTP protocol support in lib/net Rajesh Kumar ` (2 preceding siblings ...) 2026-05-07 10:13 ` [PATCH v6 3/4] doc: update release notes for PTP protocol library Rajesh Kumar @ 2026-05-07 10:13 ` Rajesh Kumar 3 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-07 10:13 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, mb, Rajesh Kumar Update ptpclient example to use IEEE 1588 PTP definitions from lib/net: - Replace PTP_PROTOCOL macro with standard RTE_ETHER_TYPE_1588 - Remove local duplicate PTP header definitions - Add net library dependency in meson.build - Leverage rte_ptp.h inline helpers for header access This aligns with DPDK library patterns and reduces code duplication. The example remains compatible with standard linuxptp (ptp4l) tooling for time synchronization. Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- examples/ptpclient/meson.build | 1 + examples/ptpclient/ptpclient.c | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/ptpclient/meson.build b/examples/ptpclient/meson.build index 2e9b7625fc..9087c987d5 100644 --- a/examples/ptpclient/meson.build +++ b/examples/ptpclient/meson.build @@ -7,6 +7,7 @@ # DPDK instance, use 'make' allow_experimental_apis = true +deps += ['net'] sources = files( 'ptpclient.c', ) diff --git a/examples/ptpclient/ptpclient.c b/examples/ptpclient/ptpclient.c index 174ca5dd70..ec6f316139 100644 --- a/examples/ptpclient/ptpclient.c +++ b/examples/ptpclient/ptpclient.c @@ -441,7 +441,7 @@ parse_fup(struct ptpv2_time_receiver_ordinary *ptp_data) /* Set multicast address 01-1B-19-00-00-00. */ rte_ether_addr_copy(ð_multicast, ð_hdr->dst_addr); - eth_hdr->ether_type = htons(PTP_PROTOCOL); + eth_hdr->ether_type = htons(RTE_ETHER_TYPE_1588); req_msg = rte_pktmbuf_mtod_offset(created_pkt, struct delay_req_msg *, sizeof(struct rte_ether_hdr)); @@ -582,7 +582,7 @@ parse_ptp_frames(uint16_t portid, struct rte_mbuf *m) { eth_hdr = rte_pktmbuf_mtod(m, struct rte_ether_hdr *); eth_type = rte_be_to_cpu_16(eth_hdr->ether_type); - if (eth_type == PTP_PROTOCOL) { + if (eth_type == RTE_ETHER_TYPE_1588) { ptp_data.m = m; ptp_data.portid = portid; ptp_hdr = rte_pktmbuf_mtod_offset(m, struct ptp_header *, -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [PATCH v7 0/4] IEEE 1588 PTP v2 protocol support in lib/net 2026-04-28 1:01 [RFC v1 0/4] introduce PTP protocol library and software relay example Rajesh Kumar ` (9 preceding siblings ...) 2026-05-07 10:13 ` [PATCH v6 0/4] PTP protocol support in lib/net Rajesh Kumar @ 2026-05-07 13:45 ` Rajesh Kumar 2026-05-07 13:45 ` [PATCH v7 1/4] lib/net: add IEEE 1588 PTP v2 protocol header definitions Rajesh Kumar ` (3 more replies) 2026-05-09 23:25 ` [PATCH v8 0/4] IEEE 1588 PTP v2 protocol support in lib/net Rajesh Kumar 11 siblings, 4 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-07 13:45 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, mb, Rajesh Kumar Add IEEE 1588-2019 Precision Time Protocol (PTP) support to DPDK via a new header library in lib/net/. This patchset provides wire-format packet structure definitions and inline helpers for Transparent Clock and ordinary clock applications. Design Rationale ================ Following DPDK conventions for protocol libraries (rte_tcp.h, rte_ip.h), PTP definitions are provided as a header-only library with inline functions for zero-overhead packet processing. All functions are suitable for real-time, performance-critical code paths. Contents ======== Patch 1: lib/net/rte_ptp.h - Header structures and inline helpers - rte_ptp_port_id: 10-byte port identity with EUI-64 clock ID - rte_ptp_hdr: 34-byte PTP v2 common message header - rte_ptp_timestamp: 10-byte nanosecond-precision timestamp - Message type constants (Sync, Delay_Req, Announce, etc.) - Flag field bits (two-step, unicast, leap indicator) - Inline helpers: rte_ptp_msg_type(), rte_ptp_seq_id(), rte_ptp_is_event(), rte_ptp_correction_ns(), rte_ptp_add_correction(), rte_ptp_timestamp_to_ns() Patch 2: examples/ptp_tap_relay_sw - Software PTP Transparent Clock relay - Relays PTP packets between a DPDK-bound physical NIC and Linux TAP - Software timestamp-based residence time measurement (CLOCK_MONOTONIC) - Accumulates residence time to correctionField per IEEE 1588-2019 §10.2 - Handles L2, VLAN/QinQ, UDP/IPv4, UDP/IPv6 PTP encapsulations - Works with unmodified Linux kernel and stock DPDK (no patches required) - Compatible with standard linuxptp (ptp4l) tooling Patch 3: doc/guides/rel_notes/release_26_07.rst - Release notes update Patch 4: examples/ptpclient - Update to use lib/net PTP definitions - Replace local struct ptp_header with struct rte_ptp_hdr - Replace local struct tstamp with struct rte_ptp_timestamp - Replace local struct clock_id with uint8_t[8] arrays - Use RTE_PTP_MSGTYPE_* constants instead of local defines - Use rte_ptp_msg_type(), rte_ptp_seq_id() inline helpers - Remove local PTP_PROTOCOL macro (use RTE_ETHER_TYPE_1588) - Add lib/net dependency in meson.build Testing ======= - Build: `ninja -C build` - clean compilation - Examples: Both ptpclient and ptp_tap_relay_sw compile and link correctly - No stale references to old patterns or removed functions Changelog ========= v7 changes: - Added sample app guide to sample_app_ug toctree to fix Sphinx warning - Fixed all checkpatch warnings (commit message line lengths <= 75 chars) - Refactored ptpclient to use shared lib/net/rte_ptp.h definitions - All 5 patches (cover + 4 functional) pass checkpatch validation v6 changes: - Restructured to lib/net (header-only, following lib/net conventions) - Removed separate DPI library functions (moved to example local parser) - Removed app/test unit tests (header-only, example-driven testing) - Removed programmer's guide (lib/net headers use Doxygen API docs only) Rajesh Kumar (4): lib/net: add IEEE 1588 PTP v2 protocol header definitions examples/ptp_tap_relay_sw: add PTP software transparent clock relay doc: update release notes for PTP protocol library examples/ptpclient: use shared PTP library definitions MAINTAINERS | 6 + doc/guides/rel_notes/release_26_07.rst | 12 + doc/guides/sample_app_ug/index.rst | 1 + doc/guides/sample_app_ug/ptp_tap_relay_sw.rst | 212 +++++++++ examples/ptp_tap_relay_sw/Makefile | 41 ++ examples/ptp_tap_relay_sw/meson.build | 13 + examples/ptp_tap_relay_sw/ptp_parse.h | 211 +++++++++ examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c | 432 ++++++++++++++++++ examples/ptpclient/meson.build | 1 + examples/ptpclient/ptpclient.c | 204 ++++----- lib/net/meson.build | 1 + lib/net/rte_ptp.h | 264 +++++++++++ 12 files changed, 1274 insertions(+), 124 deletions(-) create mode 100644 doc/guides/sample_app_ug/ptp_tap_relay_sw.rst create mode 100644 examples/ptp_tap_relay_sw/Makefile create mode 100644 examples/ptp_tap_relay_sw/meson.build create mode 100644 examples/ptp_tap_relay_sw/ptp_parse.h create mode 100644 examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c create mode 100644 lib/net/rte_ptp.h -- 2.53.0 ^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v7 1/4] lib/net: add IEEE 1588 PTP v2 protocol header definitions 2026-05-07 13:45 ` [PATCH v7 0/4] IEEE 1588 PTP v2 protocol support in lib/net Rajesh Kumar @ 2026-05-07 13:45 ` Rajesh Kumar 2026-05-07 15:27 ` Morten Brørup 2026-05-07 13:45 ` [PATCH v7 2/4] examples/ptp_tap_relay_sw: add PTP software transparent clock relay Rajesh Kumar ` (2 subsequent siblings) 3 siblings, 1 reply; 60+ messages in thread From: Rajesh Kumar @ 2026-05-07 13:45 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, mb, Rajesh Kumar Add PTP (Precision Time Protocol) header structures and inline helper functions to lib/net following DPDK conventions for protocol libraries (similar to rte_tcp.h, rte_ip.h). Provides wire-format structures with endian-annotated types: - rte_ptp_port_id: 10-byte port identity with EUI-64 clock ID - rte_ptp_hdr: 34-byte common message header with correctionField - rte_ptp_timestamp: 10-byte nanosecond-precision timestamp PTP message type constants for all IEEE 1588-2019 message types (Sync, Delay_Req, Announce, Management, etc.) and flag field bits (two-step, unicast, leap indicator). Inline accessor and utility functions for performance: - rte_ptp_msg_type(), rte_ptp_version(), rte_ptp_domain() - rte_ptp_seq_id(), rte_ptp_is_event(), rte_ptp_is_two_step() - rte_ptp_correction_ns(), rte_ptp_add_correction() - rte_ptp_timestamp_to_ns() for timestamp conversion Supports all PTP encapsulations: L2 (EtherType 0x88F7), VLAN/QinQ, UDP/IPv4, and UDP/IPv6 (ports 319/320). All inline functions — zero function call overhead, suitable for real-time packet processing. Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- MAINTAINERS | 6 + lib/net/meson.build | 1 + lib/net/rte_ptp.h | 264 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 271 insertions(+) create mode 100644 lib/net/rte_ptp.h diff --git a/MAINTAINERS b/MAINTAINERS index 0f5539f851..da31ada871 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1665,6 +1665,12 @@ F: doc/guides/prog_guide/ipsec_lib.rst M: Vladimir Medvedkin <vladimir.medvedkin@intel.com> F: app/test-sad/ +PTP - lib/net +M: Rajesh Kumar <rajesh3.kumar@intel.com> +F: lib/net/rte_ptp.h +F: examples/ptp_tap_relay_sw/ +F: doc/guides/sample_app_ug/ptp_tap_relay_sw.rst + PDCP - EXPERIMENTAL M: Anoob Joseph <anoobj@marvell.com> M: Volodymyr Fialko <vfialko@marvell.com> diff --git a/lib/net/meson.build b/lib/net/meson.build index 3fad5edc5b..63d13719f3 100644 --- a/lib/net/meson.build +++ b/lib/net/meson.build @@ -28,6 +28,7 @@ headers = files( 'rte_geneve.h', 'rte_l2tpv2.h', 'rte_ppp.h', + 'rte_ptp.h', 'rte_ib.h', ) diff --git a/lib/net/rte_ptp.h b/lib/net/rte_ptp.h new file mode 100644 index 0000000000..649b944d29 --- /dev/null +++ b/lib/net/rte_ptp.h @@ -0,0 +1,264 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2026 Intel Corporation + */ + +#ifndef _RTE_PTP_H_ +#define _RTE_PTP_H_ + +/** + * @file + * + * PTP (IEEE 1588) protocol definitions + */ + +#include <stdint.h> +#include <stdbool.h> + +#include <rte_byteorder.h> +#include <rte_common.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * PTP Constants + */ + +/** PTP over UDP event port (Sync, Delay_Req, PDelay_Req, PDelay_Resp). */ +#define RTE_PTP_EVENT_PORT 319 + +/** PTP over UDP general port (Follow_Up, Delay_Resp, Announce, etc.). */ +#define RTE_PTP_GENERAL_PORT 320 + +/** PTP multicast MAC address: 01:1B:19:00:00:00. */ +#define RTE_PTP_MULTICAST_MAC { 0x01, 0x1B, 0x19, 0x00, 0x00, 0x00 } + +/** PTP peer delay multicast MAC: 01:80:C2:00:00:0E. */ +#define RTE_PTP_PDELAY_MULTICAST_MAC { 0x01, 0x80, 0xC2, 0x00, 0x00, 0x0E } + +/* + * PTP Message Types (IEEE 1588-2019 Table 36) + */ + +#define RTE_PTP_MSGTYPE_SYNC 0x0 /**< Sync (event). */ +#define RTE_PTP_MSGTYPE_DELAY_REQ 0x1 /**< Delay_Req (event). */ +#define RTE_PTP_MSGTYPE_PDELAY_REQ 0x2 /**< Peer_Delay_Req (event). */ +#define RTE_PTP_MSGTYPE_PDELAY_RESP 0x3 /**< Peer_Delay_Resp (event). */ +#define RTE_PTP_MSGTYPE_FOLLOW_UP 0x8 /**< Follow_Up (general). */ +#define RTE_PTP_MSGTYPE_DELAY_RESP 0x9 /**< Delay_Resp (general). */ +#define RTE_PTP_MSGTYPE_PDELAY_RESP_FU 0xA /**< Peer_Delay_Resp_Follow_Up. */ +#define RTE_PTP_MSGTYPE_ANNOUNCE 0xB /**< Announce (general). */ +#define RTE_PTP_MSGTYPE_SIGNALING 0xC /**< Signaling (general). */ +#define RTE_PTP_MSGTYPE_MANAGEMENT 0xD /**< Management (general). */ + +/* + * PTP Flag Field Bits (IEEE 1588-2019 Table 37) + * + * These constants are for use after rte_be_to_cpu_16(hdr->flags). + * flagField[0] (octet 6) maps to host bits 8-15. + * flagField[1] (octet 7) maps to host bits 0-7. + */ + +#define RTE_PTP_FLAG_TWO_STEP (1 << 9) /**< Two-step flag. */ +#define RTE_PTP_FLAG_UNICAST (1 << 10) /**< Unicast flag. */ +#define RTE_PTP_FLAG_LI_61 (1 << 0) /**< Leap indicator 61. */ +#define RTE_PTP_FLAG_LI_59 (1 << 1) /**< Leap indicator 59. */ + +/* + * PTP Header Structures (IEEE 1588-2019) + */ + +/** + * PTP Port Identity (10 bytes). + */ +struct __rte_packed_begin rte_ptp_port_id { + uint8_t clock_id[8]; /**< clockIdentity (EUI-64). */ + rte_be16_t port_number; /**< portNumber. */ +} __rte_packed_end; + +/** + * PTP Common Message Header (34 bytes). + */ +struct __rte_packed_begin rte_ptp_hdr { + uint8_t msg_type; /**< transportSpecific (4) | messageType (4). */ + uint8_t version; /**< minorVersionPTP (4) | versionPTP (4). */ + rte_be16_t msg_length; /**< Total message length in bytes. */ + uint8_t domain_number; /**< PTP domain (0-255). */ + uint8_t minor_sdo_id; /**< minorSdoId (IEEE 1588-2019). */ + rte_be16_t flags; /**< Flag field (see RTE_PTP_FLAG_*). */ + rte_be64_t correction; /**< correctionField (scaled ns, 48.16 fixed). */ + rte_be32_t msg_type_specific; /**< messageTypeSpecific. */ + struct rte_ptp_port_id source_port_id; /**< sourcePortIdentity. */ + rte_be16_t sequence_id; /**< sequenceId. */ + uint8_t control; /**< controlField (deprecated in 1588-2019). */ + int8_t log_msg_interval; /**< logMessageInterval. */ +} __rte_packed_end; + +/** + * PTP Timestamp (10 bytes, used in Sync/Delay_Req/Follow_Up bodies). + */ +struct __rte_packed_begin rte_ptp_timestamp { + rte_be16_t seconds_hi; /**< Upper 16 bits of seconds. */ + rte_be32_t seconds_lo; /**< Lower 32 bits of seconds. */ + rte_be32_t nanoseconds; /**< Nanoseconds (0-999999999). */ +} __rte_packed_end; + +/* + * Inline Helpers + */ + +/** + * Extract PTP message type from header. + * + * @param hdr + * Pointer to PTP header. + * @return + * Message type (0x0-0xF). + */ +static inline uint8_t +rte_ptp_msg_type(const struct rte_ptp_hdr *hdr) +{ + return hdr->msg_type & 0x0F; +} + +/** + * Extract transport-specific field from header. + * + * @param hdr + * Pointer to PTP header. + * @return + * Transport-specific value (upper nibble, 0x0-0xF). + */ +static inline uint8_t +rte_ptp_transport_specific(const struct rte_ptp_hdr *hdr) +{ + return (hdr->msg_type >> 4) & 0x0F; +} + +/** + * Extract PTP version from header. + * + * @param hdr + * Pointer to PTP header. + * @return + * PTP version number (typically 2). + */ +static inline uint8_t +rte_ptp_version(const struct rte_ptp_hdr *hdr) +{ + return hdr->version & 0x0F; +} + +/** + * Get sequence ID from PTP header (host byte order). + * + * @param hdr + * Pointer to PTP header. + * @return + * Sequence ID in host byte order. + */ +static inline uint16_t +rte_ptp_seq_id(const struct rte_ptp_hdr *hdr) +{ + return rte_be_to_cpu_16(hdr->sequence_id); +} + +/** + * Get PTP domain number. + * + * @param hdr + * Pointer to PTP header. + * @return + * Domain number (0-255). + */ +static inline uint8_t +rte_ptp_domain(const struct rte_ptp_hdr *hdr) +{ + return hdr->domain_number; +} + +/** + * Check if PTP message type is an event message. + * Event messages (msg_type 0x0-0x3) require timestamps. + * + * @param msg_type + * PTP message type value (0x0-0xF). + * @return + * true if event message, false otherwise. + */ +static inline bool +rte_ptp_is_event(int msg_type) +{ + return msg_type >= 0 && msg_type <= RTE_PTP_MSGTYPE_PDELAY_RESP; +} + +/** + * Check if the two-step flag is set in a PTP header. + * + * @param hdr + * Pointer to PTP header. + * @return + * true if two-step flag is set. + */ +static inline bool +rte_ptp_is_two_step(const struct rte_ptp_hdr *hdr) +{ + return (rte_be_to_cpu_16(hdr->flags) & RTE_PTP_FLAG_TWO_STEP) != 0; +} + +/** + * Get correctionField value in nanoseconds (from 48.16 fixed-point). + * + * @param hdr + * Pointer to PTP header. + * @return + * Correction value in nanoseconds. + */ +static inline int64_t +rte_ptp_correction_ns(const struct rte_ptp_hdr *hdr) +{ + return (int64_t)rte_be_to_cpu_64(hdr->correction) >> 16; +} + +/** + * Add a residence time (in nanoseconds) to the correctionField. + * Used by Transparent Clocks to account for relay transit delay. + * The correctionField uses IEEE 1588 scaled nanoseconds (48.16 fixed-point). + * + * @param hdr + * Pointer to PTP header (will be modified in-place). + * @param residence_ns + * Residence time in nanoseconds to add. + */ +static inline void +rte_ptp_add_correction(struct rte_ptp_hdr *hdr, int64_t residence_ns) +{ + int64_t cf = (int64_t)rte_be_to_cpu_64(hdr->correction); + + cf += (int64_t)((uint64_t)residence_ns << 16); + hdr->correction = rte_cpu_to_be_64(cf); +} + +/** + * Convert a PTP timestamp structure to nanoseconds since epoch. + * + * @param ts + * Pointer to PTP timestamp. + * @return + * Time in nanoseconds since epoch. + */ +static inline uint64_t +rte_ptp_timestamp_to_ns(const struct rte_ptp_timestamp *ts) +{ + uint64_t sec = ((uint64_t)rte_be_to_cpu_16(ts->seconds_hi) << 32) | + rte_be_to_cpu_32(ts->seconds_lo); + + return sec * 1000000000ULL + rte_be_to_cpu_32(ts->nanoseconds); +} + +#ifdef __cplusplus +} +#endif + +#endif /* _RTE_PTP_H_ */ -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* RE: [PATCH v7 1/4] lib/net: add IEEE 1588 PTP v2 protocol header definitions 2026-05-07 13:45 ` [PATCH v7 1/4] lib/net: add IEEE 1588 PTP v2 protocol header definitions Rajesh Kumar @ 2026-05-07 15:27 ` Morten Brørup 2026-05-09 17:57 ` Kumar, Rajesh 0 siblings, 1 reply; 60+ messages in thread From: Morten Brørup @ 2026-05-07 15:27 UTC (permalink / raw) To: Rajesh Kumar, dev; +Cc: bruce.richardson, aman.deep.singh, stephen > From: Rajesh Kumar [mailto:rajesh3.kumar@intel.com] > Sent: Thursday, 7 May 2026 15.45 > To: dev@dpdk.org > > Add PTP (Precision Time Protocol) header structures and inline helper > functions to lib/net following DPDK conventions for protocol libraries > (similar to rte_tcp.h, rte_ip.h). > > Provides wire-format structures with endian-annotated types: > - rte_ptp_port_id: 10-byte port identity with EUI-64 clock ID > - rte_ptp_hdr: 34-byte common message header with correctionField > - rte_ptp_timestamp: 10-byte nanosecond-precision timestamp > > PTP message type constants for all IEEE 1588-2019 message types > (Sync, Delay_Req, Announce, Management, etc.) and flag field bits > (two-step, unicast, leap indicator). > > Inline accessor and utility functions for performance: > - rte_ptp_msg_type(), rte_ptp_version(), rte_ptp_domain() > - rte_ptp_seq_id(), rte_ptp_is_event(), rte_ptp_is_two_step() > - rte_ptp_correction_ns(), rte_ptp_add_correction() > - rte_ptp_timestamp_to_ns() for timestamp conversion > > Supports all PTP encapsulations: L2 (EtherType 0x88F7), VLAN/QinQ, > UDP/IPv4, and UDP/IPv6 (ports 319/320). > > All inline functions — zero function call overhead, suitable for > real-time packet processing. > > Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> Great progress. My next wave of comments inline below. :-) > --- > MAINTAINERS | 6 + > lib/net/meson.build | 1 + > lib/net/rte_ptp.h | 264 ++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 271 insertions(+) > create mode 100644 lib/net/rte_ptp.h > > diff --git a/MAINTAINERS b/MAINTAINERS > index 0f5539f851..da31ada871 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -1665,6 +1665,12 @@ F: doc/guides/prog_guide/ipsec_lib.rst > M: Vladimir Medvedkin <vladimir.medvedkin@intel.com> > F: app/test-sad/ > > +PTP - lib/net Please remove lib/net from this headline. Intead, suggest: PTP (IEEE 1588 Precision Time Protocol) > +M: Rajesh Kumar <rajesh3.kumar@intel.com> > +F: lib/net/rte_ptp.h > +F: examples/ptp_tap_relay_sw/ > +F: doc/guides/sample_app_ug/ptp_tap_relay_sw.rst > + > PDCP - EXPERIMENTAL > M: Anoob Joseph <anoobj@marvell.com> > M: Volodymyr Fialko <vfialko@marvell.com> > diff --git a/lib/net/meson.build b/lib/net/meson.build > index 3fad5edc5b..63d13719f3 100644 > --- a/lib/net/meson.build > +++ b/lib/net/meson.build > @@ -28,6 +28,7 @@ headers = files( > 'rte_geneve.h', > 'rte_l2tpv2.h', > 'rte_ppp.h', > + 'rte_ptp.h', > 'rte_ib.h', > ) > > diff --git a/lib/net/rte_ptp.h b/lib/net/rte_ptp.h > new file mode 100644 > index 0000000000..649b944d29 > --- /dev/null > +++ b/lib/net/rte_ptp.h > @@ -0,0 +1,264 @@ > +/* SPDX-License-Identifier: BSD-3-Clause > + * Copyright(c) 2026 Intel Corporation > + */ > + > +#ifndef _RTE_PTP_H_ > +#define _RTE_PTP_H_ > + > +/** > + * @file > + * > + * PTP (IEEE 1588) protocol definitions > + */ > + > +#include <stdint.h> > +#include <stdbool.h> > + > +#include <rte_byteorder.h> > +#include <rte_common.h> > + > +#ifdef __cplusplus > +extern "C" { > +#endif > + > +/* > + * PTP Constants > + */ > + > +/** PTP over UDP event port (Sync, Delay_Req, PDelay_Req, > PDelay_Resp). */ > +#define RTE_PTP_EVENT_PORT 319 > + > +/** PTP over UDP general port (Follow_Up, Delay_Resp, Announce, etc.). > */ > +#define RTE_PTP_GENERAL_PORT 320 The libc header <netinet/in.h> defines IPPORT_xxx. We should use something similar: #define RTE_IPPORT_PTP_EVENT and RTE_IPPORT_PTP_GENERAL > + > +/** PTP multicast MAC address: 01:1B:19:00:00:00. */ > +#define RTE_PTP_MULTICAST_MAC { 0x01, 0x1B, 0x19, 0x00, 0x00, 0x00 > } > + > +/** PTP peer delay multicast MAC: 01:80:C2:00:00:0E. */ > +#define RTE_PTP_PDELAY_MULTICAST_MAC { 0x01, 0x80, 0xC2, 0x00, 0x00, > 0x0E } Similarly here; we should establish a convention for MAC addresses, like RTE_ETHER_TYPE_xxx in DPDK <lib/net/rte_ether.h>. Suggest: RTE_ETHER_ADDR_PTP_MULTICAST and RTE_ETHER_ADDR_PTP_MULTICAST_PDELAY This also follows the general naming convention of having the broadest scope is first and the narrowest scope last. > + > +/* > + * PTP Message Types (IEEE 1588-2019 Table 36) > + */ > + > +#define RTE_PTP_MSGTYPE_SYNC 0x0 /**< Sync (event). */ > +#define RTE_PTP_MSGTYPE_DELAY_REQ 0x1 /**< Delay_Req (event). > */ > +#define RTE_PTP_MSGTYPE_PDELAY_REQ 0x2 /**< Peer_Delay_Req > (event). */ > +#define RTE_PTP_MSGTYPE_PDELAY_RESP 0x3 /**< Peer_Delay_Resp > (event). */ > +#define RTE_PTP_MSGTYPE_FOLLOW_UP 0x8 /**< Follow_Up (general). > */ > +#define RTE_PTP_MSGTYPE_DELAY_RESP 0x9 /**< Delay_Resp > (general). */ > +#define RTE_PTP_MSGTYPE_PDELAY_RESP_FU 0xA /**< > Peer_Delay_Resp_Follow_Up. */ Missing in the description: (general) For consistency, consider renaming RTE_PTP_MSGTYPE_FOLLOW_UP to RTE_PTP_MSGTYPE_FU. > +#define RTE_PTP_MSGTYPE_ANNOUNCE 0xB /**< Announce (general). > */ > +#define RTE_PTP_MSGTYPE_SIGNALING 0xC /**< Signaling (general). > */ > +#define RTE_PTP_MSGTYPE_MANAGEMENT 0xD /**< Management > (general). */ > + > +/* > + * PTP Flag Field Bits (IEEE 1588-2019 Table 37) > + * > + * These constants are for use after rte_be_to_cpu_16(hdr->flags). > + * flagField[0] (octet 6) maps to host bits 8-15. > + * flagField[1] (octet 7) maps to host bits 0-7. > + */ > + > +#define RTE_PTP_FLAG_TWO_STEP (1 << 9) /**< Two-step flag. */ > +#define RTE_PTP_FLAG_UNICAST (1 << 10) /**< Unicast flag. */ > +#define RTE_PTP_FLAG_LI_61 (1 << 0) /**< Leap indicator 61. */ > +#define RTE_PTP_FLAG_LI_59 (1 << 1) /**< Leap indicator 59. */ We don't have a RTE_BIT16() macro like the RTE_BIT32/64() macros, so maybe use: #define RTE_PTP_FLAG_TWO_STEP (UINT16_C(1) << 9) /**< Two-step flag. */ #define RTE_PTP_FLAG_UNICAST (UINT16_C(1) << 10) /**< Unicast flag. */ #define RTE_PTP_FLAG_LI_61 (UINT16_C(1) << 0) /**< Leap indicator 61. */ #define RTE_PTP_FLAG_LI_59 (UINT16_C(1) << 1) /**< Leap indicator 59. */ > + > +/* > + * PTP Header Structures (IEEE 1588-2019) > + */ > + > +/** > + * PTP Port Identity (10 bytes). > + */ > +struct __rte_packed_begin rte_ptp_port_id { > + uint8_t clock_id[8]; /**< clockIdentity (EUI-64). */ > + rte_be16_t port_number; /**< portNumber. */ > +} __rte_packed_end; > + > +/** > + * PTP Common Message Header (34 bytes). > + */ > +struct __rte_packed_begin rte_ptp_hdr { > + uint8_t msg_type; /**< transportSpecific (4) | > messageType (4). */ > + uint8_t version; /**< minorVersionPTP (4) | versionPTP > (4). */ The two fields above should be split into their nibbles, for direct access, like the version_ihl field in the IPv4 header: https://elixir.bootlin.com/dpdk/v26.03/source/lib/net/rte_ip4.h#L43 E.g.: __extension__ union { uint8_t ts_msgtype; /**< Message type */ struct { #if RTE_BYTE_ORDER == RTE_LITTLE_ENDIAN uint8_t msg_type:4; /**< messageType */ uint8_t ts:4; /**< transportSpecific */ #elif RTE_BYTE_ORDER == RTE_BIG_ENDIAN uint8_t ts:4; /**< transportSpecific */ uint8_t msg_type:4; /**< messageType */ #endif }; }; Warning: I'm not sure I got the nibble order right. Make sure you do! :-) > + rte_be16_t msg_length; /**< Total message length in bytes. */ > + uint8_t domain_number; /**< PTP domain (0-255). */ > + uint8_t minor_sdo_id; /**< minorSdoId (IEEE 1588-2019). */ > + rte_be16_t flags; /**< Flag field (see RTE_PTP_FLAG_*). > */ > + rte_be64_t correction; /**< correctionField (scaled ns, 48.16 > fixed). */ > + rte_be32_t msg_type_specific; /**< messageTypeSpecific. */ > + struct rte_ptp_port_id source_port_id; /**< sourcePortIdentity. > */ > + rte_be16_t sequence_id; /**< sequenceId. */ > + uint8_t control; /**< controlField (deprecated in 1588- > 2019). */ > + int8_t log_msg_interval; /**< logMessageInterval. */ > +} __rte_packed_end; > + > +/** > + * PTP Timestamp (10 bytes, used in Sync/Delay_Req/Follow_Up bodies). The PTP timestamp origo should be mentioned here. E.g. the UNIX time_t origo is Jan 1st 00:00:00 1970. > + */ > +struct __rte_packed_begin rte_ptp_timestamp { > + rte_be16_t seconds_hi; /**< Upper 16 bits of seconds. */ > + rte_be32_t seconds_lo; /**< Lower 32 bits of seconds. */ > + rte_be32_t nanoseconds; /**< Nanoseconds (0-999999999). */ > +} __rte_packed_end; > + > +/* > + * Inline Helpers > + */ > + > +/** > + * Extract PTP message type from header. > + * > + * @param hdr > + * Pointer to PTP header. > + * @return > + * Message type (0x0-0xF). > + */ > +static inline uint8_t > +rte_ptp_msg_type(const struct rte_ptp_hdr *hdr) > +{ > + return hdr->msg_type & 0x0F; > +} > + > +/** > + * Extract transport-specific field from header. > + * > + * @param hdr > + * Pointer to PTP header. > + * @return > + * Transport-specific value (upper nibble, 0x0-0xF). > + */ > +static inline uint8_t > +rte_ptp_transport_specific(const struct rte_ptp_hdr *hdr) > +{ > + return (hdr->msg_type >> 4) & 0x0F; > +} > + > +/** > + * Extract PTP version from header. > + * > + * @param hdr > + * Pointer to PTP header. > + * @return > + * PTP version number (typically 2). > + */ > +static inline uint8_t > +rte_ptp_version(const struct rte_ptp_hdr *hdr) > +{ > + return hdr->version & 0x0F; > +} > + > +/** > + * Get sequence ID from PTP header (host byte order). > + * > + * @param hdr > + * Pointer to PTP header. > + * @return > + * Sequence ID in host byte order. > + */ > +static inline uint16_t > +rte_ptp_seq_id(const struct rte_ptp_hdr *hdr) > +{ > + return rte_be_to_cpu_16(hdr->sequence_id); > +} > + > +/** > + * Get PTP domain number. > + * > + * @param hdr > + * Pointer to PTP header. > + * @return > + * Domain number (0-255). > + */ > +static inline uint8_t > +rte_ptp_domain(const struct rte_ptp_hdr *hdr) > +{ > + return hdr->domain_number; > +} The above "get" accessor functions are superfluous. Please remove: rte_ptp_msg_type() rte_ptp_transport_specific() rte_ptp_version() rte_ptp_seq_id() rte_ptp_domain() > + > +/** > + * Check if PTP message type is an event message. > + * Event messages (msg_type 0x0-0x3) require timestamps. > + * > + * @param msg_type > + * PTP message type value (0x0-0xF). > + * @return > + * true if event message, false otherwise. > + */ > +static inline bool > +rte_ptp_is_event(int msg_type) > +{ > + return msg_type >= 0 && msg_type <= RTE_PTP_MSGTYPE_PDELAY_RESP; > +} Suggest passing the parameter as const struct rte_ptp_hdr *hdr, like in rte_ptp_is_two_step() below. > + > +/** > + * Check if the two-step flag is set in a PTP header. > + * > + * @param hdr > + * Pointer to PTP header. > + * @return > + * true if two-step flag is set. > + */ > +static inline bool > +rte_ptp_is_two_step(const struct rte_ptp_hdr *hdr) > +{ > + return (rte_be_to_cpu_16(hdr->flags) & RTE_PTP_FLAG_TWO_STEP) != > 0; Faster way: return (hdr->flags & RTE_BE16(RTE_PTP_FLAG_TWO_STEP)) != 0; > +} > + > +/** > + * Get correctionField value in nanoseconds (from 48.16 fixed-point). > + * > + * @param hdr > + * Pointer to PTP header. > + * @return > + * Correction value in nanoseconds. > + */ > +static inline int64_t > +rte_ptp_correction_ns(const struct rte_ptp_hdr *hdr) > +{ > + return (int64_t)rte_be_to_cpu_64(hdr->correction) >> 16; > +} This "get" accessor function is superfluous. Please remove: rte_ptp_correction_ns() The "correction" field in the PTP header structure should sufficiently describe the field's fixed-point encoding. > + > +/** > + * Add a residence time (in nanoseconds) to the correctionField. > + * Used by Transparent Clocks to account for relay transit delay. > + * The correctionField uses IEEE 1588 scaled nanoseconds (48.16 fixed- > point). > + * > + * @param hdr > + * Pointer to PTP header (will be modified in-place). > + * @param residence_ns > + * Residence time in nanoseconds to add. > + */ > +static inline void > +rte_ptp_add_correction(struct rte_ptp_hdr *hdr, int64_t residence_ns) > +{ > + int64_t cf = (int64_t)rte_be_to_cpu_64(hdr->correction); > + > + cf += (int64_t)((uint64_t)residence_ns << 16); > + hdr->correction = rte_cpu_to_be_64(cf); > +} I don't think negative time can be added, so please use uint64_t instead of int64_t for the parameter and inside the function. Note to reviewers: "residence time" is the term in the standard. "sojourn time" is not used. > + > +/** > + * Convert a PTP timestamp structure to nanoseconds since epoch. > + * > + * @param ts > + * Pointer to PTP timestamp. > + * @return > + * Time in nanoseconds since epoch. > + */ > +static inline uint64_t > +rte_ptp_timestamp_to_ns(const struct rte_ptp_timestamp *ts) > +{ > + uint64_t sec = ((uint64_t)rte_be_to_cpu_16(ts->seconds_hi) << 32) > | > + rte_be_to_cpu_32(ts->seconds_lo); > + > + return sec * 1000000000ULL + rte_be_to_cpu_32(ts->nanoseconds); > +} 1000000000ULL -> UINT64_C(1000000000) > + > +#ifdef __cplusplus > +} > +#endif > + > +#endif /* _RTE_PTP_H_ */ > -- > 2.53.0 ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v7 1/4] lib/net: add IEEE 1588 PTP v2 protocol header definitions 2026-05-07 15:27 ` Morten Brørup @ 2026-05-09 17:57 ` Kumar, Rajesh 0 siblings, 0 replies; 60+ messages in thread From: Kumar, Rajesh @ 2026-05-09 17:57 UTC (permalink / raw) To: Morten Brørup, dev; +Cc: bruce.richardson, aman.deep.singh, stephen On 07-05-2026 08:57 pm, Morten Brørup wrote: >> From: Rajesh Kumar [mailto:rajesh3.kumar@intel.com] >> Sent: Thursday, 7 May 2026 15.45 >> To: dev@dpdk.org >> >> Add PTP (Precision Time Protocol) header structures and inline helper >> functions to lib/net following DPDK conventions for protocol libraries >> (similar to rte_tcp.h, rte_ip.h). >> >> Provides wire-format structures with endian-annotated types: >> - rte_ptp_port_id: 10-byte port identity with EUI-64 clock ID >> - rte_ptp_hdr: 34-byte common message header with correctionField >> - rte_ptp_timestamp: 10-byte nanosecond-precision timestamp >> >> PTP message type constants for all IEEE 1588-2019 message types >> (Sync, Delay_Req, Announce, Management, etc.) and flag field bits >> (two-step, unicast, leap indicator). >> >> Inline accessor and utility functions for performance: >> - rte_ptp_msg_type(), rte_ptp_version(), rte_ptp_domain() >> - rte_ptp_seq_id(), rte_ptp_is_event(), rte_ptp_is_two_step() >> - rte_ptp_correction_ns(), rte_ptp_add_correction() >> - rte_ptp_timestamp_to_ns() for timestamp conversion >> >> Supports all PTP encapsulations: L2 (EtherType 0x88F7), VLAN/QinQ, >> UDP/IPv4, and UDP/IPv6 (ports 319/320). >> >> All inline functions — zero function call overhead, suitable for >> real-time packet processing. >> >> Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> > Great progress. > > My next wave of comments inline below. :-) Appreciate your time and feedback :-) > >> --- >> MAINTAINERS | 6 + >> lib/net/meson.build | 1 + >> lib/net/rte_ptp.h | 264 ++++++++++++++++++++++++++++++++++++++++++++ >> 3 files changed, 271 insertions(+) >> create mode 100644 lib/net/rte_ptp.h >> >> diff --git a/MAINTAINERS b/MAINTAINERS >> index 0f5539f851..da31ada871 100644 >> --- a/MAINTAINERS >> +++ b/MAINTAINERS >> @@ -1665,6 +1665,12 @@ F: doc/guides/prog_guide/ipsec_lib.rst >> M: Vladimir Medvedkin <vladimir.medvedkin@intel.com> >> F: app/test-sad/ >> >> +PTP - lib/net > Please remove lib/net from this headline. > Intead, suggest: > PTP (IEEE 1588 Precision Time Protocol) Done. Changed to PTP (IEEE 1588 Precision Time Protocol) > >> +M: Rajesh Kumar <rajesh3.kumar@intel.com> >> +F: lib/net/rte_ptp.h >> +F: examples/ptp_tap_relay_sw/ >> +F: doc/guides/sample_app_ug/ptp_tap_relay_sw.rst >> + >> PDCP - EXPERIMENTAL >> M: Anoob Joseph <anoobj@marvell.com> >> M: Volodymyr Fialko <vfialko@marvell.com> >> diff --git a/lib/net/meson.build b/lib/net/meson.build >> index 3fad5edc5b..63d13719f3 100644 >> --- a/lib/net/meson.build >> +++ b/lib/net/meson.build >> @@ -28,6 +28,7 @@ headers = files( >> 'rte_geneve.h', >> 'rte_l2tpv2.h', >> 'rte_ppp.h', >> + 'rte_ptp.h', >> 'rte_ib.h', >> ) >> >> diff --git a/lib/net/rte_ptp.h b/lib/net/rte_ptp.h >> new file mode 100644 >> index 0000000000..649b944d29 >> --- /dev/null >> +++ b/lib/net/rte_ptp.h >> @@ -0,0 +1,264 @@ >> +/* SPDX-License-Identifier: BSD-3-Clause >> + * Copyright(c) 2026 Intel Corporation >> + */ >> + >> +#ifndef _RTE_PTP_H_ >> +#define _RTE_PTP_H_ >> + >> +/** >> + * @file >> + * >> + * PTP (IEEE 1588) protocol definitions >> + */ >> + >> +#include <stdint.h> >> +#include <stdbool.h> >> + >> +#include <rte_byteorder.h> >> +#include <rte_common.h> >> + >> +#ifdef __cplusplus >> +extern "C" { >> +#endif >> + >> +/* >> + * PTP Constants >> + */ >> + >> +/** PTP over UDP event port (Sync, Delay_Req, PDelay_Req, >> PDelay_Resp). */ >> +#define RTE_PTP_EVENT_PORT 319 >> + >> +/** PTP over UDP general port (Follow_Up, Delay_Resp, Announce, etc.). >> */ >> +#define RTE_PTP_GENERAL_PORT 320 > The libc header <netinet/in.h> defines IPPORT_xxx. > We should use something similar: > #define RTE_IPPORT_PTP_EVENT and RTE_IPPORT_PTP_GENERAL Done. Renamed RTE_PTP_EVENT_PORT → RTE_IPPORT_PTP_EVENT and RTE_PTP_GENERAL_PORT → RTE_IPPORT_PTP_GENERAL. Updated all usages in ptp_parse.h (IPv4 and IPv6 sections). >> + >> +/** PTP multicast MAC address: 01:1B:19:00:00:00. */ >> +#define RTE_PTP_MULTICAST_MAC { 0x01, 0x1B, 0x19, 0x00, 0x00, 0x00 >> } >> + >> +/** PTP peer delay multicast MAC: 01:80:C2:00:00:0E. */ >> +#define RTE_PTP_PDELAY_MULTICAST_MAC { 0x01, 0x80, 0xC2, 0x00, 0x00, >> 0x0E } > Similarly here; we should establish a convention for MAC addresses, > like RTE_ETHER_TYPE_xxx in DPDK <lib/net/rte_ether.h>. > Suggest: > RTE_ETHER_ADDR_PTP_MULTICAST and RTE_ETHER_ADDR_PTP_MULTICAST_PDELAY > > This also follows the general naming convention of having the broadest scope is first and the narrowest scope last. Done. Renamed to RTE_ETHER_ADDR_PTP_MULTICAST and RTE_ETHER_ADDR_PTP_MULTICAST_PDELAY. > >> + >> +/* >> + * PTP Message Types (IEEE 1588-2019 Table 36) >> + */ >> + >> +#define RTE_PTP_MSGTYPE_SYNC 0x0 /**< Sync (event). */ >> +#define RTE_PTP_MSGTYPE_DELAY_REQ 0x1 /**< Delay_Req (event). >> */ >> +#define RTE_PTP_MSGTYPE_PDELAY_REQ 0x2 /**< Peer_Delay_Req >> (event). */ >> +#define RTE_PTP_MSGTYPE_PDELAY_RESP 0x3 /**< Peer_Delay_Resp >> (event). */ >> +#define RTE_PTP_MSGTYPE_FOLLOW_UP 0x8 /**< Follow_Up (general). >> */ >> +#define RTE_PTP_MSGTYPE_DELAY_RESP 0x9 /**< Delay_Resp >> (general). */ >> +#define RTE_PTP_MSGTYPE_PDELAY_RESP_FU 0xA /**< >> Peer_Delay_Resp_Follow_Up. */ > Missing in the description: (general) Done. Changed to Peer_Delay_Resp_Follow_Up (general). > > For consistency, consider renaming RTE_PTP_MSGTYPE_FOLLOW_UP to RTE_PTP_MSGTYPE_FU. Done. Renamed RTE_PTP_MSGTYPE_FOLLOW_UP to RTE_PTP_MSGTYPE_FU. > >> +#define RTE_PTP_MSGTYPE_ANNOUNCE 0xB /**< Announce (general). >> */ >> +#define RTE_PTP_MSGTYPE_SIGNALING 0xC /**< Signaling (general). >> */ >> +#define RTE_PTP_MSGTYPE_MANAGEMENT 0xD /**< Management >> (general). */ >> + >> +/* >> + * PTP Flag Field Bits (IEEE 1588-2019 Table 37) >> + * >> + * These constants are for use after rte_be_to_cpu_16(hdr->flags). >> + * flagField[0] (octet 6) maps to host bits 8-15. >> + * flagField[1] (octet 7) maps to host bits 0-7. >> + */ >> + >> +#define RTE_PTP_FLAG_TWO_STEP (1 << 9) /**< Two-step flag. */ >> +#define RTE_PTP_FLAG_UNICAST (1 << 10) /**< Unicast flag. */ >> +#define RTE_PTP_FLAG_LI_61 (1 << 0) /**< Leap indicator 61. */ >> +#define RTE_PTP_FLAG_LI_59 (1 << 1) /**< Leap indicator 59. */ > We don't have a RTE_BIT16() macro like the RTE_BIT32/64() macros, so maybe use: > > #define RTE_PTP_FLAG_TWO_STEP (UINT16_C(1) << 9) /**< Two-step flag. */ > #define RTE_PTP_FLAG_UNICAST (UINT16_C(1) << 10) /**< Unicast flag. */ > #define RTE_PTP_FLAG_LI_61 (UINT16_C(1) << 0) /**< Leap indicator 61. */ > #define RTE_PTP_FLAG_LI_59 (UINT16_C(1) << 1) /**< Leap indicator 59. */ Done. All four flag macros now use (UINT16_C(1) << N). >> + >> +/* >> + * PTP Header Structures (IEEE 1588-2019) >> + */ >> + >> +/** >> + * PTP Port Identity (10 bytes). >> + */ >> +struct __rte_packed_begin rte_ptp_port_id { >> + uint8_t clock_id[8]; /**< clockIdentity (EUI-64). */ >> + rte_be16_t port_number; /**< portNumber. */ >> +} __rte_packed_end; >> + >> +/** >> + * PTP Common Message Header (34 bytes). >> + */ >> +struct __rte_packed_begin rte_ptp_hdr { >> + uint8_t msg_type; /**< transportSpecific (4) | >> messageType (4). */ >> + uint8_t version; /**< minorVersionPTP (4) | versionPTP >> (4). */ > The two fields above should be split into their nibbles, for direct access, like the version_ihl field in the IPv4 header: > https://elixir.bootlin.com/dpdk/v26.03/source/lib/net/rte_ip4.h#L43 > > E.g.: > > __extension__ > union { > uint8_t ts_msgtype; /**< Message type */ > struct { > #if RTE_BYTE_ORDER == RTE_LITTLE_ENDIAN > uint8_t msg_type:4; /**< messageType */ > uint8_t ts:4; /**< transportSpecific */ > #elif RTE_BYTE_ORDER == RTE_BIG_ENDIAN > uint8_t ts:4; /**< transportSpecific */ > uint8_t msg_type:4; /**< messageType */ > #endif > }; > }; > > Warning: I'm not sure I got the nibble order right. Make sure you do! :-) Done. Used __extension__ union { uint8_t ts_msgtype; struct { uint8_t msg_type:4; uint8_t ts:4; }; }; with RTE_BYTE_ORDER conditional for endianness, matching rte_ipv4_hdr convention. > >> + rte_be16_t msg_length; /**< Total message length in bytes. */ >> + uint8_t domain_number; /**< PTP domain (0-255). */ >> + uint8_t minor_sdo_id; /**< minorSdoId (IEEE 1588-2019). */ >> + rte_be16_t flags; /**< Flag field (see RTE_PTP_FLAG_*). >> */ >> + rte_be64_t correction; /**< correctionField (scaled ns, 48.16 >> fixed). */ >> + rte_be32_t msg_type_specific; /**< messageTypeSpecific. */ >> + struct rte_ptp_port_id source_port_id; /**< sourcePortIdentity. >> */ >> + rte_be16_t sequence_id; /**< sequenceId. */ >> + uint8_t control; /**< controlField (deprecated in 1588- >> 2019). */ >> + int8_t log_msg_interval; /**< logMessageInterval. */ >> +} __rte_packed_end; >> + >> +/** >> + * PTP Timestamp (10 bytes, used in Sync/Delay_Req/Follow_Up bodies). > The PTP timestamp origo should be mentioned here. > E.g. the UNIX time_t origo is Jan 1st 00:00:00 1970. Done. Added Seconds since PTP epoch (1 January 1970 00:00:00 TAI) to struct docstring. > >> + */ >> +struct __rte_packed_begin rte_ptp_timestamp { >> + rte_be16_t seconds_hi; /**< Upper 16 bits of seconds. */ >> + rte_be32_t seconds_lo; /**< Lower 32 bits of seconds. */ >> + rte_be32_t nanoseconds; /**< Nanoseconds (0-999999999). */ >> +} __rte_packed_end; >> + >> +/* >> + * Inline Helpers >> + */ >> + >> +/** >> + * Extract PTP message type from header. >> + * >> + * @param hdr >> + * Pointer to PTP header. >> + * @return >> + * Message type (0x0-0xF). >> + */ >> +static inline uint8_t >> +rte_ptp_msg_type(const struct rte_ptp_hdr *hdr) >> +{ >> + return hdr->msg_type & 0x0F; >> +} >> + >> +/** >> + * Extract transport-specific field from header. >> + * >> + * @param hdr >> + * Pointer to PTP header. >> + * @return >> + * Transport-specific value (upper nibble, 0x0-0xF). >> + */ >> +static inline uint8_t >> +rte_ptp_transport_specific(const struct rte_ptp_hdr *hdr) >> +{ >> + return (hdr->msg_type >> 4) & 0x0F; >> +} >> + >> +/** >> + * Extract PTP version from header. >> + * >> + * @param hdr >> + * Pointer to PTP header. >> + * @return >> + * PTP version number (typically 2). >> + */ >> +static inline uint8_t >> +rte_ptp_version(const struct rte_ptp_hdr *hdr) >> +{ >> + return hdr->version & 0x0F; >> +} >> + >> +/** >> + * Get sequence ID from PTP header (host byte order). >> + * >> + * @param hdr >> + * Pointer to PTP header. >> + * @return >> + * Sequence ID in host byte order. >> + */ >> +static inline uint16_t >> +rte_ptp_seq_id(const struct rte_ptp_hdr *hdr) >> +{ >> + return rte_be_to_cpu_16(hdr->sequence_id); >> +} >> + >> +/** >> + * Get PTP domain number. >> + * >> + * @param hdr >> + * Pointer to PTP header. >> + * @return >> + * Domain number (0-255). >> + */ >> +static inline uint8_t >> +rte_ptp_domain(const struct rte_ptp_hdr *hdr) >> +{ >> + return hdr->domain_number; >> +} > The above "get" accessor functions are superfluous. Please remove: > rte_ptp_msg_type() > rte_ptp_transport_specific() > rte_ptp_version() > rte_ptp_seq_id() > rte_ptp_domain() Done. All five removed. Callers updated to use direct field access: hdr->msg_type, hdr->version, hdr->domain_number, rte_be_to_cpu_16(hdr->sequence_id), etc. > >> + >> +/** >> + * Check if PTP message type is an event message. >> + * Event messages (msg_type 0x0-0x3) require timestamps. >> + * >> + * @param msg_type >> + * PTP message type value (0x0-0xF). >> + * @return >> + * true if event message, false otherwise. >> + */ >> +static inline bool >> +rte_ptp_is_event(int msg_type) >> +{ >> + return msg_type >= 0 && msg_type <= RTE_PTP_MSGTYPE_PDELAY_RESP; >> +} > Suggest passing the parameter as const struct rte_ptp_hdr *hdr, like in rte_ptp_is_two_step() below. Done. Parameter changed from int msg_type to const struct rte_ptp_hdr *hdr. Body uses hdr->msg_type. All callers updated. >> + >> +/** >> + * Check if the two-step flag is set in a PTP header. >> + * >> + * @param hdr >> + * Pointer to PTP header. >> + * @return >> + * true if two-step flag is set. >> + */ >> +static inline bool >> +rte_ptp_is_two_step(const struct rte_ptp_hdr *hdr) >> +{ >> + return (rte_be_to_cpu_16(hdr->flags) & RTE_PTP_FLAG_TWO_STEP) != >> 0; > Faster way: > return (hdr->flags & RTE_BE16(RTE_PTP_FLAG_TWO_STEP)) != 0; Done. Uses hdr->flags & RTE_BE16(RTE_PTP_FLAG_TWO_STEP) for compile-time constant comparison. > >> +} >> + >> +/** >> + * Get correctionField value in nanoseconds (from 48.16 fixed-point). >> + * >> + * @param hdr >> + * Pointer to PTP header. >> + * @return >> + * Correction value in nanoseconds. >> + */ >> +static inline int64_t >> +rte_ptp_correction_ns(const struct rte_ptp_hdr *hdr) >> +{ >> + return (int64_t)rte_be_to_cpu_64(hdr->correction) >> 16; >> +} > This "get" accessor function is superfluous. Please remove: > rte_ptp_correction_ns() > > The "correction" field in the PTP header structure should sufficiently describe the field's fixed-point encoding. > >> + >> +/** >> + * Add a residence time (in nanoseconds) to the correctionField. >> + * Used by Transparent Clocks to account for relay transit delay. >> + * The correctionField uses IEEE 1588 scaled nanoseconds (48.16 fixed- >> point). >> + * >> + * @param hdr >> + * Pointer to PTP header (will be modified in-place). >> + * @param residence_ns >> + * Residence time in nanoseconds to add. >> + */ >> +static inline void >> +rte_ptp_add_correction(struct rte_ptp_hdr *hdr, int64_t residence_ns) >> +{ >> + int64_t cf = (int64_t)rte_be_to_cpu_64(hdr->correction); >> + >> + cf += (int64_t)((uint64_t)residence_ns << 16); >> + hdr->correction = rte_cpu_to_be_64(cf); >> +} > I don't think negative time can be added, so please use uint64_t instead of int64_t for the parameter and inside the function. > > Note to reviewers: "residence time" is the term in the standard. "sojourn time" is not used. Done. Parameter changed from int64_t residence_ns to uint64_t residence_ns. > >> + >> +/** >> + * Convert a PTP timestamp structure to nanoseconds since epoch. >> + * >> + * @param ts >> + * Pointer to PTP timestamp. >> + * @return >> + * Time in nanoseconds since epoch. >> + */ >> +static inline uint64_t >> +rte_ptp_timestamp_to_ns(const struct rte_ptp_timestamp *ts) >> +{ >> + uint64_t sec = ((uint64_t)rte_be_to_cpu_16(ts->seconds_hi) << 32) >> | >> + rte_be_to_cpu_32(ts->seconds_lo); >> + >> + return sec * 1000000000ULL + rte_be_to_cpu_32(ts->nanoseconds); >> +} > 1000000000ULL -> UINT64_C(1000000000) Done. Changed 1000000000ULL -> UINT64_C(1000000000). > >> + >> +#ifdef __cplusplus >> +} >> +#endif >> + >> +#endif /* _RTE_PTP_H_ */ >> -- >> 2.53.0 ^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v7 2/4] examples/ptp_tap_relay_sw: add PTP software transparent clock relay 2026-05-07 13:45 ` [PATCH v7 0/4] IEEE 1588 PTP v2 protocol support in lib/net Rajesh Kumar 2026-05-07 13:45 ` [PATCH v7 1/4] lib/net: add IEEE 1588 PTP v2 protocol header definitions Rajesh Kumar @ 2026-05-07 13:45 ` Rajesh Kumar 2026-05-07 13:45 ` [PATCH v7 3/4] doc: update release notes for PTP protocol library Rajesh Kumar 2026-05-07 13:45 ` [PATCH v7 4/4] examples/ptpclient: use shared PTP library definitions Rajesh Kumar 3 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-07 13:45 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, mb, Rajesh Kumar Add a new example application demonstrating a software PTP Transparent Clock relay between a DPDK-bound physical NIC and a Linux kernel TAP virtual interface. The relay uses software timestamps (CLOCK_MONOTONIC) to measure residence time and accumulates it into the PTP correctionField per IEEE 1588-2019 §10.2, enabling synchronized time distribution via standard linuxptp (ptp4l) on both sides. Features: - Handles L2, VLAN/QinQ, and UDP/IPv4/IPv6 PTP encapsulations - Supports PTP v2 event messages (Sync, Delay_Req, PDelay_Req, PDelay_Resp) - Two-pass burst processing: classify before TX, timestamp before relay - Unmodified Linux kernel and stock DPDK (no kernel patches required) - Bidirectional relay: PHY ↔ TAP Includes: - ptp_tap_relay_sw.c: Main relay logic with burst processing - ptp_parse.h: Local DPI parser for PTP classification (not a library API) - Sample app guide with topology, command-line options, and example output Uses lib/net/rte_ptp.h inline helpers for correctionField manipulation and header parsing. Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- doc/guides/sample_app_ug/index.rst | 1 + doc/guides/sample_app_ug/ptp_tap_relay_sw.rst | 212 +++++++++ examples/ptp_tap_relay_sw/Makefile | 41 ++ examples/ptp_tap_relay_sw/meson.build | 13 + examples/ptp_tap_relay_sw/ptp_parse.h | 211 +++++++++ examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c | 432 ++++++++++++++++++ 6 files changed, 910 insertions(+) create mode 100644 doc/guides/sample_app_ug/ptp_tap_relay_sw.rst create mode 100644 examples/ptp_tap_relay_sw/Makefile create mode 100644 examples/ptp_tap_relay_sw/meson.build create mode 100644 examples/ptp_tap_relay_sw/ptp_parse.h create mode 100644 examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c diff --git a/doc/guides/sample_app_ug/index.rst b/doc/guides/sample_app_ug/index.rst index e895f692f9..f12623bb66 100644 --- a/doc/guides/sample_app_ug/index.rst +++ b/doc/guides/sample_app_ug/index.rst @@ -51,6 +51,7 @@ Sample Applications User Guides dist_app vm_power_management ptpclient + ptp_tap_relay_sw fips_validation ipsec_secgw bbdev_app diff --git a/doc/guides/sample_app_ug/ptp_tap_relay_sw.rst b/doc/guides/sample_app_ug/ptp_tap_relay_sw.rst new file mode 100644 index 0000000000..15727383c1 --- /dev/null +++ b/doc/guides/sample_app_ug/ptp_tap_relay_sw.rst @@ -0,0 +1,212 @@ +.. SPDX-License-Identifier: BSD-3-Clause + Copyright(c) 2026 Intel Corporation. + +PTP Software Relay Sample Application +====================================== + +The PTP Software Relay sample application demonstrates how to build a +minimal PTP Transparent Clock relay between a DPDK-bound physical NIC +and a kernel TAP interface using **software timestamps only**. It uses +the PTP definitions from ``rte_ptp.h`` (in ``lib/net/``) together with a +local packet parser. + +The application works with an unmodified Linux kernel and stock DPDK. + +For background on PTP see: +`Precision Time Protocol +<https://en.wikipedia.org/wiki/Precision_Time_Protocol>`_. + + +Limitations +----------- + +* Tested with L2 PTP (EtherType 0x88F7) on the wire. + The local parser also classifies VLAN/QinQ and UDP/IPv4/IPv6. +* Only PTP v2 messages are processed. +* Software timestamps have microsecond-class jitter; sub-microsecond + precision depends on system load and NIC-to-TAP forwarding latency. +* The PTP time transmitter must be reachable on the physical NIC's L2 network. +* Only one physical port and one TAP port are supported. + + +How the Application Works +------------------------- + +Topology +~~~~~~~~ + +:: + + PTP Time Transmitter Physical NIC TAP (kernel) + (ptp4l -H) ──L2── (DPDK vfio-pci) ────── dtap0 + │ │ + ptp_tap_relay_sw ptp4l -S + (correctionField += (SW timestamps, + residence time) adjusts CLOCK_REALTIME) + +The relay sits between a DPDK-owned physical NIC and a kernel TAP +virtual interface. ``ptp4l`` runs on the TAP interface in software +timestamp mode (``-S``) as a PTP time receiver. + +Packet Flow +~~~~~~~~~~~ + +1. The physical NIC receives PTP (and non-PTP) packets via DPDK RX. +2. A software RX timestamp is recorded using + ``clock_gettime(CLOCK_MONOTONIC)``. +3. Each packet is parsed to locate the PTP header. +4. For PTP **event** messages (Sync, Delay_Req, PDelay_Req, PDelay_Resp), + a TX software timestamp is taken just before transmission. +5. The residence time (``tx_ts − rx_ts``) is added to the PTP + ``correctionField`` via ``rte_ptp_add_correction()`` — standard + IEEE 1588-2019 Transparent Clock behaviour (§10.2). +6. Packets are forwarded bidirectionally: + + * PHY → TAP (network → ptp4l) + * TAP → PHY (ptp4l → network) + +A two-pass design is used: first all packets are classified and PTP +header pointers saved, then a single TX timestamp is taken immediately +before applying corrections and calling ``rte_eth_tx_burst()``. +This minimises the gap between the measured timestamp and the actual +wire egress. + + +Compiling the Application +------------------------- + +To compile the sample application see :doc:`compiling`. + +The application is located in the ``ptp_tap_relay_sw`` sub-directory. + +.. note:: + + The application uses ``rte_ptp.h`` from ``lib/net/`` (built by default) + and a local ``ptp_parse.h`` header for packet classification. + + +Running the Application +----------------------- + +Prerequisites +~~~~~~~~~~~~~ + +* A PTP-capable physical NIC bound to DPDK (e.g. via ``vfio-pci``). +* ``linuxptp`` (``ptp4l``) installed on the system. +* A PTP time transmitter reachable on the same L2 network. + +Start the relay +~~~~~~~~~~~~~~~~ + +.. code-block:: console + + ./<build_dir>/examples/dpdk-ptp_tap_relay_sw \ + -l 18-19 -a 0000:cc:00.1 --vdev=net_tap0,iface=dtap0 -- \ + -p 0 -t 1 -T 10 + +Command-line Options +~~~~~~~~~~~~~~~~~~~~ + +* ``-p PORT`` — Physical NIC port ID (default: 0). +* ``-t PORT`` — TAP port ID (default: 1). +* ``-T SECS`` — Statistics print interval in seconds (default: 10). + +Start PTP time transmitter +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +On a separate terminal or remote host, start ``ptp4l`` as time +transmitter with hardware timestamps on the physical NIC: + +.. code-block:: console + + ptp4l -i <iface> -m -2 -H --serverOnly=1 \ + --logSyncInterval=-4 --logMinDelayReqInterval=-4 + +Start PTP time receiver +~~~~~~~~~~~~~~~~~~~~~~~ + +On the TAP interface, start ``ptp4l`` in software timestamp mode: + +.. code-block:: console + + ptp4l -i dtap0 -m -2 -s -S \ + --delay_filter=moving_median --delay_filter_length=10 + +The time receiver will enter UNCALIBRATED state for approximately 60 +seconds while the PI servo estimates the frequency offset, then step +the clock and enter time-receiver (synchronized) state. +Steady-state RMS offset of 500–1000 ns is typical on a lightly loaded +system with a hardware-timestamped time transmitter. + +Example Output +~~~~~~~~~~~~~~ + +Relay statistics printed every ``-T`` seconds: + +:: + + [PTP-SW] === Statistics === + [PTP-SW] PHY RX total: 5646 + [PTP-SW] PHY RX PTP: 5598 + [PTP-SW] TAP TX: 5646 + [PTP-SW] TAP RX total: 1800 + [PTP-SW] TAP RX PTP: 1788 + [PTP-SW] PHY TX: 1800 + [PTP-SW] Corrections: 3635 + +Time receiver ``ptp4l`` output after convergence: + +:: + + ptp4l[451534.520]: rms 630 max 1166 freq -44365 +/- 100 delay 37668 +/- 71 + ptp4l[451539.525]: rms 602 max 1177 freq -44339 +/- 119 delay 37517 +/- 43 + ptp4l[451544.530]: rms 535 max 1194 freq -44345 +/- 103 delay 37410 +/- 81 + + +Code Explanation +---------------- + +The following sections explain the main components of the application. + +Relay Burst Function +~~~~~~~~~~~~~~~~~~~~ + +The core relay logic is in ``relay_burst()``, which handles one direction +(PHY→TAP or TAP→PHY) per call: + +**Pass 1 — Classify:** + +For each received packet, ``ptp_hdr_find()`` locates the PTP header +(if present). For event messages, the header pointer is saved for the +second pass. + +**Pass 2 — Timestamp and correct:** + +A single software TX timestamp is taken via +``clock_gettime(CLOCK_MONOTONIC)``. The residence time +(``tx_ts − rx_ts``) is added to each saved PTP header's +``correctionField`` using ``rte_ptp_add_correction()``. +The burst is then transmitted with ``rte_eth_tx_burst()``. + +Main Loop +~~~~~~~~~ + +The ``relay_loop()`` function polls both directions in a tight loop: + +.. code-block:: c + + while (!force_quit) { + relay_burst(phy_port, tap_port, ...); /* PHY → TAP */ + relay_burst(tap_port, phy_port, ...); /* TAP → PHY */ + } + +Statistics are printed at the interval specified by ``-T``. + +Timestamp Source +~~~~~~~~~~~~~~~~ + +``CLOCK_MONOTONIC`` is used rather than ``CLOCK_REALTIME`` because +the PTP time receiver's servo continuously adjusts ``CLOCK_REALTIME``. +Using ``CLOCK_REALTIME`` would corrupt residence time measurements +during clock stepping or frequency slewing. ``CLOCK_MONOTONIC`` is +portable across Linux and FreeBSD. diff --git a/examples/ptp_tap_relay_sw/Makefile b/examples/ptp_tap_relay_sw/Makefile new file mode 100644 index 0000000000..fd178f46ae --- /dev/null +++ b/examples/ptp_tap_relay_sw/Makefile @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2026 Intel Corporation + +# binary name +APP = dpdk-ptp_tap_relay_sw + +# all source are stored in SRCS-y +SRCS-y := ptp_tap_relay_sw.c + +PKGCONF ?= pkg-config + +# Build using pkg-config variables if possible +ifneq ($(shell $(PKGCONF) --exists libdpdk && echo 0),0) +$(error "no installation of DPDK found") +endif + +all: shared +.PHONY: shared static +shared: build/$(APP)-shared + ln -sf $(APP)-shared build/$(APP) +static: build/$(APP)-static + ln -sf $(APP)-static build/$(APP) + +PC_FILE := $(shell $(PKGCONF) --path libdpdk 2>/dev/null) +CFLAGS += -O3 $(shell $(PKGCONF) --cflags libdpdk) +LDFLAGS_SHARED = $(shell $(PKGCONF) --libs libdpdk) +LDFLAGS_STATIC = $(shell $(PKGCONF) --static --libs libdpdk) + +build/$(APP)-shared: $(SRCS-y) Makefile $(PC_FILE) | build + $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_SHARED) + +build/$(APP)-static: $(SRCS-y) Makefile $(PC_FILE) | build + $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_STATIC) + +build: + @mkdir -p $@ + +.PHONY: clean +clean: + rm -f build/$(APP) build/$(APP)-static build/$(APP)-shared + test -d build && rmdir -p build || true diff --git a/examples/ptp_tap_relay_sw/meson.build b/examples/ptp_tap_relay_sw/meson.build new file mode 100644 index 0000000000..34a4d86439 --- /dev/null +++ b/examples/ptp_tap_relay_sw/meson.build @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2026 Intel Corporation + +# meson file, for building this example as part of a main DPDK build. +# +# To build this example as a standalone application with an already-installed +# DPDK instance, use 'make' + +sources = files( + 'ptp_tap_relay_sw.c', +) +deps += ['net'] +cflags += no_shadow_cflag diff --git a/examples/ptp_tap_relay_sw/ptp_parse.h b/examples/ptp_tap_relay_sw/ptp_parse.h new file mode 100644 index 0000000000..db0dcfe5c1 --- /dev/null +++ b/examples/ptp_tap_relay_sw/ptp_parse.h @@ -0,0 +1,211 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2026 Intel Corporation + * + * PTP packet parser — locates PTP headers through L2, VLAN, and UDP + * encapsulations. This is a DPI helper for use within example + * applications; it does not belong in the core library. + */ + +#ifndef _PTP_PARSE_H_ +#define _PTP_PARSE_H_ + +#include <rte_mbuf.h> +#include <rte_ether.h> +#include <rte_ip.h> +#include <rte_udp.h> +#include <rte_ptp.h> + +/** Not a PTP packet. */ +#define PTP_MSGTYPE_INVALID (-1) + +/** + * Locate the PTP header within a packet. + * + * Handles L2 (EtherType 0x88F7), VLAN-tagged L2 (single/double, + * TPIDs 0x8100/0x88A8), PTP over UDP/IPv4, PTP over UDP/IPv6, + * and VLAN-tagged UDP variants. + * + * @param m + * Pointer to the mbuf. + * @return + * Pointer to the PTP header, or NULL if not a PTP packet. + */ +static inline struct rte_ptp_hdr * +ptp_hdr_find(const struct rte_mbuf *m) +{ + const struct rte_ether_hdr *eth; + uint16_t ether_type; + uint32_t offset; + + if (rte_pktmbuf_data_len(m) < sizeof(struct rte_ether_hdr)) + return NULL; + + eth = rte_pktmbuf_mtod(m, const struct rte_ether_hdr *); + ether_type = rte_be_to_cpu_16(eth->ether_type); + offset = sizeof(struct rte_ether_hdr); + + /* Strip VLAN / QinQ tags */ + if (ether_type == RTE_ETHER_TYPE_VLAN || + ether_type == RTE_ETHER_TYPE_QINQ) { + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_vlan_hdr)) + return NULL; + const struct rte_vlan_hdr *vlan = + rte_pktmbuf_mtod_offset(m, + const struct rte_vlan_hdr *, offset); + ether_type = rte_be_to_cpu_16(vlan->eth_proto); + offset += sizeof(struct rte_vlan_hdr); + + /* Second tag (QinQ inner or stacked VLAN) */ + if (ether_type == RTE_ETHER_TYPE_VLAN || + ether_type == RTE_ETHER_TYPE_QINQ) { + if (rte_pktmbuf_data_len(m) < + offset + sizeof(struct rte_vlan_hdr)) + return NULL; + vlan = rte_pktmbuf_mtod_offset(m, + const struct rte_vlan_hdr *, offset); + ether_type = rte_be_to_cpu_16(vlan->eth_proto); + offset += sizeof(struct rte_vlan_hdr); + } + } + + /* L2 PTP: EtherType 0x88F7 */ + if (ether_type == RTE_ETHER_TYPE_1588) { + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ptp_hdr)) + return NULL; + return rte_pktmbuf_mtod_offset(m, + struct rte_ptp_hdr *, offset); + } + + /* PTP over UDP/IPv4 */ + if (ether_type == RTE_ETHER_TYPE_IPV4) { + const struct rte_ipv4_hdr *iph; + uint16_t ihl; + + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ipv4_hdr)) + return NULL; + + iph = rte_pktmbuf_mtod_offset(m, + const struct rte_ipv4_hdr *, offset); + if (iph->next_proto_id != IPPROTO_UDP) + return NULL; + + ihl = (iph->version_ihl & 0x0F) * 4; + if (ihl < 20) + return NULL; + offset += ihl; + + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_udp_hdr)) + return NULL; + + const struct rte_udp_hdr *udp = + rte_pktmbuf_mtod_offset(m, + const struct rte_udp_hdr *, offset); + uint16_t dst_port = rte_be_to_cpu_16(udp->dst_port); + + if (dst_port != RTE_PTP_EVENT_PORT && + dst_port != RTE_PTP_GENERAL_PORT) + return NULL; + + offset += sizeof(struct rte_udp_hdr); + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ptp_hdr)) + return NULL; + + return rte_pktmbuf_mtod_offset(m, + struct rte_ptp_hdr *, offset); + } + + /* PTP over UDP/IPv6 */ + if (ether_type == RTE_ETHER_TYPE_IPV6) { + const struct rte_ipv6_hdr *ip6h; + + if (rte_pktmbuf_data_len(m) < + offset + sizeof(struct rte_ipv6_hdr)) + return NULL; + + ip6h = rte_pktmbuf_mtod_offset(m, + const struct rte_ipv6_hdr *, offset); + if (ip6h->proto != IPPROTO_UDP) + return NULL; + + offset += sizeof(struct rte_ipv6_hdr); + + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_udp_hdr)) + return NULL; + + const struct rte_udp_hdr *udp = + rte_pktmbuf_mtod_offset(m, + const struct rte_udp_hdr *, offset); + uint16_t dst_port = rte_be_to_cpu_16(udp->dst_port); + + if (dst_port != RTE_PTP_EVENT_PORT && + dst_port != RTE_PTP_GENERAL_PORT) + return NULL; + + offset += sizeof(struct rte_udp_hdr); + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ptp_hdr)) + return NULL; + + return rte_pktmbuf_mtod_offset(m, + struct rte_ptp_hdr *, offset); + } + + return NULL; +} + +/** + * Classify a packet as PTP and return the message type. + * + * @param m + * Pointer to the mbuf to classify. + * @return + * PTP message type (0x0-0xF) on success, PTP_MSGTYPE_INVALID (-1) + * if the packet is not PTP. + */ +static inline int +ptp_classify(const struct rte_mbuf *m) +{ + struct rte_ptp_hdr *hdr = ptp_hdr_find(m); + + if (hdr == NULL) + return PTP_MSGTYPE_INVALID; + + return rte_ptp_msg_type(hdr); +} + +/** PTP message type name table. */ +static const char * const ptp_msg_names[] = { + [RTE_PTP_MSGTYPE_SYNC] = "Sync", + [RTE_PTP_MSGTYPE_DELAY_REQ] = "Delay_Req", + [RTE_PTP_MSGTYPE_PDELAY_REQ] = "PDelay_Req", + [RTE_PTP_MSGTYPE_PDELAY_RESP] = "PDelay_Resp", + [0x4] = "Reserved_4", + [0x5] = "Reserved_5", + [0x6] = "Reserved_6", + [0x7] = "Reserved_7", + [RTE_PTP_MSGTYPE_FOLLOW_UP] = "Follow_Up", + [RTE_PTP_MSGTYPE_DELAY_RESP] = "Delay_Resp", + [RTE_PTP_MSGTYPE_PDELAY_RESP_FU] = "PDelay_Resp_Follow_Up", + [RTE_PTP_MSGTYPE_ANNOUNCE] = "Announce", + [RTE_PTP_MSGTYPE_SIGNALING] = "Signaling", + [RTE_PTP_MSGTYPE_MANAGEMENT] = "Management", + [0xE] = "Reserved_E", + [0xF] = "Reserved_F", +}; + +/** + * Get a human-readable name for a PTP message type. + * + * @param msg_type + * PTP message type (0x0-0xF or PTP_MSGTYPE_INVALID). + * @return + * Static string with the message type name. + */ +static inline const char * +ptp_msg_type_str(int msg_type) +{ + if (msg_type < 0 || msg_type > 0xF) + return "Not_PTP"; + return ptp_msg_names[msg_type]; +} + +#endif /* _PTP_PARSE_H_ */ diff --git a/examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c b/examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c new file mode 100644 index 0000000000..998df2ac3b --- /dev/null +++ b/examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c @@ -0,0 +1,432 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2026 Intel Corporation + */ + +/* + * PTP Software Relay + * + * A minimal PTP relay between a DPDK-bound physical NIC and a kernel + * TAP interface using software timestamps only. + * + * How it works: + * 1. Physical NIC receives PTP (and non-PTP) packets via DPDK RX. + * 2. For PTP event messages (Sync, Delay_Req, PDelay_Req, PDelay_Resp) + * the relay records an RX software timestamp (clock_gettime). + * 3. Just before TX on the other side it records a TX software timestamp. + * 4. The relay residence time (tx_ts − rx_ts) is added to the PTP + * correctionField via rte_ptp_add_correction() — standard + * Transparent Clock behaviour (IEEE 1588-2019 §10.2). + * 5. Packets are forwarded bi-directionally: + * PHY → TAP (network → ptp4l) + * TAP → PHY (ptp4l → network) + * + * ptp4l runs in software-timestamping mode on the TAP interface: + * + * ptp4l -i dtap0 -m -s -S # -S = software timestamps + * + * Topology: + * + * Time Transmitter (remote) ──L2── Physical NIC (DPDK) + * │ + * PTP SW Relay ← correctionField update + * │ + * TAP (kernel) ── ptp4l -S (time receiver) + * + * Usage: + * dpdk-ptp_tap_relay_sw -l 0-1 --vdev=net_tap0,iface=dtap0 -- \ + * -p 0 -t 1 + * + * Parameters: + * -p PORT Physical NIC port ID (default: 0) + * -t PORT TAP port ID (default: 1) + * -T SECS Stats print interval in seconds (default: 10) + */ + +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <stdbool.h> +#include <signal.h> +#include <getopt.h> +#include <time.h> + +#include <rte_eal.h> +#include <rte_ethdev.h> +#include <rte_mbuf.h> +#include <rte_cycles.h> +#include <rte_lcore.h> + +#include "ptp_parse.h" + +/* Ring sizes */ +#define RX_RING_SIZE 1024 +#define TX_RING_SIZE 1024 + +/* Mempool */ +#define NUM_MBUFS 8191 +#define MBUF_CACHE 250 +#define BURST_SIZE 32 + +#define NSEC_PER_SEC 1000000000ULL + +/* Logging helpers */ +#define LOG_INFO(fmt, ...) \ + fprintf(stdout, "[PTP-SW] " fmt "\n", ##__VA_ARGS__) +#define LOG_ERR(fmt, ...) \ + fprintf(stderr, "[PTP-SW ERROR] " fmt "\n", ##__VA_ARGS__) + +static volatile bool force_quit; + +/* Port IDs */ +static uint16_t phy_port; +static uint16_t tap_port = 1; +static unsigned int stats_interval = 10; /* seconds */ + +/* Statistics */ +static struct { + uint64_t phy_rx; /* total packets from PHY */ + uint64_t phy_rx_ptp; /* PTP packets from PHY */ + uint64_t tap_tx; /* packets forwarded to TAP */ + uint64_t tap_rx; /* total packets from TAP */ + uint64_t tap_rx_ptp; /* PTP packets from TAP */ + uint64_t phy_tx; /* packets forwarded to PHY */ + uint64_t corrections; /* correctionField updates */ +} stats; + +static void +signal_handler(int signum) +{ + if (signum == SIGINT || signum == SIGTERM) { + LOG_INFO("Signal %d received, shutting down...", signum); + force_quit = true; + } +} + +/* Helpers */ + +/* Read monotonic clock in nanoseconds (for residence time). */ +static inline uint64_t +sw_timestamp_ns(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * NSEC_PER_SEC + (uint64_t)ts.tv_nsec; +} + +/* Port Init */ + +static int +port_init(uint16_t port, struct rte_mempool *mp) +{ + struct rte_eth_conf port_conf; + struct rte_eth_dev_info dev_info; + uint16_t nb_rxd = RX_RING_SIZE; + uint16_t nb_txd = TX_RING_SIZE; + int ret; + + memset(&port_conf, 0, sizeof(port_conf)); + + ret = rte_eth_dev_info_get(port, &dev_info); + if (ret != 0) { + LOG_ERR("rte_eth_dev_info_get(port %u) failed: %d", port, ret); + return ret; + } + + if (dev_info.tx_offload_capa & RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE) + port_conf.txmode.offloads |= + RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE; + + ret = rte_eth_dev_configure(port, 1, 1, &port_conf); + if (ret != 0) + return ret; + + ret = rte_eth_dev_adjust_nb_rx_tx_desc(port, &nb_rxd, &nb_txd); + if (ret != 0) + return ret; + + ret = rte_eth_rx_queue_setup(port, 0, nb_rxd, + rte_eth_dev_socket_id(port), NULL, mp); + if (ret < 0) + return ret; + + ret = rte_eth_tx_queue_setup(port, 0, nb_txd, + rte_eth_dev_socket_id(port), NULL); + if (ret < 0) + return ret; + + ret = rte_eth_dev_start(port); + if (ret < 0) + return ret; + + ret = rte_eth_promiscuous_enable(port); + if (ret != 0) { + LOG_ERR("Failed to enable promiscuous on port %u: %s", + port, rte_strerror(-ret)); + return ret; + } + + return 0; +} + +/* Relay one direction */ + +/* + * Forward packets from src_port to dst_port. + * For PTP event messages, record SW timestamps around the + * relay path and add the residence time to the correctionField. + * + * This implements a Transparent Clock (IEEE 1588-2019 §10.2): + * correctionField += (t_egress − t_ingress) + * + * Note: a single rx_ts / tx_ts pair is used for the entire burst. + * At typical PTP rates (logSyncInterval >= -4, i.e. <= 16 pkt/s) + * bursts contain at most one packet, so this is exact. At higher + * rates, early packets in a burst are slightly under-corrected and + * late ones over-corrected by up to one poll-loop iteration. + */ +static void +relay_burst(uint16_t src_port, uint16_t dst_port, + uint64_t *rx_cnt, uint64_t *rx_ptp_cnt, + uint64_t *tx_cnt, uint64_t *corr_cnt) +{ + struct rte_mbuf *bufs[BURST_SIZE]; + struct rte_ptp_hdr *ptp_hdrs[BURST_SIZE]; + uint64_t rx_ts; + uint16_t nb_rx, nb_tx, i; + + nb_rx = rte_eth_rx_burst(src_port, 0, bufs, BURST_SIZE); + if (nb_rx == 0) + return; + + /* Record a single RX software timestamp for the whole burst. + * All packets in one burst arrived at essentially the same instant + * from rte_eth_rx_burst()'s perspective. + */ + rx_ts = sw_timestamp_ns(); + + *rx_cnt += nb_rx; + + /* + * Pass 1: Parse each packet once and remember PTP event headers. + * This avoids taking the TX timestamp too early — we want it as + * close to the actual rte_eth_tx_burst() call as possible. + */ + memset(ptp_hdrs, 0, sizeof(ptp_hdrs[0]) * nb_rx); + for (i = 0; i < nb_rx; i++) { + struct rte_ptp_hdr *hdr = ptp_hdr_find(bufs[i]); + + if (hdr == NULL) + continue; + + (*rx_ptp_cnt)++; + + /* Only event messages carry timestamps that need correction */ + if (!rte_ptp_is_event(rte_ptp_msg_type(hdr))) + continue; + + ptp_hdrs[i] = hdr; + } + + /* + * Pass 2: Take a single TX timestamp right before transmission. + * This minimises the gap between the measured tx_ts and the + * actual kernel write inside rte_eth_tx_burst(), giving the + * most accurate residence time we can achieve with SW timestamps. + * + * residence_time = tx_ts − rx_ts + * + * Remaining untracked delays: + * - Pre-RX: NIC DMA → rx_burst return (~1-5 µs, unavoidable) + * - Post-TX: tx_ts → kernel TAP write (~1-2 µs) + * Both are symmetric for Sync and Delay_Req so they largely + * cancel in the ptp4l offset calculation. + */ + uint64_t tx_ts = sw_timestamp_ns(); + int64_t residence_ns = (int64_t)(tx_ts - rx_ts); + + for (i = 0; i < nb_rx; i++) { + if (ptp_hdrs[i] == NULL) + continue; + rte_ptp_add_correction(ptp_hdrs[i], residence_ns); + (*corr_cnt)++; + } + + /* Forward the burst */ + nb_tx = rte_eth_tx_burst(dst_port, 0, bufs, nb_rx); + *tx_cnt += nb_tx; + + /* Free any unsent packets */ + for (i = nb_tx; i < nb_rx; i++) + rte_pktmbuf_free(bufs[i]); +} + +/* Print statistics */ + +static void +print_stats(void) +{ + LOG_INFO("=== Statistics ==="); + LOG_INFO(" PHY RX total: %"PRIu64, stats.phy_rx); + LOG_INFO(" PHY RX PTP: %"PRIu64, stats.phy_rx_ptp); + LOG_INFO(" TAP TX: %"PRIu64, stats.tap_tx); + LOG_INFO(" TAP RX total: %"PRIu64, stats.tap_rx); + LOG_INFO(" TAP RX PTP: %"PRIu64, stats.tap_rx_ptp); + LOG_INFO(" PHY TX: %"PRIu64, stats.phy_tx); + LOG_INFO(" Corrections: %"PRIu64, stats.corrections); +} + +/* Main relay loop */ + +static int +relay_loop(__rte_unused void *arg) +{ + uint64_t last_stats = rte_rdtsc(); + uint64_t stats_tsc = rte_get_tsc_hz() * stats_interval; + + LOG_INFO("Relay loop started on lcore %u", rte_lcore_id()); + LOG_INFO(" PHY port %u <--> TAP port %u", phy_port, tap_port); + LOG_INFO(" Correction field updates: enabled for event messages"); + + while (!force_quit) { + /* PHY → TAP */ + relay_burst(phy_port, tap_port, + &stats.phy_rx, &stats.phy_rx_ptp, + &stats.tap_tx, &stats.corrections); + + /* TAP → PHY */ + relay_burst(tap_port, phy_port, + &stats.tap_rx, &stats.tap_rx_ptp, + &stats.phy_tx, &stats.corrections); + + /* Periodic stats */ + if (rte_rdtsc() - last_stats > stats_tsc) { + print_stats(); + last_stats = rte_rdtsc(); + } + } + + print_stats(); + return 0; +} + +/* Argument parsing */ + +static void +usage(const char *prog) +{ + fprintf(stderr, + "Usage: %s [EAL options] -- [options]\n" + " -p PORT Physical NIC port ID (default: 0)\n" + " -t PORT TAP port ID (default: 1)\n" + " -T SECS Stats interval in seconds (default: 10)\n" + "\n" + "Example:\n" + " %s -l 0-1 --vdev=net_tap0,iface=dtap0 -- -p 0 -t 1\n" + "\n" + "Then run ptp4l with software timestamps:\n" + " ptp4l -i dtap0 -m -s -S\n", + prog, prog); +} + +static int +parse_args(int argc, char **argv) +{ + int opt; + + while ((opt = getopt(argc, argv, "p:t:T:h")) != -1) { + switch (opt) { + case 'p': + phy_port = (uint16_t)atoi(optarg); + break; + case 't': + tap_port = (uint16_t)atoi(optarg); + break; + case 'T': + stats_interval = (unsigned int)atoi(optarg); + break; + case 'h': + default: + usage(argv[0]); + return -1; + } + } + + return 0; +} + +/* Main */ + +int +main(int argc, char **argv) +{ + struct rte_mempool *mp; + uint16_t nb_ports; + int ret; + + /* EAL init */ + ret = rte_eal_init(argc, argv); + if (ret < 0) + rte_exit(EXIT_FAILURE, "EAL init failed\n"); + argc -= ret; + argv += ret; + + /* App args */ + ret = parse_args(argc, argv); + if (ret < 0) + rte_exit(EXIT_FAILURE, "Invalid arguments\n"); + + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + + nb_ports = rte_eth_dev_count_avail(); + if (nb_ports < 2) + rte_exit(EXIT_FAILURE, + "Need at least 2 ports (PHY + TAP).\n" + "Use --vdev=net_tap0,iface=dtap0\n"); + + if (!rte_eth_dev_is_valid_port(phy_port)) + rte_exit(EXIT_FAILURE, "Invalid PHY port %u\n", phy_port); + if (!rte_eth_dev_is_valid_port(tap_port)) + rte_exit(EXIT_FAILURE, "Invalid TAP port %u\n", tap_port); + + mp = rte_pktmbuf_pool_create("MBUF_POOL", NUM_MBUFS * nb_ports, + MBUF_CACHE, 0, + RTE_MBUF_DEFAULT_BUF_SIZE, + rte_socket_id()); + if (mp == NULL) + rte_exit(EXIT_FAILURE, "Cannot create mbuf pool\n"); + + LOG_INFO("Initializing PHY port %u...", phy_port); + ret = port_init(phy_port, mp); + if (ret != 0) + rte_exit(EXIT_FAILURE, "Cannot init PHY port %u (%d)\n", + phy_port, ret); + + LOG_INFO("Initializing TAP port %u...", tap_port); + ret = port_init(tap_port, mp); + if (ret != 0) + rte_exit(EXIT_FAILURE, "Cannot init TAP port %u (%d)\n", + tap_port, ret); + + LOG_INFO("PTP Software Relay ready"); + LOG_INFO(" PHY port: %u", phy_port); + LOG_INFO(" TAP port: %u", tap_port); + LOG_INFO(" Stats every: %u seconds", stats_interval); + LOG_INFO(" Correction: Transparent Clock (SW timestamps)"); + LOG_INFO(""); + LOG_INFO("Run ptp4l: ptp4l -i dtap0 -m -s -S"); + + /* Run relay on main lcore */ + relay_loop(NULL); + + /* Cleanup */ + LOG_INFO("Stopping ports..."); + rte_eth_dev_stop(phy_port); + rte_eth_dev_stop(tap_port); + rte_eth_dev_close(phy_port); + rte_eth_dev_close(tap_port); + rte_eal_cleanup(); + + return 0; +} -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [PATCH v7 3/4] doc: update release notes for PTP protocol library 2026-05-07 13:45 ` [PATCH v7 0/4] IEEE 1588 PTP v2 protocol support in lib/net Rajesh Kumar 2026-05-07 13:45 ` [PATCH v7 1/4] lib/net: add IEEE 1588 PTP v2 protocol header definitions Rajesh Kumar 2026-05-07 13:45 ` [PATCH v7 2/4] examples/ptp_tap_relay_sw: add PTP software transparent clock relay Rajesh Kumar @ 2026-05-07 13:45 ` Rajesh Kumar 2026-05-07 13:45 ` [PATCH v7 4/4] examples/ptpclient: use shared PTP library definitions Rajesh Kumar 3 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-07 13:45 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, mb, Rajesh Kumar Update release notes with IEEE 1588 PTP additions: - PTP protocol definitions in lib/net/rte_ptp.h (header-only library with inline helpers and wire-format structures) - PTP software relay example application (ptp_tap_relay_sw) Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- doc/guides/rel_notes/release_26_07.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/doc/guides/rel_notes/release_26_07.rst b/doc/guides/rel_notes/release_26_07.rst index f012d47a4b..c64e6e141b 100644 --- a/doc/guides/rel_notes/release_26_07.rst +++ b/doc/guides/rel_notes/release_26_07.rst @@ -63,6 +63,18 @@ New Features ``rte_eal_init`` and the application is responsible for probing each device, * ``--auto-probing`` enables the initial bus probing, which is the current default behavior. +* **Added PTP protocol definitions (rte_ptp.h).** + + Added IEEE 1588 Precision Time Protocol header structures, constants, + and inline helpers to ``lib/net/rte_ptp.h``. Provides wire-format + structures with endian-annotated types and correctionField manipulation + for Transparent Clock implementations. + +* **Added PTP software relay example application.** + + Added a new example application ``ptp_tap_relay_sw`` demonstrating a + software PTP Transparent Clock relay between a DPDK port and a kernel + TAP interface. Removed Items ------------- -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [PATCH v7 4/4] examples/ptpclient: use shared PTP library definitions 2026-05-07 13:45 ` [PATCH v7 0/4] IEEE 1588 PTP v2 protocol support in lib/net Rajesh Kumar ` (2 preceding siblings ...) 2026-05-07 13:45 ` [PATCH v7 3/4] doc: update release notes for PTP protocol library Rajesh Kumar @ 2026-05-07 13:45 ` Rajesh Kumar 3 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-07 13:45 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, mb, Rajesh Kumar Update ptpclient to use the lib/net/rte_ptp.h library instead of duplicated local PTP header structures and message type constants. Changes: - Add #include <rte_ptp.h> - Replace local struct ptp_header with struct rte_ptp_hdr - Replace local struct tstamp with struct rte_ptp_timestamp - Replace local struct clock_id with uint8_t[8] arrays - Use RTE_PTP_MSGTYPE_* constants instead of local message types - Use rte_ptp_msg_type(), rte_ptp_seq_id() inline helpers - Remove local PTP_PROTOCOL definition (use RTE_ETHER_TYPE_1588) Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- examples/ptpclient/meson.build | 1 + examples/ptpclient/ptpclient.c | 204 +++++++++++++-------------------- 2 files changed, 81 insertions(+), 124 deletions(-) diff --git a/examples/ptpclient/meson.build b/examples/ptpclient/meson.build index 2e9b7625fc..9087c987d5 100644 --- a/examples/ptpclient/meson.build +++ b/examples/ptpclient/meson.build @@ -7,6 +7,7 @@ # DPDK instance, use 'make' allow_experimental_apis = true +deps += ['net'] sources = files( 'ptpclient.c', ) diff --git a/examples/ptpclient/ptpclient.c b/examples/ptpclient/ptpclient.c index 174ca5dd70..ce3798d1d3 100644 --- a/examples/ptpclient/ptpclient.c +++ b/examples/ptpclient/ptpclient.c @@ -17,6 +17,7 @@ #include <rte_lcore.h> #include <rte_mbuf.h> #include <rte_ip.h> +#include <rte_ptp.h> #include <limits.h> #include <sys/time.h> #include <getopt.h> @@ -30,21 +31,8 @@ static volatile bool force_quit; #define NUM_MBUFS 8191 #define MBUF_CACHE_SIZE 250 -/* Values for the PTP messageType field. */ -#define SYNC 0x0 -#define DELAY_REQ 0x1 -#define PDELAY_REQ 0x2 -#define PDELAY_RESP 0x3 -#define FOLLOW_UP 0x8 -#define DELAY_RESP 0x9 -#define PDELAY_RESP_FOLLOW_UP 0xA -#define ANNOUNCE 0xB -#define SIGNALING 0xC -#define MANAGEMENT 0xD - #define NSEC_PER_SEC 1000000000L #define KERNEL_TIME_ADJUST_LIMIT 20000 -#define PTP_PROTOCOL 0x88F7 struct rte_mempool *mbuf_pool; uint32_t ptp_enabled_port_mask; @@ -55,69 +43,39 @@ static const struct rte_ether_addr ether_multicast = { .addr_bytes = {0x01, 0x1b, 0x19, 0x0, 0x0, 0x0} }; -/* Structs used for PTP handling. */ -struct __rte_packed_begin tstamp { - uint16_t sec_msb; - uint32_t sec_lsb; - uint32_t ns; -} __rte_packed_end; - -struct clock_id { - uint8_t id[8]; -}; - -struct __rte_packed_begin port_id { - struct clock_id clock_id; - uint16_t port_number; -} __rte_packed_end; - -struct __rte_packed_begin ptp_header { - uint8_t msg_type; - uint8_t ver; - uint16_t message_length; - uint8_t domain_number; - uint8_t reserved1; - uint8_t flag_field[2]; - int64_t correction; - uint32_t reserved2; - struct port_id source_port_id; - uint16_t seq_id; - uint8_t control; - int8_t log_message_interval; -} __rte_packed_end; - +/* Structs used for PTP handling - using library definitions from rte_ptp.h */ struct __rte_packed_begin sync_msg { - struct ptp_header hdr; - struct tstamp origin_tstamp; + struct rte_ptp_hdr hdr; + struct rte_ptp_timestamp origin_tstamp; } __rte_packed_end; struct __rte_packed_begin follow_up_msg { - struct ptp_header hdr; - struct tstamp precise_origin_tstamp; + struct rte_ptp_hdr hdr; + struct rte_ptp_timestamp precise_origin_tstamp; uint8_t suffix[]; } __rte_packed_end; struct __rte_packed_begin delay_req_msg { - struct ptp_header hdr; - struct tstamp origin_tstamp; + struct rte_ptp_hdr hdr; + struct rte_ptp_timestamp origin_tstamp; } __rte_packed_end; struct __rte_packed_begin delay_resp_msg { - struct ptp_header hdr; - struct tstamp rx_tstamp; - struct port_id req_port_id; - uint8_t suffix[]; + struct rte_ptp_hdr hdr; + struct rte_ptp_timestamp rx_tstamp; + struct rte_ptp_port_id req_port_id; + uint8_t suffix[]; } __rte_packed_end; -struct ptp_message { +struct __rte_packed_begin ptp_message { union __rte_packed_begin { - struct ptp_header header; - struct sync_msg sync; - struct delay_req_msg delay_req; - struct follow_up_msg follow_up; - struct delay_resp_msg delay_resp; + struct rte_ptp_hdr header; + struct sync_msg sync; + struct delay_req_msg delay_req; + struct follow_up_msg follow_up; + struct delay_resp_msg delay_resp; } __rte_packed_end; -}; +} __rte_packed_end; struct ptpv2_time_receiver_ordinary { struct rte_mbuf *m; @@ -125,8 +83,8 @@ struct ptpv2_time_receiver_ordinary { struct timespec tstamp2; struct timespec tstamp3; struct timespec tstamp4; - struct clock_id client_clock_id; - struct clock_id transmitter_clock_id; + uint8_t client_clock_id[8]; + uint8_t transmitter_clock_id[8]; struct timeval new_adj; int64_t delta; uint16_t portid; @@ -272,14 +230,14 @@ print_clock_info(struct ptpv2_time_receiver_ordinary *ptp_data) struct timespec net_time, sys_time; printf("time transmitter clock id: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", - ptp_data->transmitter_clock_id.id[0], - ptp_data->transmitter_clock_id.id[1], - ptp_data->transmitter_clock_id.id[2], - ptp_data->transmitter_clock_id.id[3], - ptp_data->transmitter_clock_id.id[4], - ptp_data->transmitter_clock_id.id[5], - ptp_data->transmitter_clock_id.id[6], - ptp_data->transmitter_clock_id.id[7]); + ptp_data->transmitter_clock_id[0], + ptp_data->transmitter_clock_id[1], + ptp_data->transmitter_clock_id[2], + ptp_data->transmitter_clock_id[3], + ptp_data->transmitter_clock_id[4], + ptp_data->transmitter_clock_id[5], + ptp_data->transmitter_clock_id[6], + ptp_data->transmitter_clock_id[7]); printf("\nT2 - time receiver clock. %lds %ldns", (ptp_data->tstamp2.tv_sec), @@ -356,20 +314,21 @@ delta_eval(struct ptpv2_time_receiver_ordinary *ptp_data) static void parse_sync(struct ptpv2_time_receiver_ordinary *ptp_data, uint16_t rx_tstamp_idx) { - struct ptp_header *ptp_hdr; + struct rte_ptp_hdr *ptp_hdr; - ptp_hdr = rte_pktmbuf_mtod_offset(ptp_data->m, struct ptp_header *, + ptp_hdr = rte_pktmbuf_mtod_offset(ptp_data->m, struct rte_ptp_hdr *, sizeof(struct rte_ether_hdr)); - ptp_data->seqID_SYNC = rte_be_to_cpu_16(ptp_hdr->seq_id); + ptp_data->seqID_SYNC = rte_ptp_seq_id(ptp_hdr); if (ptp_data->ptpset == 0) { - ptp_data->transmitter_clock_id = ptp_hdr->source_port_id.clock_id; + memcpy(ptp_data->transmitter_clock_id, + ptp_hdr->source_port_id.clock_id, 8); ptp_data->ptpset = 1; } - if (memcmp(&ptp_data->transmitter_clock_id, - &ptp_hdr->source_port_id.clock_id, - sizeof(struct clock_id)) == 0) { + if (memcmp(ptp_data->transmitter_clock_id, + ptp_hdr->source_port_id.clock_id, + 8) == 0) { if (ptp_data->ptpset == 1) rte_eth_timesync_read_rx_timestamp(ptp_data->portid, @@ -386,12 +345,11 @@ parse_fup(struct ptpv2_time_receiver_ordinary *ptp_data) { struct rte_ether_hdr *eth_hdr; struct rte_ether_addr eth_addr; - struct ptp_header *ptp_hdr; - struct clock_id *client_clkid; + struct rte_ptp_hdr *ptp_hdr; struct ptp_message *ptp_msg; struct delay_req_msg *req_msg; struct rte_mbuf *created_pkt; - struct tstamp *origin_tstamp; + struct rte_ptp_timestamp *origin_tstamp; struct rte_ether_addr eth_multicast = ether_multicast; size_t pkt_size; int wait_us; @@ -399,22 +357,22 @@ parse_fup(struct ptpv2_time_receiver_ordinary *ptp_data) int ret; eth_hdr = rte_pktmbuf_mtod(m, struct rte_ether_hdr *); - ptp_hdr = rte_pktmbuf_mtod_offset(m, struct ptp_header *, + ptp_hdr = rte_pktmbuf_mtod_offset(m, struct rte_ptp_hdr *, sizeof(struct rte_ether_hdr)); - if (memcmp(&ptp_data->transmitter_clock_id, - &ptp_hdr->source_port_id.clock_id, - sizeof(struct clock_id)) != 0) + if (memcmp(ptp_data->transmitter_clock_id, + ptp_hdr->source_port_id.clock_id, + 8) != 0) return; - ptp_data->seqID_FOLLOWUP = rte_be_to_cpu_16(ptp_hdr->seq_id); + ptp_data->seqID_FOLLOWUP = rte_ptp_seq_id(ptp_hdr); ptp_msg = rte_pktmbuf_mtod_offset(m, struct ptp_message *, sizeof(struct rte_ether_hdr)); origin_tstamp = &ptp_msg->follow_up.precise_origin_tstamp; - ptp_data->tstamp1.tv_nsec = ntohl(origin_tstamp->ns); + ptp_data->tstamp1.tv_nsec = rte_be_to_cpu_32(origin_tstamp->nanoseconds); ptp_data->tstamp1.tv_sec = - ((uint64_t)ntohl(origin_tstamp->sec_lsb)) | - (((uint64_t)ntohs(origin_tstamp->sec_msb)) << 32); + ((uint64_t)rte_be_to_cpu_32(origin_tstamp->seconds_lo)) | + (((uint64_t)rte_be_to_cpu_16(origin_tstamp->seconds_hi)) << 32); if (ptp_data->seqID_FOLLOWUP == ptp_data->seqID_SYNC) { ret = rte_eth_macaddr_get(ptp_data->portid, ð_addr); @@ -441,34 +399,30 @@ parse_fup(struct ptpv2_time_receiver_ordinary *ptp_data) /* Set multicast address 01-1B-19-00-00-00. */ rte_ether_addr_copy(ð_multicast, ð_hdr->dst_addr); - eth_hdr->ether_type = htons(PTP_PROTOCOL); + eth_hdr->ether_type = htons(RTE_ETHER_TYPE_1588); req_msg = rte_pktmbuf_mtod_offset(created_pkt, struct delay_req_msg *, sizeof(struct rte_ether_hdr)); - req_msg->hdr.seq_id = htons(ptp_data->seqID_SYNC); - req_msg->hdr.msg_type = DELAY_REQ; - req_msg->hdr.ver = 2; - req_msg->hdr.control = 1; - req_msg->hdr.log_message_interval = 127; - req_msg->hdr.message_length = + req_msg->hdr.sequence_id = htons(ptp_data->seqID_SYNC); + req_msg->hdr.msg_type = RTE_PTP_MSGTYPE_DELAY_REQ; + req_msg->hdr.version = 2; + req_msg->hdr.msg_length = htons(sizeof(struct delay_req_msg)); req_msg->hdr.domain_number = ptp_hdr->domain_number; /* Set up clock id. */ - client_clkid = - &req_msg->hdr.source_port_id.clock_id; - - client_clkid->id[0] = eth_hdr->src_addr.addr_bytes[0]; - client_clkid->id[1] = eth_hdr->src_addr.addr_bytes[1]; - client_clkid->id[2] = eth_hdr->src_addr.addr_bytes[2]; - client_clkid->id[3] = 0xFF; - client_clkid->id[4] = 0xFE; - client_clkid->id[5] = eth_hdr->src_addr.addr_bytes[3]; - client_clkid->id[6] = eth_hdr->src_addr.addr_bytes[4]; - client_clkid->id[7] = eth_hdr->src_addr.addr_bytes[5]; - - ptp_data->client_clock_id = *client_clkid; + ptp_data->client_clock_id[0] = eth_hdr->src_addr.addr_bytes[0]; + ptp_data->client_clock_id[1] = eth_hdr->src_addr.addr_bytes[1]; + ptp_data->client_clock_id[2] = eth_hdr->src_addr.addr_bytes[2]; + ptp_data->client_clock_id[3] = 0xFF; + ptp_data->client_clock_id[4] = 0xFE; + ptp_data->client_clock_id[5] = eth_hdr->src_addr.addr_bytes[3]; + ptp_data->client_clock_id[6] = eth_hdr->src_addr.addr_bytes[4]; + ptp_data->client_clock_id[7] = eth_hdr->src_addr.addr_bytes[5]; + + memcpy(req_msg->hdr.source_port_id.clock_id, + ptp_data->client_clock_id, 8); /* Enable flag for hardware timestamping. */ created_pkt->ol_flags |= RTE_MBUF_F_TX_IEEE1588_TMST; @@ -534,21 +488,21 @@ parse_drsp(struct ptpv2_time_receiver_ordinary *ptp_data) { struct rte_mbuf *m = ptp_data->m; struct ptp_message *ptp_msg; - struct tstamp *rx_tstamp; + struct rte_ptp_timestamp *rx_tstamp; uint16_t seq_id; ptp_msg = rte_pktmbuf_mtod_offset(m, struct ptp_message *, sizeof(struct rte_ether_hdr)); - seq_id = rte_be_to_cpu_16(ptp_msg->delay_resp.hdr.seq_id); - if (memcmp(&ptp_data->client_clock_id, - &ptp_msg->delay_resp.req_port_id.clock_id, - sizeof(struct clock_id)) == 0) { + seq_id = rte_ptp_seq_id(&ptp_msg->delay_resp.hdr); + if (memcmp(ptp_data->client_clock_id, + ptp_msg->delay_resp.req_port_id.clock_id, + 8) == 0) { if (seq_id == ptp_data->seqID_FOLLOWUP) { rx_tstamp = &ptp_msg->delay_resp.rx_tstamp; - ptp_data->tstamp4.tv_nsec = ntohl(rx_tstamp->ns); + ptp_data->tstamp4.tv_nsec = rte_be_to_cpu_32(rx_tstamp->nanoseconds); ptp_data->tstamp4.tv_sec = - ((uint64_t)ntohl(rx_tstamp->sec_lsb)) | - (((uint64_t)ntohs(rx_tstamp->sec_msb)) << 32); + ((uint64_t)rte_be_to_cpu_32(rx_tstamp->seconds_lo)) | + (((uint64_t)rte_be_to_cpu_16(rx_tstamp->seconds_hi)) << 32); /* Evaluate the delta for adjustment. */ ptp_data->delta = delta_eval(ptp_data); @@ -575,27 +529,29 @@ parse_drsp(struct ptpv2_time_receiver_ordinary *ptp_data) /* Parse ptp frames. 8< */ static void parse_ptp_frames(uint16_t portid, struct rte_mbuf *m) { - struct ptp_header *ptp_hdr; + struct rte_ptp_hdr *ptp_hdr; struct rte_ether_hdr *eth_hdr; uint16_t eth_type; + uint8_t msg_type; eth_hdr = rte_pktmbuf_mtod(m, struct rte_ether_hdr *); eth_type = rte_be_to_cpu_16(eth_hdr->ether_type); - if (eth_type == PTP_PROTOCOL) { + if (eth_type == RTE_ETHER_TYPE_1588) { ptp_data.m = m; ptp_data.portid = portid; - ptp_hdr = rte_pktmbuf_mtod_offset(m, struct ptp_header *, + ptp_hdr = rte_pktmbuf_mtod_offset(m, struct rte_ptp_hdr *, sizeof(struct rte_ether_hdr)); - switch (ptp_hdr->msg_type) { - case SYNC: + msg_type = rte_ptp_msg_type(ptp_hdr); + switch (msg_type) { + case RTE_PTP_MSGTYPE_SYNC: parse_sync(&ptp_data, m->timesync); break; - case FOLLOW_UP: + case RTE_PTP_MSGTYPE_FOLLOW_UP: parse_fup(&ptp_data); break; - case DELAY_RESP: + case RTE_PTP_MSGTYPE_DELAY_RESP: parse_drsp(&ptp_data); print_clock_info(&ptp_data); break; -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [PATCH v8 0/4] IEEE 1588 PTP v2 protocol support in lib/net 2026-04-28 1:01 [RFC v1 0/4] introduce PTP protocol library and software relay example Rajesh Kumar ` (10 preceding siblings ...) 2026-05-07 13:45 ` [PATCH v7 0/4] IEEE 1588 PTP v2 protocol support in lib/net Rajesh Kumar @ 2026-05-09 23:25 ` Rajesh Kumar 2026-05-09 23:25 ` [PATCH v8 1/4] lib/net: add IEEE 1588 PTP v2 protocol header definitions Rajesh Kumar ` (3 more replies) 11 siblings, 4 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-09 23:25 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, mb, Rajesh Kumar Add IEEE 1588-2019 Precision Time Protocol (PTP) support to DPDK via a new header library in lib/net/. This patchset provides wire-format packet structure definitions and inline helpers for Transparent Clock and ordinary clock applications. Design Rationale ================ Following DPDK conventions for protocol libraries (rte_tcp.h, rte_ip.h), PTP definitions are provided as a header-only library with inline functions for zero-overhead packet processing. All functions are suitable for real-time, performance-critical code paths. Contents ======== Patch 1: lib/net/rte_ptp.h - Header structures and inline helpers - rte_ptp_port_id: 10-byte port identity with EUI-64 clock ID - rte_ptp_hdr: 34-byte PTP v2 common message header with nibble bitfields for msg_type/transportSpecific and version/minorVersion - rte_ptp_timestamp: 10-byte nanosecond-precision timestamp - Message type constants (Sync, Delay_Req, Announce, etc.) - Flag field bits (two-step, unicast, leap indicator) - Port and MAC address constants (RTE_IPPORT_PTP_*, RTE_ETHER_ADDR_PTP_*) - Inline helpers: rte_ptp_is_event(), rte_ptp_is_two_step(), rte_ptp_add_correction(), rte_ptp_timestamp_to_ns() Patch 2: examples/ptp_tap_relay_sw - Software PTP Transparent Clock relay - Relays PTP packets between a DPDK-bound physical NIC and Linux TAP - Software timestamp-based residence time measurement (CLOCK_MONOTONIC) - Accumulates residence time to correctionField per IEEE 1588-2019 §10.2 - Handles L2, VLAN/QinQ, UDP/IPv4, UDP/IPv6 PTP encapsulations - Works with unmodified Linux kernel and stock DPDK (no patches required) - Compatible with standard linuxptp (ptp4l) tooling Patch 3: doc/guides/rel_notes/release_26_07.rst - Release notes update Patch 4: examples/ptpclient - Update to use lib/net PTP definitions - Replace local struct ptp_header with struct rte_ptp_hdr - Replace local struct tstamp with struct rte_ptp_timestamp - Replace local struct clock_id with uint8_t[8] arrays - Use RTE_PTP_MSGTYPE_* constants instead of local defines - Use hdr->msg_type bitfield and rte_be_to_cpu_16(hdr->sequence_id) for direct field access - Remove local PTP_PROTOCOL macro (use RTE_ETHER_TYPE_1588) - Add lib/net dependency in meson.build Testing ======= - Build: `ninja -C build` - clean compilation - Examples: Both ptpclient and ptp_tap_relay_sw compile and link correctly - Checkpatches: All 4 patches pass validation - Functional: ptpclient tested with ptp4l in L2 mode (HW timestamps) - Functional: ptp_tap_relay_sw tested with E810 master and TAP slave Changelog ========= v8 changes: - Renamed port macros: RTE_IPPORT_PTP_EVENT, RTE_IPPORT_PTP_GENERAL - Renamed MAC macros: RTE_ETHER_ADDR_PTP_MULTICAST, RTE_ETHER_ADDR_PTP_MULTICAST_PDELAY - Renamed RTE_PTP_MSGTYPE_FOLLOW_UP to RTE_PTP_MSGTYPE_FU for consistency with RTE_PTP_MSGTYPE_PDELAY_RESP_FU - Split msg_type and version bytes into 4-bit bitfields using __extension__ union pattern (matching rte_ipv4_hdr convention) - Changed flag bits to use UINT16_C(1) << N - Removed 6 accessor functions (rte_ptp_msg_type, rte_ptp_seq_id, rte_ptp_version, rte_ptp_domain, rte_ptp_transport_specific, rte_ptp_correction_ns) - use direct field access instead - Changed rte_ptp_is_event() parameter from int to const struct rte_ptp_hdr * - Changed rte_ptp_add_correction() parameter from int64_t to uint64_t - Used UINT64_C(1000000000) in rte_ptp_timestamp_to_ns() - Added "(general)" to PDELAY_RESP_FU description - Documented PTP timestamp epoch - Updated MAINTAINERS headline - Updated ptpclient and ptp_tap_relay_sw for direct field access v7 changes: - Added sample app guide to sample_app_ug toctree to fix Sphinx warning - Fixed all checkpatch warnings (commit message line lengths <= 75 chars) - Refactored ptpclient to use shared lib/net/rte_ptp.h definitions - All 5 patches (cover + 4 functional) pass checkpatch validation v6 changes: - Restructured to lib/net (header-only, following lib/net conventions) - Removed separate DPI library functions (moved to example local parser) - Removed app/test unit tests (header-only, example-driven testing) - Removed programmer's guide (lib/net headers use Doxygen API docs only) Rajesh Kumar (4): lib/net: add IEEE 1588 PTP v2 protocol header definitions examples/ptp_tap_relay_sw: add PTP software transparent clock relay doc: update release notes for PTP protocol library examples/ptpclient: use shared PTP library definitions MAINTAINERS | 6 + doc/guides/rel_notes/release_26_07.rst | 12 + doc/guides/sample_app_ug/index.rst | 1 + doc/guides/sample_app_ug/ptp_tap_relay_sw.rst | 212 +++++++++ examples/ptp_tap_relay_sw/Makefile | 41 ++ examples/ptp_tap_relay_sw/meson.build | 13 + examples/ptp_tap_relay_sw/ptp_parse.h | 211 +++++++++ examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c | 432 ++++++++++++++++++ examples/ptpclient/meson.build | 1 + examples/ptpclient/ptpclient.c | 204 ++++----- lib/net/meson.build | 1 + lib/net/rte_ptp.h | 205 +++++++++ 12 files changed, 1215 insertions(+), 124 deletions(-) create mode 100644 doc/guides/sample_app_ug/ptp_tap_relay_sw.rst create mode 100644 examples/ptp_tap_relay_sw/Makefile create mode 100644 examples/ptp_tap_relay_sw/meson.build create mode 100644 examples/ptp_tap_relay_sw/ptp_parse.h create mode 100644 examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c create mode 100644 lib/net/rte_ptp.h -- 2.53.0 ^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v8 1/4] lib/net: add IEEE 1588 PTP v2 protocol header definitions 2026-05-09 23:25 ` [PATCH v8 0/4] IEEE 1588 PTP v2 protocol support in lib/net Rajesh Kumar @ 2026-05-09 23:25 ` Rajesh Kumar 2026-05-09 23:25 ` [PATCH v8 2/4] examples/ptp_tap_relay_sw: add PTP software transparent clock relay Rajesh Kumar ` (2 subsequent siblings) 3 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-09 23:25 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, mb, Rajesh Kumar Add PTP (Precision Time Protocol) header structures and inline helper functions to lib/net following DPDK conventions for protocol libraries (similar to rte_tcp.h, rte_ip.h). Provides wire-format structures with endian-annotated types: - rte_ptp_port_id: 10-byte port identity with EUI-64 clock ID - rte_ptp_hdr: 34-byte common message header with nibble bitfields for msg_type/transportSpecific and version/minorVersion - rte_ptp_timestamp: 10-byte nanosecond-precision timestamp PTP message type constants for all IEEE 1588-2019 message types (Sync, Delay_Req, Announce, Management, etc.) and flag field bits (two-step, unicast, leap indicator). Inline utility functions: - rte_ptp_is_event(), rte_ptp_is_two_step() - rte_ptp_add_correction(), rte_ptp_timestamp_to_ns() Supports all PTP encapsulations: L2 (EtherType 0x88F7), VLAN/QinQ, UDP/IPv4, and UDP/IPv6 (ports 319/320). All inline functions — zero function call overhead, suitable for real-time packet processing. Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- MAINTAINERS | 6 ++ lib/net/meson.build | 1 + lib/net/rte_ptp.h | 205 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 212 insertions(+) create mode 100644 lib/net/rte_ptp.h diff --git a/MAINTAINERS b/MAINTAINERS index 0f5539f851..516960a189 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1665,6 +1665,12 @@ F: doc/guides/prog_guide/ipsec_lib.rst M: Vladimir Medvedkin <vladimir.medvedkin@intel.com> F: app/test-sad/ +PTP (IEEE 1588 Precision Time Protocol) +M: Rajesh Kumar <rajesh3.kumar@intel.com> +F: lib/net/rte_ptp.h +F: examples/ptp_tap_relay_sw/ +F: doc/guides/sample_app_ug/ptp_tap_relay_sw.rst + PDCP - EXPERIMENTAL M: Anoob Joseph <anoobj@marvell.com> M: Volodymyr Fialko <vfialko@marvell.com> diff --git a/lib/net/meson.build b/lib/net/meson.build index 3fad5edc5b..63d13719f3 100644 --- a/lib/net/meson.build +++ b/lib/net/meson.build @@ -28,6 +28,7 @@ headers = files( 'rte_geneve.h', 'rte_l2tpv2.h', 'rte_ppp.h', + 'rte_ptp.h', 'rte_ib.h', ) diff --git a/lib/net/rte_ptp.h b/lib/net/rte_ptp.h new file mode 100644 index 0000000000..29cab5c2e4 --- /dev/null +++ b/lib/net/rte_ptp.h @@ -0,0 +1,205 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2026 Intel Corporation + */ + +#ifndef _RTE_PTP_H_ +#define _RTE_PTP_H_ + +/** + * @file + * + * PTP (IEEE 1588) protocol definitions + */ + +#include <stdint.h> +#include <stdbool.h> + +#include <rte_byteorder.h> +#include <rte_common.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * PTP Constants + */ + +/** PTP over UDP event port (Sync, Delay_Req, PDelay_Req, PDelay_Resp). */ +#define RTE_IPPORT_PTP_EVENT 319 + +/** PTP over UDP general port (Follow_Up, Delay_Resp, Announce, etc.). */ +#define RTE_IPPORT_PTP_GENERAL 320 + +/** PTP multicast MAC address: 01:1B:19:00:00:00. */ +#define RTE_ETHER_ADDR_PTP_MULTICAST { 0x01, 0x1B, 0x19, 0x00, 0x00, 0x00 } + +/** PTP peer delay multicast MAC: 01:80:C2:00:00:0E. */ +#define RTE_ETHER_ADDR_PTP_MULTICAST_PDELAY { 0x01, 0x80, 0xC2, 0x00, 0x00, 0x0E } + +/* + * PTP Message Types (IEEE 1588-2019 Table 36) + */ + +#define RTE_PTP_MSGTYPE_SYNC 0x0 /**< Sync (event). */ +#define RTE_PTP_MSGTYPE_DELAY_REQ 0x1 /**< Delay_Req (event). */ +#define RTE_PTP_MSGTYPE_PDELAY_REQ 0x2 /**< Peer_Delay_Req (event). */ +#define RTE_PTP_MSGTYPE_PDELAY_RESP 0x3 /**< Peer_Delay_Resp (event). */ +#define RTE_PTP_MSGTYPE_FU 0x8 /**< Follow_Up (general). */ +#define RTE_PTP_MSGTYPE_DELAY_RESP 0x9 /**< Delay_Resp (general). */ +#define RTE_PTP_MSGTYPE_PDELAY_RESP_FU 0xA /**< Peer_Delay_Resp_Follow_Up (general). */ +#define RTE_PTP_MSGTYPE_ANNOUNCE 0xB /**< Announce (general). */ +#define RTE_PTP_MSGTYPE_SIGNALING 0xC /**< Signaling (general). */ +#define RTE_PTP_MSGTYPE_MANAGEMENT 0xD /**< Management (general). */ + +/* + * PTP Flag Field Bits (IEEE 1588-2019 Table 37) + * + * These constants are for use after rte_be_to_cpu_16(hdr->flags). + * flagField[0] (octet 6) maps to host bits 8-15. + * flagField[1] (octet 7) maps to host bits 0-7. + */ + +#define RTE_PTP_FLAG_TWO_STEP (UINT16_C(1) << 9) /**< Two-step flag. */ +#define RTE_PTP_FLAG_UNICAST (UINT16_C(1) << 10) /**< Unicast flag. */ +#define RTE_PTP_FLAG_LI_61 (UINT16_C(1) << 0) /**< Leap indicator 61. */ +#define RTE_PTP_FLAG_LI_59 (UINT16_C(1) << 1) /**< Leap indicator 59. */ + +/* + * PTP Header Structures (IEEE 1588-2019) + */ + +/** + * PTP Port Identity (10 bytes). + */ +struct __rte_packed_begin rte_ptp_port_id { + uint8_t clock_id[8]; /**< clockIdentity (EUI-64). */ + rte_be16_t port_number; /**< portNumber. */ +} __rte_packed_end; + +/** + * PTP Common Message Header (34 bytes). + */ +struct __rte_packed_begin rte_ptp_hdr { + __extension__ + union { + uint8_t ts_msgtype; /**< transportSpecific | messageType. */ + struct { +#if RTE_BYTE_ORDER == RTE_LITTLE_ENDIAN + uint8_t msg_type:4; /**< messageType. */ + uint8_t ts:4; /**< transportSpecific. */ +#elif RTE_BYTE_ORDER == RTE_BIG_ENDIAN + uint8_t ts:4; /**< transportSpecific. */ + uint8_t msg_type:4; /**< messageType. */ +#endif + }; + }; + __extension__ + union { + uint8_t ver; /**< minorVersionPTP | versionPTP. */ + struct { +#if RTE_BYTE_ORDER == RTE_LITTLE_ENDIAN + uint8_t version:4; /**< versionPTP. */ + uint8_t minor_version:4; /**< minorVersionPTP. */ +#elif RTE_BYTE_ORDER == RTE_BIG_ENDIAN + uint8_t minor_version:4; /**< minorVersionPTP. */ + uint8_t version:4; /**< versionPTP. */ +#endif + }; + }; + rte_be16_t msg_length; /**< Total message length in bytes. */ + uint8_t domain_number; /**< PTP domain (0-255). */ + uint8_t minor_sdo_id; /**< minorSdoId (IEEE 1588-2019). */ + rte_be16_t flags; /**< Flag field (see RTE_PTP_FLAG_*). */ + rte_be64_t correction; /**< correctionField (scaled ns, 48.16 fixed). */ + rte_be32_t msg_type_specific; /**< messageTypeSpecific. */ + struct rte_ptp_port_id source_port_id; /**< sourcePortIdentity. */ + rte_be16_t sequence_id; /**< sequenceId. */ + uint8_t control; /**< controlField (deprecated in 1588-2019). */ + int8_t log_msg_interval; /**< logMessageInterval. */ +} __rte_packed_end; + +/** + * PTP Timestamp (10 bytes, used in Sync/Delay_Req/Follow_Up bodies). + * Seconds since PTP epoch (1 January 1970 00:00:00 TAI). + */ +struct __rte_packed_begin rte_ptp_timestamp { + rte_be16_t seconds_hi; /**< Upper 16 bits of seconds. */ + rte_be32_t seconds_lo; /**< Lower 32 bits of seconds. */ + rte_be32_t nanoseconds; /**< Nanoseconds (0-999999999). */ +} __rte_packed_end; + +/* + * Inline Helpers + */ + +/** + * Check if PTP message type is an event message. + * Event messages (msg_type 0x0-0x3) require timestamps. + * + * @param hdr + * Pointer to PTP header. + * @return + * true if event message, false otherwise. + */ +static inline bool +rte_ptp_is_event(const struct rte_ptp_hdr *hdr) +{ + return hdr->msg_type <= RTE_PTP_MSGTYPE_PDELAY_RESP; +} + +/** + * Check if the two-step flag is set in a PTP header. + * + * @param hdr + * Pointer to PTP header. + * @return + * true if two-step flag is set. + */ +static inline bool +rte_ptp_is_two_step(const struct rte_ptp_hdr *hdr) +{ + return (hdr->flags & RTE_BE16(RTE_PTP_FLAG_TWO_STEP)) != 0; +} + +/** + * Add a residence time (in nanoseconds) to the correctionField. + * Used by Transparent Clocks to account for relay transit delay. + * The correctionField uses IEEE 1588 scaled nanoseconds (48.16 fixed-point). + * + * @param hdr + * Pointer to PTP header (will be modified in-place). + * @param residence_ns + * Residence time in nanoseconds to add. + */ +static inline void +rte_ptp_add_correction(struct rte_ptp_hdr *hdr, uint64_t residence_ns) +{ + uint64_t cf = rte_be_to_cpu_64(hdr->correction); + + cf += residence_ns << 16; + hdr->correction = rte_cpu_to_be_64(cf); +} + +/** + * Convert a PTP timestamp structure to nanoseconds since epoch. + * + * @param ts + * Pointer to PTP timestamp. + * @return + * Time in nanoseconds since PTP epoch. + */ +static inline uint64_t +rte_ptp_timestamp_to_ns(const struct rte_ptp_timestamp *ts) +{ + uint64_t sec = ((uint64_t)rte_be_to_cpu_16(ts->seconds_hi) << 32) | + rte_be_to_cpu_32(ts->seconds_lo); + + return sec * UINT64_C(1000000000) + rte_be_to_cpu_32(ts->nanoseconds); +} + +#ifdef __cplusplus +} +#endif + +#endif /* _RTE_PTP_H_ */ -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [PATCH v8 2/4] examples/ptp_tap_relay_sw: add PTP software transparent clock relay 2026-05-09 23:25 ` [PATCH v8 0/4] IEEE 1588 PTP v2 protocol support in lib/net Rajesh Kumar 2026-05-09 23:25 ` [PATCH v8 1/4] lib/net: add IEEE 1588 PTP v2 protocol header definitions Rajesh Kumar @ 2026-05-09 23:25 ` Rajesh Kumar 2026-05-09 23:25 ` [PATCH v8 3/4] doc: update release notes for PTP protocol library Rajesh Kumar 2026-05-09 23:25 ` [PATCH v8 4/4] examples/ptpclient: use shared PTP library definitions Rajesh Kumar 3 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-09 23:25 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, mb, Rajesh Kumar Add a new example application demonstrating a software PTP Transparent Clock relay between a DPDK-bound physical NIC and a Linux kernel TAP virtual interface. The relay uses software timestamps (CLOCK_MONOTONIC) to measure residence time and accumulates it into the PTP correctionField per IEEE 1588-2019 §10.2, enabling synchronized time distribution via standard linuxptp (ptp4l) on both sides. Features: - Handles L2, VLAN/QinQ, and UDP/IPv4/IPv6 PTP encapsulations - Supports PTP v2 event messages (Sync, Delay_Req, PDelay_Req, PDelay_Resp) - Two-pass burst processing: classify before TX, timestamp before relay - Unmodified Linux kernel and stock DPDK (no kernel patches required) - Bidirectional relay: PHY ↔ TAP Includes: - ptp_tap_relay_sw.c: Main relay logic with burst processing - ptp_parse.h: Local DPI parser for PTP classification (not a library API) - Sample app guide with topology, command-line options, and example output Uses lib/net/rte_ptp.h inline helpers for correctionField manipulation and header parsing. Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- doc/guides/sample_app_ug/index.rst | 1 + doc/guides/sample_app_ug/ptp_tap_relay_sw.rst | 212 +++++++++ examples/ptp_tap_relay_sw/Makefile | 41 ++ examples/ptp_tap_relay_sw/meson.build | 13 + examples/ptp_tap_relay_sw/ptp_parse.h | 211 +++++++++ examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c | 432 ++++++++++++++++++ 6 files changed, 910 insertions(+) create mode 100644 doc/guides/sample_app_ug/ptp_tap_relay_sw.rst create mode 100644 examples/ptp_tap_relay_sw/Makefile create mode 100644 examples/ptp_tap_relay_sw/meson.build create mode 100644 examples/ptp_tap_relay_sw/ptp_parse.h create mode 100644 examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c diff --git a/doc/guides/sample_app_ug/index.rst b/doc/guides/sample_app_ug/index.rst index e895f692f9..f12623bb66 100644 --- a/doc/guides/sample_app_ug/index.rst +++ b/doc/guides/sample_app_ug/index.rst @@ -51,6 +51,7 @@ Sample Applications User Guides dist_app vm_power_management ptpclient + ptp_tap_relay_sw fips_validation ipsec_secgw bbdev_app diff --git a/doc/guides/sample_app_ug/ptp_tap_relay_sw.rst b/doc/guides/sample_app_ug/ptp_tap_relay_sw.rst new file mode 100644 index 0000000000..15727383c1 --- /dev/null +++ b/doc/guides/sample_app_ug/ptp_tap_relay_sw.rst @@ -0,0 +1,212 @@ +.. SPDX-License-Identifier: BSD-3-Clause + Copyright(c) 2026 Intel Corporation. + +PTP Software Relay Sample Application +====================================== + +The PTP Software Relay sample application demonstrates how to build a +minimal PTP Transparent Clock relay between a DPDK-bound physical NIC +and a kernel TAP interface using **software timestamps only**. It uses +the PTP definitions from ``rte_ptp.h`` (in ``lib/net/``) together with a +local packet parser. + +The application works with an unmodified Linux kernel and stock DPDK. + +For background on PTP see: +`Precision Time Protocol +<https://en.wikipedia.org/wiki/Precision_Time_Protocol>`_. + + +Limitations +----------- + +* Tested with L2 PTP (EtherType 0x88F7) on the wire. + The local parser also classifies VLAN/QinQ and UDP/IPv4/IPv6. +* Only PTP v2 messages are processed. +* Software timestamps have microsecond-class jitter; sub-microsecond + precision depends on system load and NIC-to-TAP forwarding latency. +* The PTP time transmitter must be reachable on the physical NIC's L2 network. +* Only one physical port and one TAP port are supported. + + +How the Application Works +------------------------- + +Topology +~~~~~~~~ + +:: + + PTP Time Transmitter Physical NIC TAP (kernel) + (ptp4l -H) ──L2── (DPDK vfio-pci) ────── dtap0 + │ │ + ptp_tap_relay_sw ptp4l -S + (correctionField += (SW timestamps, + residence time) adjusts CLOCK_REALTIME) + +The relay sits between a DPDK-owned physical NIC and a kernel TAP +virtual interface. ``ptp4l`` runs on the TAP interface in software +timestamp mode (``-S``) as a PTP time receiver. + +Packet Flow +~~~~~~~~~~~ + +1. The physical NIC receives PTP (and non-PTP) packets via DPDK RX. +2. A software RX timestamp is recorded using + ``clock_gettime(CLOCK_MONOTONIC)``. +3. Each packet is parsed to locate the PTP header. +4. For PTP **event** messages (Sync, Delay_Req, PDelay_Req, PDelay_Resp), + a TX software timestamp is taken just before transmission. +5. The residence time (``tx_ts − rx_ts``) is added to the PTP + ``correctionField`` via ``rte_ptp_add_correction()`` — standard + IEEE 1588-2019 Transparent Clock behaviour (§10.2). +6. Packets are forwarded bidirectionally: + + * PHY → TAP (network → ptp4l) + * TAP → PHY (ptp4l → network) + +A two-pass design is used: first all packets are classified and PTP +header pointers saved, then a single TX timestamp is taken immediately +before applying corrections and calling ``rte_eth_tx_burst()``. +This minimises the gap between the measured timestamp and the actual +wire egress. + + +Compiling the Application +------------------------- + +To compile the sample application see :doc:`compiling`. + +The application is located in the ``ptp_tap_relay_sw`` sub-directory. + +.. note:: + + The application uses ``rte_ptp.h`` from ``lib/net/`` (built by default) + and a local ``ptp_parse.h`` header for packet classification. + + +Running the Application +----------------------- + +Prerequisites +~~~~~~~~~~~~~ + +* A PTP-capable physical NIC bound to DPDK (e.g. via ``vfio-pci``). +* ``linuxptp`` (``ptp4l``) installed on the system. +* A PTP time transmitter reachable on the same L2 network. + +Start the relay +~~~~~~~~~~~~~~~~ + +.. code-block:: console + + ./<build_dir>/examples/dpdk-ptp_tap_relay_sw \ + -l 18-19 -a 0000:cc:00.1 --vdev=net_tap0,iface=dtap0 -- \ + -p 0 -t 1 -T 10 + +Command-line Options +~~~~~~~~~~~~~~~~~~~~ + +* ``-p PORT`` — Physical NIC port ID (default: 0). +* ``-t PORT`` — TAP port ID (default: 1). +* ``-T SECS`` — Statistics print interval in seconds (default: 10). + +Start PTP time transmitter +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +On a separate terminal or remote host, start ``ptp4l`` as time +transmitter with hardware timestamps on the physical NIC: + +.. code-block:: console + + ptp4l -i <iface> -m -2 -H --serverOnly=1 \ + --logSyncInterval=-4 --logMinDelayReqInterval=-4 + +Start PTP time receiver +~~~~~~~~~~~~~~~~~~~~~~~ + +On the TAP interface, start ``ptp4l`` in software timestamp mode: + +.. code-block:: console + + ptp4l -i dtap0 -m -2 -s -S \ + --delay_filter=moving_median --delay_filter_length=10 + +The time receiver will enter UNCALIBRATED state for approximately 60 +seconds while the PI servo estimates the frequency offset, then step +the clock and enter time-receiver (synchronized) state. +Steady-state RMS offset of 500–1000 ns is typical on a lightly loaded +system with a hardware-timestamped time transmitter. + +Example Output +~~~~~~~~~~~~~~ + +Relay statistics printed every ``-T`` seconds: + +:: + + [PTP-SW] === Statistics === + [PTP-SW] PHY RX total: 5646 + [PTP-SW] PHY RX PTP: 5598 + [PTP-SW] TAP TX: 5646 + [PTP-SW] TAP RX total: 1800 + [PTP-SW] TAP RX PTP: 1788 + [PTP-SW] PHY TX: 1800 + [PTP-SW] Corrections: 3635 + +Time receiver ``ptp4l`` output after convergence: + +:: + + ptp4l[451534.520]: rms 630 max 1166 freq -44365 +/- 100 delay 37668 +/- 71 + ptp4l[451539.525]: rms 602 max 1177 freq -44339 +/- 119 delay 37517 +/- 43 + ptp4l[451544.530]: rms 535 max 1194 freq -44345 +/- 103 delay 37410 +/- 81 + + +Code Explanation +---------------- + +The following sections explain the main components of the application. + +Relay Burst Function +~~~~~~~~~~~~~~~~~~~~ + +The core relay logic is in ``relay_burst()``, which handles one direction +(PHY→TAP or TAP→PHY) per call: + +**Pass 1 — Classify:** + +For each received packet, ``ptp_hdr_find()`` locates the PTP header +(if present). For event messages, the header pointer is saved for the +second pass. + +**Pass 2 — Timestamp and correct:** + +A single software TX timestamp is taken via +``clock_gettime(CLOCK_MONOTONIC)``. The residence time +(``tx_ts − rx_ts``) is added to each saved PTP header's +``correctionField`` using ``rte_ptp_add_correction()``. +The burst is then transmitted with ``rte_eth_tx_burst()``. + +Main Loop +~~~~~~~~~ + +The ``relay_loop()`` function polls both directions in a tight loop: + +.. code-block:: c + + while (!force_quit) { + relay_burst(phy_port, tap_port, ...); /* PHY → TAP */ + relay_burst(tap_port, phy_port, ...); /* TAP → PHY */ + } + +Statistics are printed at the interval specified by ``-T``. + +Timestamp Source +~~~~~~~~~~~~~~~~ + +``CLOCK_MONOTONIC`` is used rather than ``CLOCK_REALTIME`` because +the PTP time receiver's servo continuously adjusts ``CLOCK_REALTIME``. +Using ``CLOCK_REALTIME`` would corrupt residence time measurements +during clock stepping or frequency slewing. ``CLOCK_MONOTONIC`` is +portable across Linux and FreeBSD. diff --git a/examples/ptp_tap_relay_sw/Makefile b/examples/ptp_tap_relay_sw/Makefile new file mode 100644 index 0000000000..fd178f46ae --- /dev/null +++ b/examples/ptp_tap_relay_sw/Makefile @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2026 Intel Corporation + +# binary name +APP = dpdk-ptp_tap_relay_sw + +# all source are stored in SRCS-y +SRCS-y := ptp_tap_relay_sw.c + +PKGCONF ?= pkg-config + +# Build using pkg-config variables if possible +ifneq ($(shell $(PKGCONF) --exists libdpdk && echo 0),0) +$(error "no installation of DPDK found") +endif + +all: shared +.PHONY: shared static +shared: build/$(APP)-shared + ln -sf $(APP)-shared build/$(APP) +static: build/$(APP)-static + ln -sf $(APP)-static build/$(APP) + +PC_FILE := $(shell $(PKGCONF) --path libdpdk 2>/dev/null) +CFLAGS += -O3 $(shell $(PKGCONF) --cflags libdpdk) +LDFLAGS_SHARED = $(shell $(PKGCONF) --libs libdpdk) +LDFLAGS_STATIC = $(shell $(PKGCONF) --static --libs libdpdk) + +build/$(APP)-shared: $(SRCS-y) Makefile $(PC_FILE) | build + $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_SHARED) + +build/$(APP)-static: $(SRCS-y) Makefile $(PC_FILE) | build + $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_STATIC) + +build: + @mkdir -p $@ + +.PHONY: clean +clean: + rm -f build/$(APP) build/$(APP)-static build/$(APP)-shared + test -d build && rmdir -p build || true diff --git a/examples/ptp_tap_relay_sw/meson.build b/examples/ptp_tap_relay_sw/meson.build new file mode 100644 index 0000000000..34a4d86439 --- /dev/null +++ b/examples/ptp_tap_relay_sw/meson.build @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2026 Intel Corporation + +# meson file, for building this example as part of a main DPDK build. +# +# To build this example as a standalone application with an already-installed +# DPDK instance, use 'make' + +sources = files( + 'ptp_tap_relay_sw.c', +) +deps += ['net'] +cflags += no_shadow_cflag diff --git a/examples/ptp_tap_relay_sw/ptp_parse.h b/examples/ptp_tap_relay_sw/ptp_parse.h new file mode 100644 index 0000000000..db3c650ed9 --- /dev/null +++ b/examples/ptp_tap_relay_sw/ptp_parse.h @@ -0,0 +1,211 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2026 Intel Corporation + * + * PTP packet parser — locates PTP headers through L2, VLAN, and UDP + * encapsulations. This is a DPI helper for use within example + * applications; it does not belong in the core library. + */ + +#ifndef _PTP_PARSE_H_ +#define _PTP_PARSE_H_ + +#include <rte_mbuf.h> +#include <rte_ether.h> +#include <rte_ip.h> +#include <rte_udp.h> +#include <rte_ptp.h> + +/** Not a PTP packet. */ +#define PTP_MSGTYPE_INVALID (-1) + +/** + * Locate the PTP header within a packet. + * + * Handles L2 (EtherType 0x88F7), VLAN-tagged L2 (single/double, + * TPIDs 0x8100/0x88A8), PTP over UDP/IPv4, PTP over UDP/IPv6, + * and VLAN-tagged UDP variants. + * + * @param m + * Pointer to the mbuf. + * @return + * Pointer to the PTP header, or NULL if not a PTP packet. + */ +static inline struct rte_ptp_hdr * +ptp_hdr_find(const struct rte_mbuf *m) +{ + const struct rte_ether_hdr *eth; + uint16_t ether_type; + uint32_t offset; + + if (rte_pktmbuf_data_len(m) < sizeof(struct rte_ether_hdr)) + return NULL; + + eth = rte_pktmbuf_mtod(m, const struct rte_ether_hdr *); + ether_type = rte_be_to_cpu_16(eth->ether_type); + offset = sizeof(struct rte_ether_hdr); + + /* Strip VLAN / QinQ tags */ + if (ether_type == RTE_ETHER_TYPE_VLAN || + ether_type == RTE_ETHER_TYPE_QINQ) { + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_vlan_hdr)) + return NULL; + const struct rte_vlan_hdr *vlan = + rte_pktmbuf_mtod_offset(m, + const struct rte_vlan_hdr *, offset); + ether_type = rte_be_to_cpu_16(vlan->eth_proto); + offset += sizeof(struct rte_vlan_hdr); + + /* Second tag (QinQ inner or stacked VLAN) */ + if (ether_type == RTE_ETHER_TYPE_VLAN || + ether_type == RTE_ETHER_TYPE_QINQ) { + if (rte_pktmbuf_data_len(m) < + offset + sizeof(struct rte_vlan_hdr)) + return NULL; + vlan = rte_pktmbuf_mtod_offset(m, + const struct rte_vlan_hdr *, offset); + ether_type = rte_be_to_cpu_16(vlan->eth_proto); + offset += sizeof(struct rte_vlan_hdr); + } + } + + /* L2 PTP: EtherType 0x88F7 */ + if (ether_type == RTE_ETHER_TYPE_1588) { + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ptp_hdr)) + return NULL; + return rte_pktmbuf_mtod_offset(m, + struct rte_ptp_hdr *, offset); + } + + /* PTP over UDP/IPv4 */ + if (ether_type == RTE_ETHER_TYPE_IPV4) { + const struct rte_ipv4_hdr *iph; + uint16_t ihl; + + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ipv4_hdr)) + return NULL; + + iph = rte_pktmbuf_mtod_offset(m, + const struct rte_ipv4_hdr *, offset); + if (iph->next_proto_id != IPPROTO_UDP) + return NULL; + + ihl = (iph->version_ihl & 0x0F) * 4; + if (ihl < 20) + return NULL; + offset += ihl; + + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_udp_hdr)) + return NULL; + + const struct rte_udp_hdr *udp = + rte_pktmbuf_mtod_offset(m, + const struct rte_udp_hdr *, offset); + uint16_t dst_port = rte_be_to_cpu_16(udp->dst_port); + + if (dst_port != RTE_IPPORT_PTP_EVENT && + dst_port != RTE_IPPORT_PTP_GENERAL) + return NULL; + + offset += sizeof(struct rte_udp_hdr); + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ptp_hdr)) + return NULL; + + return rte_pktmbuf_mtod_offset(m, + struct rte_ptp_hdr *, offset); + } + + /* PTP over UDP/IPv6 */ + if (ether_type == RTE_ETHER_TYPE_IPV6) { + const struct rte_ipv6_hdr *ip6h; + + if (rte_pktmbuf_data_len(m) < + offset + sizeof(struct rte_ipv6_hdr)) + return NULL; + + ip6h = rte_pktmbuf_mtod_offset(m, + const struct rte_ipv6_hdr *, offset); + if (ip6h->proto != IPPROTO_UDP) + return NULL; + + offset += sizeof(struct rte_ipv6_hdr); + + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_udp_hdr)) + return NULL; + + const struct rte_udp_hdr *udp = + rte_pktmbuf_mtod_offset(m, + const struct rte_udp_hdr *, offset); + uint16_t dst_port = rte_be_to_cpu_16(udp->dst_port); + + if (dst_port != RTE_IPPORT_PTP_EVENT && + dst_port != RTE_IPPORT_PTP_GENERAL) + return NULL; + + offset += sizeof(struct rte_udp_hdr); + if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ptp_hdr)) + return NULL; + + return rte_pktmbuf_mtod_offset(m, + struct rte_ptp_hdr *, offset); + } + + return NULL; +} + +/** + * Classify a packet as PTP and return the message type. + * + * @param m + * Pointer to the mbuf to classify. + * @return + * PTP message type (0x0-0xF) on success, PTP_MSGTYPE_INVALID (-1) + * if the packet is not PTP. + */ +static inline int +ptp_classify(const struct rte_mbuf *m) +{ + struct rte_ptp_hdr *hdr = ptp_hdr_find(m); + + if (hdr == NULL) + return PTP_MSGTYPE_INVALID; + + return hdr->msg_type; +} + +/** PTP message type name table. */ +static const char * const ptp_msg_names[] = { + [RTE_PTP_MSGTYPE_SYNC] = "Sync", + [RTE_PTP_MSGTYPE_DELAY_REQ] = "Delay_Req", + [RTE_PTP_MSGTYPE_PDELAY_REQ] = "PDelay_Req", + [RTE_PTP_MSGTYPE_PDELAY_RESP] = "PDelay_Resp", + [0x4] = "Reserved_4", + [0x5] = "Reserved_5", + [0x6] = "Reserved_6", + [0x7] = "Reserved_7", + [RTE_PTP_MSGTYPE_FU] = "Follow_Up", + [RTE_PTP_MSGTYPE_DELAY_RESP] = "Delay_Resp", + [RTE_PTP_MSGTYPE_PDELAY_RESP_FU] = "PDelay_Resp_Follow_Up", + [RTE_PTP_MSGTYPE_ANNOUNCE] = "Announce", + [RTE_PTP_MSGTYPE_SIGNALING] = "Signaling", + [RTE_PTP_MSGTYPE_MANAGEMENT] = "Management", + [0xE] = "Reserved_E", + [0xF] = "Reserved_F", +}; + +/** + * Get a human-readable name for a PTP message type. + * + * @param msg_type + * PTP message type (0x0-0xF or PTP_MSGTYPE_INVALID). + * @return + * Static string with the message type name. + */ +static inline const char * +ptp_msg_type_str(int msg_type) +{ + if (msg_type < 0 || msg_type > 0xF) + return "Not_PTP"; + return ptp_msg_names[msg_type]; +} + +#endif /* _PTP_PARSE_H_ */ diff --git a/examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c b/examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c new file mode 100644 index 0000000000..f9a44812ed --- /dev/null +++ b/examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c @@ -0,0 +1,432 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2026 Intel Corporation + */ + +/* + * PTP Software Relay + * + * A minimal PTP relay between a DPDK-bound physical NIC and a kernel + * TAP interface using software timestamps only. + * + * How it works: + * 1. Physical NIC receives PTP (and non-PTP) packets via DPDK RX. + * 2. For PTP event messages (Sync, Delay_Req, PDelay_Req, PDelay_Resp) + * the relay records an RX software timestamp (clock_gettime). + * 3. Just before TX on the other side it records a TX software timestamp. + * 4. The relay residence time (tx_ts − rx_ts) is added to the PTP + * correctionField via rte_ptp_add_correction() — standard + * Transparent Clock behaviour (IEEE 1588-2019 §10.2). + * 5. Packets are forwarded bi-directionally: + * PHY → TAP (network → ptp4l) + * TAP → PHY (ptp4l → network) + * + * ptp4l runs in software-timestamping mode on the TAP interface: + * + * ptp4l -i dtap0 -m -s -S # -S = software timestamps + * + * Topology: + * + * Time Transmitter (remote) ──L2── Physical NIC (DPDK) + * │ + * PTP SW Relay ← correctionField update + * │ + * TAP (kernel) ── ptp4l -S (time receiver) + * + * Usage: + * dpdk-ptp_tap_relay_sw -l 0-1 --vdev=net_tap0,iface=dtap0 -- \ + * -p 0 -t 1 + * + * Parameters: + * -p PORT Physical NIC port ID (default: 0) + * -t PORT TAP port ID (default: 1) + * -T SECS Stats print interval in seconds (default: 10) + */ + +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <stdbool.h> +#include <signal.h> +#include <getopt.h> +#include <time.h> + +#include <rte_eal.h> +#include <rte_ethdev.h> +#include <rte_mbuf.h> +#include <rte_cycles.h> +#include <rte_lcore.h> + +#include "ptp_parse.h" + +/* Ring sizes */ +#define RX_RING_SIZE 1024 +#define TX_RING_SIZE 1024 + +/* Mempool */ +#define NUM_MBUFS 8191 +#define MBUF_CACHE 250 +#define BURST_SIZE 32 + +#define NSEC_PER_SEC 1000000000ULL + +/* Logging helpers */ +#define LOG_INFO(fmt, ...) \ + fprintf(stdout, "[PTP-SW] " fmt "\n", ##__VA_ARGS__) +#define LOG_ERR(fmt, ...) \ + fprintf(stderr, "[PTP-SW ERROR] " fmt "\n", ##__VA_ARGS__) + +static volatile bool force_quit; + +/* Port IDs */ +static uint16_t phy_port; +static uint16_t tap_port = 1; +static unsigned int stats_interval = 10; /* seconds */ + +/* Statistics */ +static struct { + uint64_t phy_rx; /* total packets from PHY */ + uint64_t phy_rx_ptp; /* PTP packets from PHY */ + uint64_t tap_tx; /* packets forwarded to TAP */ + uint64_t tap_rx; /* total packets from TAP */ + uint64_t tap_rx_ptp; /* PTP packets from TAP */ + uint64_t phy_tx; /* packets forwarded to PHY */ + uint64_t corrections; /* correctionField updates */ +} stats; + +static void +signal_handler(int signum) +{ + if (signum == SIGINT || signum == SIGTERM) { + LOG_INFO("Signal %d received, shutting down...", signum); + force_quit = true; + } +} + +/* Helpers */ + +/* Read monotonic clock in nanoseconds (for residence time). */ +static inline uint64_t +sw_timestamp_ns(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * NSEC_PER_SEC + (uint64_t)ts.tv_nsec; +} + +/* Port Init */ + +static int +port_init(uint16_t port, struct rte_mempool *mp) +{ + struct rte_eth_conf port_conf; + struct rte_eth_dev_info dev_info; + uint16_t nb_rxd = RX_RING_SIZE; + uint16_t nb_txd = TX_RING_SIZE; + int ret; + + memset(&port_conf, 0, sizeof(port_conf)); + + ret = rte_eth_dev_info_get(port, &dev_info); + if (ret != 0) { + LOG_ERR("rte_eth_dev_info_get(port %u) failed: %d", port, ret); + return ret; + } + + if (dev_info.tx_offload_capa & RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE) + port_conf.txmode.offloads |= + RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE; + + ret = rte_eth_dev_configure(port, 1, 1, &port_conf); + if (ret != 0) + return ret; + + ret = rte_eth_dev_adjust_nb_rx_tx_desc(port, &nb_rxd, &nb_txd); + if (ret != 0) + return ret; + + ret = rte_eth_rx_queue_setup(port, 0, nb_rxd, + rte_eth_dev_socket_id(port), NULL, mp); + if (ret < 0) + return ret; + + ret = rte_eth_tx_queue_setup(port, 0, nb_txd, + rte_eth_dev_socket_id(port), NULL); + if (ret < 0) + return ret; + + ret = rte_eth_dev_start(port); + if (ret < 0) + return ret; + + ret = rte_eth_promiscuous_enable(port); + if (ret != 0) { + LOG_ERR("Failed to enable promiscuous on port %u: %s", + port, rte_strerror(-ret)); + return ret; + } + + return 0; +} + +/* Relay one direction */ + +/* + * Forward packets from src_port to dst_port. + * For PTP event messages, record SW timestamps around the + * relay path and add the residence time to the correctionField. + * + * This implements a Transparent Clock (IEEE 1588-2019 §10.2): + * correctionField += (t_egress − t_ingress) + * + * Note: a single rx_ts / tx_ts pair is used for the entire burst. + * At typical PTP rates (logSyncInterval >= -4, i.e. <= 16 pkt/s) + * bursts contain at most one packet, so this is exact. At higher + * rates, early packets in a burst are slightly under-corrected and + * late ones over-corrected by up to one poll-loop iteration. + */ +static void +relay_burst(uint16_t src_port, uint16_t dst_port, + uint64_t *rx_cnt, uint64_t *rx_ptp_cnt, + uint64_t *tx_cnt, uint64_t *corr_cnt) +{ + struct rte_mbuf *bufs[BURST_SIZE]; + struct rte_ptp_hdr *ptp_hdrs[BURST_SIZE]; + uint64_t rx_ts; + uint16_t nb_rx, nb_tx, i; + + nb_rx = rte_eth_rx_burst(src_port, 0, bufs, BURST_SIZE); + if (nb_rx == 0) + return; + + /* Record a single RX software timestamp for the whole burst. + * All packets in one burst arrived at essentially the same instant + * from rte_eth_rx_burst()'s perspective. + */ + rx_ts = sw_timestamp_ns(); + + *rx_cnt += nb_rx; + + /* + * Pass 1: Parse each packet once and remember PTP event headers. + * This avoids taking the TX timestamp too early — we want it as + * close to the actual rte_eth_tx_burst() call as possible. + */ + memset(ptp_hdrs, 0, sizeof(ptp_hdrs[0]) * nb_rx); + for (i = 0; i < nb_rx; i++) { + struct rte_ptp_hdr *hdr = ptp_hdr_find(bufs[i]); + + if (hdr == NULL) + continue; + + (*rx_ptp_cnt)++; + + /* Only event messages carry timestamps that need correction */ + if (!rte_ptp_is_event(hdr)) + continue; + + ptp_hdrs[i] = hdr; + } + + /* + * Pass 2: Take a single TX timestamp right before transmission. + * This minimises the gap between the measured tx_ts and the + * actual kernel write inside rte_eth_tx_burst(), giving the + * most accurate residence time we can achieve with SW timestamps. + * + * residence_time = tx_ts − rx_ts + * + * Remaining untracked delays: + * - Pre-RX: NIC DMA → rx_burst return (~1-5 µs, unavoidable) + * - Post-TX: tx_ts → kernel TAP write (~1-2 µs) + * Both are symmetric for Sync and Delay_Req so they largely + * cancel in the ptp4l offset calculation. + */ + uint64_t tx_ts = sw_timestamp_ns(); + int64_t residence_ns = (int64_t)(tx_ts - rx_ts); + + for (i = 0; i < nb_rx; i++) { + if (ptp_hdrs[i] == NULL) + continue; + rte_ptp_add_correction(ptp_hdrs[i], residence_ns); + (*corr_cnt)++; + } + + /* Forward the burst */ + nb_tx = rte_eth_tx_burst(dst_port, 0, bufs, nb_rx); + *tx_cnt += nb_tx; + + /* Free any unsent packets */ + for (i = nb_tx; i < nb_rx; i++) + rte_pktmbuf_free(bufs[i]); +} + +/* Print statistics */ + +static void +print_stats(void) +{ + LOG_INFO("=== Statistics ==="); + LOG_INFO(" PHY RX total: %"PRIu64, stats.phy_rx); + LOG_INFO(" PHY RX PTP: %"PRIu64, stats.phy_rx_ptp); + LOG_INFO(" TAP TX: %"PRIu64, stats.tap_tx); + LOG_INFO(" TAP RX total: %"PRIu64, stats.tap_rx); + LOG_INFO(" TAP RX PTP: %"PRIu64, stats.tap_rx_ptp); + LOG_INFO(" PHY TX: %"PRIu64, stats.phy_tx); + LOG_INFO(" Corrections: %"PRIu64, stats.corrections); +} + +/* Main relay loop */ + +static int +relay_loop(__rte_unused void *arg) +{ + uint64_t last_stats = rte_rdtsc(); + uint64_t stats_tsc = rte_get_tsc_hz() * stats_interval; + + LOG_INFO("Relay loop started on lcore %u", rte_lcore_id()); + LOG_INFO(" PHY port %u <--> TAP port %u", phy_port, tap_port); + LOG_INFO(" Correction field updates: enabled for event messages"); + + while (!force_quit) { + /* PHY → TAP */ + relay_burst(phy_port, tap_port, + &stats.phy_rx, &stats.phy_rx_ptp, + &stats.tap_tx, &stats.corrections); + + /* TAP → PHY */ + relay_burst(tap_port, phy_port, + &stats.tap_rx, &stats.tap_rx_ptp, + &stats.phy_tx, &stats.corrections); + + /* Periodic stats */ + if (rte_rdtsc() - last_stats > stats_tsc) { + print_stats(); + last_stats = rte_rdtsc(); + } + } + + print_stats(); + return 0; +} + +/* Argument parsing */ + +static void +usage(const char *prog) +{ + fprintf(stderr, + "Usage: %s [EAL options] -- [options]\n" + " -p PORT Physical NIC port ID (default: 0)\n" + " -t PORT TAP port ID (default: 1)\n" + " -T SECS Stats interval in seconds (default: 10)\n" + "\n" + "Example:\n" + " %s -l 0-1 --vdev=net_tap0,iface=dtap0 -- -p 0 -t 1\n" + "\n" + "Then run ptp4l with software timestamps:\n" + " ptp4l -i dtap0 -m -s -S\n", + prog, prog); +} + +static int +parse_args(int argc, char **argv) +{ + int opt; + + while ((opt = getopt(argc, argv, "p:t:T:h")) != -1) { + switch (opt) { + case 'p': + phy_port = (uint16_t)atoi(optarg); + break; + case 't': + tap_port = (uint16_t)atoi(optarg); + break; + case 'T': + stats_interval = (unsigned int)atoi(optarg); + break; + case 'h': + default: + usage(argv[0]); + return -1; + } + } + + return 0; +} + +/* Main */ + +int +main(int argc, char **argv) +{ + struct rte_mempool *mp; + uint16_t nb_ports; + int ret; + + /* EAL init */ + ret = rte_eal_init(argc, argv); + if (ret < 0) + rte_exit(EXIT_FAILURE, "EAL init failed\n"); + argc -= ret; + argv += ret; + + /* App args */ + ret = parse_args(argc, argv); + if (ret < 0) + rte_exit(EXIT_FAILURE, "Invalid arguments\n"); + + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + + nb_ports = rte_eth_dev_count_avail(); + if (nb_ports < 2) + rte_exit(EXIT_FAILURE, + "Need at least 2 ports (PHY + TAP).\n" + "Use --vdev=net_tap0,iface=dtap0\n"); + + if (!rte_eth_dev_is_valid_port(phy_port)) + rte_exit(EXIT_FAILURE, "Invalid PHY port %u\n", phy_port); + if (!rte_eth_dev_is_valid_port(tap_port)) + rte_exit(EXIT_FAILURE, "Invalid TAP port %u\n", tap_port); + + mp = rte_pktmbuf_pool_create("MBUF_POOL", NUM_MBUFS * nb_ports, + MBUF_CACHE, 0, + RTE_MBUF_DEFAULT_BUF_SIZE, + rte_socket_id()); + if (mp == NULL) + rte_exit(EXIT_FAILURE, "Cannot create mbuf pool\n"); + + LOG_INFO("Initializing PHY port %u...", phy_port); + ret = port_init(phy_port, mp); + if (ret != 0) + rte_exit(EXIT_FAILURE, "Cannot init PHY port %u (%d)\n", + phy_port, ret); + + LOG_INFO("Initializing TAP port %u...", tap_port); + ret = port_init(tap_port, mp); + if (ret != 0) + rte_exit(EXIT_FAILURE, "Cannot init TAP port %u (%d)\n", + tap_port, ret); + + LOG_INFO("PTP Software Relay ready"); + LOG_INFO(" PHY port: %u", phy_port); + LOG_INFO(" TAP port: %u", tap_port); + LOG_INFO(" Stats every: %u seconds", stats_interval); + LOG_INFO(" Correction: Transparent Clock (SW timestamps)"); + LOG_INFO(""); + LOG_INFO("Run ptp4l: ptp4l -i dtap0 -m -s -S"); + + /* Run relay on main lcore */ + relay_loop(NULL); + + /* Cleanup */ + LOG_INFO("Stopping ports..."); + rte_eth_dev_stop(phy_port); + rte_eth_dev_stop(tap_port); + rte_eth_dev_close(phy_port); + rte_eth_dev_close(tap_port); + rte_eal_cleanup(); + + return 0; +} -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [PATCH v8 3/4] doc: update release notes for PTP protocol library 2026-05-09 23:25 ` [PATCH v8 0/4] IEEE 1588 PTP v2 protocol support in lib/net Rajesh Kumar 2026-05-09 23:25 ` [PATCH v8 1/4] lib/net: add IEEE 1588 PTP v2 protocol header definitions Rajesh Kumar 2026-05-09 23:25 ` [PATCH v8 2/4] examples/ptp_tap_relay_sw: add PTP software transparent clock relay Rajesh Kumar @ 2026-05-09 23:25 ` Rajesh Kumar 2026-05-09 23:25 ` [PATCH v8 4/4] examples/ptpclient: use shared PTP library definitions Rajesh Kumar 3 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-09 23:25 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, mb, Rajesh Kumar Update release notes with IEEE 1588 PTP additions: - PTP protocol definitions in lib/net/rte_ptp.h (header-only library with inline helpers and wire-format structures) - PTP software relay example application (ptp_tap_relay_sw) Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- doc/guides/rel_notes/release_26_07.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/doc/guides/rel_notes/release_26_07.rst b/doc/guides/rel_notes/release_26_07.rst index f012d47a4b..c64e6e141b 100644 --- a/doc/guides/rel_notes/release_26_07.rst +++ b/doc/guides/rel_notes/release_26_07.rst @@ -63,6 +63,18 @@ New Features ``rte_eal_init`` and the application is responsible for probing each device, * ``--auto-probing`` enables the initial bus probing, which is the current default behavior. +* **Added PTP protocol definitions (rte_ptp.h).** + + Added IEEE 1588 Precision Time Protocol header structures, constants, + and inline helpers to ``lib/net/rte_ptp.h``. Provides wire-format + structures with endian-annotated types and correctionField manipulation + for Transparent Clock implementations. + +* **Added PTP software relay example application.** + + Added a new example application ``ptp_tap_relay_sw`` demonstrating a + software PTP Transparent Clock relay between a DPDK port and a kernel + TAP interface. Removed Items ------------- -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
* [PATCH v8 4/4] examples/ptpclient: use shared PTP library definitions 2026-05-09 23:25 ` [PATCH v8 0/4] IEEE 1588 PTP v2 protocol support in lib/net Rajesh Kumar ` (2 preceding siblings ...) 2026-05-09 23:25 ` [PATCH v8 3/4] doc: update release notes for PTP protocol library Rajesh Kumar @ 2026-05-09 23:25 ` Rajesh Kumar 3 siblings, 0 replies; 60+ messages in thread From: Rajesh Kumar @ 2026-05-09 23:25 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, mb, Rajesh Kumar Update ptpclient to use the lib/net/rte_ptp.h library instead of duplicated local PTP header structures and message type constants. Changes: - Add #include <rte_ptp.h> - Replace local struct ptp_header with struct rte_ptp_hdr - Replace local struct tstamp with struct rte_ptp_timestamp - Replace local struct clock_id with uint8_t[8] arrays - Use RTE_PTP_MSGTYPE_* constants instead of local message types - Use hdr->msg_type bitfield and hdr->sequence_id direct access - Remove local PTP_PROTOCOL definition (use RTE_ETHER_TYPE_1588) Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com> --- examples/ptpclient/meson.build | 1 + examples/ptpclient/ptpclient.c | 204 +++++++++++++-------------------- 2 files changed, 81 insertions(+), 124 deletions(-) diff --git a/examples/ptpclient/meson.build b/examples/ptpclient/meson.build index 2e9b7625fc..9087c987d5 100644 --- a/examples/ptpclient/meson.build +++ b/examples/ptpclient/meson.build @@ -7,6 +7,7 @@ # DPDK instance, use 'make' allow_experimental_apis = true +deps += ['net'] sources = files( 'ptpclient.c', ) diff --git a/examples/ptpclient/ptpclient.c b/examples/ptpclient/ptpclient.c index 174ca5dd70..ee12623651 100644 --- a/examples/ptpclient/ptpclient.c +++ b/examples/ptpclient/ptpclient.c @@ -17,6 +17,7 @@ #include <rte_lcore.h> #include <rte_mbuf.h> #include <rte_ip.h> +#include <rte_ptp.h> #include <limits.h> #include <sys/time.h> #include <getopt.h> @@ -30,21 +31,8 @@ static volatile bool force_quit; #define NUM_MBUFS 8191 #define MBUF_CACHE_SIZE 250 -/* Values for the PTP messageType field. */ -#define SYNC 0x0 -#define DELAY_REQ 0x1 -#define PDELAY_REQ 0x2 -#define PDELAY_RESP 0x3 -#define FOLLOW_UP 0x8 -#define DELAY_RESP 0x9 -#define PDELAY_RESP_FOLLOW_UP 0xA -#define ANNOUNCE 0xB -#define SIGNALING 0xC -#define MANAGEMENT 0xD - #define NSEC_PER_SEC 1000000000L #define KERNEL_TIME_ADJUST_LIMIT 20000 -#define PTP_PROTOCOL 0x88F7 struct rte_mempool *mbuf_pool; uint32_t ptp_enabled_port_mask; @@ -55,69 +43,39 @@ static const struct rte_ether_addr ether_multicast = { .addr_bytes = {0x01, 0x1b, 0x19, 0x0, 0x0, 0x0} }; -/* Structs used for PTP handling. */ -struct __rte_packed_begin tstamp { - uint16_t sec_msb; - uint32_t sec_lsb; - uint32_t ns; -} __rte_packed_end; - -struct clock_id { - uint8_t id[8]; -}; - -struct __rte_packed_begin port_id { - struct clock_id clock_id; - uint16_t port_number; -} __rte_packed_end; - -struct __rte_packed_begin ptp_header { - uint8_t msg_type; - uint8_t ver; - uint16_t message_length; - uint8_t domain_number; - uint8_t reserved1; - uint8_t flag_field[2]; - int64_t correction; - uint32_t reserved2; - struct port_id source_port_id; - uint16_t seq_id; - uint8_t control; - int8_t log_message_interval; -} __rte_packed_end; - +/* Structs used for PTP handling - using library definitions from rte_ptp.h */ struct __rte_packed_begin sync_msg { - struct ptp_header hdr; - struct tstamp origin_tstamp; + struct rte_ptp_hdr hdr; + struct rte_ptp_timestamp origin_tstamp; } __rte_packed_end; struct __rte_packed_begin follow_up_msg { - struct ptp_header hdr; - struct tstamp precise_origin_tstamp; + struct rte_ptp_hdr hdr; + struct rte_ptp_timestamp precise_origin_tstamp; uint8_t suffix[]; } __rte_packed_end; struct __rte_packed_begin delay_req_msg { - struct ptp_header hdr; - struct tstamp origin_tstamp; + struct rte_ptp_hdr hdr; + struct rte_ptp_timestamp origin_tstamp; } __rte_packed_end; struct __rte_packed_begin delay_resp_msg { - struct ptp_header hdr; - struct tstamp rx_tstamp; - struct port_id req_port_id; - uint8_t suffix[]; + struct rte_ptp_hdr hdr; + struct rte_ptp_timestamp rx_tstamp; + struct rte_ptp_port_id req_port_id; + uint8_t suffix[]; } __rte_packed_end; -struct ptp_message { +struct __rte_packed_begin ptp_message { union __rte_packed_begin { - struct ptp_header header; - struct sync_msg sync; - struct delay_req_msg delay_req; - struct follow_up_msg follow_up; - struct delay_resp_msg delay_resp; + struct rte_ptp_hdr header; + struct sync_msg sync; + struct delay_req_msg delay_req; + struct follow_up_msg follow_up; + struct delay_resp_msg delay_resp; } __rte_packed_end; -}; +} __rte_packed_end; struct ptpv2_time_receiver_ordinary { struct rte_mbuf *m; @@ -125,8 +83,8 @@ struct ptpv2_time_receiver_ordinary { struct timespec tstamp2; struct timespec tstamp3; struct timespec tstamp4; - struct clock_id client_clock_id; - struct clock_id transmitter_clock_id; + uint8_t client_clock_id[8]; + uint8_t transmitter_clock_id[8]; struct timeval new_adj; int64_t delta; uint16_t portid; @@ -272,14 +230,14 @@ print_clock_info(struct ptpv2_time_receiver_ordinary *ptp_data) struct timespec net_time, sys_time; printf("time transmitter clock id: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", - ptp_data->transmitter_clock_id.id[0], - ptp_data->transmitter_clock_id.id[1], - ptp_data->transmitter_clock_id.id[2], - ptp_data->transmitter_clock_id.id[3], - ptp_data->transmitter_clock_id.id[4], - ptp_data->transmitter_clock_id.id[5], - ptp_data->transmitter_clock_id.id[6], - ptp_data->transmitter_clock_id.id[7]); + ptp_data->transmitter_clock_id[0], + ptp_data->transmitter_clock_id[1], + ptp_data->transmitter_clock_id[2], + ptp_data->transmitter_clock_id[3], + ptp_data->transmitter_clock_id[4], + ptp_data->transmitter_clock_id[5], + ptp_data->transmitter_clock_id[6], + ptp_data->transmitter_clock_id[7]); printf("\nT2 - time receiver clock. %lds %ldns", (ptp_data->tstamp2.tv_sec), @@ -356,20 +314,21 @@ delta_eval(struct ptpv2_time_receiver_ordinary *ptp_data) static void parse_sync(struct ptpv2_time_receiver_ordinary *ptp_data, uint16_t rx_tstamp_idx) { - struct ptp_header *ptp_hdr; + struct rte_ptp_hdr *ptp_hdr; - ptp_hdr = rte_pktmbuf_mtod_offset(ptp_data->m, struct ptp_header *, + ptp_hdr = rte_pktmbuf_mtod_offset(ptp_data->m, struct rte_ptp_hdr *, sizeof(struct rte_ether_hdr)); - ptp_data->seqID_SYNC = rte_be_to_cpu_16(ptp_hdr->seq_id); + ptp_data->seqID_SYNC = rte_be_to_cpu_16(ptp_hdr->sequence_id); if (ptp_data->ptpset == 0) { - ptp_data->transmitter_clock_id = ptp_hdr->source_port_id.clock_id; + memcpy(ptp_data->transmitter_clock_id, + ptp_hdr->source_port_id.clock_id, 8); ptp_data->ptpset = 1; } - if (memcmp(&ptp_data->transmitter_clock_id, - &ptp_hdr->source_port_id.clock_id, - sizeof(struct clock_id)) == 0) { + if (memcmp(ptp_data->transmitter_clock_id, + ptp_hdr->source_port_id.clock_id, + 8) == 0) { if (ptp_data->ptpset == 1) rte_eth_timesync_read_rx_timestamp(ptp_data->portid, @@ -386,12 +345,11 @@ parse_fup(struct ptpv2_time_receiver_ordinary *ptp_data) { struct rte_ether_hdr *eth_hdr; struct rte_ether_addr eth_addr; - struct ptp_header *ptp_hdr; - struct clock_id *client_clkid; + struct rte_ptp_hdr *ptp_hdr; struct ptp_message *ptp_msg; struct delay_req_msg *req_msg; struct rte_mbuf *created_pkt; - struct tstamp *origin_tstamp; + struct rte_ptp_timestamp *origin_tstamp; struct rte_ether_addr eth_multicast = ether_multicast; size_t pkt_size; int wait_us; @@ -399,22 +357,22 @@ parse_fup(struct ptpv2_time_receiver_ordinary *ptp_data) int ret; eth_hdr = rte_pktmbuf_mtod(m, struct rte_ether_hdr *); - ptp_hdr = rte_pktmbuf_mtod_offset(m, struct ptp_header *, + ptp_hdr = rte_pktmbuf_mtod_offset(m, struct rte_ptp_hdr *, sizeof(struct rte_ether_hdr)); - if (memcmp(&ptp_data->transmitter_clock_id, - &ptp_hdr->source_port_id.clock_id, - sizeof(struct clock_id)) != 0) + if (memcmp(ptp_data->transmitter_clock_id, + ptp_hdr->source_port_id.clock_id, + 8) != 0) return; - ptp_data->seqID_FOLLOWUP = rte_be_to_cpu_16(ptp_hdr->seq_id); + ptp_data->seqID_FOLLOWUP = rte_be_to_cpu_16(ptp_hdr->sequence_id); ptp_msg = rte_pktmbuf_mtod_offset(m, struct ptp_message *, sizeof(struct rte_ether_hdr)); origin_tstamp = &ptp_msg->follow_up.precise_origin_tstamp; - ptp_data->tstamp1.tv_nsec = ntohl(origin_tstamp->ns); + ptp_data->tstamp1.tv_nsec = rte_be_to_cpu_32(origin_tstamp->nanoseconds); ptp_data->tstamp1.tv_sec = - ((uint64_t)ntohl(origin_tstamp->sec_lsb)) | - (((uint64_t)ntohs(origin_tstamp->sec_msb)) << 32); + ((uint64_t)rte_be_to_cpu_32(origin_tstamp->seconds_lo)) | + (((uint64_t)rte_be_to_cpu_16(origin_tstamp->seconds_hi)) << 32); if (ptp_data->seqID_FOLLOWUP == ptp_data->seqID_SYNC) { ret = rte_eth_macaddr_get(ptp_data->portid, ð_addr); @@ -441,34 +399,30 @@ parse_fup(struct ptpv2_time_receiver_ordinary *ptp_data) /* Set multicast address 01-1B-19-00-00-00. */ rte_ether_addr_copy(ð_multicast, ð_hdr->dst_addr); - eth_hdr->ether_type = htons(PTP_PROTOCOL); + eth_hdr->ether_type = htons(RTE_ETHER_TYPE_1588); req_msg = rte_pktmbuf_mtod_offset(created_pkt, struct delay_req_msg *, sizeof(struct rte_ether_hdr)); - req_msg->hdr.seq_id = htons(ptp_data->seqID_SYNC); - req_msg->hdr.msg_type = DELAY_REQ; - req_msg->hdr.ver = 2; - req_msg->hdr.control = 1; - req_msg->hdr.log_message_interval = 127; - req_msg->hdr.message_length = + req_msg->hdr.sequence_id = htons(ptp_data->seqID_SYNC); + req_msg->hdr.msg_type = RTE_PTP_MSGTYPE_DELAY_REQ; + req_msg->hdr.version = 2; + req_msg->hdr.msg_length = htons(sizeof(struct delay_req_msg)); req_msg->hdr.domain_number = ptp_hdr->domain_number; /* Set up clock id. */ - client_clkid = - &req_msg->hdr.source_port_id.clock_id; - - client_clkid->id[0] = eth_hdr->src_addr.addr_bytes[0]; - client_clkid->id[1] = eth_hdr->src_addr.addr_bytes[1]; - client_clkid->id[2] = eth_hdr->src_addr.addr_bytes[2]; - client_clkid->id[3] = 0xFF; - client_clkid->id[4] = 0xFE; - client_clkid->id[5] = eth_hdr->src_addr.addr_bytes[3]; - client_clkid->id[6] = eth_hdr->src_addr.addr_bytes[4]; - client_clkid->id[7] = eth_hdr->src_addr.addr_bytes[5]; - - ptp_data->client_clock_id = *client_clkid; + ptp_data->client_clock_id[0] = eth_hdr->src_addr.addr_bytes[0]; + ptp_data->client_clock_id[1] = eth_hdr->src_addr.addr_bytes[1]; + ptp_data->client_clock_id[2] = eth_hdr->src_addr.addr_bytes[2]; + ptp_data->client_clock_id[3] = 0xFF; + ptp_data->client_clock_id[4] = 0xFE; + ptp_data->client_clock_id[5] = eth_hdr->src_addr.addr_bytes[3]; + ptp_data->client_clock_id[6] = eth_hdr->src_addr.addr_bytes[4]; + ptp_data->client_clock_id[7] = eth_hdr->src_addr.addr_bytes[5]; + + memcpy(req_msg->hdr.source_port_id.clock_id, + ptp_data->client_clock_id, 8); /* Enable flag for hardware timestamping. */ created_pkt->ol_flags |= RTE_MBUF_F_TX_IEEE1588_TMST; @@ -534,21 +488,21 @@ parse_drsp(struct ptpv2_time_receiver_ordinary *ptp_data) { struct rte_mbuf *m = ptp_data->m; struct ptp_message *ptp_msg; - struct tstamp *rx_tstamp; + struct rte_ptp_timestamp *rx_tstamp; uint16_t seq_id; ptp_msg = rte_pktmbuf_mtod_offset(m, struct ptp_message *, sizeof(struct rte_ether_hdr)); - seq_id = rte_be_to_cpu_16(ptp_msg->delay_resp.hdr.seq_id); - if (memcmp(&ptp_data->client_clock_id, - &ptp_msg->delay_resp.req_port_id.clock_id, - sizeof(struct clock_id)) == 0) { + seq_id = rte_be_to_cpu_16(ptp_msg->delay_resp.hdr.sequence_id); + if (memcmp(ptp_data->client_clock_id, + ptp_msg->delay_resp.req_port_id.clock_id, + 8) == 0) { if (seq_id == ptp_data->seqID_FOLLOWUP) { rx_tstamp = &ptp_msg->delay_resp.rx_tstamp; - ptp_data->tstamp4.tv_nsec = ntohl(rx_tstamp->ns); + ptp_data->tstamp4.tv_nsec = rte_be_to_cpu_32(rx_tstamp->nanoseconds); ptp_data->tstamp4.tv_sec = - ((uint64_t)ntohl(rx_tstamp->sec_lsb)) | - (((uint64_t)ntohs(rx_tstamp->sec_msb)) << 32); + ((uint64_t)rte_be_to_cpu_32(rx_tstamp->seconds_lo)) | + (((uint64_t)rte_be_to_cpu_16(rx_tstamp->seconds_hi)) << 32); /* Evaluate the delta for adjustment. */ ptp_data->delta = delta_eval(ptp_data); @@ -575,27 +529,29 @@ parse_drsp(struct ptpv2_time_receiver_ordinary *ptp_data) /* Parse ptp frames. 8< */ static void parse_ptp_frames(uint16_t portid, struct rte_mbuf *m) { - struct ptp_header *ptp_hdr; + struct rte_ptp_hdr *ptp_hdr; struct rte_ether_hdr *eth_hdr; uint16_t eth_type; + uint8_t msg_type; eth_hdr = rte_pktmbuf_mtod(m, struct rte_ether_hdr *); eth_type = rte_be_to_cpu_16(eth_hdr->ether_type); - if (eth_type == PTP_PROTOCOL) { + if (eth_type == RTE_ETHER_TYPE_1588) { ptp_data.m = m; ptp_data.portid = portid; - ptp_hdr = rte_pktmbuf_mtod_offset(m, struct ptp_header *, + ptp_hdr = rte_pktmbuf_mtod_offset(m, struct rte_ptp_hdr *, sizeof(struct rte_ether_hdr)); - switch (ptp_hdr->msg_type) { - case SYNC: + msg_type = ptp_hdr->msg_type; + switch (msg_type) { + case RTE_PTP_MSGTYPE_SYNC: parse_sync(&ptp_data, m->timesync); break; - case FOLLOW_UP: + case RTE_PTP_MSGTYPE_FU: parse_fup(&ptp_data); break; - case DELAY_RESP: + case RTE_PTP_MSGTYPE_DELAY_RESP: parse_drsp(&ptp_data); print_clock_info(&ptp_data); break; -- 2.53.0 ^ permalink raw reply related [flat|nested] 60+ messages in thread
end of thread, other threads:[~2026-05-09 18:00 UTC | newest] Thread overview: 60+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-04-28 1:01 [RFC v1 0/4] introduce PTP protocol library and software relay example Rajesh Kumar 2026-04-27 23:42 ` Stephen Hemminger 2026-04-28 16:52 ` Kumar, Rajesh 2026-04-28 1:01 ` [RFC v1 1/4] ptp: introduce PTP protocol library Rajesh Kumar 2026-04-27 23:01 ` Stephen Hemminger 2026-04-28 16:50 ` Kumar, Rajesh 2026-04-28 1:01 ` [RFC v1 2/4] doc: add PTP library programmer's guide Rajesh Kumar 2026-04-28 1:01 ` [RFC v1 3/4] examples/ptp_tap_relay_sw: add software PTP relay example Rajesh Kumar 2026-04-28 1:01 ` [RFC v1 4/4] doc: add PTP software relay sample app guide Rajesh Kumar 2026-04-28 22:28 ` [RFC v2 0/6] introduce PTP protocol library and software relay Rajesh Kumar 2026-04-28 22:28 ` [RFC v2 1/6] ptp: introduce PTP protocol library Rajesh Kumar 2026-04-28 22:28 ` [RFC v2 2/6] doc: add PTP library programmer's guide Rajesh Kumar 2026-04-28 22:28 ` [RFC v2 3/6] examples/ptp_tap_relay_sw: add software PTP relay example Rajesh Kumar 2026-04-28 22:28 ` [RFC v2 4/6] doc: add PTP software relay sample app guide Rajesh Kumar 2026-04-28 22:28 ` [RFC v2 5/6] app/test: add PTP library unit tests Rajesh Kumar 2026-04-28 22:28 ` [RFC v2 6/6] examples/ptpclient: use shared PTP library definitions Rajesh Kumar 2026-04-29 15:37 ` [RFC v2 0/6] introduce PTP protocol library and software relay Stephen Hemminger 2026-05-04 3:49 ` Kumar, Rajesh3 2026-05-04 9:17 ` [RFC v3 " Rajesh Kumar 2026-05-04 9:17 ` [RFC v3 1/6] ptp: introduce PTP protocol library Rajesh Kumar 2026-05-04 9:17 ` [RFC v3 2/6] doc: add PTP library programmer's guide Rajesh Kumar 2026-05-04 9:17 ` [RFC v3 3/6] examples/ptp_tap_relay_sw: add software PTP relay example Rajesh Kumar 2026-05-04 9:17 ` [RFC v3 4/6] doc: add PTP software relay sample app guide Rajesh Kumar 2026-05-04 9:17 ` [RFC v3 5/6] app/test: add PTP library unit tests Rajesh Kumar 2026-05-04 9:17 ` [RFC v3 6/6] examples/ptpclient: use shared PTP library definitions Rajesh Kumar 2026-05-04 17:56 ` [RFC v3 0/6] introduce PTP protocol library and software relay Stephen Hemminger 2026-05-05 16:38 ` [RFC v4 " Rajesh Kumar 2026-05-05 16:38 ` [RFC v4 1/6] ptp: introduce PTP protocol library Rajesh Kumar 2026-05-05 16:38 ` [RFC v4 2/6] doc: add PTP library programmer's guide Rajesh Kumar 2026-05-05 16:38 ` [RFC v4 3/6] examples/ptp_tap_relay_sw: add software PTP relay example Rajesh Kumar 2026-05-05 16:38 ` [RFC v4 4/6] doc: add PTP software relay sample app guide Rajesh Kumar 2026-05-05 16:38 ` [RFC v4 5/6] app/test: add PTP library unit tests Rajesh Kumar 2026-05-05 16:38 ` [RFC v4 6/6] examples/ptpclient: use shared PTP library definitions Rajesh Kumar 2026-05-06 15:41 ` [RFC v5 0/6] introduce PTP protocol library and software relay Rajesh Kumar 2026-05-06 15:41 ` [RFC v5 1/6] ptp: introduce PTP protocol library Rajesh Kumar 2026-05-06 11:02 ` Morten Brørup 2026-05-07 4:45 ` Kumar, Rajesh 2026-05-06 15:41 ` [RFC v5 2/6] doc: add PTP library programmer's guide Rajesh Kumar 2026-05-06 15:41 ` [RFC v5 3/6] examples/ptp_tap_relay_sw: add software PTP relay example Rajesh Kumar 2026-05-06 15:41 ` [RFC v5 4/6] doc: add PTP software relay sample app guide Rajesh Kumar 2026-05-06 15:41 ` [RFC v5 5/6] app/test: add PTP library unit tests Rajesh Kumar 2026-05-06 15:41 ` [RFC v5 6/6] examples/ptpclient: use shared PTP library definitions Rajesh Kumar 2026-05-06 15:45 ` [RFC v5 0/6] introduce PTP protocol library and software relay Stephen Hemminger 2026-05-07 10:13 ` [PATCH v6 0/4] PTP protocol support in lib/net Rajesh Kumar 2026-05-07 10:13 ` [PATCH v6 1/4] lib/net: add IEEE 1588 PTP v2 protocol header definitions Rajesh Kumar 2026-05-07 10:13 ` [PATCH v6 2/4] examples/ptp_tap_relay_sw: add PTP software transparent clock relay Rajesh Kumar 2026-05-07 10:13 ` [PATCH v6 3/4] doc: update release notes for PTP protocol library Rajesh Kumar 2026-05-07 10:13 ` [PATCH v6 4/4] examples/ptpclient: use shared PTP library definitions Rajesh Kumar 2026-05-07 13:45 ` [PATCH v7 0/4] IEEE 1588 PTP v2 protocol support in lib/net Rajesh Kumar 2026-05-07 13:45 ` [PATCH v7 1/4] lib/net: add IEEE 1588 PTP v2 protocol header definitions Rajesh Kumar 2026-05-07 15:27 ` Morten Brørup 2026-05-09 17:57 ` Kumar, Rajesh 2026-05-07 13:45 ` [PATCH v7 2/4] examples/ptp_tap_relay_sw: add PTP software transparent clock relay Rajesh Kumar 2026-05-07 13:45 ` [PATCH v7 3/4] doc: update release notes for PTP protocol library Rajesh Kumar 2026-05-07 13:45 ` [PATCH v7 4/4] examples/ptpclient: use shared PTP library definitions Rajesh Kumar 2026-05-09 23:25 ` [PATCH v8 0/4] IEEE 1588 PTP v2 protocol support in lib/net Rajesh Kumar 2026-05-09 23:25 ` [PATCH v8 1/4] lib/net: add IEEE 1588 PTP v2 protocol header definitions Rajesh Kumar 2026-05-09 23:25 ` [PATCH v8 2/4] examples/ptp_tap_relay_sw: add PTP software transparent clock relay Rajesh Kumar 2026-05-09 23:25 ` [PATCH v8 3/4] doc: update release notes for PTP protocol library Rajesh Kumar 2026-05-09 23:25 ` [PATCH v8 4/4] examples/ptpclient: use shared PTP library definitions Rajesh Kumar
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox