diff --git a/board/aarch64/README.md b/board/aarch64/README.md
index 61dca726b..bd6cf79e5 100644
--- a/board/aarch64/README.md
+++ b/board/aarch64/README.md
@@ -8,6 +8,7 @@ Board Specific Documentation
- [Banana Pi BPi-R4](bananapi-bpi-r4/)
- [Banana Pi BPi-R64](bananapi-bpi-r64/)
- [Marvell CN9130-CRB](marvell-cn9130-crb/)
+- [Marvell ESPRESSObin](marvell-espressobin/)
- [Microchip SparX-5i PCB135 (eMMC)](microchip-sparx5-pcb135/)
- [NanoPi R2S](friendlyarm-nanopi-r2s/)
- [Raspberry Pi 64-bit](raspberrypi-rpi64/)
diff --git a/board/aarch64/bananapi-bpi-r3/README.md b/board/aarch64/bananapi-bpi-r3/README.md
index be92ea284..fd39dcf37 100644
--- a/board/aarch64/bananapi-bpi-r3/README.md
+++ b/board/aarch64/bananapi-bpi-r3/README.md
@@ -70,11 +70,11 @@ The BPI-R3 has a 4-position DIP switch that controls boot media:
-| Position | Mode | Description |
-|----------|-------------|---------------------------------------|
-| 0000 | SD card | Boot from microSD card |
-| 0110 | eMMC | Boot from internal eMMC (recommended) |
-| 1010 | SPI NAND | Boot from SPI NAND (advanced users) |
+| ABCD | Mode | Description |
+|------|----------|---------------------------------------|
+| 0000 | SD card | Boot from microSD card |
+| 0101 | SPI NAND | Boot from SPI NAND (advanced users) |
+| 1001 | eMMC | Boot from internal eMMC (recommended) |
> [!NOTE]
> Switch position is read from left to right: "0" = OFF, "1" = ON.
@@ -150,6 +150,7 @@ From the U-Boot prompt:
usb start
fatload usb 0:1 0x50000000 infix-bpi-r3-emmc.img
setexpr blocks ${filesize} / 0x200
+mmc dev 0
mmc write 0x50000000 0x0 ${blocks}
```
diff --git a/board/aarch64/marvell-espressobin/README.md b/board/aarch64/marvell-espressobin/README.md
new file mode 100644
index 000000000..409c1aeff
--- /dev/null
+++ b/board/aarch64/marvell-espressobin/README.md
@@ -0,0 +1,232 @@
+# Marvell ESPRESSObin
+
+
+
+The [ESPRESSObin][0] is a single-board computer based on the [Marvell Armada
+3720][1] (dual Cortex-A53, AArch64) SoC and the [Marvell 88E6341][2] (Topaz)
+switch, oriented toward networking applications.
+
+The board design is old but the switch offers full Linux support, including
+advanced TSN features for [IEEE 1588-2019][3] (PTP) and [IEEE 802.1AS-2020][4]
+(gPTP).
+
+## Board Variants
+
+The board has gone through several hardware revisions:
+
+| Revision | Storage | Notes |
+|------------|---------------------|--------------------------------|
+| v1, v3, v5 | SPI NOR only | Obsolete; U-Boot always in SPI |
+| v7 | SPI NOR + 4 GB eMMC | Current; SD and eMMC usable |
+| Ultra | SPI NOR + 4 GB eMMC | High-end variant |
+
+On **all revisions** the Boot ROM is hardwired to load U-Boot from SPI NOR
+flash. There is no strap or jumper to make the Boot ROM load directly from an
+SD card. The SD card (or eMMC on v7/Ultra) is used only for the operating
+system.
+
+## Building
+
+The ESPRESSObin uses ext4 for its rootfs partitions rather than the default
+squashfs, because the stock SPI U-Boot lacks squashfs and `blkmap` support.
+The `ext4` configuration snippet enables this. Apply it once after selecting
+the defconfig, then build and compose the SD card image:
+
+```sh
+make O=x-aarch64 aarch64_defconfig
+make O=x-aarch64 apply-ext4
+make O=x-aarch64
+
+utils/mkimage.sh -r x-aarch64 marvell-espressobin
+```
+
+The resulting image (`x-aarch64/images/infix-espressobin-sdcard.img`) contains
+a GPT disk with the standard Infix partition layout, using ext4 instead of the
+read-only squashfs:
+
+| Partition | Label | Contents |
+|-----------|-----------|------------------------------|
+| 1 | aux | RAUC upgrade state (ext4) |
+| 2 | primary | Rootfs slot primary (ext4) |
+| 3 | secondary | Rootfs slot secondary (ext4) |
+| 4 | cfg | Persistent config (ext4) |
+| 5 | var | Runtime data (ext4) |
+
+## Writing to SD Card
+
+```sh
+dd if=infix-espressobin-sdcard.img of=/dev/sdX bs=4M status=progress conv=fsync
+```
+
+## Upgrading
+
+The build produces `x-aarch64/images/infix-aarch64-ext4.pkg`, a RAUC bundle
+containing the ext4 rootfs. Once the board is running Infix, upgrade over the
+network in the usual way:
+
+```
+upgrade ftp://192.168.1.1/infix-aarch64-ext4.pkg
+```
+
+RAUC writes the new rootfs to the inactive slot, updates `BOOT_ORDER` in
+`/mnt/aux/uboot.env`, and the next boot picks it up automatically.
+
+> [!NOTE]
+> Use `infix-aarch64-ext4.pkg`, not the standard `infix-aarch64.pkg`. The
+> standard bundle contains a squashfs rootfs which the stock U-Boot cannot
+> boot.
+
+## Booting with the Stock SPI U-Boot
+
+The stock Marvell U-Boot has `ext4load` and the standard variables
+(`$kernel_addr`, `$fdt_addr`, `$loadaddr`, `$console`, `$image_name`,
+`$fdt_name`) already set sensibly. Connect to the board's console port, the
+micro USB connector, at 115200 8N1, interrupt autoboot, and paste the commands
+below.
+
+### Environment Variable Reference
+
+These variables are pre-set in the stock U-Boot environment. Restore them
+with these values if they are ever lost or corrupted:
+
+```
+setenv kernel_addr 0x5000000
+setenv fdt_addr 0x4f00000
+setenv loadaddr 0x5000000
+setenv console 'console=ttyMV0,115200 earlycon=ar3700_uart,0xd0012000'
+setenv image_name boot/Image
+setenv extra_params quiet
+```
+
+`$fdt_name` selects the device tree for your specific board revision:
+
+| Board revision | `fdt_name` value |
+|----------------|-----------------------------------------------------|
+| v3 / v5 | `boot/marvell/armada-3720-espressobin.dtb` |
+| v7 | `boot/marvell/armada-3720-espressobin-v7.dtb` |
+| Ultra | `boot/marvell/armada-3720-espressobin-ultra.dtb` |
+| v3/v5 eMMC | `boot/marvell/armada-3720-espressobin-emmc.dtb` |
+| v7 eMMC | `boot/marvell/armada-3720-espressobin-v7-emmc.dtb` |
+
+```
+setenv fdt_name boot/marvell/armada-3720-espressobin.dtb # adjust for your board
+```
+
+### Simple Boot
+
+Fixed boot from the primary slot, useful for initial bring-up:
+
+```
+setenv bootcmd 'mmc dev 0; \
+ ext4load mmc 0:2 $kernel_addr $image_name; \
+ ext4load mmc 0:2 $fdt_addr $fdt_name; \
+ setenv bootargs $console root=PARTLABEL=primary rw rootwait $extra_params rauc.slot=primary; \
+ booti $kernel_addr - $fdt_addr'
+saveenv
+```
+
+### Automatic Slot Selection (RAUC Integration)
+
+The CLI `upgrade` command writes to the inactive slot and updates `uboot.env`
+on the `aux` partition with the new boot order. On the next boot U-Boot reads
+`BOOT_ORDER` from the aux partition and selects the appropriate slot. The
+setup below also defines `bootcmd_primary`, `bootcmd_secondary`, and
+`bootcmd_net` for manual use (see [Manual Slot Selection](#manual-slot-selection)
+and [Netbooting](#netbooting)):
+
+```
+setenv bootcmd_boot \
+ 'mmc dev 0; \
+ ext4load mmc 0:$bootpart $kernel_addr $image_name; \
+ ext4load mmc 0:$bootpart $fdt_addr $fdt_name; \
+ setenv bootargs $console root=PARTLABEL=$bootslot rw rootwait $extra_params rauc.slot=$bootslot; \
+ booti $kernel_addr - $fdt_addr'
+
+setenv bootcmd_primary 'setenv bootpart 2; setenv bootslot primary; run bootcmd_boot'
+setenv bootcmd_secondary 'setenv bootpart 3; setenv bootslot secondary; run bootcmd_boot'
+
+setenv bootcmd_net \
+ 'dhcp $kernel_addr $image_name; \
+ tftpboot $fdt_addr $fdt_name; \
+ setenv bootargs $console root=PARTLABEL=primary rw rootwait $extra_params rauc.slot=primary; \
+ booti $kernel_addr - $fdt_addr'
+
+setenv bootcmd \
+ 'setenv bootpart 2; setenv bootslot primary; setenv auxpart 1; \
+ if ext4load mmc 0:$auxpart $loadaddr /uboot.env; then \
+ env import -b $loadaddr $filesize BOOT_ORDER; \
+ fi; \
+ if test "$BOOT_ORDER" = "secondary primary" || \
+ test "$BOOT_ORDER" = "secondary primary net"; then \
+ setenv bootpart 3; setenv bootslot secondary; \
+ fi; \
+ if test "$BOOT_ORDER" = "net" || \
+ test "$BOOT_ORDER" = "net primary" || \
+ test "$BOOT_ORDER" = "net secondary primary"; then \
+ run bootcmd_net; \
+ fi; \
+ echo ">> Booting $bootslot from mmc 0:$bootpart ..."; \
+ run bootcmd_boot'
+
+saveenv
+```
+
+### Manual Slot Selection
+
+To boot a specific slot without waiting for the autoboot countdown, interrupt
+the bootloader (press any key) and run one of the convenience commands defined
+above:
+
+```
+run bootcmd_primary # boot from primary (partition 2)
+run bootcmd_secondary # boot from secondary (partition 3)
+run bootcmd_net # netboot via DHCP/TFTP
+```
+
+You can also force a permanent change to which slot boots next by setting
+`BOOT_ORDER` directly from Linux (the change persists across reboots):
+
+```sh
+fw_setenv BOOT_ORDER "secondary primary" # next boot: secondary
+fw_setenv BOOT_ORDER "primary secondary" # next boot: primary (default)
+```
+
+### Netbooting
+
+The stock U-Boot supports TFTP. This is useful for testing a new kernel or
+device tree without reflashing the SD card. Set up a TFTP server with the
+contents of the Infix `boot/` directory (from the built rootfs at
+`x-aarch64/target/boot/`) and configure the variables:
+
+```
+setenv serverip 192.168.1.1 # IP of your TFTP server
+setenv ipaddr 192.168.1.100 # board IP (omit if using dhcp)
+saveenv
+```
+
+Then netboot manually:
+
+```
+run bootcmd_net
+```
+
+`bootcmd_net` uses `dhcp` to obtain an IP address and the `$serverip` from
+the DHCP server (option 66), then downloads `$image_name` and `$fdt_name`
+via TFTP. The kernel mounts the primary SD card slot as root, so the SD
+card must still be present.
+
+To make netbooting the default on next boot (e.g. for iterative kernel
+development), set `BOOT_ORDER` from Linux:
+
+```sh
+fw_setenv BOOT_ORDER "net primary secondary"
+```
+
+This causes `bootcmd` to attempt netboot first; on failure it falls through
+to the primary slot on the SD card.
+
+[0]: https://wiki.espressobin.net/
+[1]: https://www.marvell.com/content/dam/marvell/en/public-collateral/embedded-processors/marvell-embedded-processors-armada-37xx-hardware-specifications.pdf
+[2]: https://www.marvell.com/content/dam/marvell/en/public-collateral/switching/marvell-link-street-88E6341-product-brief.pdf
+[3]: https://standards.ieee.org/ieee/1588/6825/
+[4]: https://standards.ieee.org/ieee/802.1AS/7121/
diff --git a/board/aarch64/marvell-espressobin/espressobin.png b/board/aarch64/marvell-espressobin/espressobin.png
new file mode 100644
index 000000000..14147c562
Binary files /dev/null and b/board/aarch64/marvell-espressobin/espressobin.png differ
diff --git a/board/aarch64/marvell-espressobin/genimage.cfg b/board/aarch64/marvell-espressobin/genimage.cfg
deleted file mode 100644
index 7da1c2cd5..000000000
--- a/board/aarch64/marvell-espressobin/genimage.cfg
+++ /dev/null
@@ -1,25 +0,0 @@
-image cfg.ext4 {
- ext4 {
- label = "cfg"
- }
- empty = true
- size = 16M
-}
-
-image sdcard.img {
- hdimage {
- }
-
- partition rootfs {
- partition-type = 0x83
- image = "rootfs.ext4"
- }
-
- partition cfg {
- partition-type = 0x83
- image = "cfg.ext4"
- }
-}
-
-# Silence genimage warnings
-config {}
diff --git a/board/aarch64/marvell-espressobin/genimage.cfg.in b/board/aarch64/marvell-espressobin/genimage.cfg.in
new file mode 100644
index 000000000..f1c89ff50
--- /dev/null
+++ b/board/aarch64/marvell-espressobin/genimage.cfg.in
@@ -0,0 +1,61 @@
+image cfg.ext4 {
+ empty = true
+ temporary = true
+ size = 128M
+ ext4 {
+ label = "cfg"
+ use-mke2fs = true
+ features = "uninit_bg"
+ extraargs = "-m 0 -i 4096"
+ }
+}
+
+# The /var partition will be expanded automatically at first boot
+# to use the full size of the SD-card.
+image var.ext4 {
+ empty = true
+ temporary = true
+ size = 128M
+ ext4 {
+ label = "var"
+ use-mke2fs = true
+ features = "uninit_bg"
+ extraargs = "-m 0 -i 4096"
+ }
+}
+
+image #INFIX_ID##VERSION#-espressobin-#TARGET#.img {
+ hdimage {
+ partition-table-type = "gpt"
+ }
+
+ # No bootloader partition: U-Boot lives in SPI NOR flash on all
+ # ESPRESSObin board revisions. The SD card carries the OS only.
+
+ partition aux {
+ partition-uuid = D4EF35A0-0652-45A1-B3DE-D63339C82035
+ image = "aux.ext4"
+ }
+
+ partition primary {
+ partition-type-uuid = 0FC63DAF-8483-4772-8E79-3D69D8477DE4
+ bootable = true
+ image = "rootfs.ext2"
+ }
+
+ partition secondary {
+ partition-type-uuid = 0FC63DAF-8483-4772-8E79-3D69D8477DE4
+ bootable = true
+ image = "rootfs.ext2"
+ }
+
+ partition cfg {
+ partition-uuid = 7aa497f0-73b5-47e5-b2ab-8752d8a48105
+ image = "cfg.ext4"
+ }
+
+ partition var {
+ partition-uuid = 8046A06A-E45A-4A14-A6AD-6684704A393F
+ image = "var.ext4"
+ }
+}
diff --git a/board/common/Config.in b/board/common/Config.in
index ed192024e..468a208a3 100644
--- a/board/common/Config.in
+++ b/board/common/Config.in
@@ -5,6 +5,7 @@ source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-itb-aux/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-itb-qcow/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-itb-gns3a/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-itb-rauc/Config.in"
+source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-ext4-rauc/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-itb-dl-release/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-readme/Config.in"
diff --git a/board/common/image/image-ext4-rauc/Config.in b/board/common/image/image-ext4-rauc/Config.in
new file mode 100644
index 000000000..53ad796c4
--- /dev/null
+++ b/board/common/image/image-ext4-rauc/Config.in
@@ -0,0 +1,27 @@
+config IMAGE_EXT4_RAUC
+ bool "RAUC upgrade bundle (ext4)"
+ depends on BR2_TARGET_ROOTFS_EXT2
+ select BR2_PACKAGE_HOST_RAUC
+ help
+ Create a RAUC upgrade bundle for targets using an ext4 rootfs
+ image. Intended for development boards whose bootloader does
+ not support squashfs.
+
+config IMAGE_EXT4_RAUC_KEY
+ string "signing key"
+ depends on IMAGE_EXT4_RAUC
+ default "${BR2_EXTERNAL_INFIX_PATH}/board/common/signing-keys/development/infix.key"
+ help
+ Path to the private key, in PKCS#8 format, used to sign
+ the RAUC bundle; or a PKCS#11 URI.
+
+config IMAGE_EXT4_RAUC_CERT
+ string "signing certificate"
+ depends on IMAGE_EXT4_RAUC
+ default "${BR2_EXTERNAL_INFIX_PATH}/board/common/signing-keys/development/infix.crt"
+ help
+ Path to the X509 certificate which will be associated with
+ the bundle signature.
+
+ NOTE: This cert MUST be included in the trust store of the
+ system on which this bundle is to be installed.
diff --git a/board/common/image/image-ext4-rauc/generate.sh b/board/common/image/image-ext4-rauc/generate.sh
new file mode 100755
index 000000000..03cd767c7
--- /dev/null
+++ b/board/common/image/image-ext4-rauc/generate.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+set -e
+
+ext2="${BINARIES_DIR}/rootfs.ext2"
+pkg="${BINARIES_DIR}/${ARTIFACT}-ext4.pkg"
+
+# RAUC internally uses the file extension to find a suitable install
+# handler, hence the name must be .img
+cp -f "${ext2}" "${WORKDIR}/rootfs.img"
+
+cat >"${WORKDIR}/manifest.raucm" < name:phc2sys :%i log:prio:daemon,tag:phc2sys-%i \
+ [2345] phc2sys -a -z /var/run/ptp4l-%i \
+ -- PHC synchronization for PTP instance %i
diff --git a/board/common/rootfs/etc/finit.d/available/ptp4l@.conf b/board/common/rootfs/etc/finit.d/available/ptp4l@.conf
new file mode 100644
index 000000000..caf4d7c2c
--- /dev/null
+++ b/board/common/rootfs/etc/finit.d/available/ptp4l@.conf
@@ -0,0 +1,3 @@
+service name:ptp4l :%i log:prio:daemon,tag:ptp4l-%i \
+ [2345] ptp4l -f /etc/linuxptp/ptp4l-%i.conf \
+ -- PTP instance %i
diff --git a/board/common/rootfs/usr/libexec/infix/init.d/00-probe b/board/common/rootfs/usr/libexec/infix/init.d/00-probe
index aca2a85d9..e2054cefb 100755
--- a/board/common/rootfs/usr/libexec/infix/init.d/00-probe
+++ b/board/common/rootfs/usr/libexec/infix/init.d/00-probe
@@ -567,6 +567,59 @@ def probe_wifi_radios(out):
out["wifi-radios"].append(info)
+def probe_ptp_capabilities(out):
+ """Probe PTP timestamping capabilities per physical interface via ethtool --json -T.
+
+ Only physical interfaces (those with a 'device' sysfs symlink) are probed;
+ virtual interfaces such as bridges, VLANs, and tun/tap devices are skipped.
+ Results are stored under out["interfaces"][]["ptp-capabilities"].
+ """
+ net_base = "/sys/class/net"
+ if not os.path.exists(net_base):
+ return
+
+ ifaces = {}
+ for ifname in sorted(os.listdir(net_base)):
+ if ifname == "lo":
+ continue
+ # Physical interfaces have a 'device' symlink; virtual ones do not.
+ if not os.path.exists(os.path.join(net_base, ifname, "device")):
+ continue
+
+ try:
+ result = subprocess.run(
+ ["ethtool", "--json", "-T", ifname],
+ capture_output=True, text=True, timeout=5
+ )
+ if result.returncode != 0:
+ continue
+ data = json.loads(result.stdout)[0]
+ except Exception:
+ continue
+
+ caps = {
+ "capabilities": data.get("capabilities", []),
+ "tx-types": data.get("tx-types", []),
+ "rx-filters": data.get("rx-filters", []),
+ }
+
+ # phc-index is -1 when no PHC is present; omit in that case.
+ phc = data.get("phc-index", -1)
+ if phc >= 0:
+ caps["phc-index"] = phc
+
+ # hwtstamp provider fields are present only on newer kernels/hardware.
+ if (idx := data.get("hwtstamp-provider-index")) is not None:
+ caps["hwtstamp-provider-index"] = idx
+ if (qual := data.get("hwtstamp-provider-qualifier")) is not None:
+ caps["hwtstamp-provider-qualifier"] = qual
+
+ ifaces[ifname] = {"ptp-capabilities": caps}
+
+ if ifaces:
+ out.setdefault("interfaces", {}).update(ifaces)
+
+
def main():
out = {
"vendor": None,
@@ -593,6 +646,7 @@ def main():
return err
probe_wifi_radios(out)
+ probe_ptp_capabilities(out)
if not out["factory-password-hash"]:
sys.stdout.write("\n\n\033[31mCRITICAL BOOTSTRAP ERROR\n" +
diff --git a/configs/aarch64_defconfig b/configs/aarch64_defconfig
index 4a5e4ba12..517c97a5d 100644
--- a/configs/aarch64_defconfig
+++ b/configs/aarch64_defconfig
@@ -75,6 +75,7 @@ BR2_PACKAGE_IPERF3=y
BR2_PACKAGE_IPROUTE2=y
BR2_PACKAGE_IPTABLES_NFTABLES=y
BR2_PACKAGE_IPUTILS=y
+BR2_PACKAGE_LINUXPTP=y
BR2_PACKAGE_LLDPD=y
BR2_PACKAGE_MSTPD=y
BR2_PACKAGE_MTR=y
diff --git a/configs/aarch64_minimal_defconfig b/configs/aarch64_minimal_defconfig
index d518f506c..42433884c 100644
--- a/configs/aarch64_minimal_defconfig
+++ b/configs/aarch64_minimal_defconfig
@@ -68,6 +68,7 @@ BR2_PACKAGE_FRR=y
# BR2_PACKAGE_IFUPDOWN_SCRIPTS is not set
BR2_PACKAGE_IPROUTE2=y
BR2_PACKAGE_IPUTILS=y
+BR2_PACKAGE_LINUXPTP=y
BR2_PACKAGE_LLDPD=y
BR2_PACKAGE_MSTPD=y
BR2_PACKAGE_NETCALC=y
diff --git a/configs/arm_defconfig b/configs/arm_defconfig
index 6bf8bc2ed..c986bfa14 100644
--- a/configs/arm_defconfig
+++ b/configs/arm_defconfig
@@ -74,6 +74,7 @@ BR2_PACKAGE_FRR=y
BR2_PACKAGE_IPERF3=y
BR2_PACKAGE_IPROUTE2=y
BR2_PACKAGE_IPUTILS=y
+BR2_PACKAGE_LINUXPTP=y
BR2_PACKAGE_LLDPD=y
BR2_PACKAGE_MSTPD=y
BR2_PACKAGE_MTR=y
diff --git a/configs/arm_minimal_defconfig b/configs/arm_minimal_defconfig
index ce46662fb..04a7559aa 100644
--- a/configs/arm_minimal_defconfig
+++ b/configs/arm_minimal_defconfig
@@ -70,6 +70,7 @@ BR2_PACKAGE_FRR=y
# BR2_PACKAGE_IFUPDOWN_SCRIPTS is not set
BR2_PACKAGE_IPROUTE2=y
BR2_PACKAGE_IPUTILS=y
+BR2_PACKAGE_LINUXPTP=y
BR2_PACKAGE_LLDPD=y
BR2_PACKAGE_MSTPD=y
BR2_PACKAGE_NETCALC=y
diff --git a/configs/riscv64_defconfig b/configs/riscv64_defconfig
index 88ac2a742..30d59e690 100644
--- a/configs/riscv64_defconfig
+++ b/configs/riscv64_defconfig
@@ -85,6 +85,7 @@ BR2_PACKAGE_IPERF3=y
BR2_PACKAGE_IPROUTE2=y
BR2_PACKAGE_IPTABLES_NFTABLES=y
BR2_PACKAGE_IPUTILS=y
+BR2_PACKAGE_LINUXPTP=y
BR2_PACKAGE_LLDPD=y
BR2_PACKAGE_MSTPD=y
BR2_PACKAGE_MTR=y
diff --git a/configs/snippets/ext4.conf b/configs/snippets/ext4.conf
index 2b0b2eb40..20cfc728d 100644
--- a/configs/snippets/ext4.conf
+++ b/configs/snippets/ext4.conf
@@ -1,3 +1,4 @@
BR2_TARGET_ROOTFS_EXT2=y
BR2_TARGET_ROOTFS_EXT2_4=y
BR2_TARGET_ROOTFS_EXT2_SIZE="512M"
+IMAGE_EXT4_RAUC=y
diff --git a/configs/x86_64_defconfig b/configs/x86_64_defconfig
index 6404565d5..f961fb86d 100644
--- a/configs/x86_64_defconfig
+++ b/configs/x86_64_defconfig
@@ -74,6 +74,7 @@ BR2_PACKAGE_IPERF3=y
BR2_PACKAGE_IPROUTE2=y
BR2_PACKAGE_IPTABLES_NFTABLES=y
BR2_PACKAGE_IPUTILS=y
+BR2_PACKAGE_LINUXPTP=y
BR2_PACKAGE_LLDPD=y
BR2_PACKAGE_MSTPD=y
BR2_PACKAGE_MTR=y
diff --git a/configs/x86_64_minimal_defconfig b/configs/x86_64_minimal_defconfig
index 8ce0c5afd..15c66ce42 100644
--- a/configs/x86_64_minimal_defconfig
+++ b/configs/x86_64_minimal_defconfig
@@ -67,6 +67,7 @@ BR2_PACKAGE_FRR=y
# BR2_PACKAGE_IFUPDOWN_SCRIPTS is not set
BR2_PACKAGE_IPROUTE2=y
BR2_PACKAGE_IPUTILS=y
+BR2_PACKAGE_LINUXPTP=y
BR2_PACKAGE_LLDPD=y
BR2_PACKAGE_MSTPD=y
BR2_PACKAGE_NETCALC=y
diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md
index 7c7840c9f..f5db06add 100644
--- a/doc/ChangeLog.md
+++ b/doc/ChangeLog.md
@@ -10,6 +10,9 @@ All notable changes to the project are documented in this file.
- Upgrade Linux kernel to 6.18.24 (LTS)
- Upgrade Buildroot to 2025.02.13 (LTS)
+- Add support for PTP/gPTP (IEEE 1588-2019 / 802.1AS) clock synchronization.
+ Supported clock types: Ordinary Clock, Boundary Clock, and Transparent Clock.
+ See the User Guide for configuration details
- Add support for [Banana Pi BPI-R4][BPI-R4], quad-core Cortex-A73 router with
4x 2.5 GbE switching, dual 10 GbE SFP+. Variants BPI-R4-2g5 and BPI-R4P have
one SFP+ replaced by a 2.5 GbE RJ45, with optional PoE on the R4P
diff --git a/doc/ptp.md b/doc/ptp.md
new file mode 100644
index 000000000..5814ca8e0
--- /dev/null
+++ b/doc/ptp.md
@@ -0,0 +1,461 @@
+# PTP — Precision Time Protocol
+
+The Precision Time Protocol (PTP), defined in IEEE 1588-2019, synchronises
+clocks across a network to sub-microsecond accuracy. Where NTP (Network Time
+Protocol) aims at millisecond accuracy over wide-area networks, PTP is
+designed for local-area networks and relies on hardware timestamping in the
+network interface to eliminate software-induced jitter.
+
+PTP works by exchanging timestamped messages between devices. A *grandmaster
+clock* — elected by the **Best TimeTransmitter Clock Algorithm (BTCA)** based
+on priority, clock class, and accuracy — distributes time to the rest of the
+network. Each synchronising device measures the one-way message delay to its
+time-transmitter and continuously adjusts its local clock to compensate.
+
+> [!NOTE]
+> The IEEE 1588g-2022 amendment to IEEE 1588-2019 introduced the terms
+> *timeTransmitter* and *timeReceiver* as replacements for the former
+> *master* and *slave* terminology, and *Best TimeTransmitter Clock
+> Algorithm (BTCA)* in place of *BMCA*. This document uses the updated
+> terms throughout. You may even see the short forms transmitter and
+> receiver here and in online documentation.
+
+## Clock roles
+
+Every device in a PTP network takes one of the following roles:
+
+| Role | Description |
+|----------------------------|---------------------------------------------------------------------------------------------|
+| **Grandmaster (GM)** | Network-wide time source; elected by BTCA |
+| **Time-transmitter** | Sends Sync messages downstream on a port |
+| **Time-receiver** | Synchronises to a time-transmitter on a port |
+| **Boundary Clock (BC)** | Terminates PTP on each port; acts as time-receiver upstream and time-transmitter downstream |
+| **Transparent Clock (TC)** | Passes PTP messages while correcting the residence-time delay accumulated in the device |
+
+An **Ordinary Clock (OC)** has a single PTP port and is either a
+time-transmitter (acting as a grandmaster candidate) or a time-receiver
+(a leaf node synchronising to the network).
+
+## PTP profiles
+
+A **PTP profile** (as defined in IEEE 1588-2019 §3.1) is a document that
+specifies a consistent set of required, permitted, and prohibited PTP
+options for a particular application domain — much like a dialect of the
+protocol. Examples from the standards world include profiles for power
+utilities (IEC/IEEE C37.238), telecom (ITU-T G.8265.1), and
+Time-Sensitive Networks.
+
+Each profile sets a unique value in the `majorSdoId` field of PTP message
+headers — a 4-bit identifier that lets devices distinguish traffic belonging
+to different profiles on the same link. Profile also determines the network
+transport (UDP or Ethernet) and the delay measurement mechanism.
+
+Currently, two profiles are supported via the `profile` leaf in `default-ds`:
+
+| `profile` | Standard | majorSdoId | Transport | Delay |
+|----------------------|-------------------|:----------:|-----------|----------------|
+| `ieee1588` (default) | IEEE 1588-2019 | `0x0` | UDP/IPv4 | `e2e` or `p2p` |
+| `ieee802-dot1as` | IEEE 802.1AS-2020 | `0x1` | L2 | `p2p` |
+
+The **gPTP** (generalized Precision Time Protocol) profile from IEEE 802.1AS-2020
+is used in **TSN** (Time-Sensitive Networking) and **AVB** (Audio/Video Bridging)
+applications. Setting `profile ieee802-dot1as` applies all protocol-mandatory
+settings automatically — Layer 2 transport, P2P delay measurement, 802.1AS
+multicast addressing, path trace, follow-up information, and neighbour propagation
+delay thresholds. The user still configures `priority1`, `priority2`,
+`domain-number`, `time-receiver-only`, and timer interval leaves.
+
+The `ieee1588` profile leaves transport and delay mechanism user-configurable
+per port.
+
+## Delay mechanisms
+
+PTP measures the link delay between neighbours using one of two mechanisms:
+
+- **End-to-End (E2E)**: Each time-receiver measures the delay to the
+ grandmaster by sending a `DELAY_REQ` message upstream. Simple to
+ configure; works with any network topology.
+- **Peer-to-Peer (P2P)**: Each port measures its delay to its *immediate
+ neighbour* independently using `PDELAY_REQ` messages. Enables faster
+ path-delay updates and is required by the gPTP profile.
+
+## Data Sets
+
+IEEE 1588 organises protocol state into named **Data Sets (DS)** — each a
+collection of related attributes for one aspect of a PTP instance. You
+will encounter these directly in the CLI and in the `show ptp` output:
+
+| Data Set | CLI node | Contents |
+|------------------|----------------|----------------------------------------------------------|
+| Default DS | `default-ds` | Instance identity, clock class, priority, domain number |
+| Current DS | `current-ds` | Live offset-from-GM, mean path delay, steps-removed |
+| Parent DS | `parent-ds` | Grandmaster identity and quality attributes |
+| Time Properties DS | `time-properties-ds` | UTC offset, leap-second flags, time source |
+| Port DS | `port-ds` | Per-port state, delay mechanism, message intervals |
+
+## Domains
+
+A **PTP domain** (0–255) is a logical partition of the network. Devices
+only synchronise with others in the same domain. Running multiple
+instances on the same device — one per domain, or one per profile — is
+fully supported; each instance is independent.
+
+Each PTP instance is identified on the network by its
+`(domain-number, profile)` pair, which must be unique across all instances
+on a device.
+
+> [!NOTE]
+> The `show ptp` offset values reflect **PHC** (PTP Hardware Clock)
+> synchronisation only. A PHC is the hardware clock exposed by the network
+> interface; it tracks the PTP grandmaster but is independent of the Linux
+> system clock, which currently is **not** automatically adjusted.
+
+## Ordinary Clock (time-receiver)
+
+A typical time-receiver Ordinary Clock, synchronising on interface
+`eth0` using the default IEEE 1588 profile:
+
+
admin@example:/> configure
+admin@example:/config/> edit ptp instance 0
+admin@example:/config/ptp/instance/0/> set default-ds domain-number 0
+admin@example:/config/ptp/instance/0/> set default-ds time-receiver-only true
+admin@example:/config/ptp/instance/0/> edit port 1
+admin@example:/config/ptp/…/0/port/1/> set underlying-interface eth0
+admin@example:/config/ptp/…/0/port/1/> leave
+
+
+## Ordinary Clock (time-transmitter / grandmaster)
+
+A grandmaster clock with high priority, domain 0:
+
+
admin@example:/config/> edit ptp instance 0
+admin@example:/config/ptp/instance/0/> set default-ds domain-number 0
+admin@example:/config/ptp/instance/0/> set default-ds priority1 1
+admin@example:/config/ptp/instance/0/> set default-ds priority2 1
+admin@example:/config/ptp/instance/0/> edit port 1
+admin@example:/config/ptp/…/0/port/1/> set underlying-interface eth0
+admin@example:/config/ptp/…/0/port/1/> leave
+
+
+Lower `priority1` values win in the BTCA. A clock with `priority1 1` will
+be preferred over the default `128` in any compliant network.
+
+## Boundary Clock
+
+A Boundary Clock terminates PTP on each port and re-originates it. Add one
+port per interface:
+
+
admin@example:/config/> edit ptp instance 0
+admin@example:/config/ptp/instance/0/> set default-ds instance-type bc
+admin@example:/config/ptp/instance/0/> set default-ds domain-number 0
+admin@example:/config/ptp/instance/0/> edit port 1
+admin@example:/config/ptp/…/0/port/1/> set underlying-interface eth0
+admin@example:/config/ptp/…/0/port/1/> end
+admin@example:/config/ptp/instance/0/> edit port 2
+admin@example:/config/ptp/…/0/port/2/> set underlying-interface eth1
+admin@example:/config/ptp/…/0/port/2/> leave
+
+
+> [!TIP]
+> PTP port numbers are assigned sorted by `port-index`, so `port-index 1`
+> becomes PTP port 1, `port-index 2` becomes PTP port 2, and so on.
+
+## Transparent Clock
+
+Transparent Clocks correct timestamps end-to-end without terminating PTP.
+Use `instance-type p2p-tc` for a P2P TC (preferred in TSN networks) or
+`instance-type e2e-tc` for an E2E TC:
+
+
admin@example:/config/> edit ptp instance 0
+admin@example:/config/ptp/instance/0/> set default-ds instance-type p2p-tc
+admin@example:/config/ptp/instance/0/> set default-ds domain-number 0
+admin@example:/config/ptp/instance/0/> edit port 1
+admin@example:/config/ptp/…/0/port/1/> set underlying-interface eth0
+admin@example:/config/ptp/…/0/port/1/> end
+admin@example:/config/ptp/instance/0/> edit port 2
+admin@example:/config/ptp/…/0/port/2/> set underlying-interface eth1
+admin@example:/config/ptp/…/0/port/2/> leave
+
+
+> [!NOTE]
+> For Transparent Clocks the delay mechanism is determined globally by the
+> `instance-type` (`p2p-tc` → P2P, `e2e-tc` → E2E). Per-port
+> `delay-mechanism` settings have no effect for TC instances.
+
+## gPTP / IEEE 802.1AS
+
+The gPTP profile is used in TSN and AVB applications. Setting
+`profile ieee802-dot1as` applies all protocol-mandatory options from
+IEEE 802.1AS-2020 automatically — Layer 2 transport, P2P delay
+measurement, 802.1AS multicast addressing, and related protocol features.
+
+
admin@example:/config/> edit ptp instance 0
+admin@example:/config/ptp/instance/0/> set default-ds profile ieee802-dot1as
+admin@example:/config/ptp/instance/0/> set default-ds domain-number 0
+admin@example:/config/ptp/instance/0/> set default-ds time-receiver-only true
+admin@example:/config/ptp/instance/0/> edit port 1
+admin@example:/config/ptp/…/0/port/1/> set underlying-interface eth0
+admin@example:/config/ptp/…/0/port/1/> leave
+
+
+> [!NOTE]
+> The `ieee802-dot1as` profile enforces Layer 2 transport and P2P delay
+> measurement globally, as required by IEEE 802.1AS-2020. Per-port
+> `delay-mechanism` settings have no effect for 802.1AS instances.
+
+## Multiple Instances
+
+Multiple PTP instances can run simultaneously, one per domain or profile
+combination. Each instance must have a unique `(domain-number, profile)`
+pair and an independent set of ports:
+
+
admin@example:/config/> edit ptp instance 0
+admin@example:/config/ptp/instance/0/> set default-ds domain-number 0
+admin@example:/config/ptp/instance/0/> set default-ds profile ieee1588
+admin@example:/config/ptp/instance/0/> edit port 1
+admin@example:/config/ptp/…/0/port/1/> set underlying-interface eth0
+admin@example:/config/ptp/…/0/port/1/> end
+admin@example:/config/ptp/instance/0/> end
+admin@example:/config/ptp/> edit instance 1
+admin@example:/config/ptp/instance/1/> set default-ds domain-number 0
+admin@example:/config/ptp/instance/1/> set default-ds profile ieee802-dot1as
+admin@example:/config/ptp/instance/1/> edit port 1
+admin@example:/config/ptp/…/1/port/1/> set underlying-interface eth1
+admin@example:/config/ptp/…/1/port/1/> leave
+
+
+## Port states
+
+Each PTP port progresses through a state machine. The current state is
+shown in the `show ptp` port table:
+
+| State | Meaning |
+|------------------------|----------------------------------------------------------------|
+| `initializing` | Port is starting up, not yet ready to exchange messages |
+| `faulty` | A fault condition has been detected on this port |
+| `disabled` | Port is administratively disabled |
+| `listening` | Awaiting `ANNOUNCE` messages; BTCA has not yet resolved |
+| `pre-time-transmitter` | Transitioning towards time-transmitter state |
+| `time-transmitter` | Port is acting as time-transmitter on this link |
+| `passive` | Another port on this device is already time-transmitter |
+| `uncalibrated` | Receiving sync; local clock not yet locked to time-transmitter |
+| `time-receiver` | Port is locked and tracking its time-transmitter |
+
+A port in `uncalibrated` will typically transition to `time-receiver`
+within a few seconds once the clock servo has converged.
+
+## Monitoring
+
+> [!TIP] Use the ++question++ key in the CLI
+> The `show ptp` command has sub-commands — tap ++question++ after
+> `show ptp` to see them, or use ++tab++ to complete.
+
+### Show all PTP instances
+
+
+
+Port state is colour-coded: green for `time-transmitter` and `time-receiver`
+(actively synchronising), yellow for transient states (`listening`,
+`uncalibrated`, `pre-time-transmitter`), and red for fault states (`faulty`,
+`disabled`). The *Message Statistics* section is omitted when no counts are
+available.
+
+### Show a specific instance
+
+
admin@example:/> show ptp 0
+
+
+## Tuning port intervals
+
+Adjust announcement, sync, and delay-request intervals per port. Values
+are expressed as log₂ of the interval in seconds (e.g. `-3` = 125 ms,
+`0` = 1 s, `1` = 2 s):
+
+
admin@example:/config/ptp/…/0/port/1/> set port-ds log-announce-interval 0
+admin@example:/config/ptp/…/0/port/1/> set port-ds log-sync-interval -3
+admin@example:/config/ptp/…/0/port/1/> set port-ds log-min-delay-req-interval 0
+admin@example:/config/ptp/…/0/port/1/> set announce-receipt-timeout 3
+
+
+`announce-receipt-timeout` is a count of announce intervals, not a duration
+in seconds. With `log-announce-interval 0` (1 s) and
+`announce-receipt-timeout 3`, a port waits 3 s without receiving an
+`ANNOUNCE` before declaring the time-transmitter lost and returning to
+`listening`.
+
+## Message exchange
+
+PTP distributes time using a small set of messages, all of which carry
+hardware timestamps at the network interface:
+
+| Message | Timestamped | Purpose |
+|-------------------------|:-----------:|-----------------------------------------------------|
+| `ANNOUNCE` | No | Advertises clock quality for BTCA election |
+| `SYNC` | Yes | Carries transmitter timestamp to receivers |
+| `FOLLOW_UP` | No | Carries precise `t1` in two-step mode |
+| `DELAY_REQ` | Yes | Receiver-initiated E2E delay measurement |
+| `DELAY_RESP` | No | Time-transmitter reply to `DELAY_REQ` |
+| `PDELAY_REQ` | Yes | Initiates P2P neighbour-delay measurement |
+| `PDELAY_RESP` | Yes | Neighbour reply to `PDELAY_REQ` |
+| `PDELAY_RESP_FOLLOW_UP` | No | Carries precise `PDELAY_RESP` `t3` in two-step mode |
+
+In **one-step** mode the timestamp is embedded directly into each `SYNC`
+message as it leaves the wire, eliminating the need for `FOLLOW_UP`.
+In **two-step** mode the `SYNC` carries a placeholder and the precise
+transmit timestamp arrives in a subsequent `FOLLOW_UP`. Hardware
+timestamping gives high accuracy in both modes; one-step reduces message
+overhead at the cost of more demanding hardware support.
+
+## Message format
+
+Every PTP message begins with a common 34-octet header, regardless of type.
+The structure below follows the traditional IETF bit-field layout: each row
+is four octets wide, bit 7 (MSB) is on the left and bit 0 (LSB) on the
+right within each octet.
+
+```
+ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ 0-3 |trSpec |msgType| rsv | ver | messageLength |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ 4-7 | domainNumber | minorSdoId | flags |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ 8-15 | |
+ + correctionField +
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+16-19 | messageTypeSpecific |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+20-27 | |
+ + clockIdentity +
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+28-31 | portNumber | sequenceId |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+32-33 | controlField | logMsgIntvl |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+```
+
+- **`trSpec`** (`transportSpecific`, bits 7–4 of octet 0): 4-bit profile
+ identifier. `0x0` = IEEE 1588, `0x1` = gPTP (802.1AS). Set implicitly
+ by the `profile` configuration leaf.
+- **`msgType`** (`messageType`, bits 3–0 of octet 0): `0x0` SYNC ·
+ `0x1` DELAY_REQ · `0x2` PDELAY_REQ · `0x3` PDELAY_RESP ·
+ `0x8` FOLLOW_UP · `0x9` DELAY_RESP · `0xA` PDELAY_RESP_FOLLOW_UP ·
+ `0xB` ANNOUNCE.
+- **`rsv`** (reserved, bits 7–4 of octet 1): Set to zero; ignored on
+ receipt.
+- **`ver`** (`versionPTP`, bits 3–0 of octet 1): PTP version; `2` for
+ IEEE 1588-2008 and IEEE 1588-2019.
+- **`messageLength`** (octets 2–3): Total message length in octets,
+ including the header.
+- **`domainNumber`** (octet 4): PTP domain; receivers silently discard
+ messages that do not match their configured domain.
+- **`minorSdoId`** (octet 5): Reserved in IEEE 1588-2008; carries a
+ profile sub-identifier in IEEE 1588-2019.
+- **`flags`** (octets 6–7): Per-message flags — includes the two-step
+ flag (set when a FOLLOW_UP will follow a SYNC), UTC offset valid, and
+ leap-second indicators.
+- **`correctionField`** (octets 8–15): Accumulated path correction in
+ nanoseconds × 2¹⁶. Transparent Clocks add their measured residence
+ time and link delay here as they forward each message, so the final
+ time-receiver can subtract the total accumulated delay.
+- **`messageTypeSpecific`** (octets 16–19): Reserved in IEEE 1588-2008;
+ carries message-type-specific data in IEEE 1588-2019.
+- **`clockIdentity`** (octets 20–27): EUI-64 identity of the sending
+ clock — the value shown as "Clock identity" in `show ptp`.
+- **`portNumber`** (octets 28–29): Port number of the sender within its
+ clock; together with `clockIdentity` it forms the unique
+ `sourcePortIdentity`.
+- **`sequenceId`** (octets 30–31): Increments with each message; used to
+ match a DELAY_REQ to its DELAY_RESP.
+- **`controlField`** (octet 32): Deprecated in PTPv2; set to fixed
+ values per message type for backward compatibility with PTPv1.
+- **`logMsgIntvl`** (`logMessageInterval`, octet 33): Log₂ of the
+ expected interval between messages of this type; `0x7F` means not
+ applicable.
+
+The `transportSpecific` and `domainNumber` fields are the quickest way to
+verify on the wire that a device is using the profile and domain you
+configured.
+
+### Decoding with Wireshark
+
+Wireshark decodes PTP messages automatically, expanding every header field
+and message-type-specific payload in the packet tree. PTP travels over
+two UDP ports — 319 for event messages (SYNC, DELAY_REQ, PDELAY_REQ and
+their responses) and 320 for general messages (ANNOUNCE, FOLLOW_UP) — as
+well as directly over Ethernet (EtherType `0x88F7`) when layer-2 transport
+is in use.
+
+Use the display filter `ptp` to isolate PTP traffic:
+
+```
+ptp
+```
+
+To narrow down to a specific domain or profile (exact field names can be
+verified in Wireshark via **View → Internals → Supported Protocols**,
+filtering for `ptp`):
+
+```
+ptp.v2.domainnumber == 0
+ptp.v2.transportspecific == 1
+```
+
+This makes it straightforward to confirm which grandmaster a port is
+tracking, verify that `correctionField` is being updated by a Transparent
+Clock, or diagnose why the BTCA is not electing the expected grandmaster.
+
+## Glossary
+
+| Abbreviation | Expansion | Notes |
+|--------------|--------------------------------------|-------------------------------------------------------------------|
+| AVB | Audio/Video Bridging | IEEE 802.1 precursor to TSN; real-time AV over Ethernet |
+| IETF | Internet Engineering Task Force | Standards body; defines RFC for layer-3 and up |
+| UDP | User Datagram Protocol | IP transport used by PTP; port 319 (event) and 320 (general) |
+| EUI-64 | Extended Unique Identifier (64-bit) | IEEE identifier format used as `clockIdentity` in PTP |
+| EtherType | Ethernet frame type field | `0x88F7` identifies PTP over layer-2 Ethernet |
+| BC | Boundary Clock | Terminates and re-originates PTP on each port |
+| BTCA | Best TimeTransmitter Clock Algorithm | Elects the GM; replaces BMCA from IEEE 1588-2008 |
+| CMLDS | Common Mean Link Delay Service | IEEE 1588-2019 §16.6; shared delay service for multiple instances |
+| DS | Data Set | Named attribute collection in IEEE 1588 (default-ds, port-ds, …) |
+| E2E | End-to-End | Delay mechanism: measures path from GM to time-receiver |
+| GM | Grandmaster | PTP network-wide time source, elected by BTCA |
+| gPTP | generalized Precision Time Protocol | IEEE 802.1AS profile; used in TSN and AVB |
+| NTP | Network Time Protocol | Millisecond-accuracy time protocol for wide-area use |
+| OC | Ordinary Clock | Single-port PTP clock; time-transmitter or time-receiver |
+| P2P | Peer-to-Peer | Delay mechanism: measures delay to immediate neighbour |
+| PHC | PTP Hardware Clock | Hardware clock in the NIC used for PTP timestamping |
+| PTP | Precision Time Protocol | IEEE 1588 sub-microsecond clock synchronisation protocol |
+| SDO | Standards Development Organization | Body that defines a PTP profile; encoded in `sdo-id` |
+| TC | Transparent Clock | Forwards PTP messages, correcting for residence-time delay |
+| TSN | Time-Sensitive Networking | IEEE 802.1 standard set for deterministic Ethernet |
diff --git a/doc/testing.md b/doc/testing.md
index eb3d14760..99abffdcd 100644
--- a/doc/testing.md
+++ b/doc/testing.md
@@ -359,6 +359,53 @@ $ make test-spec
...
```
+### Node and Link Capabilities
+
+Logical topology files (`topology.dot`) declare what each node and link
+*requires*; physical topology files declare what each node and link
+*provides*. When mapping a logical topology to physical hardware, infamy
+only assigns a physical node to a logical node when the physical node's
+`provides` set is a superset of the logical node's `requires` set. Tests
+are skipped if no matching physical topology can be found.
+
+#### Declaring requirements (logical topology)
+
+```dot
+dut [
+ requires="infix",
+];
+
+host:data -- dut:data [requires="ptp-hwts"]
+```
+
+#### Declaring capabilities (physical topology)
+
+```dot
+switch1 [
+ provides="infix",
+];
+
+switch1:eth0 -- switch2:eth0 [provides="ptp-hwts"]
+```
+
+#### Node capabilities
+
+| Capability | Meaning |
+|-------------------|-------------------------------------------------------------------------|
+| `controller` | Reserved for the host/controller node; never assigned to a DUT |
+| `infix` | Node runs Infix OS — required by virtually all DUT nodes |
+| `gps` | Node has a GPS receiver available as a time reference |
+| `watchdog` | Node has a hardware watchdog device |
+
+#### Link capabilities
+
+| Capability | Meaning |
+|-------------------|-------------------------------------------------------------------------|
+| `mgmt` | Link is a management path (typically coloured grey in diagrams) |
+| `ieee-mc` | Link carries IEEE multicast traffic (required by LAG and some L2 tests) |
+| `link-ctrl copper`| Link supports copper speed/duplex control |
+| `ptp-hwts` | Both ends of this link support PTP hardware timestamping (PHC); required for sub-microsecond accuracy |
+
### Test Development
For adding a new test to the automated regression test suite, it's best
diff --git a/mkdocs.yml b/mkdocs.yml
index bc640dc3d..9e92c10d6 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -47,6 +47,7 @@ nav:
- Device Discovery: discovery.md
- DHCP Server: dhcp.md
- NTP Server: ntp.md
+ - PTP (IEEE 1588/802.1AS): ptp.md
- System:
- Boot Procedure: boot.md
- Configuration: system.md
diff --git a/patches/ethtool/0001-ethtool-Add-json-support-for-T-show-time-stamping.patch b/patches/ethtool/0001-ethtool-Add-json-support-for-T-show-time-stamping.patch
new file mode 100644
index 000000000..8ea2455ba
--- /dev/null
+++ b/patches/ethtool/0001-ethtool-Add-json-support-for-T-show-time-stamping.patch
@@ -0,0 +1,309 @@
+From ccd5fe71878cc3eeea2a3137c7b8d5715e3f4253 Mon Sep 17 00:00:00 2001
+From: Joachim Wiberg
+Date: Thu, 9 Apr 2026 11:09:51 +0200
+Subject: [PATCH] ethtool: Add --json support for -T (show-time-stamping)
+Organization: Wires
+
+Wire up JSON output for `ethtool -T`:
+
+ - Mark the -T command with .json = true in the command table
+ - Wrap tsinfo_reply_cb output in open/close_json_object, use
+ print_string for the header, and handle the PHC index/none
+ case with proper JSON primitives
+ - Convert tsinfo_dump_list to open a JSON array (keyed by the
+ new json_key parameter) and close it on return; update callers
+ in tsinfo.c and tsconfig.c with appropriate keys
+ - Convert tsinfo_dump_cb to use print_string(PRINT_ANY) so
+ each set bit is emitted as a JSON array element in JSON mode
+ and as an indented text line in plain mode
+ - Wrap the netlink request in nl_tsinfo with new_json_obj /
+ delete_json_obj
+
+Example output on a Marvell 88e6341 (Topaz) switch (lan0):
+
+ $ ethtool --json -T lan0 | jq
+ [
+ {
+ "ifname": "lan0",
+ "capabilities": [
+ "hardware-transmit",
+ "hardware-receive",
+ "software-receive",
+ "software-system-clock",
+ "hardware-raw-clock"
+ ],
+ "hwtstamp-provider-index": 0,
+ "hwtstamp-provider-qualifier": "Precise (IEEE 1588 quality)",
+ "tx-types": [
+ "off",
+ "on"
+ ],
+ "rx-filters": [
+ "none",
+ "ptpv2-l4-event",
+ "ptpv2-l4-sync",
+ "ptpv2-l4-delay-req",
+ "ptpv2-l2-event",
+ "ptpv2-l2-sync",
+ "ptpv2-l2-delay-req",
+ "ptpv2-event",
+ "ptpv2-sync",
+ "ptpv2-delay-req"
+ ]
+ }
+ ]
+
+Example output on a Qemu VM with e1000 NIC (e1):
+
+ $ ethtool --json -T e1 | jq
+ [
+ {
+ "ifname": "e1",
+ "capabilities": [
+ "software-transmit",
+ "software-receive",
+ "software-system-clock"
+ ],
+ "phc-index": -1,
+ "tx-types": [],
+ "rx-filters": []
+ }
+ ]
+
+Signed-off-by: Joachim Wiberg
+---
+ ethtool.c | 1 +
+ netlink/ts.h | 2 +-
+ netlink/tsconfig.c | 6 +--
+ netlink/tsinfo.c | 106 ++++++++++++++++++++++++++++++---------------
+ 4 files changed, 75 insertions(+), 40 deletions(-)
+
+diff --git a/ethtool.c b/ethtool.c
+index 9c8a542..f845dae 100644
+--- a/ethtool.c
++++ b/ethtool.c
+@@ -5985,6 +5985,7 @@ static const struct option args[] = {
+ },
+ {
+ .opts = "-T|--show-time-stamping",
++ .json = true,
+ .func = do_tsinfo,
+ .nlfunc = nl_tsinfo,
+ .help = "Show time stamping capabilities",
+diff --git a/netlink/ts.h b/netlink/ts.h
+index 9442b44..07f140a 100644
+--- a/netlink/ts.h
++++ b/netlink/ts.h
+@@ -17,6 +17,6 @@ int tsinfo_qualifier_parser(struct nl_context *nlctx,
+ void *dest);
+ int tsinfo_dump_list(struct nl_context *nlctx, const struct nlattr *attr,
+ const char *label, const char *if_empty,
+- unsigned int stringset_id);
++ unsigned int stringset_id, const char *json_key);
+
+ #endif /* ETHTOOL_NETLINK_TS_H__ */
+diff --git a/netlink/tsconfig.c b/netlink/tsconfig.c
+index d427c7b..aab9859 100644
+--- a/netlink/tsconfig.c
++++ b/netlink/tsconfig.c
+@@ -52,19 +52,19 @@ int tsconfig_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+
+ ret = tsinfo_dump_list(nlctx, tb[ETHTOOL_A_TSCONFIG_TX_TYPES],
+ "Hardware Transmit Timestamp Mode", " none",
+- ETH_SS_TS_TX_TYPES);
++ ETH_SS_TS_TX_TYPES, "tx-types");
+ if (ret < 0)
+ return err_ret;
+
+ ret = tsinfo_dump_list(nlctx, tb[ETHTOOL_A_TSCONFIG_RX_FILTERS],
+ "Hardware Receive Filter Mode", " none",
+- ETH_SS_TS_RX_FILTERS);
++ ETH_SS_TS_RX_FILTERS, "rx-filters");
+ if (ret < 0)
+ return err_ret;
+
+ ret = tsinfo_dump_list(nlctx, tb[ETHTOOL_A_TSCONFIG_HWTSTAMP_FLAGS],
+ "Hardware Flags", " none",
+- ETH_SS_TS_FLAGS);
++ ETH_SS_TS_FLAGS, "hwtstamp-flags");
+ if (ret < 0)
+ return err_ret;
+
+diff --git a/netlink/tsinfo.c b/netlink/tsinfo.c
+index 187c3ad..da64b50 100644
+--- a/netlink/tsinfo.c
++++ b/netlink/tsinfo.c
+@@ -52,6 +52,7 @@ int tsinfo_show_hwprov(const struct nlattr *nest)
+ return 0;
+ }
+
++
+ static int tsinfo_show_stats(const struct nlattr *nest)
+ {
+ const struct nlattr *tb[ETHTOOL_A_TS_STAT_MAX + 1] = {};
+@@ -109,39 +110,57 @@ err_close_stats:
+ static void tsinfo_dump_cb(unsigned int idx, const char *name, bool val,
+ void *data __maybe_unused)
+ {
++ char buf[16];
++
+ if (!val)
+ return;
+
+- if (name)
+- printf("\t%s\n", name);
+- else
+- printf("\tbit%u\n", idx);
++ if (!name) {
++ snprintf(buf, sizeof(buf), "bit%u", idx);
++ name = buf;
++ }
++ print_string(PRINT_ANY, NULL, "\t%s\n", name);
+ }
+
+ int tsinfo_dump_list(struct nl_context *nlctx, const struct nlattr *attr,
+ const char *label, const char *if_empty,
+- unsigned int stringset_id)
++ unsigned int stringset_id, const char *json_key)
+ {
+ const struct stringset *strings = NULL;
+- int ret;
++ bool empty;
++ int ret = 0;
+
+- printf("%s:", label);
+- ret = 0;
+- if (!attr || bitset_is_empty(attr, false, &ret)) {
+- printf("%s\n", if_empty);
+- return ret;
+- }
+- putchar('\n');
+- if (ret < 0)
+- return ret;
++ empty = !attr || bitset_is_empty(attr, false, &ret);
+
+- if (bitset_is_compact(attr)) {
+- ret = netlink_init_ethnl2_socket(nlctx);
++ if (!is_json_context()) {
++ printf("%s:", label);
++ if (empty) {
++ printf("%s\n", if_empty);
++ return ret;
++ }
++ putchar('\n');
++ if (ret < 0)
++ return ret;
++ } else {
+ if (ret < 0)
+ return ret;
+- strings = global_stringset(stringset_id, nlctx->ethnl2_socket);
++ open_json_array(json_key, "");
++ }
++
++ if (!empty) {
++ if (bitset_is_compact(attr)) {
++ ret = netlink_init_ethnl2_socket(nlctx);
++ if (ret < 0)
++ goto out_close;
++ strings = global_stringset(stringset_id, nlctx->ethnl2_socket);
++ }
++ ret = walk_bitset(attr, strings, tsinfo_dump_cb, NULL);
+ }
+- return walk_bitset(attr, strings, tsinfo_dump_cb, NULL);
++
++out_close:
++ if (is_json_context())
++ close_json_array("");
++ return ret;
+ }
+
+ int tsinfo_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+@@ -163,47 +182,59 @@ int tsinfo_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+ return err_ret;
+
+ if (silent)
+- putchar('\n');
+- printf("Time stamping parameters for %s:\n", nlctx->devname);
++ print_nl();
++
++ open_json_object(NULL);
++ print_string(PRINT_ANY, "ifname",
++ "Time stamping parameters for %s:\n", nlctx->devname);
+
+ ret = tsinfo_dump_list(nlctx, tb[ETHTOOL_A_TSINFO_TIMESTAMPING],
+- "Capabilities", "", ETH_SS_SOF_TIMESTAMPING);
++ "Capabilities", "", ETH_SS_SOF_TIMESTAMPING,
++ "capabilities");
+ if (ret < 0)
+- return err_ret;
++ goto err_close_dev;
+
+ if (tb[ETHTOOL_A_TSINFO_HWTSTAMP_PROVIDER]) {
+ ret = tsinfo_show_hwprov(tb[ETHTOOL_A_TSINFO_HWTSTAMP_PROVIDER]);
+ if (ret < 0)
+- return err_ret;
+-
++ goto err_close_dev;
+ } else if (tb[ETHTOOL_A_TSINFO_PHC_INDEX]) {
+- printf("PTP Hardware Clock: ");
+- printf("%d\n",
+- mnl_attr_get_u32(tb[ETHTOOL_A_TSINFO_PHC_INDEX]));
++ print_uint(PRINT_ANY, "phc-index",
++ "PTP Hardware Clock: %d\n",
++ mnl_attr_get_u32(tb[ETHTOOL_A_TSINFO_PHC_INDEX]));
+ } else {
+- printf("PTP Hardware Clock: ");
+- printf("none\n");
++ if (is_json_context())
++ print_int(PRINT_JSON, "phc-index", NULL, -1);
++ else
++ printf("PTP Hardware Clock: none\n");
+ }
+
+ ret = tsinfo_dump_list(nlctx, tb[ETHTOOL_A_TSINFO_TX_TYPES],
+ "Hardware Transmit Timestamp Modes", " none",
+- ETH_SS_TS_TX_TYPES);
++ ETH_SS_TS_TX_TYPES, "tx-types");
+ if (ret < 0)
+- return err_ret;
++ goto err_close_dev;
+
+ ret = tsinfo_dump_list(nlctx, tb[ETHTOOL_A_TSINFO_RX_FILTERS],
+ "Hardware Receive Filter Modes", " none",
+- ETH_SS_TS_RX_FILTERS);
++ ETH_SS_TS_RX_FILTERS, "rx-filters");
+ if (ret < 0)
+- return err_ret;
++ goto err_close_dev;
+
+ if (tb[ETHTOOL_A_TSINFO_STATS]) {
+ ret = tsinfo_show_stats(tb[ETHTOOL_A_TSINFO_STATS]);
+ if (ret < 0)
+- return err_ret;
++ goto err_close_dev;
+ }
+
++ if (!silent)
++ print_nl();
++ close_json_object();
+ return MNL_CB_OK;
++
++err_close_dev:
++ close_json_object();
++ return err_ret;
+ }
+
+ int tsinfo_qualifier_parser(struct nl_context *nlctx,
+@@ -271,5 +302,8 @@ int nl_tsinfo(struct cmd_context *ctx)
+ if (ret < 0)
+ return ret;
+
+- return nlsock_send_get_request(nlsk, tsinfo_reply_cb);
++ new_json_obj(ctx->json);
++ ret = nlsock_send_get_request(nlsk, tsinfo_reply_cb);
++ delete_json_obj();
++ return ret;
+ }
+--
+2.43.0
+
diff --git a/patches/linuxptp/0001-port-Fix-unicast-negotiation-doesn-t-recover-after-F.patch b/patches/linuxptp/0001-port-Fix-unicast-negotiation-doesn-t-recover-after-F.patch
new file mode 100644
index 000000000..08c5dae7e
--- /dev/null
+++ b/patches/linuxptp/0001-port-Fix-unicast-negotiation-doesn-t-recover-after-F.patch
@@ -0,0 +1,114 @@
+From d68e6a149340d987ebd19f86a7d792f4aaefdf98 Mon Sep 17 00:00:00 2001
+From: Vincent Cheng
+Date: Tue, 17 Sep 2024 15:23:54 -0400
+Subject: [PATCH 01/13] port: Fix unicast negotiation doesn't recover after
+ FAULT_DETECTED
+Organization: Wires
+
+_Problem_
+After a port link down/up or a tx_timestamp timeout issue, a port acting
+as unicast master does not issue ANNC messages after granting unicast
+request for ANNC.
+
+_Analysis_
+When a port FAULT occurs, the port transitions to FAULTY on FAULT_DETECTED
+and subsequently port_disable(p) and port_initialize(p) are called on port recovery.
+
+A port acting as a unicast master, stores clients in p->unicast_service->queue.
+
+When a port receives a unicast request, unicast_service_add() is called.
+
+In unicast_service_add(), if the request does not match an entry in
+p->unicast_service->queue, FD_UNICAST_SRV_TIMER is started via
+unicast_service_rearm_timer().
+
+If the unicast request matches an existing p->unicast_service->queue entry
+the request is considered an extension and FD_UNICAST_SRV_TIMER must
+already be running.
+
+port_disable() clears FD_UNICAST_SRV_TIMER, ie. stops FD_UNICAST_SRV_TIMER.
+However, port_disable() does not clear p->unicast_service->queue.
+When the port is restarted, the port retains the previous client data.
+
+After port recovery, when the client attempts to restart the unicast
+service, the request matches an existing entry in p->unicast_service->queue,
+and so FD_UNICAST_SRV_TIMER is not started because the port expected
+that the FD_UNICAST_SRV_TIMER is already running.
+
+_Fix_
+This patch clears the unicast client data in port_disable() so
+that upon recovery, the initial unicast request will be considered
+a new request and trigger the start of the FD_UNICAST_SRV_TIMER.
+
+v2:
+- Add missing sign-off
+- Send to develop-request instead of users list
+
+Signed-off-by: Vincent Cheng
+Signed-off-by: Joachim Wiberg
+---
+ port.c | 1 +
+ unicast_service.c | 21 +++++++++++++++++++++
+ unicast_service.h | 6 ++++++
+ 3 files changed, 28 insertions(+)
+
+diff --git a/port.c b/port.c
+index db35a44..282be66 100644
+--- a/port.c
++++ b/port.c
+@@ -1985,6 +1985,7 @@ void port_disable(struct port *p)
+ flush_peer_delay(p);
+
+ p->best = NULL;
++ unicast_service_clear_clients(p);
+ free_foreign_masters(p);
+ transport_close(p->trp, &p->fda);
+
+diff --git a/unicast_service.c b/unicast_service.c
+index 687468c..d7a4ecd 100644
+--- a/unicast_service.c
++++ b/unicast_service.c
+@@ -571,3 +571,24 @@ int unicast_service_timer(struct port *p)
+ }
+ return err;
+ }
++
++void unicast_service_clear_clients(struct port *p)
++{
++ struct unicast_client_address *client, *temp;
++ struct unicast_service_interval *interval;
++
++ if (!p->unicast_service) {
++ return;
++ }
++
++ while ((interval = pqueue_extract(p->unicast_service->queue)) != NULL) {
++
++ LIST_REMOVE(interval, list);
++
++ LIST_FOREACH_SAFE(client, &interval->clients, list, temp) {
++ LIST_REMOVE(client, list);
++ free(client);
++ }
++ free(interval);
++ }
++}
+\ No newline at end of file
+diff --git a/unicast_service.h b/unicast_service.h
+index f0d6487..8ea1a59 100644
+--- a/unicast_service.h
++++ b/unicast_service.h
+@@ -87,4 +87,10 @@ void unicast_service_remove(struct port *p, struct ptp_message *m,
+ */
+ int unicast_service_timer(struct port *p);
+
++/**
++ * Clears unicast clients on a given port.
++ * @param p The port in question.
++ */
++void unicast_service_clear_clients(struct port *p);
++
+ #endif
+--
+2.43.0
+
diff --git a/patches/linuxptp/0002-udp-Fix-port-specific-ptp-p2p_dst_ipv4-configuration.patch b/patches/linuxptp/0002-udp-Fix-port-specific-ptp-p2p_dst_ipv4-configuration.patch
new file mode 100644
index 000000000..bad0848c6
--- /dev/null
+++ b/patches/linuxptp/0002-udp-Fix-port-specific-ptp-p2p_dst_ipv4-configuration.patch
@@ -0,0 +1,92 @@
+From 4ea423b94264e4eeb0c2706fc3485b1fe283cc11 Mon Sep 17 00:00:00 2001
+From: Miroslav Lichvar
+Date: Wed, 25 Sep 2024 14:37:20 +0200
+Subject: [PATCH 02/13] udp: Fix port-specific ptp/p2p_dst_ipv4 configuration.
+Organization: Wires
+
+If different ports are configured with a different ptp_dst_ipv4 or
+p2p_dst_ipv4 address, only the last port in the configuration works
+correctly. This is caused by a global variable holding the
+destination address for all ports using the udp transport.
+
+Move the address to the udp structure to avoid the conflict between
+different ports, same as when port-specific scope in udp6 was fixed
+in commit a48666bee3dd ("udp6: Make mc6_addr transport-local").
+
+Fixes: 8a26c94cc88e ("udp+udp6: Make IP addresses configurable.")
+Signed-off-by: Miroslav Lichvar
+Signed-off-by: Joachim Wiberg
+---
+ udp.c | 16 ++++++++--------
+ 1 file changed, 8 insertions(+), 8 deletions(-)
+
+diff --git a/udp.c b/udp.c
+index 38d0ec4..c9b5f39 100644
+--- a/udp.c
++++ b/udp.c
+@@ -44,6 +44,7 @@ struct udp {
+ struct transport t;
+ struct address ip;
+ struct address mac;
++ struct in_addr mcast_addr[2];
+ };
+
+ static int mcast_bind(int fd, int index)
+@@ -146,8 +147,6 @@ no_socket:
+
+ enum { MC_PRIMARY, MC_PDELAY };
+
+-static struct in_addr mcast_addr[2];
+-
+ static int udp_open(struct transport *t, struct interface *iface,
+ struct fdarray *fda, enum timestamp_type ts_type)
+ {
+@@ -165,22 +164,22 @@ static int udp_open(struct transport *t, struct interface *iface,
+ sk_interface_addr(name, AF_INET, &udp->ip);
+
+ str = config_get_string(t->cfg, name, "ptp_dst_ipv4");
+- if (!inet_aton(str, &mcast_addr[MC_PRIMARY])) {
++ if (!inet_aton(str, &udp->mcast_addr[MC_PRIMARY])) {
+ pr_err("invalid ptp_dst_ipv4 %s", str);
+ return -1;
+ }
+
+ str = config_get_string(t->cfg, name, "p2p_dst_ipv4");
+- if (!inet_aton(str, &mcast_addr[MC_PDELAY])) {
++ if (!inet_aton(str, &udp->mcast_addr[MC_PDELAY])) {
+ pr_err("invalid p2p_dst_ipv4 %s", str);
+ return -1;
+ }
+
+- efd = open_socket(name, mcast_addr, EVENT_PORT, ttl);
++ efd = open_socket(name, udp->mcast_addr, EVENT_PORT, ttl);
+ if (efd < 0)
+ goto no_event;
+
+- gfd = open_socket(name, mcast_addr, GENERAL_PORT, ttl);
++ gfd = open_socket(name, udp->mcast_addr, GENERAL_PORT, ttl);
+ if (gfd < 0)
+ goto no_general;
+
+@@ -223,6 +222,7 @@ static int udp_send(struct transport *t, struct fdarray *fda,
+ enum transport_event event, int peer, void *buf, int len,
+ struct address *addr, struct hw_timestamp *hwts)
+ {
++ struct udp *udp = container_of(t, struct udp, t);
+ struct address addr_buf;
+ unsigned char junk[1600];
+ ssize_t cnt;
+@@ -243,8 +243,8 @@ static int udp_send(struct transport *t, struct fdarray *fda,
+ if (!addr) {
+ memset(&addr_buf, 0, sizeof(addr_buf));
+ addr_buf.sin.sin_family = AF_INET;
+- addr_buf.sin.sin_addr = peer ? mcast_addr[MC_PDELAY] :
+- mcast_addr[MC_PRIMARY];
++ addr_buf.sin.sin_addr = peer ? udp->mcast_addr[MC_PDELAY] :
++ udp->mcast_addr[MC_PRIMARY];
+ addr_buf.len = sizeof(addr_buf.sin);
+ addr = &addr_buf;
+ }
+--
+2.43.0
+
diff --git a/patches/linuxptp/0003-pmc-Avoid-race-conditions-in-agent-update.patch b/patches/linuxptp/0003-pmc-Avoid-race-conditions-in-agent-update.patch
new file mode 100644
index 000000000..2245812ec
--- /dev/null
+++ b/patches/linuxptp/0003-pmc-Avoid-race-conditions-in-agent-update.patch
@@ -0,0 +1,82 @@
+From e76bb37019605dea4acd9ccec620a3faec1ba402 Mon Sep 17 00:00:00 2001
+From: Miroslav Lichvar
+Date: Thu, 17 Oct 2024 15:05:21 +0200
+Subject: [PATCH 03/13] pmc: Avoid race conditions in agent update.
+Organization: Wires
+
+The pmc_agent_update() function updates the subscription to
+notifications and also the current UTC offset. It uses a timeout of 0
+to avoid blocking. When the pmc client sends the first request, the
+response from ptp4l may not come quickly enough to be received in the
+same run_pmc() call. It then sends the other request and checks for the
+response. If it is the response to the first request, it will be ignored.
+The update works correctly only if both responses are quick enough to be
+received in the same call, or are both slow enough that they are
+received in the next call of the pmc_agent_update() function.
+
+The function needs to be called a random number of times in order to
+finish one update. If the mismatch between requests and responses
+happened consistently, the agent would never reach the up-to-date state
+and phc2sys would not enter the main synchronization loop.
+
+Split the update into two phases, where only one thing is updated at a
+time. The function now needs to be called at most 3 times to update both
+the subscription and UTC offset, assuming it is not interrupted by
+another request outside of the agent's update.
+
+Signed-off-by: Miroslav Lichvar
+Reviewed-by: Jacob Keller
+Signed-off-by: Joachim Wiberg
+---
+ pmc_agent.c | 24 ++++++++++++++++++------
+ 1 file changed, 18 insertions(+), 6 deletions(-)
+
+diff --git a/pmc_agent.c b/pmc_agent.c
+index 86b6ee6..d1a3367 100644
+--- a/pmc_agent.c
++++ b/pmc_agent.c
+@@ -37,6 +37,7 @@ struct pmc_agent {
+ struct pmc *pmc;
+ uint64_t pmc_last_update;
+ uint64_t update_interval;
++ int update_phase;
+
+ struct defaultDS dds;
+ bool dds_valid;
+@@ -427,16 +428,27 @@ int pmc_agent_update(struct pmc_agent *node)
+ ts = tp.tv_sec * NS_PER_SEC + tp.tv_nsec;
+
+ if (ts - node->pmc_last_update >= node->update_interval) {
+- if (node->stay_subscribed) {
+- renew_subscription(node, 0);
+- }
+- if (!pmc_agent_query_utc_offset(node, 0)) {
++ switch (node->update_phase) {
++ case 0:
++ if (node->stay_subscribed &&
++ renew_subscription(node, 0))
++ break;
++ node->update_phase++;
++ /* Fall through */
++ case 1:
++ if (pmc_agent_query_utc_offset(node, 0))
++ break;
++ node->update_phase++;
++ /* Fall through */
++ default:
+ node->pmc_last_update = ts;
++ node->update_phase = 0;
++ break;
+ }
++ } else {
++ run_pmc(node, 0, -1, &msg);
+ }
+
+- run_pmc(node, 0, -1, &msg);
+-
+ return 0;
+ }
+
+--
+2.43.0
+
diff --git a/patches/linuxptp/0004-phc2sys-Wait-until-pmc-agent-is-subscribed.patch b/patches/linuxptp/0004-phc2sys-Wait-until-pmc-agent-is-subscribed.patch
new file mode 100644
index 000000000..0a25b03d1
--- /dev/null
+++ b/patches/linuxptp/0004-phc2sys-Wait-until-pmc-agent-is-subscribed.patch
@@ -0,0 +1,45 @@
+From 1942fde263f40d318d56acf824626641263facd0 Mon Sep 17 00:00:00 2001
+From: Miroslav Lichvar
+Date: Thu, 17 Oct 2024 15:05:22 +0200
+Subject: [PATCH 04/13] phc2sys: Wait until pmc agent is subscribed.
+Organization: Wires
+
+When phc2sys is configured with multiple domains, different domains may
+have their pmc agent subscribed after different number of calls of the
+pmc_agent_update() function depending on how quickly responses from
+ptp4l are received. If one domain triggers reconfiguration and the other
+domain does not have its agent subscribed yet, it will not have any of
+its clocks synchronized until a port changes state and triggers another
+reconfiguration of the domain.
+
+To avoid this problem, wait for each domain to have its agent subscribed
+before entering the main synchronization loop. Use a 10ms update
+interval to speed up the start of phc2sys.
+
+Signed-off-by: Miroslav Lichvar
+Reviewed-by: Jacob Keller
+Signed-off-by: Joachim Wiberg
+---
+ phc2sys.c | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/phc2sys.c b/phc2sys.c
+index 6113539..47e896e 100644
+--- a/phc2sys.c
++++ b/phc2sys.c
+@@ -962,6 +962,12 @@ static int auto_init_ports(struct domain *domain)
+ return -1;
+ }
+
++ while (!pmc_agent_is_subscribed(domain->agent)) {
++ usleep(10000);
++ if (pmc_agent_update(domain->agent) < 0)
++ return -1;
++ }
++
+ for (i = 1; i <= number_ports; i++) {
+ err = pmc_agent_query_port_properties(domain->agent, 1000, i,
+ &state, ×tamping,
+--
+2.43.0
+
diff --git a/patches/linuxptp/0005-Fix-issue-where-MAC-driver-is-configured-with-incorr.patch b/patches/linuxptp/0005-Fix-issue-where-MAC-driver-is-configured-with-incorr.patch
new file mode 100644
index 000000000..f4c71b442
--- /dev/null
+++ b/patches/linuxptp/0005-Fix-issue-where-MAC-driver-is-configured-with-incorr.patch
@@ -0,0 +1,51 @@
+From 9e1b0df61c4dc9bb1a4aac11acb06e4f1f2c68f7 Mon Sep 17 00:00:00 2001
+From: William Comly
+Date: Thu, 31 Oct 2024 15:10:20 -0400
+Subject: [PATCH 05/13] Fix issue where MAC driver is configured with incorrect
+ adjustment flags sometimes returned by SIOCGHWTSTAMP test.
+Organization: Wires
+
+Once the check for the VLAN bonding flag is complete, clear the ifreq
+message to ensure only the intended configuration and flags are set
+in the driver.
+
+Signed-off-by: William Comly
+Signed-off-by: Joachim Wiberg
+---
+ sk.c | 20 +++++++++-----------
+ 1 file changed, 9 insertions(+), 11 deletions(-)
+
+diff --git a/sk.c b/sk.c
+index aadb237..4860af2 100644
+--- a/sk.c
++++ b/sk.c
+@@ -69,17 +69,15 @@ static int hwts_init(int fd, const char *device, int rx_filter,
+ /* Test if VLAN over bond is supported. */
+ cfg.flags = HWTSTAMP_FLAG_BONDED_PHC_INDEX;
+ err = ioctl(fd, SIOCGHWTSTAMP, &ifreq);
+- if (err < 0) {
+- /*
+- * Fall back without flag if user runs new build on old kernel
+- * or if driver does not support SIOCGHWTSTAMP ioctl.
+- */
+- if (errno == EINVAL || errno == EOPNOTSUPP) {
+- init_ifreq(&ifreq, &cfg, device);
+- } else {
+- pr_err("ioctl SIOCGHWTSTAMP failed: %m");
+- return err;
+- }
++ if (err < 0 && errno != EINVAL && errno != EOPNOTSUPP) {
++ pr_err("ioctl SIOCGHWTSTAMP failed: %m");
++ return err;
++ }
++
++ init_ifreq(&ifreq, &cfg, device);
++ /* If VLAN over bond supported in kernel, configure flag in driver. */
++ if (err == 0) {
++ cfg.flags = HWTSTAMP_FLAG_BONDED_PHC_INDEX;
+ }
+
+ switch (sk_hwts_filter_mode) {
+--
+2.43.0
+
diff --git a/patches/linuxptp/0006-pmc_agent-Use-longer-update-interval-when-not-subscr.patch b/patches/linuxptp/0006-pmc_agent-Use-longer-update-interval-when-not-subscr.patch
new file mode 100644
index 000000000..20d4cac36
--- /dev/null
+++ b/patches/linuxptp/0006-pmc_agent-Use-longer-update-interval-when-not-subscr.patch
@@ -0,0 +1,50 @@
+From 0bb9080c1bf631ae0265475d75860b6acd92ed2c Mon Sep 17 00:00:00 2001
+From: Miroslav Lichvar
+Date: Tue, 26 Nov 2024 15:10:32 +0100
+Subject: [PATCH 06/13] pmc_agent: Use longer update interval when not
+ subscribed.
+Organization: Wires
+
+When phc2sys is started with the -w option, the pmc agent is not
+subscribed to events by the pmc_agent_subscribe() function, which also
+sets the update interval. The update interval in this case is zero,
+which means the pmc agent is trying to update the currentUtcOffset value
+on every call of pmc_agent_update(), i.e. on every clock update in
+phc2sys.
+
+Set a default update interval of 60 seconds to reduce the rate of
+pmc requests.
+
+Fixes: e3ca7ea90a9e ("pmc_agent: Make update interval configurable.")
+Signed-off-by: Miroslav Lichvar
+Reviewed-by: Jacob Keller
+Signed-off-by: Joachim Wiberg
+---
+ pmc_agent.c | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/pmc_agent.c b/pmc_agent.c
+index d1a3367..663adc0 100644
+--- a/pmc_agent.c
++++ b/pmc_agent.c
+@@ -33,6 +33,9 @@
+ #define UPDATES_PER_SUBSCRIPTION 3
+ #define MIN_UPDATE_INTERVAL 10
+
++/* Update interval if the agent not subscribed, just polling the UTC offset */
++#define DEFAULT_UPDATE_INTERVAL 60
++
+ struct pmc_agent {
+ struct pmc *pmc;
+ uint64_t pmc_last_update;
+@@ -253,6 +256,7 @@ int init_pmc_node(struct config *cfg, struct pmc_agent *node, const char *uds,
+ }
+ node->recv_subscribed = recv_subscribed;
+ node->recv_context = context;
++ node->update_interval = DEFAULT_UPDATE_INTERVAL * NS_PER_SEC;
+
+ return 0;
+ }
+--
+2.43.0
+
diff --git a/patches/linuxptp/0007-phc2sys-Don-t-disable-pmc-agent-with-s-d-w-options.patch b/patches/linuxptp/0007-phc2sys-Don-t-disable-pmc-agent-with-s-d-w-options.patch
new file mode 100644
index 000000000..a759b8cde
--- /dev/null
+++ b/patches/linuxptp/0007-phc2sys-Don-t-disable-pmc-agent-with-s-d-w-options.patch
@@ -0,0 +1,40 @@
+From 5a97466a0ed88bae244566c2a5dba85ce72e4f01 Mon Sep 17 00:00:00 2001
+From: Miroslav Lichvar
+Date: Tue, 26 Nov 2024 15:10:33 +0100
+Subject: [PATCH 07/13] phc2sys: Don't disable pmc agent with -s -d -w options.
+Organization: Wires
+
+When phc2sys is started with -s and -d options to combine a PPS device
+and PHC device as a time source, but without an offset specified by
+the -O option, the pmc agent is disabled after waiting for ptp4l to have
+a port in a synchronized state. This prevents phc2sys from following
+changes in the currentUtcOffset value.
+
+Disable the pmc agent only if no PHC device is specified by the -s
+option, i.e. there are no PHC readings to which the UTC offset could be
+applied.
+
+Fixes: 5f1b419c4102 ("phc2sys: Replace magical test with a proper test.")
+Signed-off-by: Miroslav Lichvar
+Reviewed-by: Jacob Keller
+Signed-off-by: Joachim Wiberg
+---
+ phc2sys.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/phc2sys.c b/phc2sys.c
+index 47e896e..5962f6c 100644
+--- a/phc2sys.c
++++ b/phc2sys.c
+@@ -1547,7 +1547,7 @@ int main(int argc, char *argv[])
+
+ if (domains[0].forced_sync_offset ||
+ !phc2sys_using_systemclock(&domains[0]) ||
+- hardpps_configured(pps_fd)) {
++ (hardpps_configured(pps_fd) && !src_name)) {
+ pmc_agent_disable(domains[0].agent);
+ }
+ }
+--
+2.43.0
+
diff --git a/patches/linuxptp/0008-port_signaling.c-ensure-that-signaling-messages-resp.patch b/patches/linuxptp/0008-port_signaling.c-ensure-that-signaling-messages-resp.patch
new file mode 100644
index 000000000..1f526fc8c
--- /dev/null
+++ b/patches/linuxptp/0008-port_signaling.c-ensure-that-signaling-messages-resp.patch
@@ -0,0 +1,29 @@
+From 9822b22a07e05eb087cedfbb4a21ceecbb8ec74e Mon Sep 17 00:00:00 2001
+From: William Comly
+Date: Wed, 19 Feb 2025 10:12:28 -0500
+Subject: [PATCH 08/13] port_signaling.c: ensure that signaling messages
+ respect ptp_minor_version in message header
+Organization: Wires
+
+Signed-off-by: William Comly
+Signed-off-by: Joachim Wiberg
+---
+ port_signaling.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/port_signaling.c b/port_signaling.c
+index ca4202f..cf28756 100644
+--- a/port_signaling.c
++++ b/port_signaling.c
+@@ -41,7 +41,7 @@ struct ptp_message *port_signaling_construct(struct port *p,
+ }
+ msg->hwts.type = p->timestamping;
+ msg->header.tsmt = SIGNALING | p->transportSpecific;
+- msg->header.ver = PTP_VERSION;
++ msg->header.ver = ptp_hdr_ver;
+ msg->header.messageLength = sizeof(struct signaling_msg);
+ msg->header.domainNumber = clock_domain_number(p->clock);
+ msg->header.sourcePortIdentity = p->portIdentity;
+--
+2.43.0
+
diff --git a/patches/linuxptp/0009-port-Refresh-link-status-on-faults.patch b/patches/linuxptp/0009-port-Refresh-link-status-on-faults.patch
new file mode 100644
index 000000000..5e0ac7e3e
--- /dev/null
+++ b/patches/linuxptp/0009-port-Refresh-link-status-on-faults.patch
@@ -0,0 +1,82 @@
+From b8a3020e806dd6252e7edd434ae3940706a0b516 Mon Sep 17 00:00:00 2001
+From: Miroslav Lichvar
+Date: Tue, 4 Mar 2025 15:53:37 +0100
+Subject: [PATCH 09/13] port: Refresh link status on faults.
+Organization: Wires
+
+ptp4l gets the ENOBUFS error on the netlink socket when the kernel has
+to drop messages due to full socket buffer. If ptp4l has a port in the
+faulty state waiting for the link to go up and that event corresponds
+to one of the dropped netlink messages, the port will be stuck in the
+faulty state until the link goes down and up again.
+
+To prevent the port from getting stuck, request the current link status
+when dispatching the EV_FAULT_DETECTED event. Also, reopen the socket to
+get rid of the buffered messages when handling the fault and again when
+reinitializing the port.
+
+Signed-off-by: Miroslav Lichvar
+Reviewed-by: Jacob Keller
+Signed-off-by: Joachim Wiberg
+---
+ port.c | 30 +++++++++++++++++++++++-------
+ 1 file changed, 23 insertions(+), 7 deletions(-)
+
+diff --git a/port.c b/port.c
+index 282be66..5eea2f2 100644
+--- a/port.c
++++ b/port.c
+@@ -1975,6 +1975,20 @@ static int port_cmlds_initialize(struct port *p)
+ return port_cmlds_renew(p, now.tv_sec);
+ }
+
++static void port_rtnl_initialize(struct port *p)
++{
++ /* Reopen the socket to get rid of buffered messages */
++ if (p->fda.fd[FD_RTNL] >= 0) {
++ rtnl_close(p->fda.fd[FD_RTNL]);
++ }
++ p->fda.fd[FD_RTNL] = rtnl_open();
++ if (p->fda.fd[FD_RTNL] >= 0) {
++ rtnl_link_query(p->fda.fd[FD_RTNL], interface_name(p->iface));
++ }
++
++ clock_fda_changed(p->clock);
++}
++
+ void port_disable(struct port *p)
+ {
+ int i;
+@@ -2088,13 +2102,8 @@ int port_initialize(struct port *p)
+ if (p->bmca == BMCA_NOOP) {
+ port_set_delay_tmo(p);
+ }
+- if (p->fda.fd[FD_RTNL] == -1) {
+- p->fda.fd[FD_RTNL] = rtnl_open();
+- }
+- if (p->fda.fd[FD_RTNL] >= 0) {
+- const char *ifname = interface_name(p->iface);
+- rtnl_link_query(p->fda.fd[FD_RTNL], ifname);
+- }
++
++ port_rtnl_initialize(p);
+ }
+
+ port_nrate_initialize(p);
+@@ -3769,6 +3778,13 @@ int port_state_update(struct port *p, enum fsm_event event, int mdiff)
+ if (port_link_status_get(p) && clear_fault_asap(&i)) {
+ pr_notice("%s: clearing fault immediately", p->log_name);
+ next = p->state_machine(next, EV_FAULT_CLEARED, 0);
++ } else if (event == EV_FAULT_DETECTED) {
++ /*
++ * Reopen the netlink socket and refresh the link
++ * status in case the fault was triggered by a missed
++ * netlink message (ENOBUFS).
++ */
++ port_rtnl_initialize(p);
+ }
+ }
+
+--
+2.43.0
+
diff --git a/patches/linuxptp/0010-uds-Copy-ownership-of-server-socket-in-pmc-clients.patch b/patches/linuxptp/0010-uds-Copy-ownership-of-server-socket-in-pmc-clients.patch
new file mode 100644
index 000000000..a0dcb5a58
--- /dev/null
+++ b/patches/linuxptp/0010-uds-Copy-ownership-of-server-socket-in-pmc-clients.patch
@@ -0,0 +1,58 @@
+From 3dece8ac8b324e702bf54875b53aee1eb49340e0 Mon Sep 17 00:00:00 2001
+From: "Miroslav Lichvar (via linuxptp-devel Mailing List)"
+
+Date: Thu, 31 Jul 2025 11:35:46 +0200
+Subject: [PATCH 10/13] uds: Copy ownership of server socket in pmc clients.
+Organization: Wires
+
+ptp4l sending a response to a pmc client needs to have permissions to
+write to the client's UNIX domain socket. If ptp4l runs under a non-root
+user, it cannot write to sockets bound by the pmc client if it did that
+as root.
+
+After binding the client socket, change its owner to the owner of the
+server socket, so it can send the client a response.
+
+Signed-off-by: Miroslav Lichvar
+Reviewed-by: Jacob Keller
+Signed-off-by: Joachim Wiberg
+---
+ uds.c | 15 +++++++++++++++
+ 1 file changed, 15 insertions(+)
+
+diff --git a/uds.c b/uds.c
+index 4ddee7b..ce7e92d 100644
+--- a/uds.c
++++ b/uds.c
+@@ -60,6 +60,7 @@ static int uds_open(struct transport *t, struct interface *iface, struct fdarray
+ const char* file_mode_cfg;
+ struct sockaddr_un sa;
+ mode_t file_mode;
++ struct stat st;
+ int fd, err;
+
+ fd = socket(AF_LOCAL, SOCK_DGRAM, 0);
+@@ -97,6 +98,20 @@ static int uds_open(struct transport *t, struct interface *iface, struct fdarray
+ uds->address.len = sizeof(sa);
+
+ chmod(name, file_mode);
++
++ /*
++ * In the client, copy the ownership of the server's socket if it runs
++ * under a non-root user to allow it to send a response to the client
++ * running under root. Avoid following a symlink if the socket is
++ * replaced (e.g. by compromised ptp4l process).
++ */
++ if (uds_path[0] != '\0') {
++ if (!lstat(uds_path, &st) && (st.st_uid || st.st_gid) &&
++ lchown(name, st.st_uid, st.st_gid)) {
++ pr_err("uds: failed to change socket ownership: %m");
++ }
++ }
++
+ fda->fd[FD_EVENT] = -1;
+ fda->fd[FD_GENERAL] = fd;
+ return 0;
+--
+2.43.0
+
diff --git a/patches/linuxptp/0011-uds-Don-t-call-chmod-on-client-socket.patch b/patches/linuxptp/0011-uds-Don-t-call-chmod-on-client-socket.patch
new file mode 100644
index 000000000..8ec2ab92f
--- /dev/null
+++ b/patches/linuxptp/0011-uds-Don-t-call-chmod-on-client-socket.patch
@@ -0,0 +1,56 @@
+From 6bfc1de21b64ac17145b119cb636d1c1b3d90a75 Mon Sep 17 00:00:00 2001
+From: "Miroslav Lichvar (via linuxptp-devel Mailing List)"
+
+Date: Thu, 31 Jul 2025 11:35:47 +0200
+Subject: [PATCH 11/13] uds: Don't call chmod() on client socket.
+Organization: Wires
+
+The pmc clients should not need to modify the permissions of their
+socket (following the uds_file_mode setting), they can rely on their
+umask.
+
+Make the chmod() call on the bound socket only in the server.
+
+This removes a race condition between the bind() and chmod() calls that
+could potentially be exploited by ptp4l running under a non-root user.
+It could replace the socket with a symlink in order to make the client
+running under root to change the mode of a different file.
+
+Signed-off-by: Miroslav Lichvar
+Reviewed-by: Jacob Keller
+Signed-off-by: Joachim Wiberg
+---
+ uds.c | 7 ++++---
+ 1 file changed, 4 insertions(+), 3 deletions(-)
+
+diff --git a/uds.c b/uds.c
+index ce7e92d..d74b7a8 100644
+--- a/uds.c
++++ b/uds.c
+@@ -97,19 +97,20 @@ static int uds_open(struct transport *t, struct interface *iface, struct fdarray
+ uds->address.sun = sa;
+ uds->address.len = sizeof(sa);
+
+- chmod(name, file_mode);
+-
+ /*
+ * In the client, copy the ownership of the server's socket if it runs
+ * under a non-root user to allow it to send a response to the client
+ * running under root. Avoid following a symlink if the socket is
+- * replaced (e.g. by compromised ptp4l process).
++ * replaced (e.g. by compromised ptp4l process). The server just sets
++ * the permissions on its socket per configuration.
+ */
+ if (uds_path[0] != '\0') {
+ if (!lstat(uds_path, &st) && (st.st_uid || st.st_gid) &&
+ lchown(name, st.st_uid, st.st_gid)) {
+ pr_err("uds: failed to change socket ownership: %m");
+ }
++ } else {
++ chmod(name, file_mode);
+ }
+
+ fda->fd[FD_EVENT] = -1;
+--
+2.43.0
+
diff --git a/patches/linuxptp/0012-port-Allow-mixing-wildcard-identities-and-exact-iden.patch b/patches/linuxptp/0012-port-Allow-mixing-wildcard-identities-and-exact-iden.patch
new file mode 100644
index 000000000..beb608ae9
--- /dev/null
+++ b/patches/linuxptp/0012-port-Allow-mixing-wildcard-identities-and-exact-iden.patch
@@ -0,0 +1,82 @@
+From 5a192e152f4d19ff5899960154f1e14bbd0c6bd7 Mon Sep 17 00:00:00 2001
+From: "Maxime Chevallier (via linuxptp-devel Mailing List)"
+
+Date: Wed, 4 Feb 2026 10:16:11 +0100
+Subject: [PATCH 12/13] port: Allow mixing wildcard identities and exact
+ identities in messages
+Organization: Wires
+
+A Port Identity is made of a Clock Identity and a Port Number. Each of
+these fields allow wildcards values.
+
+The current implementation checks if either both these fields contain an
+exact value, or if both contains a wildcard.
+
+Make so that we check for wildcards on each field independently. To
+avoid hard to read comparisons, introduce helper functions to compare
+each field in PortIdentity.
+
+Signed-off-by: Maxime Chevallier
+Signed-off-by: Joachim Wiberg
+---
+ port_signaling.c | 6 ++++--
+ util.h | 26 ++++++++++++++++++++++++++
+ 2 files changed, 30 insertions(+), 2 deletions(-)
+
+diff --git a/port_signaling.c b/port_signaling.c
+index cf28756..b34ebe9 100644
+--- a/port_signaling.c
++++ b/port_signaling.c
+@@ -151,8 +151,10 @@ int process_signaling(struct port *p, struct ptp_message *m)
+ }
+
+ /* Ignore signaling messages not addressed to this port. */
+- if (!pid_eq(&m->signaling.targetPortIdentity, &p->portIdentity) &&
+- !pid_eq(&m->signaling.targetPortIdentity, &wildcard_pid)) {
++ if ((!pid_cid_eq(&m->signaling.targetPortIdentity, &p->portIdentity) &&
++ !pid_cid_eq(&m->signaling.targetPortIdentity, &wildcard_pid)) ||
++ (!pid_pn_eq(&m->signaling.targetPortIdentity, &p->portIdentity) &&
++ !pid_pn_eq(&m->signaling.targetPortIdentity, &wildcard_pid))) {
+ return 0;
+ }
+
+diff --git a/util.h b/util.h
+index b228745..7552353 100644
+--- a/util.h
++++ b/util.h
+@@ -158,6 +158,32 @@ static inline int pid_eq(const struct PortIdentity *a,
+ return memcmp(a, b, sizeof(*a)) == 0;
+ }
+
++/**
++ * Compare two port identities for PortIdentity.clockIdentity equality.
++ *
++ * @param a First port identity.
++ * @param b Second port identity.
++ * @return 1 if identities are equal, 0 otherwise.
++ */
++static inline int pid_cid_eq(const struct PortIdentity *a,
++ const struct PortIdentity *b)
++{
++ return cid_eq(&a->clockIdentity, &b->clockIdentity);
++}
++
++/**
++ * Compare two port identities for PortIdentity.portNumber equality.
++ *
++ * @param a First port identity.
++ * @param b Second port identity.
++ * @return 1 if identities are equal, 0 otherwise.
++ */
++static inline int pid_pn_eq(const struct PortIdentity *a,
++ const struct PortIdentity *b)
++{
++ return a->portNumber == b->portNumber;
++}
++
+ /**
+ * Convert a string containing a network address into binary form.
+ * @param type The network transport type of the address.
+--
+2.43.0
+
diff --git a/patches/linuxptp/0013-Add-pidfile-support-to-ptp4l-phc2sys-and-timemaster.patch b/patches/linuxptp/0013-Add-pidfile-support-to-ptp4l-phc2sys-and-timemaster.patch
new file mode 100644
index 000000000..e78e284a0
--- /dev/null
+++ b/patches/linuxptp/0013-Add-pidfile-support-to-ptp4l-phc2sys-and-timemaster.patch
@@ -0,0 +1,386 @@
+From 4fdb58ce4e052862f0dc7432d84b19febd5256a5 Mon Sep 17 00:00:00 2001
+From: Joachim Wiberg
+Date: Fri, 10 Apr 2026 14:33:56 +0200
+Subject: [PATCH 13/13] Add pidfile support to ptp4l, phc2sys, and timemaster
+Organization: Wires
+
+Add pidfile.c derived from OpenBSD via libite. The pidfile() function
+creates a PID file and registers an atexit() handler to clean it up on
+normal exit.
+
+For ptp4l and phc2sys the pidfile path is set via the 'pidfile' global
+config option (or -u on the ptp4l command line). For timemaster,
+which uses its own config format, the path is given with -u .
+
+Signed-off-by: Joachim Wiberg
+---
+ config.c | 1 +
+ makefile | 12 ++---
+ phc2sys.c | 7 +++
+ pidfile.c | 142 +++++++++++++++++++++++++++++++++++++++++++++++++++
+ pidfile.h | 10 ++++
+ ptp4l.c | 14 ++++-
+ timemaster.c | 19 +++++--
+ 7 files changed, 194 insertions(+), 11 deletions(-)
+ create mode 100644 pidfile.c
+ create mode 100644 pidfile.h
+
+diff --git a/config.c b/config.c
+index d0bc32c..4b46542 100644
+--- a/config.c
++++ b/config.c
+@@ -341,6 +341,7 @@ struct config_item config_tab[] = {
+ PORT_ITEM_INT("power_profile.2011.networkTimeInaccuracy", 0xFFFFFFFF, -1, INT_MAX),
+ PORT_ITEM_INT("power_profile.2017.totalTimeInaccuracy", 0xFFFFFFFF, -1, INT_MAX),
+ PORT_ITEM_INT("power_profile.grandmasterID", 0, 0, 0xFFFF),
++ GLOB_ITEM_STR("pidfile", NULL),
+ GLOB_ITEM_INT("priority1", 128, 0, UINT8_MAX),
+ GLOB_ITEM_INT("priority2", 128, 0, UINT8_MAX),
+ GLOB_ITEM_STR("productDescription", ";;"),
+diff --git a/makefile b/makefile
+index 3c2406b..67622e1 100644
+--- a/makefile
++++ b/makefile
+@@ -31,9 +31,9 @@ TS2PHC = ts2phc.o lstab.o nmea.o serial.o sock.o ts2phc_generic_pps_source.o \
+ ts2phc_nmea_pps_source.o ts2phc_phc_pps_source.o ts2phc_pps_sink.o ts2phc_pps_source.o
+ OBJ = bmc.o clock.o clockadj.o clockcheck.o config.o designated_fsm.o \
+ e2e_tc.o fault.o $(FILTERS) fsm.o hash.o interface.o monitor.o msg.o phc.o \
+- pmc_common.o port.o port_signaling.o pqueue.o print.o ptp4l.o p2p_tc.o rtnl.o \
+- $(SECURITY) $(SERVOS) sk.o stats.o tc.o $(TRANSP) telecom.o tlv.o tsproc.o \
+- unicast_client.o unicast_fsm.o unicast_service.o util.o version.o
++ pidfile.o pmc_common.o port.o port_signaling.o pqueue.o print.o ptp4l.o \
++ p2p_tc.o rtnl.o $(SECURITY) $(SERVOS) sk.o stats.o tc.o $(TRANSP) telecom.o \
++ tlv.o tsproc.o unicast_client.o unicast_fsm.o unicast_service.o util.o version.o
+
+ OBJECTS = $(OBJ) hwstamp_ctl.o nsm.o phc2sys.o phc_ctl.o pmc.o pmc_agent.o \
+ pmc_common.o sysoff.o timemaster.o $(TS2PHC) tz2alt.o
+@@ -78,14 +78,14 @@ pmc: config.o hash.o interface.o msg.o phc.o pmc.o pmc_common.o print.o \
+ $(SECURITY) sk.o tlv.o $(TRANSP) util.o version.o
+
+ phc2sys: clockadj.o clockcheck.o config.o hash.o interface.o msg.o \
+- phc.o phc2sys.o pmc_agent.o pmc_common.o print.o $(SECURITY) $(SERVOS) \
+- sk.o stats.o sysoff.o tlv.o $(TRANSP) util.o version.o
++ phc.o phc2sys.o pidfile.o pmc_agent.o pmc_common.o print.o $(SECURITY) \
++ $(SERVOS) sk.o stats.o sysoff.o tlv.o $(TRANSP) util.o version.o
+
+ hwstamp_ctl: hwstamp_ctl.o version.o
+
+ phc_ctl: phc_ctl.o phc.o sk.o util.o clockadj.o sysoff.o print.o version.o
+
+-timemaster: phc.o print.o rtnl.o sk.o timemaster.o util.o version.o
++timemaster: phc.o pidfile.o print.o rtnl.o sk.o timemaster.o util.o version.o
+
+ ts2phc: config.o clockadj.o hash.o interface.o msg.o phc.o pmc_agent.o \
+ pmc_common.o print.o $(SECURITY) $(SERVOS) sk.o $(TS2PHC) tlv.o transport.o \
+diff --git a/phc2sys.c b/phc2sys.c
+index 5962f6c..b272adf 100644
+--- a/phc2sys.c
++++ b/phc2sys.c
+@@ -56,6 +56,7 @@
+ #include "sysoff.h"
+ #include "tlv.h"
+ #include "uds.h"
++#include "pidfile.h"
+ #include "util.h"
+ #include "version.h"
+
+@@ -1432,6 +1433,12 @@ int main(int argc, char *argv[])
+ print_set_syslog(config_get_int(cfg, NULL, "use_syslog"));
+ print_set_level(config_get_int(cfg, NULL, "logging_level"));
+
++ if (config_get_string(cfg, NULL, "pidfile") &&
++ pidfile(config_get_string(cfg, NULL, "pidfile"))) {
++ fprintf(stderr, "failed to create pidfile\n");
++ goto end;
++ }
++
+ settings.free_running = config_get_int(cfg, NULL, "free_running");
+ settings.servo_type = config_get_int(cfg, NULL, "clock_servo");
+ if (settings.free_running || settings.servo_type == CLOCK_SERVO_NTPSHM) {
+diff --git a/pidfile.c b/pidfile.c
+new file mode 100644
+index 0000000..42121b0
+--- /dev/null
++++ b/pidfile.c
+@@ -0,0 +1,142 @@
++/* Updated by troglobit for libite/finit/uftpd projects 2016/07/04 */
++/* $OpenBSD: pidfile.c,v 1.11 2015/06/03 02:24:36 millert Exp $ */
++/* $NetBSD: pidfile.c,v 1.4 2001/02/19 22:43:42 cgd Exp $ */
++
++/*-
++ * Copyright (c) 1999 The NetBSD Foundation, Inc.
++ * All rights reserved.
++ *
++ * This code is derived from software contributed to The NetBSD Foundation
++ * by Jason R. Thorpe.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ * 1. Redistributions of source code must retain the above copyright
++ * notice, this list of conditions and the following disclaimer.
++ * 2. Redistributions in binary form must reproduce the above copyright
++ * notice, this list of conditions and the following disclaimer in the
++ * documentation and/or other materials provided with the distribution.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
++ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
++ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
++ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
++ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
++ * POSSIBILITY OF SUCH DAMAGE.
++ */
++
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++
++#include "pidfile.h"
++
++static char *pidfile_path = NULL;
++static pid_t pidfile_pid = 0;
++
++static void pidfile_cleanup(void);
++
++static const char *pidfile_rundir = _PATH_VARRUN;
++extern char *__progname;
++
++/**
++ * Create or update mtime of process PID file.
++ * @param basename Program name, or NULL, may start with '/'
++ *
++ * If @p basename is NULL the implicit @a __progname variable from the
++ * C-library is used. If @p basename starts with '/' it is used as the
++ * absolute path to the PID file.
++ *
++ * @returns POSIX OK(0) on success, non-zero with errno set on error.
++ */
++int pidfile(const char *basename)
++{
++ int save_errno;
++ int atexit_already;
++ pid_t pid;
++ FILE *f;
++
++ if (basename == NULL)
++ basename = __progname;
++
++ pid = getpid();
++ atexit_already = 0;
++
++ if (pidfile_path != NULL) {
++ if (!access(pidfile_path, R_OK) && pid == pidfile_pid) {
++ utimensat(0, pidfile_path, NULL, 0);
++ return 0;
++ }
++ free(pidfile_path);
++ pidfile_path = NULL;
++ atexit_already = 1;
++ }
++
++ if (basename[0] != '/') {
++ size_t len = strlen(pidfile_rundir);
++ int slash = pidfile_rundir[len > 0 ? len - 1 : 0] != '/';
++
++ if (asprintf(&pidfile_path, "%s%s%s.pid",
++ pidfile_rundir, slash ? "/" : "", basename) == -1)
++ return -1;
++ } else {
++ if (asprintf(&pidfile_path, "%s", basename) == -1)
++ return -1;
++ }
++
++ if ((f = fopen(pidfile_path, "w")) == NULL) {
++ save_errno = errno;
++ free(pidfile_path);
++ pidfile_path = NULL;
++ errno = save_errno;
++ return -1;
++ }
++
++ if (fprintf(f, "%ld\n", (long)pid) <= 0 || fflush(f) != 0) {
++ save_errno = errno;
++ (void)fclose(f);
++ (void)unlink(pidfile_path);
++ free(pidfile_path);
++ pidfile_path = NULL;
++ errno = save_errno;
++ return -1;
++ }
++ (void)fclose(f);
++
++ if (atexit_already)
++ return 0;
++
++ pidfile_pid = pid;
++ if (atexit(pidfile_cleanup) < 0) {
++ save_errno = errno;
++ (void)unlink(pidfile_path);
++ free(pidfile_path);
++ pidfile_path = NULL;
++ pidfile_pid = 0;
++ errno = save_errno;
++ return -1;
++ }
++
++ return 0;
++}
++
++static void pidfile_cleanup(void)
++{
++ if (pidfile_path != NULL && pidfile_pid == getpid()) {
++ (void)unlink(pidfile_path);
++ free(pidfile_path);
++ pidfile_path = NULL;
++ }
++}
+diff --git a/pidfile.h b/pidfile.h
+new file mode 100644
+index 0000000..7b68c78
+--- /dev/null
++++ b/pidfile.h
+@@ -0,0 +1,10 @@
++/**
++ * @file pidfile.h
++ * @brief PID file support, derived from OpenBSD via libite.
++ */
++#ifndef HAVE_PIDFILE_H
++#define HAVE_PIDFILE_H
++
++int pidfile(const char *basename);
++
++#endif
+diff --git a/ptp4l.c b/ptp4l.c
+index ac2ef96..12f5e20 100644
+--- a/ptp4l.c
++++ b/ptp4l.c
+@@ -34,6 +34,7 @@
+ #include "transport.h"
+ #include "udp6.h"
+ #include "uds.h"
++#include "pidfile.h"
+ #include "util.h"
+ #include "version.h"
+
+@@ -63,6 +64,7 @@ static void usage(char *progname)
+ " -l [num] set the logging level to 'num'\n"
+ " -m print messages to stdout\n"
+ " -q do not print messages to the syslog\n"
++ " -u [file] write process ID to 'file'\n"
+ " -v prints the software version and exits\n"
+ " -h prints this message and exits\n"
+ "\n",
+@@ -90,7 +92,7 @@ int main(int argc, char *argv[])
+ /* Process the command line arguments. */
+ progname = strrchr(argv[0], '/');
+ progname = progname ? 1+progname : argv[0];
+- while (EOF != (c = getopt_long(argc, argv, "AEP246HSLf:i:p:sl:mqvh",
++ while (EOF != (c = getopt_long(argc, argv, "AEP246HSLf:i:p:sl:mqu:vh",
+ opts, &index))) {
+ switch (c) {
+ case 0:
+@@ -163,6 +165,10 @@ int main(int argc, char *argv[])
+ case 'q':
+ config_set_int(cfg, "use_syslog", 0);
+ break;
++ case 'u':
++ if (config_set_string(cfg, "pidfile", optarg))
++ goto out;
++ break;
+ case 'v':
+ version_show(stdout);
+ return 0;
+@@ -188,6 +194,12 @@ int main(int argc, char *argv[])
+ print_set_syslog(config_get_int(cfg, NULL, "use_syslog"));
+ print_set_level(config_get_int(cfg, NULL, "logging_level"));
+
++ if (config_get_string(cfg, NULL, "pidfile") &&
++ pidfile(config_get_string(cfg, NULL, "pidfile"))) {
++ fprintf(stderr, "failed to create pidfile\n");
++ goto out;
++ }
++
+ assume_two_step = config_get_int(cfg, NULL, "assume_two_step");
+ sk_check_fupsync = config_get_int(cfg, NULL, "check_fup_sync");
+ sk_tx_timeout = config_get_int(cfg, NULL, "tx_timestamp_timeout");
+diff --git a/timemaster.c b/timemaster.c
+index b367b2f..873083f 100644
+--- a/timemaster.c
++++ b/timemaster.c
+@@ -38,6 +38,7 @@
+ #include
+ #include
+
++#include "pidfile.h"
+ #include "print.h"
+ #include "rtnl.h"
+ #include "sk.h"
+@@ -1531,6 +1532,7 @@ static void usage(char *progname)
+ "\nusage: %s [options] -f file\n\n"
+ " -f file specify path to configuration file\n"
+ " -n only print generated files and commands\n"
++ " -u file write process ID to 'file'\n"
+ " -l level set logging level (6)\n"
+ " -m print messages to stdout\n"
+ " -q do not print messages to syslog\n"
+@@ -1543,7 +1545,7 @@ int main(int argc, char **argv)
+ {
+ struct timemaster_config *config;
+ struct script *script;
+- char *progname, *config_path = NULL;
++ char *progname, *config_path = NULL, *pid_file = NULL;
+ int c, ret = 0, log_stdout = 0, log_syslog = 1, dry_run = 0;
+
+ progname = strrchr(argv[0], '/');
+@@ -1553,7 +1555,7 @@ int main(int argc, char **argv)
+ print_set_verbose(1);
+ print_set_syslog(0);
+
+- while (EOF != (c = getopt(argc, argv, "f:nl:mqvh"))) {
++ while (EOF != (c = getopt(argc, argv, "f:nu:l:mqvh"))) {
+ switch (c) {
+ case 'f':
+ config_path = optarg;
+@@ -1561,6 +1563,9 @@ int main(int argc, char **argv)
+ case 'n':
+ dry_run = 1;
+ break;
++ case 'u':
++ pid_file = optarg;
++ break;
+ case 'l':
+ print_set_level(atoi(optarg));
+ break;
+@@ -1599,10 +1604,16 @@ int main(int argc, char **argv)
+ print_set_verbose(log_stdout);
+ print_set_syslog(log_syslog);
+
+- if (dry_run)
++ if (dry_run) {
+ script_print(script);
+- else
++ } else {
++ if (pid_file && pidfile(pid_file)) {
++ pr_err("failed to create pidfile %s: %m", pid_file);
++ script_destroy(script);
++ return 1;
++ }
+ ret = script_run(script);
++ }
+
+ script_destroy(script);
+
+--
+2.43.0
+
diff --git a/src/bin/show/__init__.py b/src/bin/show/__init__.py
index c48c365b7..266e642b8 100755
--- a/src/bin/show/__init__.py
+++ b/src/bin/show/__init__.py
@@ -711,6 +711,23 @@ def keystore(args: List[str]) -> None:
print("Usage: show keystore [symmetric | asymmetric ]")
+def ptp(args: List[str]) -> None:
+ data = get_json("/ieee1588-ptp-tt:ptp")
+ if not data:
+ print("PTP: no instances running.")
+ return
+
+ if RAW_OUTPUT:
+ print(json.dumps(data, indent=2))
+ return
+
+ # Optional: filter to a specific instance-index
+ if args and args[0].isdigit():
+ cli_pretty(data, "show-ptp", args[0])
+ else:
+ cli_pretty(data, "show-ptp")
+
+
def execute_command(command: str, args: List[str]):
command_mapping = {
'bfd': bfd,
@@ -725,6 +742,7 @@ def execute_command(command: str, args: List[str]):
'nacm': nacm,
'ntp': ntp,
'ospf': ospf,
+ 'ptp': ptp,
'rip': rip,
'routes': routes,
'services': services,
diff --git a/src/confd/src/Makefile.am b/src/confd/src/Makefile.am
index f457f1ad5..447117994 100644
--- a/src/confd/src/Makefile.am
+++ b/src/confd/src/Makefile.am
@@ -49,6 +49,7 @@ confd_plugin_la_SOURCES = \
keystore.c \
system.c \
ntp.c \
+ ptp.c \
syslog.c \
factory-default.c \
routing.c \
diff --git a/src/confd/src/core.c b/src/confd/src/core.c
index 5e4fddaa2..dfd0261d4 100644
--- a/src/confd/src/core.c
+++ b/src/confd/src/core.c
@@ -511,6 +511,10 @@ static int change_cb(sr_session_ctx_t *session, uint32_t sub_id, const char *mod
if ((rc = ntp_change(session, config, diff, event, confd)))
goto free_diff;
+ /* ieee1588-ptp-tt */
+ if ((rc = ptp_change(session, config, diff, event, confd)))
+ goto free_diff;
+
/* infix-services */
if ((rc = services_change(session, config, diff, event, confd)))
goto free_diff;
@@ -706,6 +710,11 @@ int sr_plugin_init_cb(sr_session_ctx_t *session, void **priv)
ERROR("Failed to subscribe to infix-meta");
goto err;
}
+ rc = subscribe_model("ieee1588-ptp-tt", &confd, 0);
+ if (rc) {
+ ERROR("Failed to subscribe to ieee1588-ptp-tt");
+ goto err;
+ }
rc = system_rpc_init(&confd);
if (rc)
diff --git a/src/confd/src/core.h b/src/confd/src/core.h
index f25d91bd4..b56c8bf32 100644
--- a/src/confd/src/core.h
+++ b/src/confd/src/core.h
@@ -271,4 +271,7 @@ int ntp_cand(sr_session_ctx_t *session, uint32_t sub_id, const char *module,
const char *path, sr_event_t event, unsigned request_id, void *priv);
int ntp_candidate_init(struct confd *confd);
+/* ptp.c */
+int ptp_change(sr_session_ctx_t *session, struct lyd_node *config, struct lyd_node *diff, sr_event_t event, struct confd *confd);
+
#endif /* CONFD_CORE_H_ */
diff --git a/src/confd/src/ptp.c b/src/confd/src/ptp.c
new file mode 100644
index 000000000..e35e2cc85
--- /dev/null
+++ b/src/confd/src/ptp.c
@@ -0,0 +1,543 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include "core.h"
+
+#define XPATH_PTP_ "/ieee1588-ptp-tt:ptp"
+#define PTP_CONF_DIR "/etc/linuxptp"
+
+/*
+ * Map instance-type string to ptp4l clockType keyword.
+ * Returns NULL for oc/bc (no explicit clockType needed in [global]).
+ */
+static const char *instance_type_to_clock_type(const char *type)
+{
+ if (!type)
+ return NULL;
+ if (!strcmp(type, "p2p-tc"))
+ return "P2P_TC";
+ if (!strcmp(type, "e2e-tc"))
+ return "E2E_TC";
+
+ return NULL;
+}
+
+/*
+ * Emit all protocol-mandatory [global] settings for the chosen profile.
+ * Returns true when the profile is ieee802-dot1as (802.1AS/gPTP), which
+ * the caller uses to suppress per-port delay_mechanism output — identical
+ * to the guard already used for Transparent Clock instances.
+ *
+ * ieee802-dot1as sets the full gPTP option set as required by the standard:
+ * transportSpecific, network_transport, delay_mechanism, multicast MACs,
+ * gmCapable, follow_up_info, assume_two_step, path_trace_enabled,
+ * and the tighter neighborPropDelayThresh.
+ */
+static bool emit_profile_globals(FILE *fp, const char *profile, bool hw_ts)
+{
+ bool dot1as = profile && !strcmp(profile, "ieee802-dot1as");
+
+ if (dot1as) {
+ fprintf(fp, "transportSpecific 1\n");
+ fprintf(fp, "network_transport L2\n");
+ fprintf(fp, "delay_mechanism P2P\n");
+ fprintf(fp, "ptp_dst_mac 01:80:C2:00:00:0E\n");
+ fprintf(fp, "p2p_dst_mac 01:80:C2:00:00:0E\n");
+ fprintf(fp, "gmCapable 1\n");
+ fprintf(fp, "follow_up_info 1\n");
+ fprintf(fp, "assume_two_step 1\n");
+ fprintf(fp, "path_trace_enabled 1\n");
+ /*
+ * 802.1AS P2P gate: if meanLinkDelay exceeds this threshold the
+ * port stays asCapable=false and never leaves LISTENING. 800 ns
+ * is the 802.1AS default and is appropriate only for hardware
+ * timestamping — software timestamps (QEMU, tap interfaces) can
+ * easily produce peer delays in the microsecond range, which
+ * would keep both ports stuck in LISTENING indefinitely.
+ */
+ if (hw_ts)
+ fprintf(fp, "neighborPropDelayThresh 800\n");
+ } else {
+ fprintf(fp, "transportSpecific 0\n");
+ }
+ return dot1as;
+}
+
+/*
+ * Return true if ifname has hardware TX timestamping capability according
+ * to the probed data in system.json (confd->root). If the interface is
+ * absent from system.json (virtual interface, QEMU tap, etc.) or the
+ * "hardware-transmit" capability string is missing, returns false.
+ */
+static bool iface_has_hw_timestamp(json_t *root, const char *ifname)
+{
+ json_t *caps, *list, *entry;
+ size_t i;
+
+ if (!root || !ifname)
+ return false;
+
+ caps = json_object_get(json_object_get(
+ json_object_get(root, "interfaces"),
+ ifname), "ptp-capabilities");
+ if (!caps)
+ return false;
+
+ list = json_object_get(caps, "capabilities");
+ if (!json_is_array(list))
+ return false;
+
+ json_array_foreach(list, i, entry) {
+ const char *s = json_string_value(entry);
+ if (s && !strcmp(s, "hardware-transmit"))
+ return true;
+ }
+ return false;
+}
+
+static uint16_t instance(struct lyd_node *inst)
+{
+ return (uint16_t)atoi(lydx_get_cattr(inst, "instance-index"));
+}
+
+/*
+ * Scan all ports of inst and determine whether to use hardware or software
+ * timestamping. Emits a syslog WARNING when falling back to software due
+ * to a mixed or software-only set of port interfaces.
+ */
+static const char *instance_time_stamping(uint16_t idx, struct lyd_node *inst, json_t *root)
+{
+ const char *ifname = NULL;
+ struct lyd_node *port;
+
+ LYX_LIST_FOR_EACH(lyd_child(lydx_get_child(inst, "ports")), port, "port") {
+ const char *iface = lydx_get_cattr(port, "underlying-interface");
+
+ if (iface_has_hw_timestamp(root, iface))
+ continue;
+
+ ifname = iface;
+ break;
+ }
+
+ if (ifname) {
+ WARN("PTP instance %u will use software based timestamping due to "
+ "missing hardware support on %s", idx, ifname);
+ return "software";
+ }
+
+ return "hardware";
+}
+
+/*
+ * Write ptp4l config for one PTP instance.
+ * Config file: /etc/linuxptp/ptp4l-.conf+ (staging)
+ *
+ * ptp4l key config layout:
+ * [global] — instance-wide settings
+ * [eth0] — per-port interface sections, sorted by port-index
+ */
+static int write_instance_conf(struct lyd_node *inst, json_t *root)
+{
+ const char *instance_type, *clock_type, *profile, *ts;
+ struct lyd_node *default_ds, *port, *port_ds, *servo;
+ bool tc, bc, dot1as;
+ char path[256];
+ const char *v;
+ uint16_t idx;
+ FILE *fp;
+
+ idx = instance(inst);
+
+ snprintf(path, sizeof(path), PTP_CONF_DIR "/ptp4l-%u.conf+", idx);
+ fp = fopen(path, "w");
+ if (!fp) {
+ ERRNO("Failed creating %s", path);
+ return SR_ERR_SYS;
+ }
+
+ fprintf(fp, "# Generated by confd — do not edit\n\n");
+ fprintf(fp, "[global]\n");
+
+ default_ds = lydx_get_child(inst, "default-ds");
+ instance_type = lydx_get_cattr(default_ds, "instance-type");
+ profile = lydx_get_cattr(default_ds, "profile");
+
+ clock_type = instance_type_to_clock_type(instance_type);
+ tc = (clock_type != NULL);
+ bc = instance_type && !strcmp(instance_type, "bc");
+
+ /* Unique UDS socket per instance — required for pmc with multiple instances */
+ fprintf(fp, "uds_address /var/run/ptp4l-%u\n", idx);
+
+ /* Timestamping mode: hardware if all ports support it, software otherwise */
+ ts = instance_time_stamping(idx, inst, root);
+ fprintf(fp, "time_stamping %s\n", ts);
+
+ /* Profile — sets transportSpecific and all protocol-mandatory options */
+ dot1as = emit_profile_globals(fp, profile, !strcmp(ts, "hardware"));
+
+ /* domainNumber */
+ v = lydx_get_cattr(default_ds, "domain-number");
+ if (v)
+ fprintf(fp, "domainNumber %s\n", v);
+
+ /* Transparent Clock clock_type */
+ if (tc)
+ fprintf(fp, "clock_type %s\n", clock_type);
+
+ /*
+ * Multi-port instances (BC and TC) may span ports on different PHC
+ * devices when the ports belong to different switch chips (e.g. a
+ * three-chip board where each mv88e6xxx chip owns its own /dev/ptpN).
+ * boundary_clock_jbod silences ptp4l's startup PHC-mismatch check and
+ * lets each port use its own PHC. It is a no-op when all ports share
+ * the same PHC (single-chip board or software timestamping).
+ * On multi-chip hardware, phc2sys -a disciplines the secondary PHCs;
+ * see needs_phc2sys() / activate_phc2sys().
+ */
+ if (tc || bc)
+ fprintf(fp, "boundary_clock_jbod 1\n");
+
+ /* priority1 / priority2 (not applicable for TC, but harmless) */
+ v = lydx_get_cattr(default_ds, "priority1");
+ if (v)
+ fprintf(fp, "priority1 %s\n", v);
+ v = lydx_get_cattr(default_ds, "priority2");
+ if (v)
+ fprintf(fp, "priority2 %s\n", v);
+
+ /* clientOnly (OC only) — inclusive replacement for slaveOnly in ptp4l 4.x */
+ v = lydx_get_cattr(default_ds, "time-receiver-only");
+ if (v && !strcmp(v, "true"))
+ fprintf(fp, "clientOnly 1\n");
+
+ /* maxStepsRemoved */
+ v = lydx_get_cattr(default_ds, "max-steps-removed");
+ if (v)
+ fprintf(fp, "maxStepsRemoved %s\n", v);
+
+ /* servo: step_threshold (0.0 = slew-only, never step) */
+ servo = lydx_get_child(inst, "servo");
+ if (servo) {
+ v = lydx_get_cattr(servo, "step-threshold");
+ if (v)
+ fprintf(fp, "step_threshold %s\n", v);
+ }
+
+ /*
+ * Transparent Clocks set delay_mechanism globally; ptp4l ignores
+ * per-port delay_mechanism for TCs. 802.1AS mandates P2P globally
+ * (already emitted by emit_profile_globals).
+ */
+ if (tc) {
+ if (!strcmp(clock_type, "P2P_TC"))
+ fprintf(fp, "delay_mechanism P2P\n");
+ else
+ fprintf(fp, "delay_mechanism E2E\n");
+ }
+
+ fprintf(fp, "\n");
+
+ /* Per-port [interface] sections, sorted by port-index */
+ LYX_LIST_FOR_EACH(lyd_child(lydx_get_child(inst, "ports")), port, "port") {
+ const char *iface;
+
+ iface = lydx_get_cattr(port, "underlying-interface");
+ if (!iface)
+ continue;
+
+ port_ds = lydx_get_child(port, "port-ds");
+ if (!port_ds)
+ continue;
+
+ if (!lydx_is_enabled(port_ds, "port-enable"))
+ continue;
+
+ fprintf(fp, "[%s]\n", iface);
+
+ v = lydx_get_cattr(port_ds, "log-announce-interval");
+ if (v)
+ fprintf(fp, "logAnnounceInterval %s\n", v);
+
+ v = lydx_get_cattr(port_ds, "announce-receipt-timeout");
+ if (v)
+ fprintf(fp, "announceReceiptTimeout %s\n", v);
+
+ v = lydx_get_cattr(port_ds, "log-sync-interval");
+ if (v)
+ fprintf(fp, "logSyncInterval %s\n", v);
+
+ v = lydx_get_cattr(port_ds, "log-min-delay-req-interval");
+ if (v)
+ fprintf(fp, "logMinDelayReqInterval %s\n", v);
+
+ v = lydx_get_cattr(port_ds, "log-min-pdelay-req-interval");
+ if (v)
+ fprintf(fp, "logMinPdelayReqInterval %s\n", v);
+
+ /*
+ * delay_mechanism per port — only for OC/BC on ieee1588 profile.
+ * TC and 802.1AS both set it globally; ptp4l ignores per-port
+ * overrides in those cases.
+ */
+ if (!tc && !dot1as) {
+ const char *dm = lydx_get_cattr(port_ds, "delay-mechanism");
+
+ if (dm) {
+ if (!strcmp(dm, "p2p"))
+ fprintf(fp, "delay_mechanism P2P\n");
+ else if (!strcmp(dm, "e2e"))
+ fprintf(fp, "delay_mechanism E2E\n");
+ }
+ }
+
+ v = lydx_get_cattr(port_ds, "delay-asymmetry");
+ if (v && strcmp(v, "0"))
+ fprintf(fp, "delayAsymmetry %s\n", v);
+
+ if (lydx_is_enabled(port_ds, "time-transmitter-only"))
+ fprintf(fp, "masterOnly 1\n");
+
+ fprintf(fp, "\n");
+ }
+
+ fclose(fp);
+ return SR_ERR_OK;
+}
+
+/*
+ * True when a PTP instance needs a phc2sys companion to keep all its
+ * PHC devices in sync. Required for BC and TC instances on hardware
+ * with multiple switch chips, where each chip has its own /dev/ptpN.
+ * On single-chip hardware the function is a no-op: phc2sys -a finds
+ * no second PHC and exits immediately. OC has one port → one PHC,
+ * so no sync is ever needed.
+ */
+static bool needs_phc2sys(struct lyd_node *inst, json_t *root)
+{
+ struct lyd_node *default_ds = lydx_get_child(inst, "default-ds");
+ const char *type = lydx_get_cattr(default_ds, "instance-type");
+
+ if (!type)
+ return false;
+ if (strcmp(type, "bc") && strcmp(type, "p2p-tc") && strcmp(type, "e2e-tc"))
+ return false;
+
+ return !strcmp(instance_time_stamping(instance(inst), inst, root), "hardware");
+}
+
+/*
+ * Enable the phc2sys@ companion service for a multi-port HW instance.
+ * phc2sys -a subscribes to ptp4l's UDS, discovers the active slave
+ * port via BMCA, and disciplines all other PHCs to match it.
+ * No config file is needed — the UDS path is passed on the command line.
+ */
+static void activate_phc2sys(uint16_t idx)
+{
+ finit_enablef("phc2sys@%u", idx);
+ finit_reloadf("phc2sys@%u", idx);
+}
+
+static void deactivate_phc2sys(uint16_t idx)
+{
+ finit_disablef("phc2sys@%u", idx);
+}
+
+/*
+ * Disable any phc2sys@ services whose index is no longer configured.
+ */
+static void cleanup_stale_phc2sys(struct lyd_node *config)
+{
+ const struct dirent *ent;
+ DIR *d;
+
+ d = opendir(FINIT_RCSD "/enabled");
+ if (!d)
+ return;
+
+ while ((ent = readdir(d))) {
+ struct lyd_node *inst, *instances;
+ bool found = false;
+ int idx;
+
+ if (sscanf(ent->d_name, "phc2sys@%d.conf", &idx) != 1)
+ continue;
+
+ instances = lydx_get_descendant(config, "ptp", "instances", "instance", NULL);
+ LYX_LIST_FOR_EACH(instances, inst, "instance") {
+ if (instance(inst) == idx) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ deactivate_phc2sys((uint16_t)idx);
+ }
+
+ closedir(d);
+}
+
+/*
+ * Remove staging config for one instance.
+ */
+static void remove_staging(uint16_t idx)
+{
+ char path[256];
+
+ snprintf(path, sizeof(path), PTP_CONF_DIR "/ptp4l-%u.conf+", idx);
+ (void)remove(path);
+}
+
+/*
+ * Activate one instance: rename staging → live, enable finit service.
+ */
+static int activate_instance(uint16_t idx)
+{
+ char staging[256], live[256];
+
+ snprintf(staging, sizeof(staging), PTP_CONF_DIR "/ptp4l-%u.conf+", idx);
+ snprintf(live, sizeof(live), PTP_CONF_DIR "/ptp4l-%u.conf", idx);
+
+ if (!fexist(staging)) {
+ (void)remove(live);
+ return SR_ERR_OK;
+ }
+
+ if (rename(staging, live)) {
+ ERRNO("Failed renaming %s → %s", staging, live);
+ return SR_ERR_SYS;
+ }
+
+ finit_enablef("ptp4l@%u", idx);
+ return finit_reloadf("ptp4l@%u", idx);
+}
+
+/*
+ * Deactivate (disable) one instance and remove its live config.
+ */
+static void deactivate_instance(uint16_t idx)
+{
+ char live[256];
+
+ finit_disablef("ptp4l@%u", idx);
+
+ snprintf(live, sizeof(live), PTP_CONF_DIR "/ptp4l-%u.conf", idx);
+ (void)remove(live);
+}
+
+/*
+ * Disable any ptp4l@ services in finit enabled/ whose index is not in the
+ * currently configured set. Called from SR_EV_DONE after enabling active
+ * instances, to clean up stale services from a previous config.
+ */
+static void cleanup_stale_instances(struct lyd_node *config)
+{
+ const struct dirent *ent;
+ DIR *d;
+
+ d = opendir(FINIT_RCSD "/enabled");
+ if (!d)
+ return;
+
+ while ((ent = readdir(d))) {
+ struct lyd_node *inst, *instances;
+ bool found = false;
+ int idx;
+
+ if (sscanf(ent->d_name, "ptp4l@%d.conf", &idx) != 1)
+ continue;
+
+ /* Is this index still configured? */
+ instances = lydx_get_descendant(config, "ptp", "instances", "instance", NULL);
+ LYX_LIST_FOR_EACH(instances, inst, "instance") {
+ if (instance(inst) == idx) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ deactivate_instance((uint16_t)idx);
+ }
+
+ closedir(d);
+}
+
+static int change(sr_session_ctx_t *session, struct lyd_node *config,
+ struct lyd_node *diff, sr_event_t event, struct confd *confd)
+{
+ struct lyd_node *instances, *inst;
+ int rc = SR_ERR_OK;
+
+ if (diff && !lydx_get_xpathf(diff, XPATH_PTP_))
+ return SR_ERR_OK;
+
+ switch (event) {
+ case SR_EV_ENABLED:
+ case SR_EV_CHANGE:
+ break;
+
+ case SR_EV_ABORT:
+ /* Remove any staging files */
+ instances = lydx_get_descendant(config, "ptp", "instances", "instance", NULL);
+ LYX_LIST_FOR_EACH(instances, inst, "instance")
+ remove_staging(instance(inst));
+ return SR_ERR_OK;
+
+ case SR_EV_DONE:
+ /* Activate all configured instances */
+ instances = lydx_get_descendant(config, "ptp", "instances", "instance", NULL);
+ LYX_LIST_FOR_EACH(instances, inst, "instance") {
+ uint16_t idx = instance(inst);
+
+ if ((rc = activate_instance(idx)))
+ return rc;
+
+ if (needs_phc2sys(inst, confd->root))
+ activate_phc2sys(idx);
+ else
+ deactivate_phc2sys(idx);
+ }
+
+ /* Disable stale services not in current config */
+ cleanup_stale_instances(config);
+ cleanup_stale_phc2sys(config);
+ return SR_ERR_OK;
+
+ default:
+ return SR_ERR_OK;
+ }
+
+ /* SR_EV_ENABLED / SR_EV_CHANGE — generate staging configs */
+ instances = lydx_get_descendant(config, "ptp", "instances", "instance", NULL);
+ if (!instances)
+ return SR_ERR_OK;
+
+ if (mkdir(PTP_CONF_DIR, 0755) && errno != EEXIST) {
+ ERRNO("Failed creating " PTP_CONF_DIR);
+ return SR_ERR_SYS;
+ }
+
+ LYX_LIST_FOR_EACH(instances, inst, "instance") {
+ rc = write_instance_conf(inst, confd->root);
+ if (rc)
+ return rc;
+ }
+
+ return SR_ERR_OK;
+}
+
+int ptp_change(sr_session_ctx_t *session, struct lyd_node *config,
+ struct lyd_node *diff, sr_event_t event, struct confd *confd)
+{
+ return change(session, config, diff, event, confd);
+}
diff --git a/src/confd/yang/confd.inc b/src/confd/yang/confd.inc
index 27bc3fe9b..83bea27e4 100644
--- a/src/confd/yang/confd.inc
+++ b/src/confd/yang/confd.inc
@@ -47,10 +47,13 @@ MODULES=(
"ieee802-ethernet-interface@2019-06-21.yang"
"infix-ethernet-interface@2024-02-27.yang"
"infix-factory-default@2023-06-28.yang"
- "infix-interfaces@2025-11-06.yang -e vlan-filtering"
+ "infix-interfaces@2026-04-09.yang -e vlan-filtering"
"ietf-crypto-types -e cleartext-symmetric-keys"
"infix-crypto-types@2026-02-14.yang"
"ietf-keystore -e symmetric-keys"
"infix-ntp@2026-03-09.yang"
"infix-keystore@2025-12-17.yang"
+ "ieee1588-ptp-tt@2023-08-14.yang -e timestamp-correction"
+ "ieee802-dot1as-gptp@2025-12-10.yang"
+ "infix-ptp@2026-04-07.yang"
)
diff --git a/src/confd/yang/confd/ieee1588-ptp-tt@2023-08-14.yang b/src/confd/yang/confd/ieee1588-ptp-tt@2023-08-14.yang
new file mode 100644
index 000000000..5ccc5ab63
--- /dev/null
+++ b/src/confd/yang/confd/ieee1588-ptp-tt@2023-08-14.yang
@@ -0,0 +1,4326 @@
+module ieee1588-ptp-tt {
+ yang-version 1.1;
+ namespace urn:ieee:std:1588:yang:ieee1588-ptp-tt;
+ prefix "ptp-tt";
+
+ import ietf-yang-types {
+ prefix yang;
+ }
+ import ietf-interfaces {
+ prefix if;
+ }
+
+ organization "IEEE 1588 Working Group";
+ contact
+ "Web: https://sagroups.ieee.org/1588/
+ E-mail: 1588officers@listserv.ieee.org
+
+ Postal: C/O IEEE 1588 Working Group Chair
+ IEEE Standards Association
+ 445 Hoes Lane
+ Piscataway, NJ 08854
+ USA";
+ description
+ "This YANG module defines a data model for the configuration
+ and state of IEEE Std 1588 clocks. IEEE Std 1588 specifies the
+ Precision Time Protocol (PTP).
+
+ The nodes in this YANG module are designed for compatibility
+ with ietf-ptp.yang, the YANG data model for IEEE Std 1588-2008,
+ as specified in IETF RFC 8575.
+
+ NOTE regarding default value:
+ PTP's concept of 'initialization value' is analogous to YANG's
+ concept of a 'default value'. According to 8.1.3.4 of
+ IEEE Std 1588-2019, the initialization value for configuration
+ is specified in IEEE Std 1588, but that value can be overridden
+ by a PTP Profile specification, or by the product that
+ implements PTP. This makes it challenging to repeat the
+ specification of initialization value using a YANG 'default'
+ statement, because there is no straightforward mechanism for
+ a PTP Profile's (or product's) YANG module to import this
+ module and override its YANG default. Since a YANG management
+ client can read the default value from the operational
+ datastore, there is no need to re-specify the default in YANG.
+ The implementer of PTP refers to the relevant PTP
+ specifications for the default (not YANG modules).
+ Therefore, this YANG module avoids use of the YANG 'default'
+ statement.
+
+ NOTE regarding IEEE Std 1588 classification:
+ 8.1.2 of IEEE Std 1588-2019 specifies a classification of
+ each data set member, which corresponds to a leaf in YANG.
+ The relationship between 1588 classification and
+ YANG 'config' (i.e., whether the leaf is read-write) is:
+ - 1588 static: The leaf is 'config false' (read-only).
+ - 1588 configurable: The leaf is 'config true', which is
+ the default value for a YANG leaf.
+ - 1588 dynamic: A judgement is made on a member-by-member
+ basis. If the member corresponds to the first item of
+ 8.1.2.1.2 of IEEE Std 1588-2019 (i.e., value from protocol
+ only, such as log of protocol behavior), the YANG leaf
+ is 'config false'. Otherwise, the member's value can be
+ provided by an entity outside PTP (e.g., NETCONF or
+ RESTCONF client), and therefore the YANG leaf is
+ 'config true'.
+
+ NOTE regarding terminology (two YANG modules):
+ To accommodate the need by some organizations to use the
+ original terminology specified by IEEE Std 1588, and the
+ need by some other organizations to use the alternative
+ terminology specified in 4.4 of IEEE Std 1588g-2022,
+ two YANG modules are provided by IEEE Std 1588e (MIB and
+ YANG Data Models). For a detailed explanation, see 15.4.2.11
+ of IEEE Std 1588e.
+ This module uses the alternative terminology specified in
+ 4.4 of IEEE Std 1588g-2022 (timeTransmitter/timeReceiver).";
+
+ revision 2023-08-14 {
+ description
+ "Initial revision.";
+ reference
+ "IEEE Std 1588e-2024, IEEE Standard for a Precision Clock
+ Synchronization Protocol for Networked Measurement and
+ Control Systems - MIB and YANG Data Models.";
+ }
+
+ feature fault-log {
+ description
+ "Logging of faults detected in the PTP Instance.";
+ reference
+ "8.2.6 of IEEE Std 1588-2019";
+ }
+
+ feature unicast-negotiation {
+ description
+ "Unicast negotiation conducted through use of TLVs.";
+ reference
+ "16.1 of IEEE Std 1588-2019";
+ }
+
+ feature path-trace {
+ description
+ "Use of the PATH_TRACE TLV for tracing the route of
+ a PTP Announce message through the PTP Network.";
+ reference
+ "16.2 of IEEE Std 1588-2019";
+ }
+
+ feature alternate-timescale {
+ description
+ "The transmission of an ALTERNATE_TIME_OFFSET_INDICATOR TLV
+ entity from the Grandmaster PTP Instance may indicate the
+ offset of an alternate timescale from the timescale in
+ use in the domain.";
+ reference
+ "16.3 of IEEE Std 1588-2019";
+ }
+
+ feature holdover-upgrade {
+ description
+ "A holdover-upgradable PTP Instance can potentially
+ become the Grandmaster PTP Instance in the event the
+ previous Grandmaster PTP Instance is disconnected
+ or its characteristics degrade.";
+ reference
+ "16.4 of IEEE Std 1588-2019";
+ }
+
+ feature cmlds {
+ description
+ "The Common Mean Link Delay Service (CMLDS) is an optional
+ service that enables any PTP Port that would normally obtain
+ the value of a link's and
+ using the peer-to-peer method to instead obtain these
+ values from this optional service. The CMLDS service is
+ available to all PTP Instances communicating with a specific
+ transport mechanism, over the physical link between two PTP
+ Nodes.";
+ reference
+ "16.6 of IEEE Std 1588-2019";
+ }
+
+ feature timestamp-correction {
+ description
+ "Correction of timestamps using configurable management data.";
+ reference
+ "16.7 of IEEE Std 1588-2019";
+ }
+
+ feature asymmetry-correction {
+ description
+ "Calculation of the on a Direct PTP Link
+ between two PTP Instances connected using an applicable
+ bidirectional medium.";
+ reference
+ "16.8 of IEEE Std 1588-2019";
+ }
+
+ feature time-receiver-monitoring {
+ description
+ "Mechanism for monitoring timing information in a PTP Port
+ in the timereceiver state. The time-receiver-monitoring feature
+ specifies TLVs that the TimeReceiver PTP Instance transmits
+ with this information, typically in a Signaling message.";
+ reference
+ "16.11 of IEEE Std 1588-2019";
+ }
+
+ feature enhanced-metrics {
+ description
+ "Mechanism for propagating estimates of various
+ inaccuracy components affecting the overall expected
+ PTP Instance Time accuracy. The metrics will be updated
+ and available for utilization at the various points along
+ the PTP timing chain: from the Grandmaster Instance, up to
+ a leaf PTP Instance in the synchronization tree. Each
+ PTP Instance along the timing path updates the
+ relevant metrics based on its contribution to the expected
+ degradation in PTP Instance Time accuracy due to various
+ induced timing error components.";
+ reference
+ "16.12 of IEEE Std 1588-2019";
+ }
+
+ feature grandmaster-cluster {
+ description
+ "Mechanism for faster selection of the Grandmaster PTP Instance
+ from the set of PTP Instances for which this option is both
+ implemented and enabled.";
+ reference
+ "17.2 of IEEE Std 1588-2019";
+ }
+
+ feature alternate-time-transmitter {
+ description
+ "Mechanism for PTP Ports on a PTP Communication Path that
+ are not currently the time-transmitter port of that
+ PTP Communicatio Path to exchange PTP timing information with
+ other PTP Ports on the same PTP Communication Path, and for
+ each of the other PTP Ports to acquire knowledge of the
+ characteristics of the transmission path between itself and
+ each alternate timeTransmitter PTP Port.";
+ reference
+ "17.3 of IEEE Std 1588-2019";
+ }
+
+ feature unicast-discovery {
+ description
+ "Mechanism for PTP to be used over a network that does not
+ provide multicast. A PTP Instance is configured with the
+ addresses of PTP Ports of other PTP Instances with which
+ it should attempt to establish unicast communication.
+ The PTP Instance may request that these PTP Ports transmit
+ unicast Announce, Sync, and Delay_Resp messages to it.";
+ reference
+ "17.4 of IEEE Std 1588-2019";
+ }
+
+ feature acceptable-time-transmitter {
+ description
+ "Mechanism that allows PTP Ports in the time-receiver state
+ to be configured to refuse to synchronize to PTP Instances not
+ on the acceptable timeTransmitter list.";
+ reference
+ "17.5 of IEEE Std 1588-2019";
+ }
+
+ feature external-port-config {
+ description
+ "External port configuration allows an external entity
+ (such as YANG-based remote management) to disable the
+ IEEE Std 1588 state machines that control each port's
+ state, including the BTCA. Each port's state is
+ then configured by the external entity.";
+ reference
+ "17.6 of IEEE Std 1588-2019";
+ }
+
+ feature performance-monitoring {
+ description
+ "Collection of performance monitoring logs that can be
+ read using management.";
+ reference
+ "Annex J of IEEE Std 1588-2019";
+ }
+
+ feature l1-sync {
+ description
+ "Layer 1-based synchronization performance
+ enhancement.";
+ reference
+ "Annex L of IEEE Std 1588-2019";
+ }
+
+ identity network-protocol {
+ description
+ "Enumeration for the protocol used by a PTP Instance to
+ transport PTP messages.
+ YANG identity is used so that a PTP Profile's YANG augment
+ can assign values, using numeric range F000 to FFFD hex.";
+ reference
+ "7.4.1 of IEEE Std 1588-2019";
+ }
+ identity udp-ipv4 {
+ base network-protocol;
+ description
+ "UDP on IPv4. Numeric value is 0001 hex.";
+ }
+ identity udp-ipv6 {
+ base network-protocol;
+ description
+ "UDP on IPv6. Numeric value is 0002 hex.";
+ }
+ identity ieee802-3 {
+ base network-protocol;
+ description
+ "IEEE Std 802.3 (Ethernet). Numeric value is 0003 hex.";
+ }
+ identity devicenet {
+ base network-protocol;
+ description
+ "DeviceNet. Numeric value is 0004 hex.";
+ }
+ identity controlnet {
+ base network-protocol;
+ description
+ "ControlNet. Numeric value is 0005 hex.";
+ }
+ identity profinet {
+ base network-protocol;
+ description
+ "PROFINET. Numeric value is 0006 hex.";
+ }
+ identity otn {
+ base network-protocol;
+ description
+ "Optical Transport Network (OTN). Numeric value
+ is 0007 hex.";
+ }
+ identity unknown {
+ base network-protocol;
+ description
+ "Unknown. Numeric value is FFFE hex.";
+ }
+
+ identity clock-class {
+ description
+ "Enumeration that denotes the traceability, synchronization
+ state and expected performance of the time or frequency
+ distributed by the Grandmaster PTP Instance.
+ IEEE Std 1588 does not specify a name for each clock-class,
+ but the names below are intended to be as intuitive as possible.
+ YANG identity is used so that a PTP Profile's YANG augment
+ can assign values using a numeric range designated for use by
+ alternate PTP Profiles.";
+ reference
+ "7.6.2.5 of IEEE Std 1588-2019";
+ }
+ identity cc-primary-sync {
+ base clock-class;
+ description
+ "A PTP Instance that is synchronized to a primary
+ reference time source. The timescale distributed shall be PTP.
+ Numeric value is 6 decimal.";
+ }
+ identity cc-primary-sync-lost {
+ base clock-class;
+ description
+ "A PTP Instance that has previously been designated
+ as clockClass 6, but that has lost the ability to
+ synchronize to a primary reference time source and is in
+ holdover mode and within holdover specifications. Or a PTP
+ Instance designated with clockClass 7 based on the Holdover
+ Upgrade option. The timescale distributed shall be PTP.
+ Numeric value is 7 decimal.";
+ }
+ identity cc-application-specific-sync {
+ base clock-class;
+ description
+ "A PTP Instance that is synchronized to an
+ application-specific source of time. The timescale
+ distributed shall be ARB.
+ Numeric value is 13 decimal.";
+ }
+ identity cc-application-specific-sync-lost {
+ base clock-class;
+ description
+ "A PTP Instance that has previously been designated as
+ clockClass 13, but that has lost the ability to synchronize
+ to an application-specific source of time and is in
+ holdover mode and within holdover specifications. Or a PTP
+ Instance designated with clockClass 14 based on the Holdover
+ Upgrade option. The timescale distributed shall be ARB.
+ Numeric value is 14 decimal.";
+ }
+ identity cc-primary-sync-alternative-a {
+ base clock-class;
+ description
+ "Degradation alternative A for a PTP Instance of
+ clockClass 7 that is not within holdover specification
+ or that is based on the specifications of the Holdover
+ Upgrade option.
+ Numeric value is 52 decimal.";
+ }
+ identity cc-application-specific-alternative-a {
+ base clock-class;
+ description
+ "Degradation alternative A for a PTP Instance of
+ clockClass 14 that is not within holdover specification or
+ that is based on the specifications of the Holdover Upgrade
+ option.
+ Numeric value is 58 decimal.";
+ }
+ identity cc-primary-sync-alternative-b {
+ base clock-class;
+ description
+ "Degradation alternative B for a PTP Instance of
+ clockClass 7 that is not within holdover specification
+ or that is based on the specifications of the Holdover
+ Upgrade option.
+ Numeric value is 187 decimal.";
+ }
+ identity cc-application-specific-alternative-b {
+ base clock-class;
+ description
+ "Degradation alternative B for a PTP Instance of
+ clockClass 14 that is not within holdover specification or
+ that is based on the specifications of the Holdover Upgrade
+ option.
+ Numeric value is 193 decimal.";
+ }
+ identity cc-default {
+ base clock-class;
+ description
+ "Default clockClass, used if none of the other
+ clockClass definitions apply.
+ Numeric value is 248 decimal.";
+ }
+ identity cc-time-receiver-only {
+ base clock-class;
+ description
+ "A PTP Instance that is timeReceiver only.
+ Numeric value is 255 decimal.";
+ }
+
+ identity clock-accuracy {
+ description
+ "Enumeration that indicates the expected accuracy of a
+ PTP Instance when it is the Grandmaster PTP Instance,
+ or in the event it becomes the Grandmaster PTP Instance.
+ The value shall be conservatively estimated by the PTP
+ Instance to a precision consistent with the value of the
+ selected clock-accuracy and of the next lower enumerated
+ value, for example, for clockAccuracy = 23 hex, between
+ 250 ns and 1000 ns.
+ IEEE Std 1588 does not specify a name for each clock-accuracy,
+ but the names below are intended to be as intuitive as possible.
+ YANG identity is used so that a PTP Profile's YANG augment
+ can assign values, using numeric range 80 to FD hex.";
+ reference
+ "7.6.2.6 of IEEE Std 1588-2019";
+ }
+ identity ca-time-accurate-to-1000-fs {
+ base clock-accuracy;
+ description
+ "The time is accurate to within 1 ps (1000 fs).
+ Numeric value is 17 hex.";
+ }
+ identity ca-time-accurate-to-2500-fs {
+ base clock-accuracy;
+ description
+ "The time is accurate to within 2.5 ps (2500 fs).
+ Numeric value is 18 hex.";
+ }
+ identity ca-time-accurate-to-10-ps {
+ base clock-accuracy;
+ description
+ "The time is accurate to within 10 ps.
+ Numeric value is 19 hex.";
+ }
+ identity ca-time-accurate-to-25ps {
+ base clock-accuracy;
+ description
+ "The time is accurate to within 25 ps.
+ Numeric value is 1A hex.";
+ }
+ identity ca-time-accurate-to-100-ps {
+ base clock-accuracy;
+ description
+ "The time is accurate to within 100 ps.
+ Numeric value is 1B hex.";
+ }
+ identity ca-time-accurate-to-250-ps {
+ base clock-accuracy;
+ description
+ "The time is accurate to within 250 ps.
+ Numeric value is 1C hex.";
+ }
+ identity ca-time-accurate-to-1000-ps {
+ base clock-accuracy;
+ description
+ "The time is accurate to within 1ns (1000 ps).
+ Numeric value is 1D hex.";
+ }
+ identity ca-time-accurate-to-2500-ps {
+ base clock-accuracy;
+ description
+ "The time is accurate to within 2.5 ns (2500 ps).
+ Numeric value is 1E hex.";
+ }
+ identity ca-time-accurate-to-10-ns {
+ base clock-accuracy;
+ description
+ "The time is accurate to within 10 ns.
+ Numeric value is 1F hex.";
+ }
+ identity ca-time-accurate-to-25-ns {
+ base clock-accuracy;
+ description
+ "The time is accurate to within 25 ns.
+ Numeric value is 20 hex.";
+ }
+ identity ca-time-accurate-to-100-ns {
+ base clock-accuracy;
+ description
+ "The time is accurate to within 100 ns.
+ Numeric value is 21 hex.";
+ }
+ identity ca-time-accurate-to-250-ns {
+ base clock-accuracy;
+ description
+ "The time is accurate to within 250 ns.
+ Numeric value is 22 hex.";
+ }
+ identity ca-time-accurate-to-1000-ns {
+ base clock-accuracy;
+ description
+ "The time is accurate to within 1 us (1000 ns).
+ Numeric value is 23 hex.";
+ }
+ identity ca-time-accurate-to-2500-ns {
+ base clock-accuracy;
+ description
+ "The time is accurate to within 2.5 us (2500 ns).
+ Numeric value is 24 hex.";
+ }
+ identity ca-time-accurate-to-10-us {
+ base clock-accuracy;
+ description
+ "The time is accurate to within 10 us.
+ Numeric value is 25 hex.";
+ }
+ identity ca-time-accurate-to-25-us {
+ base clock-accuracy;
+ description
+ "The time is accurate to within 25 us.
+ Numeric value is 26 hex.";
+ }
+ identity ca-time-accurate-to-100-us {
+ base clock-accuracy;
+ description
+ "The time is accurate to within 100 us.
+ Numeric value is 27 hex.";
+ }
+ identity ca-time-accurate-to-250-us {
+ base clock-accuracy;
+ description
+ "The time is accurate to within 250 us.
+ Numeric value is 28 hex.";
+ }
+ identity ca-time-accurate-to-1000-us {
+ base clock-accuracy;
+ description
+ "The time is accurate to within 1 ms (1000 us).
+ Numeric value is 29 hex.";
+ }
+ identity ca-time-accurate-to-2500-us {
+ base clock-accuracy;
+ description
+ "The time is accurate to within 2.5 ms (2500 us).
+ Numeric value is 2A hex.";
+ }
+ identity ca-time-accurate-to-10-ms {
+ base clock-accuracy;
+ description
+ "The time is accurate to within 10 ms.
+ Numeric value is 2B hex.";
+ }
+ identity ca-time-accurate-to-25-ms {
+ base clock-accuracy;
+ description
+ "The time is accurate to within 25 ms.
+ Numeric value is 2Chex.";
+ }
+ identity ca-time-accurate-to-100-ms {
+ base clock-accuracy;
+ description
+ "The time is accurate to within 100 ms.
+ Numeric value is 2D hex.";
+ }
+ identity ca-time-accurate-to-250-ms {
+ base clock-accuracy;
+ description
+ "The time is accurate to within 250 ms.
+ Numeric value is 2E hex.";
+ }
+ identity ca-time-accurate-to-1-s {
+ base clock-accuracy;
+ description
+ "The time is accurate to within 1 s.
+ Numeric value is 2F hex.";
+ }
+ identity ca-time-accurate-to-10-s {
+ base clock-accuracy;
+ description
+ "The time is accurate to within 10 s.
+ Numeric value is 30 hex.";
+ }
+ identity ca-time-accurate-to-gt-10-s {
+ base clock-accuracy;
+ description
+ "The time accuracy exceeds 10 s.
+ Numeric value is 31 hex.";
+ }
+
+ identity time-source {
+ description
+ "Enumeration for the source of time used by the Grandmaster
+ PTP Instance.
+ YANG identity is used so that a PTP Profile's YANG augment
+ can assign values, using numeric range F0 to FE hex.";
+ reference
+ "7.6.2.8 of IEEE Std 1588-2019";
+ }
+ identity atomic-clock {
+ base time-source;
+ description
+ "Any PTP Instance that is based on an atomic resonance
+ for frequency, or a PTP Instance directly connected
+ to a device that is based on an atomic resonance for
+ frequency. Numeric value is 10 hex.";
+ }
+ identity gnss {
+ base time-source;
+ description
+ "Any PTP Instance synchronized to a satellite system that
+ distributes time and frequency. Numeric value is 20 hex.";
+ }
+ identity terrestrial-radio {
+ base time-source;
+ description
+ "Any PTP Instance synchronized via any of the radio
+ distribution systems that distribute time and frequency.
+ Numeric value is 30 hex.";
+ }
+ identity serial-time-code {
+ base time-source;
+ description
+ "Any PTP Instance synchronized via any of the serial
+ time code distribution systems that distribute time
+ and frequency, for example, IRIG-B.
+ Numeric value is 39 hex.";
+ }
+ identity ptp {
+ base time-source;
+ description
+ "Any PTP Instance synchronized to a PTP-based source
+ of time external to the domain. Numeric value is 40 hex.";
+ }
+ identity ntp {
+ base time-source;
+ description
+ "Any PTP Instance synchronized via NTP or Simple Network
+ Time Protocol (SNTP) servers that distribute time and
+ frequency. Numeric value is 50 hex.";
+ }
+ identity hand-set {
+ base time-source;
+ description
+ "Used for any PTP Instance whose time has been set by
+ means of a human interface based on observation of a
+ source of time to within the claimed clock accuracy.
+ Numeric value is 60 hex.";
+ }
+ identity other {
+ base time-source;
+ description
+ "Other source of time and/or frequency not covered by
+ other values. Numeric value is 90 hex.";
+ }
+ identity internal-oscillator {
+ base time-source;
+ description
+ "Any PTP Instance whose frequency is not based on atomic
+ resonance, and whose time is based on a free-running
+ oscillator with epoch determined in an arbitrary or
+ unknown manner. Numeric value is A0 hex.";
+ }
+
+ typedef time-interval {
+ type int64;
+ description
+ "Time interval, expressed in nanoseconds, multiplied by 2^16.
+ Positive or negative time intervals outside the maximum range
+ of this data type shall be encoded as the largest positive and
+ negative values of the data type, respectively.";
+ reference
+ "5.3.2 of IEEE Std 1588-2019";
+ }
+
+ typedef clock-identity {
+ type string {
+ pattern "[0-9A-F]{2}(-[0-9A-F]{2}){7}";
+ }
+ description
+ "Identifies unique entities within a PTP Network,
+ e.g. a PTP Instance or an entity of a common service.
+ The identity is an 8-octet array, constructed according
+ to specifications in IEEE Std 1588, using an
+ organization identifier from the IEEE Registration
+ Authority.
+ Each octet is represented in YANG as a pair of
+ hexadecimal characters, using uppercase for a letter.
+ Each octet in the array is separated by the dash
+ character.";
+ reference
+ "5.3.4 of IEEE Std 1588-2019
+ 7.5.2.2 of IEEE Std 1588-2019";
+ }
+
+ typedef relative-difference {
+ type int64;
+ description
+ "Relative difference expressed as a dimensionless
+ fraction and multiplied by 2^62, with any
+ remaining fractional part truncated.";
+ reference
+ "5.3.11 of IEEE Std 1588-2019";
+ }
+
+ typedef instance-type {
+ type enumeration {
+ enum oc {
+ value 0;
+ description
+ "Ordinary Clock";
+ }
+ enum bc {
+ value 1;
+ description
+ "Boundary Clock";
+ }
+ enum p2p-tc {
+ value 2;
+ description
+ "Peer-to-peer Transparent Clock";
+ }
+ enum e2e-tc {
+ value 3;
+ description
+ "End-to-end Transparent Clock";
+ }
+ }
+ description
+ "Enumeration for the type of PTP Instance.
+ Values for this enumeration are specified by the IEEE 1588
+ standard exclusively.";
+ reference
+ "8.2.1.5.5 of IEEE Std 1588-2019";
+ }
+
+ typedef fault-severity {
+ type enumeration {
+ enum emergency {
+ value 0;
+ description
+ "Emergency: system is unusable";
+ }
+ enum alert {
+ value 1;
+ description
+ "Alert: immediate action needed";
+ }
+ enum critical {
+ value 2;
+ description
+ "Critical: critical conditions";
+ }
+ enum error {
+ value 3;
+ description
+ "Error: error conditions";
+ }
+ enum warning {
+ value 4;
+ description
+ "Warning: warning conditions";
+ }
+ enum notice {
+ value 5;
+ description
+ "Notice: normal but significant condition";
+ }
+ enum informational {
+ value 6;
+ description
+ "Informational: informational messages";
+ }
+ enum debug {
+ value 7;
+ description
+ "Debug: debug-level messages";
+ }
+ }
+ description
+ "Enumeration for the severity of a fault record.
+ Values for this enumeration are specified by the IEEE 1588
+ standard exclusively.";
+ reference
+ "8.2.6.3 of IEEE Std 1588-2019";
+ }
+
+ typedef port-state {
+ type enumeration {
+ enum initializing {
+ value 1;
+ description
+ "The PTP Port is initializing its data sets, hardware, and
+ communication facilities. The PTP Port shall not place any
+ PTP messages on its communication path.";
+ }
+ enum faulty {
+ value 2;
+ description
+ "The fault state of the protocol. Except for PTP management
+ messages that are a required response to a PTP message
+ received from the applicable management mechanism,
+ a PTP Port in this state shall not transmit any PTP related
+ messages. In a Boundary Clock, no activity on a faulty
+ PTP Port shall affect the other PTP Ports of the
+ PTP Instance. If fault activity on a PTP Port in this state
+ cannot be confined to the faulty PTP Port, then all
+ PTP Ports shall be in the faulty state.";
+ }
+ enum disabled {
+ value 3;
+ description
+ "The PTP Port is disabled. Except for PTP management
+ messages that are a required response to a PTP message
+ received from the applicable management mechanism,
+ a PTP Port in this state shall not transmit any PTP related
+ messages. In a Boundary Clock, no activity at the PTP Port
+ shall be allowed to affect the activity at any other
+ PTP Port of the Boundary Clock. A PTP Port in this state
+ shall discard all received PTP messages except for PTP
+ management messages.";
+ }
+ enum listening {
+ value 4;
+ description
+ "The PTP Port is waiting for the announce-receipt-timeout
+ to expire or to receive an Announce message from a
+ TimeTransmitter PTP Instance. The purpose of this state
+ is to allow orderly addition of PTP Instances to a domain
+ (i.e. to know if this PTP Port is truly a port of the
+ Grandmaster PTP Instance prior to taking that role).";
+ }
+ enum pre-time-transmitter {
+ value 5;
+ description
+ "This port state provides an additional mechanism to
+ support more orderly reconfiguration of PTP Networks when
+ PTP Instances are added or deleted, PTP Instance
+ characteristics change, or connection topology changes.
+ In this state, a PTP Port behaves as it would if it were in
+ the time-transmitter state except that it does not place
+ certain classes of PTP messages on the PTP Communication
+ Path associated with the PTP Port.";
+ }
+ enum time-transmitter {
+ value 6;
+ description
+ "The PTP Port is the source of time on the
+ PTP Communication Path.";
+ }
+ enum passive {
+ value 7;
+ description
+ "The PTP Port is not the source of time on the
+ PTP Communication Path nor does it synchronize to a
+ TimeTransmitter Clock (receive time). The PTP Port can
+ potentially change to time-receiver when PTP Instances are
+ added or deleted, PTP Instance characteristics change, or
+ connection topology changes.";
+ }
+ enum uncalibrated {
+ value 8;
+ description
+ "The PTP Port is anticipating a change to the time-receiver
+ state, but it has not yet satisfied all requirements
+ (implementation or PTP Profile) necessary to ensure
+ complete synchronization. For example, an implementation
+ might require a minimum number of PTP Sync messages
+ in order to completely synchronize its servo algorithm.";
+ }
+ enum time-receiver {
+ value 9;
+ description
+ "The PTP Port synchronizes to the PTP Port on the
+ PTP Communication Path that is in the time-transmitter
+ state (i.e. receives time).";
+ }
+ }
+ description
+ "Enumeration for the state of the protocol engine associated
+ with the PTP Port. Values for this enumeration are specified
+ by the IEEE 1588 standard exclusively.";
+ reference
+ "8.2.15.3.1 of IEEE Std 1588-2019
+ 9.2.5 of IEEE Std 1588-2019";
+ }
+
+ typedef delay-mechanism {
+ type enumeration {
+ enum e2e {
+ value 1;
+ description
+ "The PTP Port is configured to use the delay
+ request-response mechanism.";
+ }
+ enum p2p {
+ value 2;
+ description
+ "The PTP Port is configured to use the peer-to-peer
+ delay mechanism.";
+ }
+ enum no-mechanism {
+ value 254;
+ description
+ "The PTP Port does not implement the delay mechanism.
+ This value shall not be used except when the applicable
+ PTP Profile specifies either:
+ 1) that the PTP Instance only supports frequency
+ transfer (syntonization) and that neither path delay
+ mechanism is to be used or
+ 2) that the PTP Instance participates in time transfer,
+ but the system accuracy requirements are such that,
+ for a segment of the system path, delays can be neglected
+ allowing PTP Instances in that portion of the PTP Network
+ to use the no-mechanism value.";
+ }
+ enum common-p2p {
+ value 3;
+ description
+ "The PTP Port is configured to use the Common Mean Link
+ Delay Service option.";
+ }
+ enum special {
+ value 4;
+ description
+ "Special Ports do not use either delay mechanism.";
+ }
+ }
+ description
+ "Enumeration for the path delay measuring mechanism.
+ Values for this enumeration are specified by the IEEE 1588
+ standard exclusively.";
+ reference
+ "8.2.15.4.4 of IEEE Std 1588-2019";
+ }
+
+ typedef l1sync-state {
+ type enumeration {
+ enum disabled {
+ value 1;
+ description
+ "L1Sync is not enabled on this PTP Port,
+ or the event L1SYNC_RESET has occurred.";
+ }
+ enum idle {
+ value 2;
+ description
+ "L1Sync is enabled on this PTP Port. The PTP Port
+ sends messages with the L1_SYNC TLV. Initialization
+ occurs in this state.";
+ }
+ enum link-alive {
+ value 3;
+ description
+ "The PTP Port sends messages with the L1_SYNC TLV.
+ The PTP Port is receiving valid L1_SYNC TLV
+ from a peer PTP Port.";
+ }
+ enum config-match {
+ value 4;
+ description
+ "The PTP Port sends messages with the L1_SYNC TLV.
+ The PTP Port has a compatible configuration profile
+ when compared with its peer PTP Port configuration
+ profile received in the L1_SYNC TLV.";
+ }
+ enum l1-sync-up {
+ value 5;
+ description
+ "The PTP Port sends messages with the L1_SYNC TLV.
+ The relationship required by configuration is currently
+ in place. Synchronization enhancements are performed.";
+ }
+ }
+ description
+ "Enumeration for states of an L1Sync state machine associated
+ with an L1Sync port.
+ Values for this enumeration are specified by the IEEE 1588
+ standard exclusively.";
+ reference
+ "L.5.3.5 of IEEE Std 1588-2019
+ L.7.2 of IEEE Std 1588-2019";
+ }
+
+ grouping timestamp {
+ description
+ "The IEEE Std 1588 Timestamp type represents a
+ positive time with respect to the epoch
+ of PTP Instance Time.
+ This type is represented in YANG as a grouping,
+ with leafs seconds-field and nanoseconds-field.";
+ reference
+ "5.3.3 of IEEE Std 1588-2019
+ 8.2.6.3 of IEEE Std 1588-2019";
+
+ leaf seconds-field {
+ type uint64 {
+ range "0..281474976710655";
+ }
+ description
+ "The seconds-field member is the integer portion
+ of the timestamp in units of seconds. Since the
+ IEEE 1588 type is UInteger48, only 48 bits
+ are represented in YANG.";
+ }
+
+ leaf nanoseconds-field {
+ type uint32;
+ description
+ "The nanoseconds-field member is the fractional
+ portion of the timestamp in units of nanoseconds.";
+ }
+ }
+ grouping port-identity {
+ description
+ "The IEEE Std 1588 PortIdentity type identifies a
+ PTP Port or Link Port.";
+ reference
+ "5.3.5 of IEEE Std 1588-2019";
+
+ leaf clock-identity {
+ type clock-identity;
+ description
+ "IEEE Std 1588 clockIdentity.";
+ }
+
+ leaf port-number {
+ type uint16;
+ description
+ "IEEE Std 1588 portNumber.
+ If portNumber is unavailable, the value 0 can
+ be used, or this leaf can be omitted from the
+ operational datastore.";
+ reference
+ "7.5.2.3 of IEEE Std 1588-2019";
+ }
+ }
+
+ grouping port-address {
+ description
+ "The IEEE Std 1588 PortAddress type represents the
+ protocol address of a PTP Port.";
+ reference
+ "5.3.6 of IEEE Std 1588-2019";
+
+ leaf network-protocol {
+ type identityref {
+ base network-protocol;
+ }
+ description
+ "Protocol used by a PTP Instance to transport
+ PTP messages.";
+ }
+
+ leaf address-length {
+ type uint16;
+ description
+ "Number of octets in address-field.";
+ }
+
+ leaf address-field {
+ type string {
+ pattern "[0-9A-F]{2}(-[0-9A-F]{2})*";
+ }
+ description
+ "The protocol address of a PTP Port in the format
+ defined by the mapping annex of the protocol as
+ identified by the network-protocol leaf.
+ The most significant octet of the address-field
+ is mapped into the octet of the address-field
+ member with index 0.
+ Each octet is represented in YANG as a pair of
+ hexadecimal characters, using uppercase for a letter.
+ Each octet in the array is separated by the dash
+ character.";
+ }
+ }
+
+ grouping clock-quality {
+ description
+ "Quality of a PTP Instance, which contains IEEE Std 1588
+ clockClass, clockAccuracy and offsetScaledLogVariance.
+ PTP Instances with better quality are more likely to
+ become the Grandmaster PTP Instance.";
+ reference
+ "5.3.7 of IEEE Std 1588-2019
+ 8.2.1.3.1 of IEEE Std 1588-2019";
+
+ leaf clock-class {
+ type identityref {
+ base clock-class;
+ }
+ description
+ "The clockClass denotes the traceability of the time
+ or frequency distributed by the clock.";
+ reference
+ "7.6.2.5 of IEEE Std 1588-2019
+ 8.2.1.3.1.2 of IEEE Std 1588-2019";
+ }
+
+ leaf clock-accuracy {
+ type identityref {
+ base clock-accuracy;
+ }
+ description
+ "The clockAccuracy indicates the accuracy of the clock
+ (Local Clock of the PTP Instance).";
+ reference
+ "7.6.2.6 of IEEE Std 1588-2019
+ 8.2.1.3.1.3 of IEEE Std 1588-2019";
+ }
+
+ leaf offset-scaled-log-variance {
+ type uint16;
+ description
+ "The offsetScaledLogVariance indicates the stability of the
+ clock (Local Clock of the PTP Instance). It provides an
+ estimate of the variations of the clock from a linear timescale
+ when it is not synchronized to another clock using the
+ protocol.";
+ reference
+ "7.6.2.7 of IEEE Std 1588-2019";
+ }
+ }
+
+ grouping fault-record {
+ description
+ "Record of a fault in the PTP Instance.
+
+ NOTE - IEEE Std 1588 specifies a member
+ faultRecordLength for this type, which is needed
+ for PTP Management Messages, but is not needed for
+ YANG management.";
+ reference
+ "5.3.10 of IEEE Std 1588-2019";
+
+ container time {
+ description
+ "Time the fault occurred as indicated by the Timestamping
+ Clock of the PTP Instance. A value of all 1's for the
+ fields in the timestamp shall indicate that the occurrence
+ time is not available.";
+ uses timestamp;
+ }
+
+ leaf severity {
+ type fault-severity;
+ description
+ "Severity of the fault.";
+ }
+
+ leaf name {
+ type string;
+ description
+ "Name for the fault, unique within the implementation.";
+ }
+
+ leaf value {
+ type string;
+ description
+ "Any value that may be associated with the fault that is
+ necessary for fault diagnosis.";
+ }
+
+ leaf description {
+ type string;
+ description
+ "Any supplementary description of the fault.";
+ }
+ }
+
+ grouping communication-capabilities {
+ description
+ "Multicast/unicast capabilities for a port
+ and message type.
+ These attributes report the values that are transmitted
+ by this PTP Instance to other PTP Instance(s) in the
+ network to indicate the multicast/unicast capabilities
+ for a port and message type. Therefore, the context is
+ protocol communication, and not YANG configuration.";
+ reference
+ "5.3.12 of IEEE Std 1588-2019
+ 8.2.25 of IEEE Std 1588-2019
+ 16.9.2 of IEEE Std 1588-2019";
+
+ leaf multicast-capable {
+ type boolean;
+ description
+ "True if the PTP Port is capable of transmitting
+ PTP messages using multicast communication,
+ otherwise it shall be false.";
+ }
+
+ leaf unicast-capable {
+ type boolean;
+ description
+ "True if the PTP Port is capable of transmitting
+ PTP messages using unicast communication,
+ otherwise it shall be false.";
+ }
+
+ leaf unicast-negotiation-capable {
+ type boolean;
+ description
+ "True if the PTP Port is capable negotiating unicast
+ communication using the unicast negotiation feature,
+ and unicast-negotiation-port-ds/enable is true,
+ otherwise the value of shall be false.";
+ }
+
+ leaf unicast-negotiation-required {
+ type boolean;
+ description
+ "True if the value of unicast-negotiation-capable is true
+ and the use of the unicast negotiation feature is
+ required by the implementation, otherwise the value
+ shall be false.";
+ }
+ }
+
+ grouping ptp-instance-performance-parameters {
+ description
+ "PTP Instance Performance Monitoring Parameters,
+ related to the PTP Port or Link Port in the
+ time-receiver state.";
+ reference
+ "Table J.1 of IEEE Std 1588-2019";
+
+ leaf average-time-transmitter-time-receiver-delay {
+ type time-interval;
+ description
+ "Average of the TimeTransmitterTimeReceiverDelay for this
+ interval.";
+ }
+ leaf minimum-time-transmitter-time-receiver-delay {
+ type time-interval;
+ description
+ "Minimum of the TimeTransmitterTimeReceiverDelay for this
+ interval.";
+ }
+ leaf maximum-time-transmitter-time-receiver-delay {
+ type time-interval;
+ description
+ "Maximum of the TimeTransmitterTimeReceiverDelay for this
+ interval.";
+ }
+ leaf stddev-time-transmitter-time-receiver-delay {
+ type time-interval;
+ description
+ "StdDev of the TimeTransmitterTimeReceiverDelay for this
+ interval.";
+ }
+ leaf average-time-receiver-time-transmitter-delay {
+ type time-interval;
+ description
+ "Average of the TimeReceiverTimeTransmitterDelay for this
+ interval.";
+ }
+ leaf minimum-time-receiver-time-transmitter-delay {
+ type time-interval;
+ description
+ "Minimum of the TimeReceiverTimeTransmitterDelay for this
+ interval.";
+ }
+ leaf maximum-time-receiver-time-transmitter-delay {
+ type time-interval;
+ description
+ "Maximum of the TimeReceiverTimeTransmitterDelay for this
+ interval.";
+ }
+ leaf stddev-time-receiver-time-transmitter-delay {
+ type time-interval;
+ description
+ "StdDev of the TimeReceiverTimeTransmitterDelay for this
+ interval.";
+ }
+ leaf average-mean-path-delay {
+ type time-interval;
+ description
+ "Average of the this interval.";
+ }
+ leaf minimum-mean-path-delay {
+ type time-interval;
+ description
+ "Minimum of the for this interval.";
+ }
+ leaf maximum-mean-path-delay {
+ type time-interval;
+ description
+ "Maximum of the for this interval.";
+ }
+ leaf stddev-mean-path-delay {
+ type time-interval;
+ description
+ "StdDev of the for this interval.";
+ }
+ leaf average-offset-from-time-transmitter {
+ type time-interval;
+ description
+ "Average of the for this
+ interval.";
+ }
+ leaf minimum-offset-from-time-transmitter {
+ type time-interval;
+ description
+ "Minimum of the for this
+ interval.";
+ }
+ leaf maximum-offset-from-time-transmitter {
+ type time-interval;
+ description
+ "Maximum of the for this
+ interval.";
+ }
+ leaf stddev-offset-from-time-transmitter {
+ type time-interval;
+ description
+ "StdDev of the for this
+ interval.";
+ }
+ }
+
+ grouping ptp-port-performance-parameters-peer-delay {
+ description
+ "PTP Port Performance Monitoring Parameters,
+ related to the PTP Port or Link Port using the
+ peer-to-peer delay mechanism.";
+ reference
+ "Table J.2 of IEEE Std 1588-2019";
+
+ leaf average-mean-link-delay {
+ type time-interval;
+ description
+ "Average of the for this interval.";
+ }
+ leaf min-mean-link-delay {
+ type time-interval;
+ description
+ "Minimum of the for this interval.";
+ }
+ leaf max-mean-link-delay {
+ type time-interval;
+ description
+ "Maximum of the for this interval.";
+ }
+ leaf stddev-mean-link-delay {
+ type time-interval;
+ description
+ "StdDev of the for this interval.";
+ }
+ }
+
+ grouping additional-performance-parameters {
+ description
+ "Additional Performance Monitoring Parameters,
+ intended to complement ptp-instance-performance-parameters.";
+ reference
+ "Table J.3 of IEEE Std 1588-2019";
+
+ leaf announce-tx {
+ type yang:zero-based-counter32;
+ description
+ "Counter indicating the number of Announce
+ messages that have been transmitted for this
+ interval.";
+ }
+ leaf announce-rx {
+ type yang:zero-based-counter32;
+ description
+ "Counter indicating the number of Announce
+ messages from the current GM that have been
+ received for this interval.";
+ }
+ leaf announce-foreign-rx {
+ type yang:zero-based-counter32;
+ description
+ "Counter indicating the total number of Announce
+ messages from the foreign TimeTransmitters that have been
+ received for this interval.";
+ }
+ leaf sync-tx {
+ type yang:zero-based-counter32;
+ description
+ "Counter indicating the number of Sync
+ messages that have been transmitted for this
+ interval.";
+ }
+ leaf sync-rx {
+ type yang:zero-based-counter32;
+ description
+ "Counter indicating the number of Sync
+ messages that have been received for this
+ interval.";
+ }
+ leaf follow-up-tx {
+ type yang:zero-based-counter32;
+ description
+ "Counter indicating the number of Follow_Up
+ messages that have been transmitted for this
+ interval.";
+ }
+ leaf follow-up-rx {
+ type yang:zero-based-counter32;
+ description
+ "Counter indicating the number of Follow_Up
+ messages that have been received for this
+ interval.";
+ }
+ leaf delay-req-tx {
+ type yang:zero-based-counter32;
+ description
+ "Counter indicating the number of Delay_Req
+ messages that have been transmitted for this
+ interval.";
+ }
+ leaf delay-req-rx {
+ type yang:zero-based-counter32;
+ description
+ "Counter indicating the number of Delay_Req
+ messages that have been received for this
+ interval.";
+ }
+ leaf delay-resp-tx {
+ type yang:zero-based-counter32;
+ description
+ "Counter indicating the number of Delay_Resp
+ messages that have been transmitted for this
+ interval.";
+ }
+ leaf delay-resp-rx {
+ type yang:zero-based-counter32;
+ description
+ "Counter indicating the number of Delay_Resp
+ messages that have been received for this
+ interval.";
+ }
+ leaf pdelay-req-tx {
+ type yang:zero-based-counter32;
+ description
+ "Counter indicating the number of Pdelay_Req
+ messages that have been transmitted for this
+ interval.";
+ }
+ leaf pdelay-req-rx {
+ type yang:zero-based-counter32;
+ description
+ "Counter indicating the number of Pdelay_Req
+ messages that have been received for this
+ interval.";
+ }
+ leaf pdelay-resp-tx {
+ type yang:zero-based-counter32;
+ description
+ "Counter indicating the number of Pdelay_Resp
+ messages that have been transmitted for this
+ interval.";
+ }
+ leaf pdelay-resp-rx {
+ type yang:zero-based-counter32;
+ description
+ "Counter indicating the number of Pdelay_Resp
+ messages that have been received for this
+ interval.";
+ }
+ leaf pdelay-resp-follow-up-tx {
+ type yang:zero-based-counter32;
+ description
+ "Counter indicating the number of
+ Pdelay_Resp_Follow_Up messages that have
+ been transmitted for this interval.";
+ }
+ leaf pdelay-resp-follow-up-rx {
+ type yang:zero-based-counter32;
+ description
+ "Counter indicating the number of
+ Pdelay_Resp_Follow_Up messages that have
+ been transmitted for this interval.";
+ }
+ }
+
+ grouping clock-performance-monitoring-data-record {
+ description
+ "The IEEE Std 1588 ClockPerformanceMonitoringDataRecord
+ type is used for PTP Instance performance monitoring
+ statistics.";
+ reference
+ "Table J.4.1 of IEEE Std 1588-2019";
+
+ leaf index {
+ type uint16;
+ description
+ "Index to each record in the list (0-99).";
+ }
+
+ leaf measurement-valid {
+ type boolean;
+ description
+ "The measurement-valid flag shall indicate the data
+ can be correctly interpreted. Validity is
+ implementation specific and may be defined in
+ a PTP Profile. If for some periods the data is not
+ valid for part of the data collection interval
+ (e.g. the clock is not locked), a specific
+ implementation can report the statistics only for
+ valid data and with measurement-valid true.
+
+ This flag applies to all parameters for a
+ given measurement period, including PTP Port
+ and Link Port related.";
+ }
+
+ leaf period-complete {
+ type boolean;
+ description
+ "The period-complete flag shall indicate that
+ measurements were performed during the entire
+ period (15-minute or 24-hour). For example,
+ if the PTP Instance is disabled for five minutes
+ of a 15-minute period, period-complete is false.
+ The period-complete flag is not related to the
+ validity of measurements that were performed.
+
+ This flag applies to all parameters for a
+ given measurement period, including PTP Port
+ and Link Port related.";
+ }
+
+ leaf pm-time {
+ type yang:timestamp;
+ description
+ "Time of the beginning of the measurement record.
+ This leaf's type is YANG timestamp, which is based
+ on system time (also known as local time). System
+ time is an unsigned integer in units of
+ 10 milliseconds, using an epoch defined by the
+ implementation (typically time of boot-up).";
+ reference
+ "IETF RFC 6991";
+ }
+
+ uses ptp-instance-performance-parameters;
+ }
+
+ grouping port-performance-monitoring-peer-delay-data-record {
+ description
+ "The IEEE Std 1588 PortPerformanceMonitoringPeerDelayDataRecord
+ type is used for the PTP Port related performance monitoring
+ statistics for the peer-to-peer delay measurement mechanism.";
+ reference
+ "Table J.4.1 of IEEE Std 1588-2019";
+
+ leaf index {
+ type uint16;
+ description
+ "Index to each record in the list (0-99).";
+ }
+
+ leaf pm-time {
+ type yang:timestamp;
+ description
+ "Time of the beginning of the measurement record.
+ This leaf's type is YANG timestamp, which is based
+ on system time (also known as local time). System
+ time is an unsigned integer in units of
+ 10 milliseconds, using an epoch defined by the
+ implementation (typically time of boot-up).";
+ reference
+ "RFC 6991";
+ }
+
+ uses ptp-port-performance-parameters-peer-delay;
+ }
+
+ grouping port-performance-monitoring-data-record {
+ description
+ "The IEEE Std 1588 PortPerformanceMonitoringDataRecord
+ type is used for additional PTP Port related performance
+ monitoring statistics.";
+ reference
+ "Table J.4.1 of IEEE Std 1588-2019";
+
+ leaf index {
+ type uint16;
+ description
+ "Index to each record in the list (0-99).";
+ }
+
+ leaf pm-time {
+ type yang:timestamp;
+ description
+ "Time of the beginning of the measurement record.
+ This leaf's type is YANG timestamp, which is based
+ on system time (also known as local time). System
+ time is an unsigned integer in units of
+ 10 milliseconds, using an epoch defined by the
+ implementation (typically time of boot-up).";
+ reference
+ "RFC 6991";
+ }
+
+ uses additional-performance-parameters;
+ }
+
+ container ptp {
+ description
+ "Contains all YANG nodes for the PTP data sets.
+ This hierarchy can be augmented with YANG nodes
+ for a specific vendor or PTP Profile.";
+
+ container instances {
+ description
+ "YANG container that is used to get all PTP Instances.
+ YANG does not allow get of all elements in a YANG list,
+ so a YANG container wrapping the YANG list is provided for
+ that purpose. The naming convention uses plural for the
+ wrapping YANG container, and singular for the YANG list.";
+
+ list instance {
+
+ key "instance-index";
+
+ description
+ "List of one or more PTP Instances in the product (PTP Node).
+ Each PTP Instance represents a distinct instance of PTP
+ implementation (i.e. distinct Ordinary Clock, Boundary Clock,
+ or Transparent Clock), maintaining a distinct time.
+ PTP Instances may be created or deleted dynamically in
+ implementations that support dynamic create/delete.";
+ reference
+ "8.1.4.2 of IEEE Std 1588-2019";
+
+ leaf instance-index {
+ type uint32;
+ description
+ "The instance list is indexed using a number that is
+ unique per PTP Instance within the PTP Node, applicable
+ to the management context only (i.e. not used in PTP
+ messages). The domain-number of the PTP Instance is not
+ used as the key to instance-list, since it is possible
+ for a PTP Node to contain multiple PTP Instances using
+ the same domain-number.";
+ reference
+ "8.1.4.2 of IEEE Std 1588-2019";
+ }
+
+ container default-ds {
+ description
+ "The default data set of the PTP Instance.";
+ reference
+ "8.2.1 of IEEE Std 1588-2019";
+
+ leaf two-step-flag {
+ type boolean;
+ config false;
+ status deprecated;
+ description
+ "When set to true, the PTP Instance is two-step,
+ otherwise the PTP Instance is one-step.
+ This data set member is no longer used. However,
+ the twoStepFlag of the PTP common header is used.
+ One step or two step egress behavior is allowed to
+ be specified per PTP Port, or per PTP Instance.
+ Management of the one/two step egress behavior of
+ a PTP Port is not provided by this standard, but
+ can be specified as extensions to the data sets by a
+ PTP Profile or a product specification.";
+ reference
+ "8.2.1.2.1 of IEEE Std 1588-2019";
+ }
+
+ leaf clock-identity {
+ type clock-identity;
+ config false;
+ description
+ "The IEEE Std 1588 clockIdentity of the PTP Instance.";
+ reference
+ "8.2.1.2.2 of IEEE Std 1588-2019";
+ }
+
+ leaf number-ports {
+ type uint16;
+ config false;
+ description
+ "The number of PTP Ports on the PTP Instance.
+ For an Ordinary Clock, the value shall be one.";
+ reference
+ "8.2.1.2.3 of IEEE Std 1588-2019";
+ }
+
+ container clock-quality {
+ description
+ "The IEEE Std 1588 clockQuality of the PTP Instance.
+ PTP Instances with better quality are more likely to
+ become the Grandmaster PTP Instance.";
+ reference
+ "8.2.1.3.1 of IEEE Std 1588-2019";
+ uses clock-quality;
+ }
+
+ leaf priority1 {
+ type uint8;
+ description
+ "The IEEE Std 1588 priority1 of the PTP Instance.
+ Since priority1 is one of the first comparisons
+ performed by the Best TimeTransmitter Clock Algorithm
+ (BTCA), this leaf's configuration can be used to
+ explicitly select a Grandmaster PTP Instance.
+ Lower values take precedence.
+ The value of priority1 shall be configurable to any
+ value in the range 0 to 255, unless restricted by
+ limits established by the applicable PTP Profile.";
+ reference
+ "7.6.2.3 of IEEE Std 1588-2019
+ 8.2.1.4.1 of IEEE Std 1588-2019";
+ }
+
+ leaf priority2 {
+ type uint8;
+ description
+ "The IEEE Std 1588 priority2 of the PTP Instance.
+ The priority2 member is compared by the
+ Best TimeTransmitter Clock Algorithm (BTCA) after
+ priority1 and clockQuality.
+ Lower values take precedence.
+ The value of priority2 shall be configurable to any
+ value in the range 0 to 255, unless restricted by
+ limits established by the applicable PTP Profile.";
+ reference
+ "7.6.2.4 of IEEE Std 1588-2019
+ 8.2.1.4.2 of IEEE Std 1588-2019";
+ }
+
+ leaf domain-number {
+ type uint8;
+ description
+ "The IEEE Std 1588 domainNumber of the PTP Instance.
+ A domain consists of one or more PTP Instances
+ communicating with each other as defined by the
+ protocol. A domain shall define the scope of PTP message
+ communication, state, operations, data sets, and
+ timescale. Therefore, each domain represents a distinct
+ time.
+ Within a PTP Network, a domain is identified by two
+ data set members: domainNumber and sdoId.
+ The domainNumber is the primary mechanism for end users
+ and system integrators to isolate the operation of a
+ PTP Instance from PTP messages used in other domains.
+ The value of the domainNumber shall be configurable
+ to values permitted in IEEE Std 1588, unless the
+ allowed values are further restricted by the applicable
+ PTP Profile.";
+ reference
+ "7.1 of IEEE Std 1588-2019
+ 8.2.1.4.3 of IEEE Std 1588-2019";
+ }
+
+ leaf time-receiver-only {
+ type boolean;
+ description
+ "The value of time-receiver-only shall be true if the
+ PTP Instance is a time-receiver-only PTP Instance
+ (false for not time-receiver-only).
+ The time-receiver-only member can be true for
+ Ordinary Clocks only.
+ When time-receiver-only is true, the PTP Instance
+ implements special behavior in the context of the state
+ machines that determine port-state.";
+ reference
+ "8.2.1.4.4 of IEEE Std 1588-2019
+ 9.2.2.1 of IEEE Std 1588-2019";
+ }
+
+ leaf sdo-id {
+ type uint16 {
+ range "0..4095";
+ }
+ description
+ "The IEEE Std 1588 sdoId of the PTP Instance.
+ A domain consists of one or more PTP Instances
+ communicating with each other as defined by the
+ protocol. A domain shall define the scope of PTP message
+ communication, state, operations, data sets, and
+ timescale. Therefore, each domain represents a distinct
+ time.
+ Within a PTP Network, a domain is identified by two
+ data set members: domainNumber and sdoId.
+ The sdoId of a domain is a 12-bit integer in the
+ closed range 0 to 4095.
+ The sdoId member is the primary mechanism for providing
+ isolation of PTP Instances operating a PTP Profile
+ specified by a Standards Development Organization (SDO),
+ from other PTP Instances operating a PTP Profile
+ specified by a different SDO.";
+ reference
+ "7.1 of IEEE Std 1588-2019
+ 8.2.1.4.5 of IEEE Std 1588-2019
+ 16.5 of IEEE Std 1588-2019";
+ }
+
+ container current-time {
+ description
+ "For management read, this member shall return the
+ current value of the PTP Instance Time.
+ When management write is supported, this member
+ shall set the PTP Instance Time.
+ Time originates in the Grandmaster PTP Instance and
+ is distributed by PTP to other PTP Instances in
+ the domain.
+ NOTE 1 - The time in the Grandmaster PTP Instance
+ is normally determined by interacting with a primary
+ reference, e.g., GPS, by means outside the scope of
+ this standard.
+ NOTE 2 - When this member is used to set time in a
+ PTP Instance other than the Grandmaster PTP Instance,
+ the PTP Node can return a management error.
+ NOTE 3 - If the time is set in a PTP Instance other
+ than the Grandmaster PTP Instance, it will be
+ overwritten by the operation of the protocol and will
+ therefore exist only as a transient.";
+ reference
+ "8.2.1.5.1 of IEEE Std 1588-2019";
+ uses timestamp;
+ }
+
+ leaf instance-enable {
+ type boolean;
+ description
+ "Indicates if the PTP Instance is enabled for
+ PTP operation.
+ When management write is supported:
+ - Write of the value true shall cause the PTP Instance
+ to initialize, only if the value was previously false.
+ - Write of the value false shall immediately disable
+ operation of the PTP Instance (i.e. analogous to power
+ off).
+ If this leaf is not supported, the PTP Instance shall be
+ specified-by-design to be enabled (true).";
+ reference
+ "8.2.1.5.2 of IEEE Std 1588-2019";
+ }
+
+ leaf external-port-config-enable {
+ if-feature external-port-config;
+ type boolean;
+ description
+ "This value determines whether the external port
+ configuration option is in the disabled state (false)
+ or enabled state (true).
+ When this value is false, each PTP Port's state
+ is determined by PTP state machines, including
+ the Best TimeTransmitter Clock Algorithm (BTCA).
+ When this value is true, each PTP Port's state
+ is configured externally, and PTP state machines
+ are effectively disabled. External configuration
+ of PTP Port state can be accomplished using the
+ desiredState member of the port (i.e.,
+ ../ports/port[]/external-port-config-port-ds/
+ desired-state).";
+ reference
+ "8.2.1.5.3 of IEEE Std 1588-2019
+ 17.6 of IEEE Std 1588-2019";
+ }
+
+ leaf max-steps-removed {
+ type uint8 {
+ range "2..255";
+ }
+ description
+ "If the value of stepsRemoved of an Announce message
+ is greater than or equal to the value of this
+ max-steps-removed leaf, the Announce message is not
+ considered in the operation of the
+ Best TimeTransmitter Clock Algorithm (BTCA).
+ The value shall be in the closed range 2 to 255.
+ If the leaf is not supported, the value used shall
+ be 255.";
+ reference
+ "8.2.1.5.4 of IEEE Std 1588-2019
+ 9.3.2.5 of IEEE Std 1588-2019";
+ }
+
+ leaf instance-type {
+ type instance-type;
+ description
+ "The type of PTP Instance.
+ This leaf is read-only unless support for write is
+ explicitly specified by the applicable PTP Profile or
+ product specification.";
+ reference
+ "8.2.1.5.5 of IEEE Std 1588-2019";
+ }
+ }
+
+ container current-ds {
+ description
+ "Provides current data from operation
+ of the protocol.";
+ reference
+ "8.2.2 of IEEE Std 1588-2019";
+
+ leaf steps-removed {
+ type uint16;
+ config false;
+ description
+ "The number of PTP Communication Paths traversed
+ between this PTP Instance and the Grandmaster
+ PTP Instance.";
+ reference
+ "8.2.2.2 of IEEE Std 1588-2019";
+ }
+
+ leaf offset-from-time-transmitter {
+ type time-interval;
+ config false;
+ description
+ "The current value of the time difference between
+ a TimeTransmitter PTP Instance and a
+ TimeReceiver PTP Instance as computed by the
+ TimeReceiver PTP Instance.
+ NOTE - When a PTP Profile requires a Boundary
+ Clock to transfer offset information internally
+ from TimeReceiver PTP Port to TimeTransmitter
+ PTP Port(s), this value effectively returns the offset
+ from the Grandmaster PTP Instance.";
+ reference
+ "8.2.2.3 of IEEE Std 1588-2019";
+ }
+
+ leaf mean-delay {
+ type time-interval;
+ config false;
+ description
+ "The current value of the mean propagation time between
+ a TimeTransmitter PTP Instance and a
+ TimeReceiver PTP Instance as computed by the
+ TimeReceiver PTP Instance.
+ If the PTP Instance has no PTP Port in time-receiver or
+ uncalibrated state, this returns zero.
+ Otherwise, the TimeReceiver PTP Port returns this value
+ depending on its delay-mechanism:
+ e2e: mean propagation time over the
+ PTP Communication Path, i.e.
+ p2p or common-p2p: mean propagation time over the
+ PTP Link, i.e.
+ disabled or special: zero";
+ reference
+ "7.4.2 of IEEE Std 1588-2019
+ 8.2.2.4 of IEEE Std 1588-2019";
+ }
+
+ leaf mean-path-delay {
+ type time-interval;
+ config false;
+ status deprecated;
+ description
+ "In IEEE Std 1588-2008, currentDS.meanDelay was called
+ currentDS.meanPathDelay. While the specification of
+ this member is retained in the current standard, the
+ member is renamed to currentDS.meanDelay. This change
+ is consistent with other changes that ensure clarity
+ and consistency of naming, where
+ - 'path' is associated with the
+ request-response mechanism
+ - 'link' is associated with the
+ peer-to-peer delay mechanism";
+ reference
+ "8.2.2.4 of IEEE Std 1588-2008";
+ }
+
+ leaf synchronization-uncertain {
+ type boolean;
+ config false;
+ description
+ "This boolean is true when synchronization is
+ uncertain (e.g., not within specification)
+ in either the Parent PTP Port or this
+ PTP Instance. The value is copied from a
+ received Announce message to transmitted Announce
+ message, such that it reflects uncertain
+ synchronization from this PTP Instance to the
+ Grandmaster. Performance metrics for determining
+ uncertainty are specified by the applicable
+ PTP Profile.";
+ reference
+ "8.2.2.5 of IEEE Std 1588-2019";
+ }
+ }
+
+ container parent-ds {
+ description
+ "Provides data learned from the parent of this
+ PTP Instance (i.e. time-transmitter port on the other
+ side of the path/link).";
+ reference
+ "8.2.3 of IEEE Std 1588-2019";
+
+ container parent-port-identity {
+ config false;
+ description
+ "The IEEE Std 1588 portIdentity of the PTP Port on the
+ TimeTransmitter PTP Instance that issues the Sync
+ messages used in synchronizing this PTP Instance.";
+ reference
+ "8.2.3.2 of IEEE Std 1588-2019";
+ uses port-identity;
+ }
+
+ leaf parent-stats {
+ type boolean;
+ config false;
+ description
+ "When set to true, the values of
+ parent-ds/observed-parent-offset-scaled-log-variance
+ and
+ parent-ds/observed-parent-clock-phase-change-rate
+ have been measured and are valid.";
+ reference
+ "8.2.3.3 of IEEE Std 1588-2019";
+ }
+
+ leaf observed-parent-offset-scaled-log-variance {
+ type uint16;
+ config false;
+ description
+ "Estimate of the variance of the phase offset of the
+ Local PTP Clock of the Parent PTP Instance as measured
+ with respect to the Local PTP Clock in the
+ TimeReceiver PTP Instance. This measurement is
+ optional, but if not made, the value of
+ parent-ds/parent-stats shall be false.";
+ reference
+ "7.6.3.3 of IEEE Std 1588-2019
+ 7.6.3.5 of IEEE Std 1588-2019
+ 8.2.3.4 of IEEE Std 1588-2019";
+ }
+
+ leaf observed-parent-clock-phase-change-rate {
+ type int32;
+ config false;
+ description
+ "Estimate of the phase change rate of the
+ Local PTP Clock of the Parent PTP Instance as measured
+ by the TimeReceiver PTP Instance using its
+ Local PTP Clock.
+ If the estimate exceeds the capacity of its data type,
+ this value shall be set to 7FFF FFFF (base 16) or
+ 8000 0000 (base 16), as appropriate. A positive sign
+ indicates that the phase change rate in the
+ Parent PTP Instance is greater than that in the
+ TimeReceiver PTP Instance. The measurement of this
+ value is optional, but if not measured, the value of
+ parent-ds/parent-stats shall be false.";
+ reference
+ "7.6.4.4 of IEEE Std 1588-2019
+ 8.2.3.5 of IEEE Std 1588-2019";
+ }
+
+ leaf grandmaster-identity {
+ type clock-identity;
+ config false;
+ description
+ "The IEEE Std 1588 clockIdentity of the Grandmaster PTP
+ Instance.";
+ reference
+ "8.2.3.6 of IEEE Std 1588-2019";
+ }
+
+ container grandmaster-clock-quality {
+ config false;
+ description
+ "The IEEE Std 1588 clockQuality of the Grandmaster PTP
+ Instance.";
+ reference
+ "8.2.3.7 of IEEE Std 1588-2019";
+ uses clock-quality;
+ }
+
+ leaf grandmaster-priority1 {
+ type uint8;
+ config false;
+ description
+ "The IEEE Std 1588 priority1 of the Grandmaster PTP
+ Instance.";
+ reference
+ "8.2.3.8 of IEEE Std 1588-2019";
+ }
+
+ leaf grandmaster-priority2 {
+ type uint8;
+ config false;
+ description
+ "The IEEE Std 1588 priority2 of the Grandmaster PTP
+ Instance.";
+ reference
+ "8.2.3.9 of IEEE Std 1588-2019";
+ }
+
+ container protocol-address {
+ description
+ "The protocol address of the PTP Port
+ that issues the Sync messages used in synchronizing
+ this PTP Instance.";
+ reference
+ "8.2.3.10 of IEEE Std 1588-2019";
+ uses port-address;
+ }
+
+ leaf synchronization-uncertain {
+ type boolean;
+ config false;
+ description
+ "This boolean is true when synchronization is
+ uncertain in the Parent PTP Port.";
+ reference
+ "8.2.3.11 of IEEE Std 1588-2019";
+ }
+ }
+
+ container time-properties-ds {
+ description
+ "Provides data learned from the current
+ Grandmaster PTP Instance.";
+ reference
+ "8.2.4 of IEEE Std 1588-2019";
+
+ leaf current-utc-offset {
+ when "../current-utc-offset-valid='true'";
+ type int16;
+ description
+ "Specified as in IERS Bulletin C, this provides
+ the offset from UTC (TAI - UTC). The offset is in
+ units of seconds.";
+ reference
+ "7.2.4 of IEEE Std 1588-2019
+ 8.2.4.2 of IEEE Std 1588-2019";
+ }
+
+ leaf current-utc-offset-valid {
+ type boolean;
+ description
+ "The value of current-utc-offset-valid shall be true
+ if the values of current-utc-offset, leap59, and leap61
+ are known to be correct, otherwise it shall be false.
+ NOTE - The constraint for leap59 and leap61 did not
+ exist in IEEE Std 1588-2008, and for compatibility,
+ corresponding when statements were not included below.";
+ reference
+ "8.2.4.3 of IEEE Std 1588-2019";
+ }
+
+ leaf leap59 {
+ type boolean;
+ description
+ "If the timescale is PTP, a true value for leap59
+ shall indicate that the last minute of the
+ current UTC day contains 59 seconds.
+ If the timescale is not PTP, the value shall be
+ false.";
+ reference
+ "8.2.4.4 of IEEE Std 1588-2019";
+ }
+
+ leaf leap61 {
+ type boolean;
+ description
+ "If the timescale is PTP, a true value for leap61
+ shall indicate that the last minute of the
+ current UTC day contains 61 seconds.
+ If the timescale is not PTP, the value shall be
+ false.";
+ reference
+ "8.2.4.5 of IEEE Std 1588-2019";
+ }
+
+ leaf time-traceable {
+ type boolean;
+ description
+ "The value of time-traceable shall be true if the
+ timescale is traceable to a primary reference;
+ otherwise, the value shall be false.
+ The uncertainty specifications appropriate to the
+ evaluation of whether traceability to a primary
+ reference is achieved should be defined in the
+ applicable PTP Profile. In the absence of such a
+ definition the value of time-traceable is
+ implementation specific.";
+ reference
+ "8.2.4.6 of IEEE Std 1588-2019";
+ }
+
+ leaf frequency-traceable {
+ type boolean;
+ description
+ "The value of time-traceable shall be true if the
+ frequency determining the timescale is traceable
+ to a primary reference; otherwise, the value shall
+ be false.
+ The uncertainty specifications appropriate to the
+ evaluation of whether traceability to a primary
+ reference is achieved should be defined in the
+ applicable PTP Profile. In the absence of such a
+ definition the value of frequency-traceable is
+ implementation specific.";
+ reference
+ "8.2.4.7 of IEEE Std 1588-2019";
+ }
+
+ leaf ptp-timescale {
+ type boolean;
+ description
+ "If ptp-timescale is true, the timescale of
+ the Grandmaster PTP Instance is PTP, which is
+ the elapsed time since the PTP epoch measured
+ using the second defined by International Atomic
+ Time (TAI).
+ If ptp-timescale is false, the timescale of
+ the Grandmaster PTP Instance is ARB, which is
+ the elapsed time since an arbitrary epoch.";
+ reference
+ "7.2.1 of IEEE Std 1588-2019
+ 8.2.4.8 of IEEE Std 1588-2019";
+ }
+
+ leaf time-source {
+ type identityref {
+ base time-source;
+ }
+ description
+ "The source of time used by the Grandmaster
+ PTP Instance.";
+ reference
+ "7.6.2.8 of IEEE Std 1588-2019
+ 8.2.4.9 of IEEE Std 1588-2019";
+ }
+ }
+
+ container description-ds {
+ description
+ "Provides descriptive information for the PTP Instance.";
+ reference
+ "8.2.5 of IEEE Std 1588-2019";
+
+ leaf manufacturer-identity {
+ type string {
+ pattern "[0-9A-F]{2}(-[0-9A-F]{2}){2}";
+ }
+ config false;
+ description
+ "3-octet OUI or CID owned by the manufacturer of the
+ PTP Instance, assigned by the IEEE Registration
+ Authority.
+ Each octet is represented in YANG as a pair of
+ hexadecimal characters, using uppercase for a letter.
+ Each octet in the array is separated by the dash
+ character.";
+ reference
+ "8.2.5.2 of IEEE Std 1588-2019";
+ }
+
+ leaf product-description {
+ type string {
+ length "2..64";
+ }
+ config false;
+ description
+ "The product-description string shall indicate, in order:
+ - The name of the manufacturer of the PTP Instance,
+ manufacturerName, followed by a semicolon (;)
+ - The model number of the PTP Instance, modelNumber,
+ followed by a semicolon (;)
+ - A unique identifier of this PTP Instance,
+ instanceIdentifier, such as the MAC address or
+ the serial number.
+ The content and meaning of the manufacturerName,
+ modelNumber, and the instanceIdentifier strings are
+ determined by the manufacturer of the PTP Instance.";
+ reference
+ "8.2.5.3 of IEEE Std 1588-2019";
+ }
+
+ leaf product-revision {
+ type string {
+ length "2..32";
+ }
+ config false;
+ description
+ "Indicate the revisions for PTP Instance's
+ hardware (HW), firmware (FW), and software (SW).
+ This information shall be semicolon (;) separated
+ text fields in the order HW;FW;SW. Non-applicable
+ revisions shall be indicated by a text fields of
+ zero length.";
+ reference
+ "8.2.5.4 of IEEE Std 1588-2019";
+ }
+
+ leaf user-description {
+ type string {
+ length "0..128";
+ }
+ description
+ "Configurable description of the product's PTP Instance.
+ The user-description string should indicate, in order:
+ - A user-defined name of the PTP Instance,
+ e.g., Sensor-1, followed by a semicolon (;)
+ - A user-defined physical location of the PTP Instance,
+ e.g., Rack-2 Shelf-3.";
+ reference
+ "8.2.5.5 of IEEE Std 1588-2019";
+ }
+ }
+
+ container fault-log-ds {
+ if-feature fault-log;
+ config false;
+ description
+ "Represents an optional mechanism for logging of faults
+ that occur in the PTP Instance. If one member of
+ fault-log-ds is supported, all members shall be
+ supported.";
+ reference
+ "8.2.6 of IEEE Std 1588-2019";
+
+ leaf number-of-fault-records {
+ type uint16;
+ config false;
+ description
+ "The number of fault records available in
+ fault-record-list.";
+ reference
+ "8.2.6.2 of IEEE Std 1588-2019";
+ }
+
+ list fault-record-list {
+ config false;
+ description
+ "List of fault records, number-of-fault-records
+ in length.
+ The maximum length of fault-record-list is
+ implementation-specific. The fault-record-list
+ is maintained by the PTP Instance until
+ fault-log-ds.reset is used.";
+ reference
+ "8.2.6.3 of IEEE Std 1588-2019";
+
+ uses fault-record;
+ }
+
+ action reset {
+ description
+ "This action causes the contents of fault-record-list
+ to be cleared, and number-of-fault-records to be set
+ to zero.";
+ reference
+ "8.2.6.4 of IEEE Std 1588-2019";
+ }
+ }
+
+ // The nonvolatileStorageDS in 8.2.7 of IEEE Std 1588-2019
+ // is not applicable for YANG, since protocols like NETCONF
+ // and RESTCONF specify analogous features for configuration
+ // storage.
+
+ container path-trace-ds {
+ if-feature path-trace;
+ description
+ "Provides data for the optional path
+ trace mechanism.";
+ reference
+ "16.2 of IEEE Std 1588-2019";
+
+ leaf-list list {
+ type clock-identity;
+ config false;
+ description
+ "List of IEEE Std 1588 clock identity values
+ (type ClockIdentity), in the order provided in the
+ PATH_TRACE TLV.";
+ reference
+ "16.2.2.2.1 of IEEE Std 1588-2019";
+ }
+
+ leaf enable {
+ type boolean;
+ description
+ "Allows for enable/disable of the path trace mechanism
+ using management. If path-trace-ds.enable is true,
+ the path trace mechanism shall be operational.
+ If path-trace-ds.enable is false, the path trace
+ mechanism shall be inactive.";
+ reference
+ "16.2.2.3.1 of IEEE Std 1588-2019";
+ }
+ }
+
+ container alternate-timescale-ds {
+ if-feature alternate-timescale;
+ description
+ "Provides data for the optional alternate
+ timescale offsets mechanism.";
+ reference
+ "16.3 of IEEE Std 1588-2019";
+
+ leaf max-key {
+ type uint8;
+ config false;
+ description
+ "The value of max-key shall indicate the value of
+ the largest key-field in the list.";
+ reference
+ "16.3.4.3.1 of IEEE Std 1588-2019";
+ }
+
+ list list {
+ key "key-field";
+ description
+ "List of alternate timescales in the PTP Instance.
+ Elements in the list can be created or deleted, if
+ those operations are supported by management.
+
+ If management write is supported for items
+ current-offset, jump-seconds, and time-of-next-jump,
+ the value for all three items shall be provided
+ within a single write operation, and the update of
+ all three items shall be atomic. If any of the three
+ values fails to update, a management error shall be
+ returned.";
+ reference
+ "16.3.4.4.1 of IEEE Std 1588-2019";
+
+ leaf key-field {
+ type uint8;
+ description
+ "Unique identifier of each element in the list.";
+ }
+
+ leaf enable {
+ type boolean;
+ description
+ "If enable is true, the
+ ALTERNATE_TIME_OFFSET_INDICATOR TLV
+ for this alternate timescale shall be attached
+ to Announce messages. If enable is false, the TLV
+ shall not be attached.";
+ }
+
+ leaf current-offset {
+ type int32;
+ description
+ "Offset of the alternate time, in seconds, from
+ PTP Instance Time in the Grandmaster PTP Instance.";
+ }
+
+ leaf jump-seconds {
+ type int32;
+ description
+ "Size of the next discontinuity, in seconds, in the
+ alternate timescale. A value of zero indicates that
+ no discontinuity is expected. A positive value
+ indicates that the discontinuity will cause the
+ current-offset of the alternate timescale to
+ increase.";
+ }
+
+ leaf time-of-next-jump {
+ type uint64;
+ description
+ "Value of the seconds-field of the transmitting PTP
+ Instance Time at the time that the next discontinuity
+ will occur. The discontinuity occurs at the start of
+ the second indicated by the value of time-of-next-jump.
+ Only 48-bits are valid (the upper 16-bits are always
+ zero).";
+ }
+
+ leaf display-name {
+ type string {
+ length "0..10";
+ }
+ description
+ "Textual description of the alternate timescale.";
+ }
+ }
+ }
+
+ container holdover-upgrade-ds {
+ if-feature holdover-upgrade;
+ description
+ "Provides data for the optional holdover
+ upgrade mechanism.";
+ reference
+ "16.4 of IEEE Std 1588-2019";
+
+ leaf enable {
+ type boolean;
+ description
+ "Used to enable (true) or disable (false) the
+ holdover upgrade mechanism.";
+ }
+ }
+
+ container grandmaster-cluster-ds {
+ if-feature grandmaster-cluster;
+ description
+ "Provides data for the optional grandmaster
+ cluster mechanism.";
+ reference
+ "17.2.3 of IEEE Std 1588-2019";
+
+ leaf max-table-size {
+ type uint8;
+ config false;
+ description
+ "Maximum number of elements permitted
+ in the port-address list.
+
+ NOTE - The actualTableSize of IEEE Std 1588 is not
+ applicable for YANG, since YANG mechanisms can be used
+ to control the number of elements in port-address.";
+ }
+
+ leaf log-query-interval {
+ type int8;
+ description
+ "Logarithm to the base 2 of the mean interval in
+ seconds between unicast Announce messages from
+ cluster members.";
+ }
+
+ list port-address {
+ key "index";
+ description
+ "List of port addresses, one for each member of the
+ grandmaster cluster.";
+
+ leaf index {
+ type uint16;
+ description
+ "Index to a port address in the list, typically
+ sequential from 0 to N-1, where N is the number of
+ port addresses.";
+ }
+
+ uses port-address;
+ }
+ }
+
+ container acceptable-time-transmitter-ds {
+ if-feature acceptable-time-transmitter;
+ description
+ "Provides data for the optional acceptable
+ timeTransmitter table mechanism.";
+ reference
+ "17.5.3 of IEEE Std 1588-2019";
+
+ leaf max-table-size {
+ type uint16;
+ config false;
+ description
+ "Maximum number of elements permitted
+ in the list.
+
+ NOTE - The actualTableSize of IEEE Std 1588 is not
+ applicable for YANG, since YANG mechanisms can be used
+ to control the number of elements in list.";
+ reference
+ "17.5.3.3.1 of IEEE Std 1588-2019";
+ }
+
+ list list {
+ key "index";
+ description
+ "List of acceptable timeTransmitters in the
+ PTP Instance. Elements in the list can be created or
+ deleted, if those operations are supported by
+ management.
+
+ If management write is supported for items
+ acceptable-clock-identity, acceptable-port-number,
+ and alternate-priority1, the value for all three items
+ shall be provided within a single write operation,
+ and the update of all three items shall be atomic.
+ If any of the three values fails to update, a management
+ error shall be returned.";
+ reference
+ "17.5.3.4.2 of IEEE Std 1588-2019";
+
+ leaf index {
+ type uint8;
+ description
+ "Unique index to each element in the list, typically
+ sequential from 0 to N-1, where N is the number of
+ elements.";
+ }
+
+ container acceptable-port-identity {
+ description
+ "The IEEE Std 1588 portIdentity of the
+ acceptable timeTransmitter.";
+ uses port-identity;
+ }
+
+ leaf alternate-priority1 {
+ type uint8;
+ description
+ "The IEEE Std 1588 priority1 used as an alternate
+ for the acceptable timeTransmitter.";
+ }
+ }
+ }
+
+ container performance-monitoring-ds {
+ if-feature performance-monitoring;
+ description
+ "Provides data for the optional performance
+ monitoring mechanism, scoped to the PTP Instance.";
+ reference
+ "8.2.13 of IEEE Std 1588-2019
+ J.5.1 of IEEE Std 1588-2019";
+
+ leaf enable {
+ type boolean;
+ description
+ "Permits management control over the collection of
+ performance monitoring data, including
+ performance-monitoring-ds (PTP Instance),
+ ports/port[]/performance-monitoring-port-ds
+ (PTP Port of PTP Instance), and
+ common-services/cmlds/ports/port[]/
+ performance-monitoring-port-ds (CMLDS Link Port
+ associated with enabled PTP Port).";
+ reference
+ "J.5.1.1 of IEEE Std 1588-2019";
+ }
+
+ list record-list {
+ key "index";
+ config false;
+ max-elements 99;
+ description
+ "List of performance monitoring records for the
+ PTP Instance. The list is organized as follows:
+ - 97 15-minute measurement records, the current record
+ at index 0, followed by the most recent 96 records.
+ - 2 24-hour measurement records, the current record
+ at index 97, and the previous record at index 98.
+
+ If a record is not implemented for a specific index,
+ management does not return the record. For example,
+ if only four 15-minute periods are implemented,
+ a management request for performance-monitoring-ds/
+ record-list[6] returns an error.
+
+ If only some of the data is reported, the same index
+ values are used. As an example, if only the 24-hour
+ statistics are accessed, the indexes are still 97 and 98.
+
+ If a specific parameter
+ (e.g. max-time-transmitter-time-receiver-delay)
+ is not implemented, management does not return the
+ parameter (i.e., error). Parameters that are invalid
+ (not measured correctly) shall be indicated with
+ one in all bits, except the most significant. This
+ represents the largest positive value of
+ time-interval, indicating a value outside the
+ maximum range.";
+ reference
+ "J.5.1.2 of IEEE Std 1588-2019";
+
+ uses clock-performance-monitoring-data-record;
+ }
+ }
+
+ container enhanced-metrics-ds {
+ if-feature enhanced-metrics;
+ description
+ "Provides data for the optional enhanced
+ synchronization accuracy metrics mechanism.";
+ reference
+ "16.12 of IEEE Std 1588-2019";
+
+ leaf enable {
+ type boolean;
+ description
+ "If the Enhanced Synchronization Accuracy Metrics feature
+ is implemented, the value true shall indicate that
+ the feature is enabled on the PTP Instance, and the
+ value false shall indicate that the option is disabled
+ on the PTP Instance.";
+ reference
+ "8.2.14.2 of IEEE Std 1588-2019";
+ }
+ }
+
+ container ports {
+ description
+ "YANG container that is used to get all PTP Ports
+ in the PTP Instance.
+ YANG does not allow get of all elements in a YANG list,
+ so a YANG container wrapping the YANG list is provided for
+ that purpose. The naming convention uses plural for the
+ wrapping YANG container, and singular for the YANG list.";
+
+ list port {
+ key "port-index";
+ description
+ "List of data for each PTP Port in the PTP Instance.
+ While the PTP Instance is disabled, it is possible to
+ have zero PTP Ports (i.e., ports not yet created).
+ While the PTP Instance is enabled, an Ordinary Clock
+ will have one PTP Port, and a Boundary Clock or
+ Transparent Clock will have more than one PTP Port.";
+ reference
+ "8.1.4.2 of IEEE Std 1588-2019";
+
+ leaf port-index {
+ type uint16;
+ description
+ "The port list is indexed using a number that is
+ unique per PTP Port within the PTP Instance,
+ applicable to the management context only
+ (i.e., not used in PTP messages).";
+ }
+
+ leaf underlying-interface {
+ type if:interface-ref;
+ description
+ "Reference to the configured underlying IETF YANG
+ interface that is used by this PTP Port for
+ transport of PTP messages. Among other data,
+ physical identifiers for the interface
+ (e.g., MAC address) can be obtained using this
+ reference.";
+ reference
+ "RFC 8343";
+ }
+
+ container port-ds {
+ description
+ "Primary data set for the PTP Port.";
+ reference
+ "8.2.15 of IEEE Std 1588-2019";
+
+ container port-identity {
+ config false;
+ description
+ "The IEEE Std 1588 portIdentity of this PTP Port.";
+ reference
+ "8.2.15.2.1 of IEEE Std 1588-2019";
+ uses port-identity;
+ }
+
+ leaf port-state {
+ type port-state;
+ config false;
+ description
+ "Current state of the protocol engine associated
+ with this PTP Port.";
+ reference
+ "8.2.15.3.1 of IEEE Std 1588-2019";
+ }
+
+ leaf log-min-delay-req-interval {
+ type int8;
+ description
+ "Logarithm to the base 2 of the IEEE Std 1588
+ minDelayReqInterval, the minimum permitted
+ mean time interval between successive Delay_Req
+ messages sent by a TimeReceiver PTP Instance.";
+ reference
+ "7.7.2.4 of IEEE Std 1588-2019
+ 8.2.15.3.2 of IEEE Std 1588-2019";
+ }
+
+ leaf mean-link-delay {
+ type time-interval;
+ config false;
+ description
+ "If the value of the delay-mechanism leaf is p2p
+ this value shall be an estimate of the current
+ one-way propagation delay on the PTP Link attached
+ to this PTP Port, computed using the peer-to-peer
+ delay mechanism.
+ If the value of the delay-mechanism leaf is
+ common-p2p, this value shall be equal to the value of
+ ptp/common-services/cmlds/ports/port[]/port-ds/
+ mean-link-delay.
+ If the value of the delay-mechanism leaf is e2e,
+ disabled, or special, this value shall be zero.";
+ reference
+ "8.2.15.3.3 of IEEE Std 1588-2019";
+ }
+
+ leaf peer-mean-path-delay {
+ type time-interval;
+ config false;
+ status deprecated;
+ description
+ "In IEEE Std 1588-2008, this data set member was
+ called portDS.peerMeanPathDelay. While the
+ specification of this member is retained in the
+ current standard, the member is renamed to
+ portDS.meanLinkDelay (i.e., ../mean-link-delay).
+ This change is consistent with other changes that
+ ensure clarity and consistency of naming, where
+ - 'path' is associated with the
+ request-response mechanism
+ - 'link' is associated with the
+ peer-to-peer delay mechanism";
+ reference
+ "8.2.5.3.3 of IEEE Std 1588-2008";
+ }
+
+ leaf log-announce-interval {
+ type int8;
+ description
+ "Logarithm to the base 2 of the mean IEEE Std 1588
+ announceInterval, the time interval between
+ successive Announce messages sent by a PTP Port.";
+ reference
+ "7.7.2.2 of IEEE Std 1588-2019
+ 8.2.15.4.1 of IEEE Std 1588-2019";
+ }
+
+ leaf announce-receipt-timeout {
+ type uint8;
+ description
+ "The integral multiple of IEEE Std 1588
+ announceInterval that must pass without receipt of
+ an Announce message before the occurrence of the
+ event ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES. The range
+ shall be 2 to 255 subject to further restrictions of
+ the applicable PTP Profile. While 2 is permissible,
+ normally the value should be at least 3.";
+ reference
+ "7.7.3.1 of IEEE Std 1588-2019
+ 8.2.15.4.2 of IEEE Std 1588-2019";
+ }
+
+ leaf log-sync-interval {
+ type int8;
+ description
+ "Logarithm to the base 2 of the mean IEEE Std 1588
+ syncInterval, the time interval between successive
+ Sync messages, when transmitted as multicast
+ messages. The rates for unicast transmissions are
+ negotiated separately on a per PTP Port basis and
+ are not constrained by this leaf.";
+ reference
+ "7.7.2.3 of IEEE Std 1588-2019
+ 8.2.15.4.3 of IEEE Std 1588-2019";
+ }
+
+ leaf delay-mechanism {
+ type delay-mechanism;
+ description
+ "The path delay measuring mechanism used by the PTP
+ Port in computing (propagation delay).";
+ reference
+ "8.2.15.4.4 of IEEE Std 1588-2019";
+ }
+
+ leaf log-min-pdelay-req-interval {
+ type int8;
+ description
+ "Logarithm to the base 2 of the IEEE Std 1588
+ minPdelayReqInterval, the minimum permitted
+ mean time interval between successive Pdelay_Req
+ messages sent over a PTP Link.";
+ reference
+ "7.7.2.5 of IEEE Std 1588-2019
+ 8.2.15.4.5 of IEEE Std 1588-2019";
+ }
+
+ leaf version-number {
+ type uint8;
+ description
+ "The PTP major version in use on the PTP Port.
+ NOTE - This indicates the version of the
+ IEEE 1588 standard, and not the version of an
+ applicable PTP Profile.";
+ reference
+ "8.2.15.4.6 of IEEE Std 1588-2019";
+ }
+
+ leaf minor-version-number {
+ type uint8;
+ description
+ "The PTP minor version in use on the PTP Port.
+ NOTE - This indicates the version of the
+ IEEE 1588 standard, and not the version of an
+ applicable PTP Profile.";
+ reference
+ "8.2.15.4.7 of IEEE Std 1588-2019";
+ }
+
+ leaf delay-asymmetry {
+ type time-interval;
+ description
+ "The value of IEEE Std 1588
+ applicable to the PTP Port, which is the
+ difference in transmission time in one direction
+ as compared to the opposite direction.";
+ reference
+ "7.4.2 of IEEE Std 1588-2019
+ 8.2.15.4.8 of IEEE Std 1588-2019";
+ }
+
+ leaf port-enable {
+ type boolean;
+ description
+ "Indicates if the PTP Port is enabled for
+ PTP operation.
+ When management write is supported:
+ - Write of the value true causes the
+ DESIGNATED_ENABLED event to occur, even if the
+ value was previously true.
+ - Write of the value false causes the
+ DESIGNATED_DISABLED event to occur, even if the
+ value was previously false.
+ If this leaf is not supported, the PTP Port shall be
+ specified-by-design to be enabled (true).";
+ reference
+ "8.2.15.5.1 of IEEE Std 1588-2019";
+ }
+
+ leaf time-transmitter-only {
+ type boolean;
+ description
+ "If the value of time-transmitter-only is true,
+ the PTP Port shall be in the IEEE Std 1588
+ timeTransmitterOnly mode.
+ If the value is false, the PTP Port shall not be
+ in the timeTransmitterOnly mode.
+ When time-transmitter-only is true, the PTP Port
+ can never enter the time-receiver port-state.";
+ reference
+ "8.2.15.5.2 of IEEE Std 1588-2019
+ 9.2.2.2 of IEEE Std 1588-2019";
+ }
+ }
+
+ container timestamp-correction-port-ds {
+ if-feature timestamp-correction;
+ description
+ "Provides access to the configurable correction of
+ timestamps provided to the PTP protocol.";
+ reference
+ "8.2.16 of IEEE Std 1588-2019
+ 16.7 of IEEE Std 1588-2019";
+
+ leaf egress-latency {
+ type time-interval;
+ description
+ "Interval between the
+ provided for a PTP message and the time at which
+ the message timestamp point of the PTP message
+ crosses the reference plane.";
+ reference
+ "7.3.4.2 of IEEE Std 1588-2019
+ 8.2.16.2 of IEEE Std 1588-2019";
+ }
+
+ leaf ingress-latency {
+ type time-interval;
+ description
+ "Interval between the time the message timestamp
+ point of an ingress PTP message crosses the
+ reference plane and the
+ provided for the PTP message.";
+ reference
+ "7.3.4.2 of IEEE Std 1588-2019
+ 8.2.16.3 of IEEE Std 1588-2019";
+ }
+ }
+
+ container asymmetry-correction-port-ds {
+ if-feature asymmetry-correction;
+ description
+ "Provides access to asymmetry correction parameters
+ that are used to compute the value of
+ delayAsymmetry>.";
+ reference
+ "8.2.17 of IEEE Std 1588-2019
+ 16.8 of IEEE Std 1588-2019";
+
+ leaf constant-asymmetry {
+ type time-interval;
+ description
+ "Constant asymmetry used to fine adjust the
+ dynamically calculated value of ,
+ when the mechanism to calculate
+ or certain media is enabled.";
+ reference
+ "8.2.17.2 of IEEE Std 1588-2019";
+ }
+
+ leaf scaled-delay-coefficient {
+ type relative-difference;
+ description
+ "This is the .";
+ reference
+ "8.2.17.3 of IEEE Std 1588-2019";
+ }
+
+ leaf enable {
+ type boolean;
+ description
+ "When this value is true, the mechanism to calculate
+ for certain media is enabled on
+ this PTP Port. When this value is false, this
+ mechanism is disabled on this PTP Port.";
+ reference
+ "8.2.17.4 of IEEE Std 1588-2019";
+ }
+ }
+
+ container description-port-ds {
+ description
+ "Provides descriptive information for the PTP Port.";
+ reference
+ "8.2.18 of IEEE Std 1588-2019";
+
+ leaf profile-identifier {
+ type string {
+ pattern "[0-9A-F]{2}(-[0-9A-F]{2}){5}";
+ }
+ config false;
+ description
+ "When profile-identifier is supported, its value
+ shall identify the PTP Profile implemented by the
+ PTP Port, using the value assigned by the
+ organization that created the PTP Profile.
+ The profile identifier is six octets that identify
+ the PTP Profile's organization, profile within the
+ organization, and version.
+ Each octet is represented in YANG as a pair of
+ hexadecimal characters, using uppercase for a letter.
+ Each octet in the array is separated by the dash
+ character.";
+ reference
+ "8.2.18.2 of IEEE Std 1588-2019
+ 20.3.3 of IEEE Std 1588-2019";
+ }
+
+ container protocol-address {
+ config false;
+ description
+ "Protocol address which is used as the source address
+ by the network transport protocol for this
+ PTP Port.";
+ reference
+ "8.2.18.3 of IEEE Std 1588-2019";
+ uses port-address;
+ }
+ }
+
+ container unicast-negotiation-port-ds {
+ if-feature unicast-negotiation;
+ description
+ "Provides management access to the optional unicast
+ negotiation mechanism.";
+ reference
+ "16.1 of IEEE Std 1588-2019";
+
+ leaf enable {
+ type boolean;
+ description
+ "When enable is false, the unicast negotiation
+ mechanism is disabled on this PTP Port.
+ When enable is true, the unicast negotiation
+ mechanism is enabled on this PTP Port.";
+ reference
+ "8.2.19.2 of IEEE Std 1588-2019";
+ }
+ }
+
+ container alternate-time-transmitter-port-ds {
+ if-feature alternate-time-transmitter;
+ description
+ "Provides management access to the optional alternate
+ timeTransmitter mechanism.";
+ reference
+ "17.3.3 of IEEE Std 1588-2019";
+
+ leaf number-of-alt-time-transmitters {
+ type uint8;
+ description
+ "Limits the number of PTP Ports that can
+ simultaneously transmit messages with the
+ alternate timeTransmitter flag set to TRUE.";
+ reference
+ "17.3.3.2.1 of IEEE Std 1588-2019";
+ }
+
+ leaf tx-alt-multicast-sync {
+ type boolean;
+ description
+ "Controls Sync transmission. If true and the
+ PTP Port is currently transmitting multicast
+ Announce messages with alternateTimeTransmitterFlag
+ TRUE, the PTP Port shall also transmit multicast
+ Sync and, if a two-step PTP Instance,
+ Follow_Up messages. Otherwise do not transmit
+ these messages.";
+ reference
+ "17.3.3.2.2 of IEEE Std 1588-2019";
+ }
+
+ leaf log-alt-multicast-sync-interval {
+ type int8;
+ description
+ "Logarithm to the base 2 of the mean interval
+ in seconds between Sync messages transmitted
+ under the terms of this alternate timeTransmitter
+ mechanism.";
+ reference
+ "17.3.3.2.3 of IEEE Std 1588-2019";
+ }
+ }
+
+ container unicast-discovery-port-ds {
+ if-feature unicast-discovery;
+ description
+ "Provides management access to the optional unicast
+ discovery mechanism.";
+ reference
+ "17.4.3 of IEEE Std 1588-2019";
+
+ leaf max-table-size {
+ type uint16;
+ config false;
+ description
+ "Maximum number of elements permitted
+ in the port-address list.
+
+ NOTE - The actualTableSize of IEEE Std 1588 is not
+ applicable for YANG, since YANG mechanisms can be
+ used to control the number of elements in
+ port-address.";
+ }
+
+ leaf log-query-interval {
+ type int8;
+ description
+ "Logarithm to the base 2 of the mean interval in
+ seconds between requests from a PTP Instance for
+ a unicast Announce message.";
+ }
+
+ list port-address {
+ key "index";
+ description
+ "List of port addresses for unicast discovery.";
+
+ leaf index {
+ type uint16;
+ description
+ "Index to a port address in the list, typically
+ sequential from 0 to N-1, where N is the number of
+ port addresses.";
+ }
+
+ uses port-address;
+ }
+ }
+
+ container acceptable-time-transmitter-port-ds {
+ if-feature acceptable-time-transmitter;
+ description
+ "Provides management access to the optional
+ acceptable timeTransmitter mechanism.";
+ reference
+ "17.5.4 of IEEE Std 1588-2019";
+
+ leaf enable {
+ type boolean;
+ description
+ "When enable is false, the acceptable
+ timeTransmitter table option is not used on this
+ PTP Port, and the normal operation of the protocol
+ is in effect.
+ When enable is true, the acceptable timeTransmitter
+ table option is used on this PTP Port as specified
+ in the standard.";
+ reference
+ "17.5.4.2.1 of IEEE Std 1588-2019";
+ }
+ }
+
+ container l1-sync-basic-port-ds {
+ if-feature l1-sync;
+ description
+ "Provides data for operation of the optional layer-1
+ based synchronization performance enhancement feature.
+ This data is required when the feature is supported.";
+ reference
+ "8.2.23 of IEEE Std 1588-2019
+ L.5 of IEEE Std 1588-2019";
+
+ leaf enabled {
+ type boolean;
+ description
+ "Specifies whether the L1Sync option is enabled
+ on the PTP Port. If enabled is true, then the
+ L1Sync message exchange is supported and enabled.";
+ reference
+ "L.4.1 of IEEE Std 1588-2019";
+ }
+
+ leaf tx-coherent-is-required {
+ type boolean;
+ description
+ "Specifies whether the L1Sync port is required
+ to be a transmit coherent port.";
+ reference
+ "L.4.2 of IEEE Std 1588-2019";
+ }
+
+ leaf rx-coherent-is-required {
+ type boolean;
+ description
+ "Specifies whether the L1Sync port is required
+ to be a receive coherent port.";
+ reference
+ "L.4.3 of IEEE Std 1588-2019";
+ }
+
+ leaf congruent-is-required {
+ type boolean;
+ description
+ "Specifies whether the L1Sync port is required
+ to be a congruent port.";
+ reference
+ "L.4.4 of IEEE Std 1588-2019";
+ }
+
+ leaf opt-params-enabled {
+ type boolean;
+ description
+ "Specifies whether the L1Sync port transmitting
+ the L1_SYNC TLV extends this TLV with optional
+ parameters.";
+ reference
+ "L.4.5 of IEEE Std 1588-2019";
+ }
+
+ leaf log-l1sync-interval {
+ type int8;
+ description
+ "Logarithm to the base 2 of the mean IEEE Std 1588
+ L1SyncInterval, the time interval between successive
+ periodic messages sent by the L1Sync port and
+ carrying the L1_SYNC TLV.";
+ reference
+ "L.4.6 of IEEE Std 1588-2019";
+ }
+
+ leaf l1sync-receipt-timeout {
+ type uint8;
+ description
+ "The intergral number of elapsed IEEE Std 1588
+ L1SyncIntervals that must pass without receipt
+ of the L1_SYNC TLV before the L1_SYNC TLV
+ reception timeout occurs.";
+ reference
+ "L.4.7 of IEEE Std 1588-2019";
+ }
+
+ leaf link-alive {
+ type boolean;
+ config false;
+ description
+ "True when a L1_SYNC TLV is received at the PTP Port
+ and L1Sync is enaled on the PTP Port. False when the
+ L1_SYNC TLV reception timeout occurs.";
+ reference
+ "L.5.3.1 of IEEE Std 1588-2019";
+ }
+
+ leaf is-tx-coherent {
+ type boolean;
+ config false;
+ description
+ "True when the L1Sync port is a transmit coherent
+ port.";
+ reference
+ "L.5.3.2 of IEEE Std 1588-2019";
+ }
+
+ leaf is-rx-coherent {
+ type boolean;
+ config false;
+ description
+ "True when the L1Sync port is a receive coherent
+ port.";
+ reference
+ "L.5.3.3 of IEEE Std 1588-2019";
+ }
+
+ leaf is-congruent {
+ type boolean;
+ config false;
+ description
+ "True when the L1Sync port is a congruent port.";
+ reference
+ "L.5.3.4 of IEEE Std 1588-2019";
+ }
+
+ leaf l1sync-state {
+ type l1sync-state;
+ config false;
+ description
+ "Current state of the L1Sync state machine associated
+ with this L1Sync port.";
+ reference
+ "L.5.3.5 of IEEE Std 1588-2019";
+ }
+
+ leaf peer-tx-coherent-is-required {
+ type boolean;
+ config false;
+ description
+ "Specifies whether this L1Sync port is required
+ to be a transmit coherent port by a peer,
+ as indicated in the value of the TCR field of the
+ most recently received L1_SYNC TLV.";
+ reference
+ "L.5.3.6 of IEEE Std 1588-2019";
+ }
+
+ leaf peer-rx-coherent-is-required {
+ type boolean;
+ config false;
+ description
+ "Specifies whether this L1Sync port is required
+ to be a receive coherent port by a peer,
+ as indicated in the value of the RCR field of the
+ most recently received L1_SYNC TLV.";
+ reference
+ "L.5.3.7 of IEEE Std 1588-2019";
+ }
+
+ leaf peer-congruent-is-required {
+ type boolean;
+ config false;
+ description
+ "Specifies whether this L1Sync port is required
+ is required to be a congruent port by a peer,
+ as indicated in the value of the CR field of the
+ most recently received L1_SYNC TLV.";
+ reference
+ "L.5.3.8 of IEEE Std 1588-2019";
+ }
+
+ leaf peer-is-tx-coherent {
+ type boolean;
+ config false;
+ description
+ "True when the peer L1Sync port is a
+ transmit coherent port
+ (as received in the L1_SYNC TLV).";
+ reference
+ "L.5.3.9 of IEEE Std 1588-2019";
+ }
+
+ leaf peer-is-rx-coherent {
+ type boolean;
+ config false;
+ description
+ "True when the peer L1Sync port is a
+ receive coherent port
+ (as received in the L1_SYNC TLV).";
+ reference
+ "L.5.3.10 of IEEE Std 1588-2019";
+ }
+
+ leaf peer-is-congruent {
+ type boolean;
+ config false;
+ description
+ "True when the peer L1Sync port is a
+ congruent port
+ (as received in the L1_SYNC TLV).";
+ reference
+ "L.5.3.11 of IEEE Std 1588-2019";
+ }
+ }
+
+ container l1-sync-opt-params-port-ds {
+ if-feature l1-sync;
+ description
+ "Provides data for operation of the optional layer-1
+ based synchronization performance enhancement feature.
+ This data is optional when the feature is supported.";
+ reference
+ "8.2.24 of IEEE Std 1588-2019
+ L.8.4 of IEEE Std 1588-2019";
+
+ leaf timestamps-corrected-tx {
+ type boolean;
+ description
+ "When true, the L1Sync port shall correct the
+ transmitted egress timestamps with the known value
+ of the phase offset, as indicated in the Link
+ Reference Model.";
+ reference
+ "L.8.4.2.1 of IEEE Std 1588-2019";
+ }
+
+ leaf phase-offset-tx-valid {
+ type boolean;
+ config false;
+ description
+ "True if and only if the values of the transmission
+ phase offset parameters (phase-offset-tx
+ and phase-offset-tx-timestamp) are valid.";
+ reference
+ "L.8.4.3.1 of IEEE Std 1588-2019";
+ }
+
+ leaf phase-offset-tx {
+ type time-interval;
+ config false;
+ description
+ "Transmission phase offset, which is the
+ time difference between the significant instant
+ with which the passage of the message timestamp
+ point through the reference plane is aligned,
+ and the time represented by the captured
+ timestamp of this passage of the message.";
+ reference
+ "L.8.4.3.3 of IEEE Std 1588-2019";
+ }
+
+ container phase-offset-tx-timestamp {
+ config false;
+ description
+ "Transmission phase offset timestamp
+ for the associated transmission phase offset.";
+ reference
+ "L.8.4.3.4 of IEEE Std 1588-2019";
+
+ uses timestamp;
+ }
+
+ leaf frequency-offset-tx-valid {
+ type boolean;
+ config false;
+ description
+ "True if and only if the values of the transmission
+ frequency offset parameters (frequency-offset-tx
+ and frequency-offset-tx-timestamp) are valid.";
+ reference
+ "L.8.4.3.2 of IEEE Std 1588-2019";
+ }
+
+ leaf frequency-offset-tx {
+ type time-interval;
+ config false;
+ description
+ "Transmission frequency offset, multiplied
+ by one second. Transmission frequency offset
+ is the known rate of change of the transmission
+ phase offset.";
+ reference
+ "L.8.4.3.5 of IEEE Std 1588-2019";
+ }
+
+ container frequency-offset-tx-timestamp {
+ config false;
+ description
+ "Transmission frequency offset timestamp
+ for the associated transmission frequency
+ offset.";
+ reference
+ "L.8.4.3.6 of IEEE Std 1588-2019";
+
+ uses timestamp;
+ }
+ }
+
+ container communication-cap-port-ds {
+ config false;
+ description
+ "Provides data for multicast/unicast communication
+ capabilities.";
+ reference
+ "8.2.25 of IEEE Std 1588-2019";
+
+ container sync {
+ description
+ "Communication capabilities of the PTP Port with
+ respect to sending Sync messages.";
+
+ uses communication-capabilities;
+ }
+
+ container delay-resp {
+ description
+ "Communication capabilities of the PTP Port with
+ respect to sending Delay_Resp messages.";
+
+ uses communication-capabilities;
+ }
+ }
+
+ container performance-monitoring-port-ds {
+ if-feature performance-monitoring;
+ description
+ "Provides data for the optional performance
+ monitoring mechanism, scoped to each PTP Port.";
+ reference
+ "8.2.26 of IEEE Std 1588-2019
+ J.5.2 of IEEE Std 1588-2019";
+
+ list record-list-peer-delay {
+ key "index";
+ config false;
+ max-elements 99;
+ description
+ "List of performance monitoring records for the
+ PTP Port that is using the peer-to-peer delay
+ measurement mehanism. The list is organized
+ as follows:
+ - 97 15-minute measurement records, the current
+ record at index 0, followed by the most recent
+ 96 records.
+ - 2 24-hour measurement records, the current record
+ at index 97, and the previous record at index 98.
+
+ If a record is not implemented for a specific index,
+ management does not return the record. For example,
+ if only four 15-minute periods are implemented,
+ a management request for
+ performance-monitoring-port-ds/
+ record-list-peer-delay[6] returns an error.
+
+ If only some of the data is reported, the same index
+ values are used. As an example, if only the 24-hour
+ statistics are accessed, the indexes are still
+ 97 and 98.
+
+ If a specific parameter (e.g. min-mean-link-delay)
+ is not implemented, management does not return the
+ parameter (i.e., error). Parameters that are invalid
+ (not measured correctly) shall be indicated with
+ one in all bits, except the most significant. This
+ represents the largest positive value of
+ time-interval, indicating a value outside the
+ maximum range.";
+ reference
+ "J.5.2.1 of IEEE Std 1588-2019";
+
+ uses port-performance-monitoring-peer-delay-data-record;
+ }
+
+ list record-list {
+ key "index";
+ config false;
+ max-elements 99;
+ description
+ "List of performance monitoring records for the
+ PTP Port, not specific to the peer-to-peer delay
+ measurement mehanism. The list is organized
+ as follows:
+ - 97 15-minute measurement records, the current
+ record at index 0, followed by the most recent
+ 96 records.
+ - 2 24-hour measurement records, the current record
+ at index 97, and the previous record at index 98.
+
+ If a record is not implemented for a specific index,
+ management does not return the record. For example,
+ if only four 15-minute periods are implemented,
+ a management request for
+ performance-monitoring-port-ds/record-list[6]
+ returns an error.
+
+ If only some of the data is reported, the same index
+ values are used. As an example, if only the 24-hour
+ statistics are accessed, the indexes are still
+ 97 and 98.
+
+ If a specific parameter (e.g. sync-tx)
+ is not implemented, management does not return the
+ parameter (i.e., error). Parameters that are invalid
+ (not measured correctly) shall be indicated with
+ with the value zero, indicating that nothing was
+ counted.
+
+ Each counter in the record shall be initialized to
+ zero at the start of a new 15-minute and
+ 24-hour interval.";
+ reference
+ "J.5.2.2 of IEEE Std 1588-2019";
+
+ uses port-performance-monitoring-data-record;
+ }
+ }
+
+ container common-services-port-ds {
+ description
+ "Provides management access to the common services,
+ scoped to each PTP Port.";
+ reference
+ "16.6.5 of IEEE Std 1588-2019";
+
+ leaf cmlds-link-port-port-number {
+ if-feature cmlds;
+ type uint16;
+ config false;
+ description
+ "Common services operate on all PTP Instances
+ of the PTP Node. When a common service has
+ port-specific behavior, it specifies a Link Port,
+ which represents the physical port that the service
+ uses to transport PTP messages. In the context of
+ such a common service, the PTP Port represents a
+ logical port.
+ The Common Mean Link Delay Service (CMLDS) is
+ port-specific, and this leaf provides the
+ mapping of the PTP Port of this PTP Instance
+ to the corresponding Link Port in CMLDS. The
+ Link Port is identified using an IEEE Std 1588
+ portNumber. The corresponding Link Port's
+ portNumber is located in the hierarchy at
+ /ptp/common-services/cmlds/ports/port[]/port-ds/
+ port-identity/port-number.";
+ reference
+ "16.6.5.1.1.1 of IEEE Std 1588-2019";
+ }
+ }
+
+ container external-port-config-port-ds {
+ if-feature external-port-config;
+ description
+ "Provides management access to the external
+ configuration option, scoped to each PTP Port.";
+ reference
+ "17.6.3 of IEEE Std 1588-2019";
+
+ leaf desired-state {
+ type port-state;
+ description
+ "When the value of
+ default-ds/external-port-config-enable is true,
+ this desired-state is used to externally configure
+ the PTP Port's state (i.e., ../../port-ds/port-state)
+ to a desired value.";
+ reference
+ "17.6.3.2 of IEEE Std 1588-2019";
+ }
+ }
+
+ container time-receiver-monitoring-port-ds {
+ if-feature time-receiver-monitoring;
+ description
+ "Provides management access to the optional
+ TimeReceiver Event Monitor service, scoped to each
+ PTP Port.";
+ reference
+ "16.11.6 of IEEE Std 1588-2019";
+
+ leaf enable {
+ type bits {
+ bit time-receiver-rx-sync-timing-data {
+ position 0;
+ description
+ "True activates generation of the
+ TIME_RECEIVER_RX_SYNC_TIMING_DATA TLV.";
+ }
+ bit time-receiver-rx-sync-computed-data {
+ position 1;
+ description
+ "True activates generation of the
+ TIME_RECEIVER_RX_SYNC_COMPUTED_DATA TLV.";
+ }
+ bit time-receiver-tx-event-timestamps {
+ position 2;
+ description
+ "True activates generation of the
+ TIME_RECEIVER_TX_EVENT_TIMESTAMPS_DATA TLV.";
+ }
+ }
+ description
+ "Each bit (boolean flag) indicates whether
+ the data for a corresponding timeReceiver event
+ monitoring TLV is computed, and whether the data
+ is transmitted by the timeReceiver.";
+ reference
+ "16.11.6.2 of IEEE Std 1588-2019";
+ }
+
+ leaf events-per-rx-sync-timing-tlv {
+ type uint8;
+ description
+ "Indicates the number of events to report per
+ TIME_RECEIVER_RX_SYNC_TIMING_DATA TLV.";
+ reference
+ "16.11.6.3 of IEEE Std 1588-2019";
+ }
+
+ leaf events-per-rx-sync-computed-tlv {
+ type uint8;
+ description
+ "Indicates the number of events to report per
+ TIME_RECEIVER_RX_SYNC_COMPUTED_DATA TLV.";
+ reference
+ "16.11.6.4 of IEEE Std 1588-2019";
+ }
+
+ leaf events-per-tx-timestamps-tlv {
+ type uint8;
+ description
+ "Indicates the number of events to report per
+ TIME_RECEIVER_TX_EVENT_TIMESTAMPS_DATA TLV.";
+ reference
+ "16.11.6.5 of IEEE Std 1588-2019";
+ }
+
+ leaf tx-event-type {
+ type uint8;
+ description
+ "Indicates the event message type selected for
+ the egress event monitoring. The four low-order
+ bits are defined to correspond to the
+ IEEE Std 1588 messageType field.";
+ reference
+ "16.11.6.6 of IEEE Std 1588-2019";
+ }
+
+ leaf rx-sync-timing-tlv-message-m {
+ type uint8;
+ description
+ "The value M, where M indicates that every Mth
+ event message is selected for monitoring in the
+ TIME_RECEIVER_RX_SYNC_TIMING_DATA TLV.
+ For example, if the value of M is 4, every fourth
+ event message is selected for monitoring in
+ the TLV.";
+ reference
+ "16.11.6.7 of IEEE Std 1588-2019";
+ }
+
+ leaf rx-sync-computed-tlv-message-m {
+ type uint8;
+ description
+ "The value M, where M indicates that every Mth
+ event message is selected for monitoring in the
+ TIME_RECEIVER_RX_SYNC_COMPUTED_DATA TLV.
+ For example, if the value of M is 4, every fourth
+ event message is selected for monitoring in
+ the TLV.";
+ reference
+ "16.11.6.8 of IEEE Std 1588-2019";
+ }
+
+ leaf tx-timestamps-tlv-message-m {
+ type uint8;
+ description
+ "The value M, where M indicates that every Mth
+ event message is selected for monitoring in the
+ TIME_RECEIVER_TX_EVENT_TIMESTAMPS_DATA TLV.
+ For example, if the value of M is 4, every fourth
+ event message is selected for monitoring in
+ the TLV.";
+ reference
+ "16.11.6.9 of IEEE Std 1588-2019";
+ }
+ }
+ }
+ }
+ }
+ }
+
+ container transparent-clock-default-ds {
+ status deprecated;
+ description
+ "This default data set was specified in
+ IEEE Std 1588-2008, and under some interpretations,
+ it applied to all domains, which in turn means that it
+ represents multiple Transparent Clocks.
+ In IEEE Std 1588-2019, this data set is specified as
+ applying to the PTP Node (all domains), but the data set is
+ deprecated. For new designs, the standard recommends that
+ Transparent Clocks use the PTP Instance data sets
+ (i.e., /ptp/instances/instance[]), such that each
+ Transparent Clock supports a single PTP Instance and
+ domain.";
+ reference
+ "8.3.1 of IEEE Std 1588-2019";
+
+ leaf clock-identity {
+ type clock-identity;
+ config false;
+ status deprecated;
+ description
+ "The clockIdentity of the local clock.";
+ reference
+ "8.3.2.2.1 of IEEE Std 1588-2019";
+ }
+
+ leaf number-ports {
+ type uint16;
+ config false;
+ status deprecated;
+ description
+ "The number of PTP Ports of the device.";
+ reference
+ "8.3.2.2.2 of IEEE Std 1588-2019";
+ }
+
+ leaf delay-mechanism {
+ type delay-mechanism;
+ status deprecated;
+ description
+ "The propagation delay measuring mechanism (e2e or p2p).";
+ reference
+ "8.3.2.3.1 of IEEE Std 1588-2019";
+ }
+
+ leaf primary-domain {
+ type uint8;
+ status deprecated;
+ description
+ "The domainNumber of the primary syntonization domain.";
+ reference
+ "8.3.2.3.2 of IEEE Std 1588-2019";
+ }
+ }
+
+ container transparent-clock-ports {
+ status deprecated;
+ description
+ "YANG container that is used to get all ports of the
+ IEEE Std 1588 transparentClockPortDS.
+ YANG does not allow get of all elements in a YANG list,
+ so a YANG container wrapping the YANG list is provided for
+ that purpose. The naming convention uses plural for the
+ wrapping YANG container, and singular for the YANG list.";
+
+ list port {
+ key "port-index";
+ status deprecated;
+ description
+ "This list of Transparent Clock port data sets was specified
+ in IEEE Std 1588-2008, and under some interpretations,
+ it applied to all domains, which in turn means that it
+ represents multiple Transparent Clocks.
+ In IEEE Std 1588-2019, this list is specified as
+ applying to the PTP Node (all domains), but the list is
+ deprecated. For new designs, the standard recommends that
+ Transparent Clocks use the PTP Instance data sets
+ (i.e., /ptp/instances/instance[]), such that each
+ Transparent Clock supports a single PTP Instance
+ and domain.";
+ reference
+ "8.3.1 of IEEE Std 1588-2019";
+
+ leaf port-index {
+ type uint16;
+ description
+ "The port list is indexed using a number that is
+ unique per port within the Transparent Clock,
+ applicable to the management context only
+ (i.e., not used in PTP messages).";
+ }
+
+ leaf underlying-interface {
+ type if:interface-ref;
+ description
+ "Reference to the configured underlying IETF YANG
+ interface that is used by this port for
+ transport of PTP messages. Among other data,
+ physical identifiers for the interface
+ (e.g. MAC address) can be obtained using this
+ reference.";
+ reference
+ "RFC 8343";
+ }
+
+ container port-ds {
+ description
+ "IEEE Std 1588 transparentClockPortDS.";
+ reference
+ "8.3.3 of IEEE Std 1588-2019";
+
+ container port-identity {
+ config false;
+ status deprecated;
+ description
+ "The IEEE Std 1588 portIdentity of this port.";
+ reference
+ "8.3.3.2.1 of IEEE Std 1588-2019";
+ uses port-identity;
+ }
+
+ leaf log-min-pdelay-req-interval {
+ type int8;
+ status deprecated;
+ description
+ "The logarithm to the base 2 of the
+ minPdelayReqInterval (minimum permitted mean time
+ interval between successive Pdelay_Req messages).";
+ reference
+ "8.3.3.3.1 of IEEE Std 1588-2019";
+ }
+
+ leaf faulty-flag {
+ type boolean;
+ status deprecated;
+ description
+ "Shall be true if the port is faulty and false
+ if the port is operating normally.";
+ reference
+ "8.3.3.3.2 of IEEE Std 1588-2019";
+ }
+
+ leaf peer-mean-path-delay {
+ type time-interval;
+ config false;
+ status deprecated;
+ description
+ "An estimate of the current one-way propagation delay
+ on the link when the delayMechanism is P2P; otherwise,
+ it is zero.";
+ reference
+ "8.3.3.3.3 of IEEE Std 1588-2019";
+ }
+ }
+ }
+ }
+
+ container common-services {
+ description
+ "Provides management access to the common services.
+ Common services operate on all PTP Instances
+ of the PTP Node.";
+
+ container cmlds {
+ if-feature cmlds;
+ description
+ "The Common Mean Link Delay Service (CMLDS) is an
+ optional service that enables any PTP Port that would
+ normally obtain the value of a link's
+ and using the peer-to-peer method
+ to instead obtain these values from this optional service.
+ The CMLDS service is available to all PTP Instances
+ communicating with a specific transport mechanism,
+ e.g. using Annex F, over the physical link between two PTP
+ Nodes.
+
+ In this option, the term Link Port refers to the mechanism
+ enabling communication with a specific transport mechanism,
+ e.g. using Annex F, over the physical link between two PTP
+ Nodes.
+
+ The Common Mean Link Delay Service is designed to run
+ independently from any PTP Instances communicating
+ over a Link Port. The service provides information on the
+ as well as the as the
+ measured in the timescale used by the service. The service
+ runs on every Link Port where the CMLDS is present.
+ Information required by a PTP Port is requested from and
+ delivered by the service running on the associated
+ Link Port.";
+ reference
+ "16.6.4 of IEEE Std 1588-2019";
+
+ container default-ds {
+ description
+ "The default data set of CMLDS.";
+ reference
+ "16.6.4.1 of IEEE Std 1588-2019";
+
+ leaf clock-identity {
+ type clock-identity;
+ config false;
+ description
+ "The IEEE Std 1588 clockIdentity used by CMLDS.";
+ reference
+ "16.6.4.1.2.1 of IEEE Std 1588-2019";
+ }
+
+ leaf number-link-ports {
+ type uint16;
+ config false;
+ description
+ "The number of Link Ports of CMLDS.";
+ reference
+ "16.6.4.1.2.2 of IEEE Std 1588-2019";
+ }
+ }
+
+ container ports {
+ description
+ "YANG container that is used to get all Link Ports
+ of CMLDS.
+ YANG does not allow get of all elements in a YANG list,
+ so a YANG container wrapping the YANG list is provided for
+ that purpose. The naming convention uses plural for the
+ wrapping YANG container, and singular for the YANG list.";
+
+ list port {
+ key "port-index";
+ description
+ "List of data for each Link Port of CMLDS.
+ The list is structured as leafs for each member
+ of the IEEE Std 1588 cmldsLinkPortDS (primary
+ Link Port data set), followed by containers for
+ each optional Link Port data set. Members of data set
+ cmldsLinkPortDS.commonMeanLinkDelayInformation
+ are listed directly under the list, in order
+ to keep the YANG naming hierarchy as short as
+ possible.";
+ reference
+ "16.6.4.2 of IEEE Std 1588-2019";
+
+ leaf port-index {
+ type uint16;
+ description
+ "The port list is indexed using a number that is
+ unique per Link Port within the CMLDS, applicable
+ to the management context only (i.e. not used in PTP
+ messages).";
+ }
+
+ leaf underlying-interface {
+ type if:interface-ref;
+ description
+ "Reference to the configured underlying IETF YANG
+ interface that is used by this Link Port for
+ transport of PTP messages. Among other data,
+ physical identifiers for the interface
+ (e.g. MAC address) can be obtained using this
+ reference.";
+ reference
+ "RFC 8343";
+ }
+
+ container link-port-ds {
+ description
+ "The IEEE Std 1588 cmldsLinkPortDS of this Link Port.";
+ reference
+ "16.6.4.2 of IEEE Std 1588-2019";
+
+ container port-identity {
+ config false;
+ description
+ "The IEEE Std 1588 portIdentity of this Link Port.";
+ reference
+ "16.6.4.2.2.1 of IEEE Std 1588-2019";
+ uses port-identity;
+ }
+
+ leaf domain-number {
+ type uint8;
+ config false;
+ description
+ "The IEEE Std 1588 domainNumber used by this
+ Link Port. This domain number is not configurable,
+ since its value is determined by the transport
+ mechanism of the Link Port.";
+ reference
+ "16.6.4.2.2.2 of IEEE Std 1588-2019";
+ }
+
+ leaf service-measurement-valid {
+ type boolean;
+ config false;
+ description
+ "This boolean is initialized to false, and will
+ be false whenever the required PTP messages for
+ CMLDS are not received on the Link Port. When
+ the required PTP messages for CMLDS are received,
+ this boolean is true.
+ This value is obtained from the
+ CommonMeanLinkDelayInformation structure returned
+ by CMLDS.";
+ reference
+ "16.6.3.2 of IEEE Std 1588-2019";
+ }
+
+ leaf mean-link-delay {
+ type time-interval;
+ config false;
+ description
+ "Estimate of the current one-way propagation delay
+ on the PTP Link, i.e., , attached
+ to this Link Port, computed using the peer-to-peer
+ delay mechanism.
+ This value is obtained from the
+ CommonMeanLinkDelayInformation structure returned
+ by CMLDS.";
+ reference
+ "16.6.3.2 of IEEE Std 1588-2019";
+ }
+
+ leaf scaled-neighbor-rate-ratio {
+ type int32;
+ config false;
+ description
+ "Ratio of the rate of this PTP Node's clock to
+ the clock of its neighbor attached
+ to this Link Port, i.e., ,
+ scaled as specified in the standard.
+ This value is obtained from the
+ CommonMeanLinkDelayInformation structure returned
+ by CMLDS.";
+ reference
+ "16.6.3.2 of IEEE Std 1588-2019";
+ }
+
+ leaf log-min-pdelay-req-interval {
+ type int8;
+ description
+ "Logarithm to the base 2 of the IEEE Std 1588
+ minPdelayReqInterval, the minimum permitted
+ mean time interval between successive Pdelay_Req
+ messages sent by CMLDS.";
+ reference
+ "16.6.4.2.4.1 of IEEE Std 1588-2019";
+ }
+
+ leaf version-number {
+ type uint8;
+ description
+ "The PTP major version in use on the Link Port.
+ NOTE - This indicates the version of the
+ IEEE 1588 standard, and not the version of an
+ applicable PTP Profile.";
+ reference
+ "16.6.4.2.4.2 of IEEE Std 1588-2019";
+ }
+
+ leaf minor-version-number {
+ type uint8;
+ description
+ "The PTP minor version in use on the Link Port.
+ NOTE - This indicates the version of the
+ IEEE 1588 standard, and not the version of an
+ applicable PTP Profile.";
+ reference
+ "16.6.4.2.4.3 of IEEE Std 1588-2019";
+ }
+
+ leaf delay-asymmetry {
+ type time-interval;
+ description
+ "The value of IEEE Std 1588
+ applicable to the Link Port, which is the
+ difference in transmission time in one direction
+ as compared to the opposite direction.";
+ reference
+ "7.4.2 of IEEE Std 1588-2019
+ 16.6.4.2.4.4 of IEEE Std 1588-2019";
+ }
+ }
+
+ container timestamp-correction-port-ds {
+ if-feature timestamp-correction;
+ description
+ "Provides access to the configurable correction of
+ timestamps provided to the PTP protocol.";
+ reference
+ "16.6.4.3 of IEEE Std 1588-2019";
+
+ leaf egress-latency {
+ type time-interval;
+ description
+ "Interval between the
+ provided for a PTP message and the time at which
+ the message timestamp point of the PTP message
+ crosses the reference plane.";
+ reference
+ "7.3.4.2 of IEEE Std 1588-2019
+ 8.2.16.2 of IEEE Std 1588-2019";
+ }
+
+ leaf ingress-latency {
+ type time-interval;
+ description
+ "Interval between the time the message timestamp
+ point of an ingress PTP message crosses the
+ reference plane and the
+ provided for the PTP message.";
+ reference
+ "7.3.4.2 of IEEE Std 1588-2019
+ 8.2.16.3 of IEEE Std 1588-2019";
+ }
+ }
+
+ container asymmetry-correction-port-ds {
+ if-feature asymmetry-correction;
+ description
+ "Provides access to asymmetry correction parameters
+ that are used to compute the value of
+ .";
+ reference
+ "16.6.4.4 of IEEE Std 1588-2019";
+
+ leaf enable {
+ type boolean;
+ description
+ "When this value is true, the mechanism to calculate
+ for certain media is enabled on
+ this PTP Port. When this value is false, this
+ mechanism is disabled on this PTP Port.";
+ reference
+ "8.2.17.4 of IEEE Std 1588-2019";
+ }
+
+ leaf constant-asymmetry {
+ type time-interval;
+ description
+ "Constant asymmetry used to fine adjust the
+ dynamically calculated value of ,
+ when the mechanism to calculate
+ or certain media is enabled.";
+ reference
+ "8.2.17.2 of IEEE Std 1588-2019";
+ }
+
+ leaf scaled-delay-coefficient {
+ type relative-difference;
+ description
+ "This is the .";
+ reference
+ "8.2.17.3 of IEEE Std 1588-2019";
+ }
+ }
+
+ container performance-monitoring-port-ds {
+ if-feature performance-monitoring;
+ description
+ "Provides data for the optional performance
+ monitoring mechanism, scoped to each Link Port.";
+ reference
+ "16.6.4.5 of IEEE Std 1588-2019";
+
+ list record-list-peer-delay {
+ key "index";
+ config false;
+ max-elements 99;
+ description
+ "List of performance monitoring records for the
+ Link Port that is using the peer-to-peer delay
+ measurement mehanism. The list is organized
+ as follows:
+ - 97 15-minute measurement records, the current
+ record at index 0, followed by the most recent
+ 96 records.
+ - 2 24-hour measurement records, the current record
+ at index 97, and the previous record at index 98.
+
+ If a record is not implemented for a specific index,
+ management does not return the record. For example,
+ if only four 15-minute periods are implemented,
+ a management request for
+ performance-monitoring-port-ds/
+ record-list-peer-delay[6] returns an error.
+
+ If only some of the data is reported, the same index
+ values are used. As an example, if only the 24-hour
+ statistics are accessed, the indexes are still
+ 97 and 98.
+
+ If a specific parameter (e.g. min-mean-link-delay)
+ is not implemented, management does not return the
+ parameter (i.e., error). Parameters that are invalid
+ (not measured correctly) shall be indicated with
+ one in all bits, except the most significant. This
+ represents the largest positive value of
+ time-interval, indicating a value outside the
+ maximum range.";
+ reference
+ "J.5.2.1 of IEEE Std 1588-2019";
+
+ uses port-performance-monitoring-peer-delay-data-record;
+ }
+
+ list record-list {
+ key "index";
+ config false;
+ max-elements 99;
+ description
+ "List of performance monitoring records for the
+ Link Port, not specific to the peer-to-peer delay
+ measurement mehanism. The list is organized
+ as follows:
+ - 97 15-minute measurement records, the current
+ record at index 0, followed by the most recent
+ 96 records.
+ - 2 24-hour measurement records, the current record
+ at index 97, and the previous record at index 98.
+
+ If a record is not implemented for a specific index,
+ management does not return the record. For example,
+ if only four 15-minute periods are implemented,
+ a management request for
+ performance-monitoring-port-ds/record-list[6]
+ returns an error.
+
+ If only some of the data is reported, the same index
+ values are used. As an example, if only the 24-hour
+ statistics are accessed, the indexes are still
+ 97 and 98.
+
+ If a specific parameter (e.g. sync-tx)
+ is not implemented, management does not return the
+ parameter (i.e., error). Parameters that are invalid
+ (not measured correctly) shall be indicated with
+ with the value zero, indicating that nothing was
+ counted.
+
+ Each counter in the record shall be initialized to
+ zero at the start of a new 15-minute and
+ 24-hour interval.";
+ reference
+ "J.5.2.2 of IEEE Std 1588-2019";
+
+ uses port-performance-monitoring-data-record;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/confd/yang/confd/ieee802-dot1as-gptp@2025-12-10.yang b/src/confd/yang/confd/ieee802-dot1as-gptp@2025-12-10.yang
new file mode 100644
index 000000000..af2e94cc9
--- /dev/null
+++ b/src/confd/yang/confd/ieee802-dot1as-gptp@2025-12-10.yang
@@ -0,0 +1,1301 @@
+module ieee802-dot1as-gptp {
+ yang-version "1.1";
+ namespace urn:ieee:std:802.1AS:yang:ieee802-dot1as-gptp;
+ prefix dot1as-gptp;
+
+ import ietf-yang-types {
+ prefix yang;
+ }
+ import ieee1588-ptp-tt {
+ prefix ptp-tt;
+ }
+
+ organization
+ "IEEE 802.1 Working Group";
+ contact
+ "WG-URL: http://ieee802.org/1/
+ WG-EMail: stds-802-1-l@ieee.org
+
+ Contact: IEEE 802.1 Working Group Chair
+ Postal: C/O IEEE 802.1 Working Group
+ IEEE Standards Association
+ 445 Hoes Lane
+ Piscataway, NJ 08854
+ USA
+
+ E-mail: stds-802-1-chairs@ieee.org";
+ description
+ "Management objects that control timing and synchronization for
+ time sensitive applications, as specified in Clause 14 of
+ IEEE Std 802.1AS-2025.
+
+ Copyright (C) IEEE (2025). This version of this YANG module is
+ part of IEEE Std 802.1AS-2025; see the standard itself for full
+ legal notices.";
+
+ revision 2025-12-10 {
+ description
+ "Published as part of IEEE Std 802.1AS-2025.";
+ reference
+ "IEEE Std 802.1AS - Timing and Synchronization for
+ Time-Sensitive Applications: IEEE Std 802.1AS-2025.
+ IEEE Std 1588 - IEEE Standard for a Precision Clock
+ Synchronization Protocol for Networked Measurement and
+ Control Systems: IEEE Std 1588-2019, IEEE Std 1588g-2022,
+ IEEE Std 1588e-2024.";
+ }
+ typedef scaled-ns {
+ type string {
+ pattern "[0-9A-F]{2}(-[0-9A-F]{2}){11}";
+ }
+ description
+ "The IEEE Std 802.1AS ScaledNs type represents signed values
+ of time and time interval in units of 2^16 ns, as a signed
+ 96-bit integer. Each of the 12 octets is represented as a
+ pair of hexadecimal characters, using uppercase for a letter.
+ Octets are separated by a dash character. The most
+ significant octet is first.";
+ reference
+ "6.4.3.1 of IEEE Std 802.1AS";
+ }
+ typedef uscaled-ns {
+ type string {
+ pattern "[0-9A-F]{2}(-[0-9A-F]{2}){11}";
+ }
+ description
+ "The IEEE Std 802.1AS UScaledNs type represents unsigned
+ values of time and time interval in units of 2^16 ns, as an
+ unsigned 96-bit integer. Each of the 12 octets is represented
+ as a pair of hexadecimal characters, using uppercase for a
+ letter. Octets are separated by a dash character. The most
+ significant octet is first.";
+ reference
+ "6.4.3.2 of IEEE Std 802.1AS";
+ }
+ typedef float64 {
+ type string {
+ pattern "[0-9A-F]{2}(-[0-9A-F]{2}){7}";
+ }
+ description
+ "The IEEE Std 802.1AS Float64 type represents IEEE Std 754
+ binary64. Each of the 8 octets is represented as a pair of
+ hexadecimal characters, using uppercase for a letter. Octets
+ are separated by a dash character. The most significant octet
+ is first.";
+ reference
+ "6.4.2 of IEEE Std 802.1AS";
+ }
+ typedef uinteger48 {
+ type uint64 {
+ range "0..281474976710655";
+ }
+ description
+ "48-bit unsigned integer data type.";
+ reference
+ "6.4.2 of IEEE Std 802.1AS";
+ }
+
+ augment "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance"
+ + "/ptp-tt:default-ds" {
+ description
+ "Augment IEEE Std 1588 defaultDS.";
+ leaf gm-capable {
+ type boolean;
+ config false;
+ description
+ "The value is true if the time-aware system is capable of
+ being a Grandmaster, and false if the time-aware system is
+ not capable of being a Grandmaster.";
+ reference
+ "14.2.7 of IEEE Std 802.1AS";
+ }
+ leaf current-utc-offset {
+ when
+ "../current-utc-offset-valid='true'";
+ type int16;
+ config false;
+ description
+ "Offset from UTC (TAI - UTC). The offset is in units of
+ seconds. This leaf applies to the ClockTimeTransmitter
+ entity (i.e., local only, unrelated to a remote GM).";
+ reference
+ "14.2.8 of IEEE Std 802.1AS";
+ }
+ leaf current-utc-offset-valid {
+ type boolean;
+ config false;
+ description
+ "The value of current-utc-offset-valid shall be true if the
+ value of current-utc-offset is known to be correct,
+ otherwise it shall be false. This leaf applies to the
+ ClockTimeTransmitter entity (i.e., local only, unrelated to
+ a remote GM).";
+ reference
+ "14.2.9 of IEEE Std 802.1AS";
+ }
+ leaf leap59 {
+ type boolean;
+ config false;
+ description
+ "If the timescale is PTP, a true value for leap59 shall
+ indicate that the last minute of the current UTC day
+ contains 59 seconds. If the timescale is not PTP, the value
+ shall be false. This leaf applies to the
+ ClockTimeTransmitter entity (i.e., local only, unrelated to
+ a remote GM).";
+ reference
+ "14.2.10 of IEEE Std 802.1AS";
+ }
+ leaf leap61 {
+ type boolean;
+ config false;
+ description
+ "If the timescale is PTP, a true value for leap61 shall
+ indicate that the last minute of the current UTC day
+ contains 61 seconds. If the timescale is not PTP, the value
+ shall be false. This leaf applies to the
+ ClockTimeTransmitter entity (i.e., local only, unrelated to
+ a remote GM).";
+ reference
+ "14.2.11 of IEEE Std 802.1AS";
+ }
+ leaf time-traceable {
+ type boolean;
+ config false;
+ description
+ "The value of time-traceable shall be true if the timescale
+ is traceable to a primary reference; otherwise, the value
+ shall be false. This leaf applies to the
+ ClockTimeTransmitter entity (i.e., local only, unrelated to
+ a remote GM).";
+ reference
+ "14.2.12 of IEEE Std 802.1AS";
+ }
+ leaf frequency-traceable {
+ type boolean;
+ config false;
+ description
+ "The value of frequency-traceable shall be true if the
+ frequency determining the timescale is traceable to a
+ primary reference; otherwise, the value shall be false.
+ This leaf applies to the ClockTimeTransmitter entity
+ (i.e., local only, unrelated to a remote GM).";
+ reference
+ "14.2.13 of IEEE Std 802.1AS";
+ }
+ leaf ptp-timescale {
+ type boolean;
+ config false;
+ description
+ "If ptp-timescale is true, the timescale of the
+ ClockTimeTransmitter entity is PTP, which is the elapsed
+ time since the PTP epoch measured using the second defined
+ by International Atomic Time (TAI). If ptp-timescale is
+ false, the timescale of the ClockTimeTransmitter entity is
+ ARB, which is the elapsed time since an arbitrary epoch.
+ This leaf applies to the ClockTimeTransmitter entity
+ (i.e., local only, unrelated to a remote GM).";
+ reference
+ "14.2.14 of IEEE Std 802.1AS";
+ }
+ leaf time-source {
+ type identityref {
+ base ptp-tt:time-source;
+ }
+ config false;
+ description
+ "The source of time used by the Grandmaster Clock. This leaf
+ applies to the ClockTimeTransmitter entity (i.e., local
+ only, unrelated to a remote GM).";
+ reference
+ "14.2.15 of IEEE Std 802.1AS";
+ }
+ }
+
+ augment "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance"
+ + "/ptp-tt:current-ds" {
+ description
+ "Augment IEEE Std 1588 currentDS.";
+ leaf last-gm-phase-change {
+ type scaled-ns;
+ config false;
+ description
+ "Phase change that occurred on the most recent change in
+ either the Grandmaster PTP Instance or
+ gm-timebase-indicator leaf.";
+ reference
+ "14.3.4 of IEEE Std 802.1AS";
+ }
+ leaf last-gm-freq-change {
+ type float64;
+ config false;
+ description
+ "Frequency change that occurred on the most recent change in
+ either the Grandmaster PTP Instance or
+ gm-timebase-indicator leaf.";
+ reference
+ "14.3.5 of IEEE Std 802.1AS";
+ }
+ leaf gm-timebase-indicator {
+ type uint16;
+ config false;
+ description
+ "The timeBaseIndicator of the current Grandmaster PTP
+ Instance.";
+ reference
+ "14.3.6 of IEEE Std 802.1AS";
+ }
+ leaf gm-change-count {
+ type yang:counter32;
+ config false;
+ description
+ "This statistics counter tracks the number of times the
+ Grandmaster PTP Instance has changed in a gPTP domain.";
+ reference
+ "14.3.7 of IEEE Std 802.1AS";
+ }
+ leaf time-of-last-gm-change {
+ type yang:timestamp;
+ config false;
+ description
+ "System time when the most recent Grandmaster Clock change
+ occurred in a gPTP domain. This leaf's type is YANG
+ timestamp, which is based on system time. System time is an
+ unsigned integer in units of 10 milliseconds, using an
+ epoch defined by the implementation (typically time of
+ boot-up).";
+ reference
+ "14.3.8 of IEEE Std 802.1AS";
+ }
+ leaf time-of-last-phase-change {
+ type yang:timestamp;
+ config false;
+ description
+ "System time when the most recent change in Grandmaster
+ Clock phase occurred. This leaf's type is YANG timestamp,
+ which is based on system time. System time is an unsigned
+ integer in units of 10 milliseconds, using an epoch defined
+ by the implementation (typically time of boot-up).";
+ reference
+ "14.3.9 of IEEE Std 802.1AS";
+ }
+ leaf time-of-last-freq-change {
+ type yang:timestamp;
+ config false;
+ description
+ "System time when the most recent change in Grandmaster
+ Clock frequency occurred. This leaf's type is YANG
+ timestamp, which is based on system time. System time is an
+ unsigned integer in units of 10 milliseconds, using an
+ epoch defined by the implementation (typically time of
+ boot-up).";
+ reference
+ "14.3.10 of IEEE Std 802.1AS";
+ }
+ }
+
+ augment "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance"
+ + "/ptp-tt:parent-ds" {
+ description
+ "Augment IEEE Std 1588 parentDS.";
+ leaf cumulative-rate-ratio {
+ type int32;
+ config false;
+ description
+ "Estimate of the ratio of the frequency of the Grandmaster
+ Clock to the frequency of the LocalClock entity of this PTP
+ Instance. cumulative-rate-ratio is expressed as the
+ fractional frequency offset multiplied by 2^41, i.e., the
+ quantity (rateRatio - 1.0)(2^41).";
+ reference
+ "14.4.3 of IEEE Std 802.1AS";
+ }
+ }
+
+ augment
+ "/ptp-tt:ptp"+
+ "/ptp-tt:instances"+
+ "/ptp-tt:instance"+
+ "/ptp-tt:ports"+
+ "/ptp-tt:port"+
+ "/ptp-tt:port-ds" {
+ description
+ "Augment IEEE Std 1588 portDS.
+
+ 14.10.4 of IEEE Std 802.1AS specifies ptpPortEnabled
+ (ptp-port-enabled), which is provided in YANG as the
+ semantically equivalent node in ieee1588-ptp-tt named
+ port-enable (in port-ds).
+
+ 14.10.16 of IEEE Std 802.1AS specifies
+ mgtSettableLogAnnounceInterval (mgt-log-announce-interval),
+ which is provided in YANG as the semantically equivalent node
+ in ieee1588-ptp-tt named log-announce-interval (in port-ds).
+ In the context of IEEE Std 802.1AS, log-announce-interval
+ cannot be used unless use-mgt-log-announce-interval is true.
+
+ 14.10.21 of IEEE Std 802.1AS specifies
+ mgtSettableLogSyncInterval (mgt-log-sync-interval), which is
+ provided in YANG as the semantically equivalent node in
+ ieee1588-ptp-tt named log-sync-interval (in port-ds). In the
+ context of IEEE Std 802.1AS, log-sync-interval cannot be used
+ unless use-mgt-log-sync-interval is true.";
+ leaf is-measuring-delay {
+ type boolean;
+ config false;
+ description
+ "Boolean that is true if the port is measuring PTP Link
+ propagation delay.";
+ reference
+ "14.10.6 of IEEE Std 802.1AS";
+ }
+ leaf as-capable {
+ type boolean;
+ config false;
+ description
+ "Boolean that is true if and only if it is determined that
+ this PTP Instance and the PTP Instance at the other end of
+ the link attached to this port can interoperate with each
+ other via the IEEE Std 802.1AS protocol.";
+ reference
+ "10.2.5.1 of IEEE Std 802.1AS
+ 14.10.7 of IEEE Std 802.1AS";
+ }
+ leaf mean-link-delay-thresh {
+ type ptp-tt:time-interval;
+ description
+ "Propagation time threshold for mean-link-delay, above which
+ a port is not considered capable of participating in the
+ IEEE Std 802.1AS protocol.";
+ reference
+ "14.10.9 of IEEE Std 802.1AS";
+ }
+ leaf neg-mean-link-delay-thresh {
+ type ptp-tt:time-interval;
+ description
+ "The negative propagation time threshold for
+ mean-link-delay, below which a port is not considered
+ capable of participating in the IEEE Std 802.1AS
+ protocol.";
+ reference
+ "14.10.10 of IEEE Std 802.1AS";
+ }
+ leaf neighbor-rate-ratio {
+ type int32;
+ config false;
+ description
+ "Estimate of the ratio of the frequency of the LocalClock
+ entity of the PTP Instance at the other end of the link
+ attached to this PTP Port, to the frequency of the
+ LocalClock entity of this PTP Instance. neighbor-rate-ratio
+ is expressed as the fractional frequency offset multiplied
+ by 2^41, i.e., the quantity (rateRatio - 1.0)(2^41).";
+ reference
+ "14.10.12 of IEEE Std 802.1AS";
+ }
+ leaf initial-log-announce-interval {
+ type int8;
+ description
+ "When use-mgt-log-announce-interval is false (i.e., change
+ with Signaling message), this is the logarithm to base 2 of
+ the announce interval used when the port is initialized.";
+ reference
+ "14.10.13 of IEEE Std 802.1AS";
+ }
+ leaf current-log-announce-interval {
+ type int8;
+ config false;
+ description
+ "Logarithm to base 2 of the current announce interval.";
+ reference
+ "14.10.14 of IEEE Std 802.1AS";
+ }
+ leaf use-mgt-log-announce-interval {
+ type boolean;
+ description
+ "Boolean that determines the source of the announce
+ interval. If the value is true, the announce interval
+ (current-log-announce-interval) is set equal to the value
+ of mgt-log-announce-interval. If the value is false, the
+ announce interval is determined by the
+ AnnounceIntervalSetting state machine (i.e., changed with
+ Signaling message).";
+ reference
+ "14.10.15 of IEEE Std 802.1AS";
+ }
+ leaf initial-log-sync-interval {
+ type int8;
+ description
+ "When use-mgt-log-sync-interval is false (i.e., change with
+ Signaling message), this is the logarithm to base 2 of the
+ sync interval used when the port is initialized.";
+ reference
+ "14.10.18 of IEEE Std 802.1AS";
+ }
+ leaf current-log-sync-interval {
+ type int8;
+ config false;
+ description
+ "Logarithm to base 2 of the current sync interval.";
+ reference
+ "14.10.19 of IEEE Std 802.1AS";
+ }
+ leaf use-mgt-log-sync-interval {
+ type boolean;
+ description
+ "Boolean that determines the source of the sync interval.
+ If the value is true, the sync interval
+ (current-log-sync-interval) is set equal to the value of
+ mgt-log-sync-interval. If the value is false, the sync
+ interval is determined by the SyncIntervalSetting
+ state machine (i.e., changed with Signaling message).";
+ reference
+ "14.10.20 of IEEE Std 802.1AS";
+ }
+ leaf sync-receipt-timeout {
+ type uint8;
+ description
+ "Number of sync intervals that a timeReceiver port waits
+ without receiving synchronization information, before
+ assuming that the timeTransmitter is no longer transmitting
+ synchronization information and that the BTCA needs to be
+ run, if appropriate.";
+ reference
+ "14.10.22 of IEEE Std 802.1AS";
+ }
+ leaf sync-receipt-timeout-interval {
+ type uscaled-ns;
+ config false;
+ description
+ "Time interval after which sync receipt timeout occurs if
+ time-synchronization information has not been received
+ during the interval.";
+ reference
+ "14.10.23 of IEEE Std 802.1AS";
+ }
+ leaf initial-log-pdelay-req-interval {
+ type int8;
+ description
+ "When use-mgt-log-pdelay-req-interval is false (i.e., change
+ with Signaling message), this is the logarithm to base 2 of
+ the Pdelay_Req transmit interval used when the port is
+ initialized.";
+ reference
+ "14.10.24 of IEEE Std 802.1AS";
+ }
+ leaf current-log-pdelay-req-interval {
+ type int8;
+ config false;
+ description
+ "Logarithm to base 2 of the current Pdelay_Req transmit
+ interval.";
+ reference
+ "14.10.25 of IEEE Std 802.1AS";
+ }
+ leaf use-mgt-log-pdelay-req-interval {
+ type boolean;
+ description
+ "Boolean that determines the source of the Pdelay_Req
+ transmit interval. If the value is true, the Pdelay_Req
+ transmit interval (current-log-pdelay-req-interval) is set
+ equal to the value of mgt-log-pdelay-req-interval. If the
+ value is false, the Pdelay_Req transmit interval is
+ determined by the LinkDelayIntervalSetting state machine
+ (i.e., changed with Signaling message).";
+ reference
+ "14.10.26 of IEEE Std 802.1AS";
+ }
+ leaf mgt-log-pdelay-req-interval {
+ type int8;
+ description
+ "Logarithm to base 2 of the Pdelay_Req transmit interval,
+ used if use-mgt-log-pdelay-req-interval is true. This value
+ is not used if use-mgt-log-pdelay-req-interval is false.";
+ reference
+ "14.10.27 of IEEE Std 802.1AS";
+ }
+ leaf initial-log-gptp-cap-interval {
+ type int8;
+ description
+ "When use-mgt-log-gptp-cap-interval is false (i.e., change
+ with Signaling message), this is the logarithm to base 2 of
+ the gPTP capable message interval used when the port is
+ initialized.";
+ reference
+ "14.10.28 of IEEE Std 802.1AS";
+ }
+ leaf current-log-gptp-cap-interval {
+ type int8;
+ config false;
+ description
+ "Logarithm to base 2 of the current gPTP capable message
+ interval.";
+ reference
+ "14.10.29 of IEEE Std 802.1AS";
+ }
+ leaf use-mgt-log-gptp-cap-interval {
+ type boolean;
+ description
+ "Boolean that determines the source of the gPTP capable
+ message interval. If the value is true, the gPTP capable
+ message interval (current-log-gptp-cap-interval) is set
+ equal to the value of mgt-gptp-cap-req-interval. If the
+ value is false, the gPTP capable message interval is
+ determined by the GptpCapableMessageIntervalSetting state
+ machine (i.e., changed with Signaling message).";
+ reference
+ "14.10.30 of IEEE Std 802.1AS";
+ }
+ leaf mgt-log-gptp-cap-interval {
+ type int8;
+ description
+ "Logarithm to base 2 of the gPTP capable message interval,
+ used if use-mgt-log-gptp-cap-interval is true. This value
+ is not used if use-mgt-log-pdelay-req-interval is false.";
+ reference
+ "14.10.31 of IEEE Std 802.1AS";
+ }
+ leaf initial-compute-neighbor-rate-ratio {
+ type boolean;
+ description
+ "When use-mgt-compute-neighbor-rate-ratio is false
+ (i.e., change with Signaling message), this is the initial
+ value of computeNeighborRateRatio.";
+ reference
+ "14.10.32 of IEEE Std 802.1AS";
+ }
+ leaf current-compute-neighbor-rate-ratio {
+ type boolean;
+ config false;
+ description
+ "Current value of computeNeighborRateRatio.";
+ reference
+ "14.10.33 of IEEE Std 802.1AS";
+ }
+ leaf use-mgt-compute-neighbor-rate-ratio {
+ type boolean;
+ description
+ "Boolean that determines the source of
+ computeNeighborRateRatio. If the value is true,
+ computeNeighborRateRatio is set equal to the value of
+ mgt-compute-neighbor-rate-ratio. If the value is false,
+ computeNeighborRateRatio is determined by the
+ LinkDelayIntervalSetting state machine (i.e., changed with
+ Signaling message).";
+ reference
+ "14.10.34 of IEEE Std 802.1AS";
+ }
+ leaf mgt-compute-neighbor-rate-ratio {
+ type boolean;
+ description
+ "Value of computeNeighborRateRatio, used if
+ use-mgt-compute-neighbor-rate-ratio is true. This value is
+ not used if use-mgt-compute-neighbor-rate-ratio is false.";
+ reference
+ "14.10.35 of IEEE Std 802.1AS";
+ }
+ leaf initial-compute-mean-link-delay {
+ type boolean;
+ description
+ "When use-mgt-compute-mean-link-delay is false (i.e., change
+ with Signaling message), this is the initial value of
+ computeMeanLinkDelay.";
+ reference
+ "14.10.36 of IEEE Std 802.1AS";
+ }
+ leaf current-compute-mean-link-delay {
+ type boolean;
+ config false;
+ description
+ "Current value of computeMeanLinkDelay.";
+ reference
+ "14.10.37 of IEEE Std 802.1AS";
+ }
+ leaf use-mgt-compute-mean-link-delay {
+ type boolean;
+ description
+ "Boolean that determines the source of computeMeanLinkDelay.
+ If the value is true, computeMeanLinkDelay is set equal to
+ the value of mgt-compute-mean-link-delay. If the value is
+ false, computeMeanLinkDelay is determined by the
+ LinkDelayIntervalSetting state machine (i.e., changed with
+ Signaling message).";
+ reference
+ "14.10.38 of IEEE Std 802.1AS";
+ }
+ leaf mgt-compute-mean-link-delay {
+ type boolean;
+ description
+ "Value of computeMeanLinkDelay, used if
+ use-mgt-compute-mean-link-delay is true. This value is not
+ used if use-mgt-compute-mean-link-delay is false.";
+ reference
+ "14.10.39 of IEEE Std 802.1AS";
+ }
+ leaf allowed-lost-responses {
+ type uint8;
+ description
+ "Number of Pdelay_Req messages for which a valid response is
+ not received, above which a port is considered to not be
+ exchanging peer delay messages with its neighbor.";
+ reference
+ "14.10.40 of IEEE Std 802.1AS";
+ }
+ leaf allowed-faults {
+ type uint8;
+ description
+ "Number of faults above which asCapable is set to false.";
+ reference
+ "14.10.41 of IEEE Std 802.1AS";
+ }
+ leaf gptp-cap-receipt-timeout {
+ type uint8;
+ description
+ "Number of transmission intervals that a port waits without
+ receiving the gPTP-capable TLV, before assuming that the
+ neighbor port is no longer invoking the gPTP protocol.";
+ reference
+ "14.10.42 of IEEE Std 802.1AS";
+ }
+ leaf nup {
+ type float64;
+ description
+ "For an OLT port of an IEEE Std 802.3 EPON link, this value
+ is the effective index of refraction for the EPON upstream
+ wavelength light of the optical path.";
+ reference
+ "14.10.44 of IEEE Std 802.1AS";
+ }
+ leaf ndown {
+ type float64;
+ description
+ "For an OLT port of an IEEE 802.3 EPON link, this value is
+ the effective index of refraction for the EPON downstream
+ wavelength light of the optical path.";
+ reference
+ "14.10.45 of IEEE Std 802.1AS";
+ }
+ leaf one-step-tx-oper {
+ type boolean;
+ config false;
+ description
+ "This value is true if the port is sending one-step Sync
+ messages, and false if the port is sending two-step Sync
+ and Follow_Up messages.";
+ reference
+ "14.10.46 of IEEE Std 802.1AS";
+ }
+ leaf one-step-receive {
+ type boolean;
+ config false;
+ description
+ "This value is true if the port is capable of receiving and
+ processing one-step Sync messages.";
+ reference
+ "14.10.47 of IEEE Std 802.1AS";
+ }
+ leaf one-step-transmit {
+ type boolean;
+ config false;
+ description
+ "This value is true if the port is capable of transmitting
+ one-step Sync messages.";
+ reference
+ "14.10.48 of IEEE Std 802.1AS";
+ }
+ leaf initial-one-step-tx-oper {
+ type boolean;
+ description
+ "When use-mgt-one-step-tx-oper is false (i.e., change with
+ Signaling message), this is the initial value of
+ current-one-step-tx-oper.";
+ reference
+ "14.10.49 of IEEE Std 802.1AS";
+ }
+ leaf current-one-step-tx-oper {
+ type boolean;
+ config false;
+ description
+ "This value is true if the port is configured to transmit
+ one-step Sync messages, either via management
+ (mgt-one-step-tx-oper) or Signaling. If both
+ current-one-step-tx-oper and one-step-transmit are true,
+ the port transmits one-step Sync messages
+ (i.e., one-step-tx-oper true).";
+ reference
+ "14.10.50 of IEEE Std 802.1AS";
+ }
+ leaf use-mgt-one-step-tx-oper {
+ type boolean;
+ description
+ "Boolean that determines the source of
+ current-one-step-tx-oper. If the value is true,
+ current-one-step-tx-oper is set equal to the value of
+ mgt-one-step-tx-oper. If the value is false,
+ current-one-step-tx-oper is determined by the
+ OneStepTxOperSetting state machine (i.e., changed with
+ Signaling message).";
+ reference
+ "14.10.51 of IEEE Std 802.1AS";
+ }
+ leaf mgt-one-step-tx-oper {
+ type boolean;
+ description
+ "If use-mgt-one-step-tx-oper is true,
+ current-one-step-tx-oper is set equal to this value. This
+ value is not used if use-mgt-one-step-tx-oper is false.";
+ reference
+ "14.10.52 of IEEE Std 802.1AS";
+ }
+ leaf sync-locked {
+ type boolean;
+ config false;
+ description
+ "This value is true if the port will transmit a Sync as soon
+ as possible after the timeReceiver port receives a Sync
+ message.";
+ reference
+ "14.10.53 of IEEE Std 802.1AS";
+ }
+ leaf-list pdelay-truncated-timestamps {
+ type uinteger48;
+ config false;
+ description
+ "For full-duplex IEEE Std 802.3 media, and CSN media that
+ use the peer-to-peer delay mechanism to measure path delay,
+ the values of the four elements of this leaf-list
+ correspond to the timestamps t1, t2, t3, and t4, listed in
+ that order. Each timestamp is expressed in units of
+ 2^-16 ns (i.e., the value of each array element is equal to
+ the remainder obtained upon dividing the respective
+ timestamp, expressed in units of 2^-16 ns, by 2^48).
+ At any given time, the timestamp values stored in the array
+ are for the same, and most recently completed, peer delay
+ message exchange. For each timestamp, only 48-bits are
+ valid (the upper 16-bits are always zero).";
+ reference
+ "14.10.54 of IEEE Std 802.1AS";
+ }
+ }
+
+ augment
+ "/ptp-tt:ptp"+
+ "/ptp-tt:instances"+
+ "/ptp-tt:instance"+
+ "/ptp-tt:ports"+
+ "/ptp-tt:port" {
+ description
+ "Augment to add port-statistics-ds to IEEE Std 1588 PTP
+ Port.";
+ container port-statistics-ds {
+ description
+ "Provides counters associated with the port of the PTP
+ Instance.";
+ reference
+ "14.12 of IEEE Std 802.1AS";
+ leaf rx-sync-count {
+ type yang:counter32;
+ config false;
+ description
+ "Counter that increments every time synchronization
+ information is received.";
+ reference
+ "14.12.2 of IEEE Std 802.1AS";
+ }
+ leaf rx-one-step-sync-count {
+ type yang:counter32;
+ config false;
+ description
+ "Counter that increments every time a one-step Sync
+ message is received.";
+ reference
+ "14.12.3 of IEEE Std 802.1AS";
+ }
+ leaf rx-follow-up-count {
+ type yang:counter32;
+ config false;
+ description
+ "Counter that increments every time a Follow_Up message is
+ received.";
+ reference
+ "14.12.4 of IEEE Std 802.1AS";
+ }
+ leaf rx-pdelay-req-count {
+ type yang:counter32;
+ config false;
+ description
+ "Counter that increments every time a Pdelay_Req message
+ is received.";
+ reference
+ "14.12.5 of IEEE Std 802.1AS";
+ }
+ leaf rx-pdelay-resp-count {
+ type yang:counter32;
+ config false;
+ description
+ "Counter that increments every time a Pdelay_Resp message
+ is received.";
+ reference
+ "14.12.6 of IEEE Std 802.1AS";
+ }
+ leaf rx-pdelay-resp-follow-up-count {
+ type yang:counter32;
+ config false;
+ description
+ "Counter that increments every time a
+ Pdelay_Resp_Follow_Up message is received.";
+ reference
+ "14.12.7 of IEEE Std 802.1AS";
+ }
+ leaf rx-announce-count {
+ type yang:counter32;
+ config false;
+ description
+ "Counter that increments every time an Announce message is
+ received.";
+ reference
+ "14.12.8 of IEEE Std 802.1AS";
+ }
+ leaf rx-packet-discard-count {
+ type yang:counter32;
+ config false;
+ description
+ "Counter that increments every time a PTP message of the
+ respective PTP Instance is discarded.";
+ reference
+ "14.12.9 of IEEE Std 802.1AS";
+ }
+ leaf sync-receipt-timeout-count {
+ type yang:counter32;
+ config false;
+ description
+ "Counter that increments every time a sync receipt timeout
+ occurs.";
+ reference
+ "14.12.10 of IEEE Std 802.1AS";
+ }
+ leaf announce-receipt-timeout-count {
+ type yang:counter32;
+ config false;
+ description
+ "Counter that increments every time an announce receipt
+ timeout occurs.";
+ reference
+ "14.12.11 of IEEE Std 802.1AS";
+ }
+ leaf pdelay-allowed-lost-exceeded-count {
+ type yang:counter32;
+ config false;
+ description
+ "Counter that increments every time the value of the
+ variable lostResponses exceeds the value of the variable
+ allowedLostResponses, in the RESET state of the
+ MDPdelayReq state machine.";
+ reference
+ "14.12.12 of IEEE Std 802.1AS";
+ }
+ leaf tx-sync-count {
+ type yang:counter32;
+ config false;
+ description
+ "Counter that increments every time synchronization
+ information is transmitted.";
+ reference
+ "14.12.13 of IEEE Std 802.1AS";
+ }
+ leaf tx-one-step-sync-count {
+ type yang:counter32;
+ config false;
+ description
+ "Counter that increments every time a one-step Sync
+ message is transmitted.";
+ reference
+ "14.12.14 of IEEE Std 802.1AS";
+ }
+ leaf tx-follow-up-count {
+ type yang:counter32;
+ config false;
+ description
+ "Counter that increments every time a Follow_Up message is
+ transmitted.";
+ reference
+ "14.12.15 of IEEE Std 802.1AS";
+ }
+ leaf tx-pdelay-req-count {
+ type yang:counter32;
+ config false;
+ description
+ "Counter that increments every time a Pdelay_Req message
+ is transmitted.";
+ reference
+ "14.12.16 of IEEE Std 802.1AS";
+ }
+ leaf tx-pdelay-resp-count {
+ type yang:counter32;
+ config false;
+ description
+ "Counter that increments every time a Pdelay_Resp message
+ is transmitted.";
+ reference
+ "14.12.17 of IEEE Std 802.1AS";
+ }
+ leaf tx-pdelay-resp-follow-up-count {
+ type yang:counter32;
+ config false;
+ description
+ "Counter that increments every time a
+ Pdelay_Resp_Follow_Up message is transmitted.";
+ reference
+ "14.12.18 of IEEE Std 802.1AS";
+ }
+ leaf tx-announce-count {
+ type yang:counter32;
+ config false;
+ description
+ "Counter that increments every time an Announce message is
+ transmitted.";
+ reference
+ "14.12.19 of IEEE Std 802.1AS";
+ }
+ }
+ }
+
+ augment
+ "/ptp-tt:ptp"+
+ "/ptp-tt:instances"+
+ "/ptp-tt:instance"+
+ "/ptp-tt:ports"+
+ "/ptp-tt:port" {
+ description
+ "Augment to add asymmetry-measurement-mode-ds to IEEE Std 1588
+ PTP Port.";
+ container asymmetry-measurement-mode-ds {
+ description
+ "Represents the capability to enable/disable the Asymmetry
+ Compensation Measurement Procedure on a PTP Port. This data
+ set is used instead of the CMLDS
+ asymmetry-measurement-mode-ds when only a single PTP
+ Instance is present (i.e., CMLDS is not used).";
+ reference
+ "14.15 of IEEE Std 802.1AS
+ Annex G of IEEE Std 802.1AS";
+ leaf enabled {
+ type boolean;
+ description
+ "For full-duplex IEEE Std 802.3 media, the value is true
+ if an asymmetry measurement is being performed for the
+ link attached to this PTP Port, and false otherwise. For
+ all other media, the value shall be false.";
+ reference
+ "14.15.2 of IEEE Std 802.1AS";
+ }
+ }
+ }
+
+ augment
+ "/ptp-tt:ptp"+
+ "/ptp-tt:common-services"+
+ "/ptp-tt:cmlds"+
+ "/ptp-tt:ports"+
+ "/ptp-tt:port"+
+ "/ptp-tt:link-port-ds" {
+ description
+ "Augment IEEE Std 1588 cmldsLinkPortDS.";
+ leaf cmlds-link-port-enabled {
+ type boolean;
+ config false;
+ description
+ "Boolean that is true if both delay-mechanism is common-p2p
+ and the value of ptp-port-enabled is true, for at least one
+ PTP Port that uses the CMLDS; otherwise, the value is
+ false.";
+ reference
+ "11.2.18.1 of IEEE Std 802.1AS
+ 14.18.3 of IEEE Std 802.1AS";
+ }
+ leaf is-measuring-delay {
+ type boolean;
+ config false;
+ description
+ "This leaf is analogous to is-measuring-delay for a PTP
+ Port, but applicable to this Link Port.";
+ reference
+ "14.18.4 of IEEE Std 802.1AS";
+ }
+ leaf as-capable-across-domains {
+ type boolean;
+ config false;
+ description
+ "This leaf is true when all PTP Instances (domains) for this
+ Link Port detect proper exchange of Pdelay messages.";
+ reference
+ "11.2.2 of IEEE Std 802.1AS
+ 14.18.5 of IEEE Std 802.1AS";
+ }
+ leaf mean-link-delay-thresh {
+ type ptp-tt:time-interval;
+ description
+ "Propagation time threshold for mean-link-delay, above which
+ a Link Port is not considered capable of participating in
+ the IEEE Std 802.1AS protocol.";
+ reference
+ "14.18.7 of IEEE Std 802.1AS";
+ }
+ leaf neg-mean-link-delay-thresh {
+ type ptp-tt:time-interval;
+ description
+ "The negative propagation time threshold for
+ mean-link-delay, below which a Link Port is not considered
+ capable of participating in the IEEE Std 802.1AS
+ protocol.";
+ reference
+ "14.18.8 of IEEE Std 802.1AS";
+ }
+ leaf initial-log-pdelay-req-interval {
+ type int8;
+ description
+ "This leaf is analogous to initial-log-pdelay-req-interval
+ for a PTP Port, but applicable to this Link Port.";
+ reference
+ "14.18.11 of IEEE Std 802.1AS";
+ }
+ leaf current-log-pdelay-req-interval {
+ type int8;
+ config false;
+ description
+ "This leaf is analogous to current-log-pdelay-req-interval
+ for a PTP Port, but applicable to this Link Port.";
+ reference
+ "14.18.12 of IEEE Std 802.1AS";
+ }
+ leaf use-mgt-log-pdelay-req-interval {
+ type boolean;
+ description
+ "This leaf is analogous to use-mgt-log-pdelay-req-interval
+ for a PTP Port, but applicable to this Link Port.";
+ reference
+ "14.18.13 of IEEE Std 802.1AS";
+ }
+ leaf mgt-log-pdelay-req-interval {
+ type int8;
+ description
+ "This leaf is analogous to mgt-log-pdelay-req-interval for
+ a PTP Port, but applicable to this Link Port.";
+ reference
+ "14.18.14 of IEEE Std 802.1AS";
+ }
+ leaf initial-compute-neighbor-rate-ratio {
+ type boolean;
+ description
+ "This leaf is analogous to
+ initial-compute-neighbor-rate-ratio for a PTP Port, but
+ applicable to this Link Port.";
+ reference
+ "14.18.15 of IEEE Std 802.1AS";
+ }
+ leaf current-compute-neighbor-rate-ratio {
+ type boolean;
+ config false;
+ description
+ "This leaf is analogous to
+ current-compute-neighbor-rate-ratio for a PTP Port, but
+ applicable to this Link Port.";
+ reference
+ "14.18.16 of IEEE Std 802.1AS";
+ }
+ leaf use-mgt-compute-neighbor-rate-ratio {
+ type boolean;
+ description
+ "This leaf is analogous to
+ use-mgt-compute-neighbor-rate-ratio for a PTP Port, but
+ applicable to this Link Port.";
+ reference
+ "14.18.17 of IEEE Std 802.1AS";
+ }
+ leaf mgt-compute-neighbor-rate-ratio {
+ type boolean;
+ description
+ "This leaf is analogous to mgt-compute-neighbor-rate-ratio
+ for a PTP Port, but applicable to this Link Port.";
+ reference
+ "14.18.18 of IEEE Std 802.1AS";
+ }
+ leaf initial-compute-mean-link-delay {
+ type boolean;
+ description
+ "This leaf is analogous to initial-compute-mean-link-delay
+ for a PTP Port, but applicable to this Link Port.";
+ reference
+ "14.18.19 of IEEE Std 802.1AS";
+ }
+ leaf current-compute-mean-link-delay {
+ type boolean;
+ config false;
+ description
+ "This leaf is analogous to current-compute-mean-link-delay
+ for a PTP Port, but applicable to this Link Port.";
+ reference
+ "14.18.20 of IEEE Std 802.1AS";
+ }
+ leaf use-mgt-compute-mean-link-delay {
+ type boolean;
+ description
+ "This leaf is analogous to use-mgt-compute-mean-link-delay
+ for a PTP Port, but applicable to this Link Port.";
+ reference
+ "14.18.21 of IEEE Std 802.1AS";
+ }
+ leaf mgt-compute-mean-link-delay {
+ type boolean;
+ description
+ "This leaf is analogous to mgt-compute-mean-link-delay for a
+ PTP Port, but applicable to this Link Port.";
+ reference
+ "14.18.22 of IEEE Std 802.1AS";
+ }
+ leaf allowed-lost-responses {
+ type uint8;
+ description
+ "This leaf is analogous to allowed-lost-responses for a PTP
+ Port, but applicable to this Link Port.";
+ reference
+ "14.18.23 of IEEE Std 802.1AS";
+ }
+ leaf allowed-faults {
+ type uint8;
+ description
+ "This leaf is analogous to allowed-faults for a PTP Port,
+ but applicable to this Link Port.";
+ reference
+ "14.18.24 of IEEE Std 802.1AS";
+ }
+ leaf-list pdelay-truncated-timestamps {
+ type uinteger48;
+ config false;
+ description
+ "This leaf is analogous to pdelay-truncated-timestamps for a
+ PTP Port, but applicable to this Link Port.";
+ reference
+ "14.18.26 of IEEE Std 802.1AS";
+ }
+ }
+
+ augment
+ "/ptp-tt:ptp"+
+ "/ptp-tt:common-services"+
+ "/ptp-tt:cmlds"+
+ "/ptp-tt:ports"+
+ "/ptp-tt:port" {
+ description
+ "Augment to add port-statistics-ds to IEEE Std 1588 Link
+ Port.";
+ container port-statistics-ds {
+ description
+ "This container is analogous to port-statistics-ds for a PTP
+ Port, but applicable to this Link Port.";
+ reference
+ "14.19 of IEEE Std 802.1AS";
+ leaf rx-pdelay-req-count {
+ type yang:counter32;
+ config false;
+ description
+ "This leaf is analogous to rx-pdelay-req-count for a PTP
+ Port, but applicable to this Link Port.";
+ reference
+ "14.19.2 of IEEE Std 802.1AS";
+ }
+ leaf rx-pdelay-resp-count {
+ type yang:counter32;
+ config false;
+ description
+ "This leaf is analogous to rx-pdelay-resp-count for a PTP
+ Port, but applicable to this Link Port.";
+ reference
+ "14.19.3 of IEEE Std 802.1AS";
+ }
+ leaf rx-pdelay-resp-follow-up-count {
+ type yang:counter32;
+ config false;
+ description
+ "This leaf is analogous to rx-pdelay-resp-follow-up-count
+ for a PTP Port, but applicable to this Link Port.";
+ reference
+ "14.19.4 of IEEE Std 802.1AS";
+ }
+ leaf rx-packet-discard-count {
+ type yang:counter32;
+ config false;
+ description
+ "This leaf is analogous to rx-packet-discard-count for a
+ PTP Port, but applicable to this Link Port.";
+ reference
+ "14.19.5 of IEEE Std 802.1AS";
+ }
+ leaf pdelay-allowed-lost-exceeded-count {
+ type yang:counter32;
+ config false;
+ description
+ "This leaf is analogous to
+ pdelay-allowed-lost-exceeded-count for a PTP Port, but
+ applicable to this Link Port.";
+ reference
+ "14.19.6 of IEEE Std 802.1AS";
+ }
+ leaf tx-pdelay-req-count {
+ type yang:counter32;
+ config false;
+ description
+ "This leaf is analogous to tx-pdelay-req-count for a
+ PTP Port, but applicable to this Link Port.";
+ reference
+ "14.19.7 of IEEE Std 802.1AS";
+ }
+ leaf tx-pdelay-resp-count {
+ type yang:counter32;
+ config false;
+ description
+ "This leaf is analogous to tx-pdelay-resp-count for a
+ PTP Port, but applicable to this Link Port.";
+ reference
+ "14.19.8 of IEEE Std 802.1AS";
+ }
+ leaf tx-pdelay-resp-follow-up-count {
+ type yang:counter32;
+ config false;
+ description
+ "This leaf is analogous to tx-pdelay-resp-follow-up-count
+ for a PTP Port, but applicable to this Link Port.";
+ reference
+ "14.19.9 of IEEE Std 802.1AS";
+ }
+ }
+ }
+
+ augment
+ "/ptp-tt:ptp"+
+ "/ptp-tt:common-services"+
+ "/ptp-tt:cmlds"+
+ "/ptp-tt:ports"+
+ "/ptp-tt:port" {
+ description
+ "Augment to add asymmetry-measurement-mode-ds to IEEE
+ Std 1588 Link Port.";
+ container asymmetry-measurement-mode-ds {
+ description
+ "This container is analogous to
+ asymmetry-measurement-mode-ds for a PTP Port, but
+ applicable to this Link Port.";
+ reference
+ "14.20 of IEEE Std 802.1AS";
+ leaf enabled {
+ type boolean;
+ description
+ "This leaf is analogous to
+ asymmetry-measurement-mode-ds.enabled for a PTP Port, but
+ applicable to this Link Port.";
+ reference
+ "14.20.2 of IEEE Std 802.1AS";
+ }
+ }
+ }
+}
diff --git a/src/confd/yang/confd/infix-if-ptp.yang b/src/confd/yang/confd/infix-if-ptp.yang
new file mode 100644
index 000000000..256f73e83
--- /dev/null
+++ b/src/confd/yang/confd/infix-if-ptp.yang
@@ -0,0 +1,102 @@
+submodule infix-if-ptp {
+ yang-version 1.1;
+ belongs-to infix-interfaces {
+ prefix infix-if;
+ }
+
+ import ietf-interfaces {
+ prefix if;
+ }
+
+ organization "KernelKit";
+ contact "kernelkit@googlegroups.com";
+ description "PTP timestamping capabilities for ietf-interfaces.";
+
+ revision 2026-04-09 {
+ description "Initial revision.";
+ reference "internal";
+ }
+
+ /*
+ * Data Nodes
+ */
+
+ augment "/if:interfaces/if:interface" {
+ description
+ "PTP timestamping capabilities reported by the network driver.
+ Data is probed at boot via ethtool --json -T and is read-only.";
+
+ container ptp-capabilities {
+ config false;
+ description
+ "PTP hardware and software timestamping capabilities of this
+ interface, as reported by the driver via ethtool -T. Absent
+ on virtual interfaces (bridges, VLANs, etc.) that have no
+ underlying physical device.";
+
+ leaf-list capabilities {
+ type enumeration {
+ enum software-transmit {
+ description "Software TX timestamping supported.";
+ }
+ enum software-receive {
+ description "Software RX timestamping supported.";
+ }
+ enum software-system-clock {
+ description "System clock can be used for SW timestamping.";
+ }
+ enum hardware-transmit {
+ description "Hardware TX timestamping supported.";
+ }
+ enum hardware-receive {
+ description "Hardware RX timestamping supported.";
+ }
+ enum hardware-raw-clock {
+ description "Raw hardware clock (PHC) exposed to userspace.";
+ }
+ }
+ description
+ "Set of timestamping capability flags reported by the driver.
+ The presence of hardware-transmit indicates that ptp4l can
+ use hardware timestamping on this interface.";
+ }
+
+ leaf phc-index {
+ type uint32;
+ description
+ "PTP Hardware Clock device index (e.g. 0 for /dev/ptp0).
+ Absent when the interface has no associated PHC.";
+ }
+
+ leaf-list tx-types {
+ type string;
+ description
+ "Hardware TX timestamp types supported (e.g. 'off', 'on',
+ 'onestep-sync'). Empty when hardware-transmit is not in
+ the capabilities set.";
+ }
+
+ leaf-list rx-filters {
+ type string;
+ description
+ "Hardware RX timestamp filter modes supported (e.g.
+ 'ptpv2-l2-event', 'ptpv2-l4-sync'). Empty when
+ hardware-receive is not in the capabilities set.";
+ }
+
+ leaf hwtstamp-provider-index {
+ type uint32;
+ description
+ "Hardware timestamp provider index. Present only on kernels
+ and drivers that expose per-provider timestamping (Linux 6.x+).";
+ }
+
+ leaf hwtstamp-provider-qualifier {
+ type string;
+ description
+ "Human-readable quality descriptor for the hardware timestamp
+ provider, e.g. 'Precise (IEEE 1588 quality)'.";
+ }
+ }
+ }
+}
diff --git a/src/confd/yang/confd/infix-if-ptp@2026-04-09.yang b/src/confd/yang/confd/infix-if-ptp@2026-04-09.yang
new file mode 120000
index 000000000..ee7aa240b
--- /dev/null
+++ b/src/confd/yang/confd/infix-if-ptp@2026-04-09.yang
@@ -0,0 +1 @@
+infix-if-ptp.yang
\ No newline at end of file
diff --git a/src/confd/yang/confd/infix-interfaces.yang b/src/confd/yang/confd/infix-interfaces.yang
index 3e6d88ba0..0ace0fe2c 100644
--- a/src/confd/yang/confd/infix-interfaces.yang
+++ b/src/confd/yang/confd/infix-interfaces.yang
@@ -35,11 +35,17 @@ module infix-interfaces {
include infix-if-vxlan;
include infix-if-wifi;
include infix-if-wireguard;
+ include infix-if-ptp;
organization "KernelKit";
contact "kernelkit@googlegroups.com";
description "Linux bridge and lag extensions for ietf-interfaces.";
+ revision 2026-04-09 {
+ description "Add ptp-capabilities submodule for per-interface PTP timestamping info.";
+ reference "internal";
+ }
+
revision 2025-11-06 {
description "Use new tunnel-common grouping for local, remote, ttl, and tos.";
reference "internal";
diff --git a/src/confd/yang/confd/infix-interfaces@2025-11-06.yang b/src/confd/yang/confd/infix-interfaces@2026-04-09.yang
similarity index 100%
rename from src/confd/yang/confd/infix-interfaces@2025-11-06.yang
rename to src/confd/yang/confd/infix-interfaces@2026-04-09.yang
diff --git a/src/confd/yang/confd/infix-ptp.yang b/src/confd/yang/confd/infix-ptp.yang
new file mode 100644
index 000000000..4ea4da51f
--- /dev/null
+++ b/src/confd/yang/confd/infix-ptp.yang
@@ -0,0 +1,291 @@
+module infix-ptp {
+ yang-version 1.1;
+ namespace "urn:infix:ptp:ns:yang:1.0";
+ prefix infix-ptp;
+
+ import ieee1588-ptp-tt {
+ prefix ptp-tt;
+ }
+ import ieee802-dot1as-gptp {
+ prefix dot1as-gptp;
+ }
+
+ organization "KernelKit";
+ contact "kernelkit@googlegroups.com";
+ description "Augments and deviations for IEEE 1588-2019 and
+ IEEE 802.1AS-2020 PTP support.
+
+ Profile selection via the profile leaf covers all
+ protocol-mandatory settings for each profile. The
+ standard sdo-id leaf is deviated not-supported; the
+ profile leaf is the authoritative selector.
+
+ Transparent Clock support uses the modern instance-type
+ approach (p2p-tc / e2e-tc) from IEEE 1588-2019. The
+ deprecated IEEE 1588-2008 transparent-clock-default-ds
+ and transparent-clock-ports containers are deviated
+ not-supported.";
+
+ revision 2026-04-07 {
+ description "Initial revision.";
+ reference "internal";
+ }
+
+ /*
+ * Augments
+ */
+
+ augment "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance"
+ + "/ptp-tt:default-ds" {
+ description
+ "Profile selection for this PTP instance.
+
+ The profile leaf selects the PTP profile and applies all
+ protocol-mandatory settings for that profile. The standard
+ sdo-id leaf is deviated not-supported; use profile instead.";
+
+ leaf profile {
+ type enumeration {
+ enum ieee1588 {
+ value 0;
+ description
+ "IEEE 1588-2019 default profile. Uses UDP/IPv4 transport
+ and E2E delay measurement by default. Network transport
+ and delay mechanism are user-configurable per port.";
+ }
+ enum ieee802-dot1as {
+ value 1;
+ description
+ "IEEE 802.1AS-2020 gPTP profile. Applies all
+ protocol-mandatory settings: IEEE 802.3 (Layer 2)
+ transport, P2P delay measurement, and the 802.1AS
+ multicast group address. Also enables path trace,
+ follow-up information, and neighbor propagation delay
+ thresholds as required by the standard.
+
+ User-configurable: priority1, priority2, domain-number,
+ time-receiver-only, and timer interval leaves.";
+ }
+ }
+ default ieee1588;
+ description
+ "PTP profile for this instance. Selects the complete set of
+ protocol-mandatory settings for the chosen profile.
+
+ The combination of domain-number and profile must be unique
+ across all PTP instances on this node.";
+ }
+ }
+
+ augment "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance" {
+ description
+ "Clock servo parameters for this PTP instance.";
+
+ container servo {
+ description
+ "Clock servo tuning parameters.";
+
+ leaf step-threshold {
+ type decimal64 {
+ fraction-digits 9;
+ }
+ units "seconds";
+ default "0.0";
+ description
+ "Maximum offset from the time transmitter that the servo
+ corrects by slewing rather than stepping. When the measured
+ offset exceeds this threshold the clock is stepped abruptly
+ to the correct time; below the threshold the servo disciplines
+ the clock by frequency adjustment only.
+
+ The value 0.0 (default) disables stepping: the servo always
+ slews, which guarantees a monotonic clock at the cost of
+ potentially very slow convergence when starting from a large
+ initial offset.
+
+ Setting a non-zero value (for example 0.1 for 100 ms) allows
+ the servo to step the clock on first lock, achieving fast
+ initial convergence while keeping the clock monotonic once
+ it has locked.";
+ }
+ }
+ }
+
+ /*
+ * Deviations from ieee1588-ptp-tt
+ */
+
+ /*
+ * /ptp/instances/instance/default-ds
+ */
+
+ deviation "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance"
+ + "/ptp-tt:default-ds/ptp-tt:sdo-id" {
+ deviate not-supported;
+ description
+ "Only the upper 4-bit majorSdoId field is configurable.
+ Use the profile leaf to select the correct majorSdoId value.";
+ }
+
+ deviation "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance"
+ + "/ptp-tt:default-ds/ptp-tt:current-time" {
+ deviate not-supported;
+ description
+ "Setting PTP instance time via YANG is not supported.
+ The current PTP time is only observable via pmc and is
+ presented in operational data.";
+ }
+
+ deviation "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance"
+ + "/ptp-tt:default-ds/ptp-tt:external-port-config-enable" {
+ deviate not-supported;
+ description
+ "The external-port-config feature is not supported.";
+ }
+
+ /*
+ * /ptp/instances/instance/description-ds
+ */
+
+ deviation "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance"
+ + "/ptp-tt:description-ds" {
+ deviate not-supported;
+ description
+ "The description data set is not exposed by pmc and is
+ not supported in this implementation.";
+ }
+
+ /*
+ * /ptp/instances/instance/fault-log-ds
+ * (feature-gated; not enabled)
+ */
+
+ deviation "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance"
+ + "/ptp-tt:fault-log-ds" {
+ deviate not-supported;
+ description
+ "Structured fault log is not supported. ptp4l does not
+ expose a fault log via pmc. Faults are observable in
+ syslog (tagged ptp4l).";
+ }
+
+ /*
+ * /ptp/instances/instance/path-trace-ds
+ * (feature-gated; not enabled)
+ */
+
+ deviation "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance"
+ + "/ptp-tt:path-trace-ds" {
+ deviate not-supported;
+ description
+ "Path trace mechanism is not supported in this implementation.";
+ }
+
+ /*
+ * /ptp/instances/instance/alternate-timescale-ds
+ * (feature-gated; not enabled)
+ */
+
+ deviation "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance"
+ + "/ptp-tt:alternate-timescale-ds" {
+ deviate not-supported;
+ description
+ "Alternate timescale mechanism is not supported.";
+ }
+
+ /*
+ * /ptp/instances/instance/ports/port/port-ds
+ */
+
+ deviation "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance"
+ + "/ptp-tt:ports/ptp-tt:port/ptp-tt:port-ds"
+ + "/ptp-tt:peer-mean-path-delay" {
+ deviate not-supported;
+ description
+ "Deprecated in IEEE 1588-2019; superseded by mean-link-delay.
+ Not supported.";
+ }
+
+ deviation "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance"
+ + "/ptp-tt:ports/ptp-tt:port/ptp-tt:port-ds"
+ + "/ptp-tt:version-number" {
+ deviate not-supported;
+ description
+ "PTP version is determined by ptp4l at runtime and is not
+ user-configurable.";
+ }
+
+ deviation "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance"
+ + "/ptp-tt:ports/ptp-tt:port/ptp-tt:port-ds"
+ + "/ptp-tt:minor-version-number" {
+ deviate not-supported;
+ description
+ "PTP minor version is determined by ptp4l at runtime and is
+ not user-configurable.";
+ }
+
+ deviation "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance"
+ + "/ptp-tt:ports/ptp-tt:port/ptp-tt:port-ds"
+ + "/ptp-tt:port-enable" {
+ deviate add {
+ default "true";
+ }
+ description
+ "Ports are enabled by default.";
+ }
+
+ /*
+ * /ptp/transparent-clock-default-ds (deprecated IEEE 1588-2008)
+ */
+
+ deviation "/ptp-tt:ptp/ptp-tt:transparent-clock-default-ds" {
+ deviate not-supported;
+ description
+ "Deprecated IEEE 1588-2008 container. Use the modern
+ instance-type = p2p-tc or e2e-tc approach in
+ /ptp/instances/instance/default-ds/instance-type instead.";
+ }
+
+ /*
+ * /ptp/transparent-clock-ports (deprecated IEEE 1588-2008)
+ */
+
+ deviation "/ptp-tt:ptp/ptp-tt:transparent-clock-ports" {
+ deviate not-supported;
+ description
+ "Deprecated IEEE 1588-2008 container. Transparent Clock
+ ports are managed via /ptp/instances/instance/ports instead.";
+ }
+
+ /*
+ * /ptp/common-services (CMLDS — Phase 2)
+ */
+
+ deviation "/ptp-tt:ptp/ptp-tt:common-services" {
+ deviate not-supported;
+ description
+ "CMLDS (Common Mean Link Delay Service) is not supported yet.";
+ }
+
+ /*
+ * Deviations from ieee802-dot1as-gptp
+ */
+
+ deviation "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance"
+ + "/ptp-tt:ports/ptp-tt:port/ptp-tt:port-ds"
+ + "/dot1as-gptp:nup" {
+ deviate not-supported;
+ description
+ "EPON upstream refraction index — not applicable to
+ Ethernet/TSN deployments.";
+ }
+
+ deviation "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance"
+ + "/ptp-tt:ports/ptp-tt:port/ptp-tt:port-ds"
+ + "/dot1as-gptp:ndown" {
+ deviate not-supported;
+ description
+ "EPON downstream refraction index — not applicable to
+ Ethernet/TSN deployments.";
+ }
+}
diff --git a/src/confd/yang/confd/infix-ptp@2026-04-07.yang b/src/confd/yang/confd/infix-ptp@2026-04-07.yang
new file mode 120000
index 000000000..a0f235955
--- /dev/null
+++ b/src/confd/yang/confd/infix-ptp@2026-04-07.yang
@@ -0,0 +1 @@
+infix-ptp.yang
\ No newline at end of file
diff --git a/src/klish-plugin-infix/xml/infix.xml b/src/klish-plugin-infix/xml/infix.xml
index b47727f9e..250cd8378 100644
--- a/src/klish-plugin-infix/xml/infix.xml
+++ b/src/klish-plugin-infix/xml/infix.xml
@@ -422,6 +422,13 @@ echo "Public: $pub"
show nacm
+
+
+
+
+ show ptp $KLISH_PARAM_instance
+
+
diff --git a/src/statd/python/cli_pretty/cli_pretty.py b/src/statd/python/cli_pretty/cli_pretty.py
index 1fee3f0f7..bf77df3db 100755
--- a/src/statd/python/cli_pretty/cli_pretty.py
+++ b/src/statd/python/cli_pretty/cli_pretty.py
@@ -5636,6 +5636,203 @@ def show_bfd(json_data):
show_bfd_peers_brief(json_data)
+def _ptp_ns(yang_val):
+ """Convert YANG time-interval (ns × 2^16, stored as str) to integer nanoseconds."""
+ try:
+ return int(yang_val) // 65536
+ except (TypeError, ValueError):
+ return None
+
+
+def _ptp_strip(identity):
+ """Strip YANG module prefix from identityref value.
+
+ 'ieee1588-ptp-tt:cc-default' → 'cc-default'
+ """
+ if identity and ':' in identity:
+ return identity.split(':', 1)[1]
+ return identity or ''
+
+
+_PTP_INSTANCE_TYPE_NAMES = {
+ "oc": "Ordinary Clock",
+ "bc": "Boundary Clock",
+ "p2p-tc": "P2P Transparent Clock",
+ "e2e-tc": "E2E Transparent Clock",
+}
+
+_PTP_PORT_STATE_COLOR = {
+ "time-transmitter": Decore.green,
+ "time-receiver": Decore.green,
+ "pre-time-transmitter": Decore.yellow,
+ "uncalibrated": Decore.yellow,
+ "listening": Decore.yellow,
+ "passive": lambda x: x,
+ "faulty": Decore.red,
+ "disabled": Decore.red,
+ "initializing": lambda x: x,
+}
+
+
+def show_ptp(json_data, instance_index=None):
+ """Show PTP instance status."""
+ ptp = json_data.get("ieee1588-ptp-tt:ptp", {})
+ instances = ptp.get("instances", {}).get("instance", [])
+
+ if not instances:
+ print("PTP: no instances configured.")
+ return
+
+ for inst in instances:
+ idx = inst.get("instance-index", "?")
+
+ if instance_index is not None and str(idx) != str(instance_index):
+ continue
+
+ dds = inst.get("default-ds", {})
+ cds = inst.get("current-ds", {})
+ pds = inst.get("parent-ds", {})
+ tpds = inst.get("time-properties-ds", {})
+ ports = inst.get("ports", {}).get("port", [])
+
+ itype = _PTP_INSTANCE_TYPE_NAMES.get(dds.get("instance-type", "oc"), "Unknown")
+ domain = dds.get("domain-number", 0)
+ clock_id = dds.get("clock-identity", "?")
+
+ # ── Header ────────────────────────────────────────────────────────────
+ header = f"PTP Instance {idx}"
+ subtitle = f"{itype} · domain {domain}"
+ pad_len = max(1, 40 - len(header))
+ pad = " " * pad_len
+ rule_w = max(68, len(header) + pad_len + len(subtitle))
+ print(f"{Decore.bold(header)}{pad}{subtitle}")
+ print("─" * rule_w)
+
+ # ── Clock / GM identity ───────────────────────────────────────────────
+ W = 24
+ gm_id = pds.get("grandmaster-identity", "")
+ print(f" {'Clock identity':<{W}}: {clock_id}")
+ if gm_id and gm_id != clock_id:
+ print(f" {'Grandmaster':<{W}}: {gm_id}")
+ else:
+ print(f" {'Grandmaster':<{W}}: (self)")
+
+ # ── Priorities ────────────────────────────────────────────────────────
+ p1, p2 = dds.get("priority1", "?"), dds.get("priority2", "?")
+ print(f" {'Priority1/Priority2':<{W}}: {p1} / {p2}")
+
+ if gm_id and gm_id != clock_id:
+ gp1 = pds.get("grandmaster-priority1", "?")
+ gp2 = pds.get("grandmaster-priority2", "?")
+ print(f" {'GM Priority1/Priority2':<{W}}: {gp1} / {gp2}")
+
+ # ── Clock quality ─────────────────────────────────────────────────────
+ cq = dds.get("clock-quality", {})
+ cc = _ptp_strip(cq.get("clock-class", ""))
+ if cc:
+ print(f" {'Clock class':<{W}}: {cc}")
+
+ if gm_id and gm_id != clock_id:
+ gcq = pds.get("grandmaster-clock-quality", {})
+ gcc = _ptp_strip(gcq.get("clock-class", ""))
+ if gcc and gcc != cc:
+ print(f" {'GM clock class':<{W}}: {gcc}")
+
+ # ── Time source ───────────────────────────────────────────────────────
+ ts = _ptp_strip(tpds.get("time-source", ""))
+ if ts:
+ print(f" {'Time source':<{W}}: {ts}")
+
+ if dds.get("time-receiver-only"):
+ print(f" {'Mode':<{W}}: time-receiver only")
+
+ # ── Time properties ───────────────────────────────────────────────────
+ ptp_ts = "yes" if tpds.get("ptp-timescale") else "no"
+ t_trace = "yes" if tpds.get("time-traceable") else "no"
+ f_trace = "yes" if tpds.get("frequency-traceable") else "no"
+ utc_off = tpds.get("current-utc-offset")
+ utc_str = f"{utc_off} s" if utc_off is not None else "N/A"
+ print(f" {'PTP timescale':<{W}}: {ptp_ts}")
+ print(f" {'UTC offset':<{W}}: {utc_str}")
+ print(f" {'Time traceable':<{W}}: {t_trace}")
+ print(f" {'Freq. traceable':<{W}}: {f_trace}")
+
+ # ── Sync status ───────────────────────────────────────────────────────
+ offset = _ptp_ns(cds.get("offset-from-time-transmitter"))
+ delay = _ptp_ns(cds.get("mean-delay"))
+ steps = cds.get("steps-removed")
+ if offset is not None:
+ print(f" {'Offset from GM':<{W}}: {offset} ns")
+ if delay is not None:
+ print(f" {'Mean path delay':<{W}}: {delay} ns")
+ if steps is not None:
+ print(f" {'Steps removed':<{W}}: {steps}")
+
+ # ── Ports ─────────────────────────────────────────────────────────────
+ if ports:
+ print()
+ Decore.title("Ports", width=rule_w)
+
+ port_table = SimpleTable([
+ Column("PORT", align='right'),
+ Column("INTERFACE", flexible=True),
+ Column("STATE", flexible=True),
+ Column("DELAY"),
+ Column("LINK DELAY (ns)", align='right'),
+ ])
+ stats_table = SimpleTable([
+ Column("PORT", align='right'),
+ Column("INTERFACE", flexible=True),
+ Column("SYNC \u25bc", align='right'),
+ Column("SYNC \u25b2", align='right'),
+ Column("ANN \u25bc", align='right'),
+ Column("ANN \u25b2", align='right'),
+ Column("PD \u25bc", align='right'),
+ Column("PD \u25b2", align='right'),
+ ])
+ has_stats = False
+
+ for port in ports:
+ pidx = port.get("port-index", "?")
+ pds_ = port.get("port-ds", {})
+ iface = port.get("underlying-interface",
+ pds_.get("port-identity", {}).get("clock-identity", "?"))
+
+ state_raw = pds_.get("port-state", "?")
+ color_fn = _PTP_PORT_STATE_COLOR.get(state_raw, lambda x: x)
+ state_str = color_fn(state_raw)
+
+ dm = (pds_.get("delay-mechanism") or "?").upper()
+ mld = _ptp_ns(pds_.get("mean-link-delay"))
+ mld_str = str(mld) if mld is not None else ""
+
+ port_table.row(str(pidx), iface, state_str, dm, mld_str)
+
+ st = port.get("ieee802-dot1as-gptp:port-statistics-ds", {})
+ if st:
+ has_stats = True
+ stats_table.row(
+ str(pidx), iface,
+ str(st.get("rx-sync-count", 0)),
+ str(st.get("tx-sync-count", 0)),
+ str(st.get("rx-announce-count", 0)),
+ str(st.get("tx-announce-count", 0)),
+ str(st.get("rx-pdelay-req-count", 0)),
+ str(st.get("tx-pdelay-req-count", 0)),
+ )
+
+ port_table.adjust_padding(rule_w)
+ port_table.print()
+
+ if has_stats:
+ print()
+ Decore.title("Message Statistics (\u25bc\u202frx \u25b2\u202ftx)", width=rule_w)
+ stats_table.adjust_padding(rule_w)
+ stats_table.print()
+
+ print()
+
+
def main():
global UNIT_TEST
@@ -5695,6 +5892,9 @@ def main():
ks_parser.add_argument('-t', '--type', help='Key type (symmetric or asymmetric)')
ks_parser.add_argument('-n', '--name', help='Key name')
+ subparsers.add_parser('show-ptp', help='Show PTP instance status') \
+ .add_argument('instance', nargs='?', help='Instance index (optional)')
+
subparsers.add_parser('show-ntp', help='Show NTP status') \
.add_argument('-a', '--address', help='Show details for specific address')
subparsers.add_parser('show-ntp-tracking', help='Show NTP tracking status')
@@ -5768,6 +5968,8 @@ def main():
show_nacm_user(json_data)
elif args.command == "show-keystore":
show_keystore(json_data, getattr(args, 'type', None), args.name)
+ elif args.command == "show-ptp":
+ show_ptp(json_data, getattr(args, 'instance', None))
elif args.command == "show-ntp":
show_ntp(json_data, args.address)
elif args.command == "show-ntp-tracking":
diff --git a/src/statd/python/yanger/__main__.py b/src/statd/python/yanger/__main__.py
index f7097c078..c88d4648f 100644
--- a/src/statd/python/yanger/__main__.py
+++ b/src/statd/python/yanger/__main__.py
@@ -123,6 +123,9 @@ def main():
elif model == 'ietf-bfd-ip-sh':
from . import ietf_bfd_ip_sh
yang_data = ietf_bfd_ip_sh.operational()
+ elif model == 'ieee1588-ptp-tt':
+ from . import ieee1588_ptp
+ yang_data = ieee1588_ptp.operational()
else:
common.LOG.warning("Unsupported model %s", model)
sys.exit(1)
diff --git a/src/statd/python/yanger/ieee1588_ptp.py b/src/statd/python/yanger/ieee1588_ptp.py
new file mode 100644
index 000000000..eb2a0a092
--- /dev/null
+++ b/src/statd/python/yanger/ieee1588_ptp.py
@@ -0,0 +1,624 @@
+"""Operational data for ieee1588-ptp-tt (and ieee802-dot1as-gptp).
+
+Queries each running ptp4l instance via pmc and maps the output to the
+YANG model structure. One ptp4l process runs per instance-index, with
+its config at /etc/linuxptp/ptp4l-.conf and its UDS socket at
+/var/run/ptp4l-.
+"""
+
+import glob
+import os
+import re
+
+from .common import insert, LOG
+from .host import HOST
+
+
+# ---------------------------------------------------------------------------
+# pmc helpers
+# ---------------------------------------------------------------------------
+
+def _pmc_get(conf, command):
+ """Run 'pmc -b 0 -f GET ' and return parsed key→value dict.
+
+ pmc output looks like:
+ \t\t
+ Blank lines and lines not starting with whitespace are ignored.
+ Multiple response blocks (one per port for PORT_DATA_SET) each get
+ their own dict; returns a list of dicts in that case.
+ """
+ lines = HOST.run_multiline(
+ ["pmc", "-u", "-b", "0", "-f", conf, f"GET {command}"], default=[])
+
+ blocks = []
+ current = {}
+ for line in lines:
+ stripped = line.strip()
+ if not stripped or stripped.startswith("sending:") or \
+ "RESPONSE MANAGEMENT" in stripped or \
+ "SIGNALING" in stripped:
+ if current:
+ blocks.append(current)
+ current = {}
+ continue
+ m = re.match(r'^\s+(\S+)\s+(.+)$', line)
+ if m:
+ current[m.group(1)] = m.group(2).strip()
+
+ if current:
+ blocks.append(current)
+
+ return blocks
+
+
+def _pmc_get_one(conf, command):
+ """Like _pmc_get but return only the first (or only) block."""
+ blocks = _pmc_get(conf, command)
+ return blocks[0] if blocks else {}
+
+
+# ---------------------------------------------------------------------------
+# clockIdentity formatting
+# ---------------------------------------------------------------------------
+
+def _fmt_clock_identity(raw):
+ """Convert pmc clockIdentity 'aabbcc.fffe.ddeeff' to YANG format 'AA-BB-CC-FF-FE-DD-EE-FF'.
+
+ The YANG typedef clock-identity requires the pattern [0-9A-F]{2}(-[0-9A-F]{2}){7}.
+ pmc outputs in its own dotted notation e.g. '005182.fffe.112202'.
+ """
+ raw = raw.replace(".", "").replace("-", "").replace(":", "").upper()
+ if len(raw) == 16:
+ return "-".join(raw[i:i+2] for i in range(0, 16, 2))
+ return raw
+
+
+def _fmt_port_identity(raw):
+ """Convert 'aabbccfffe001122-1' to dict {clock-identity, port-number}."""
+ parts = raw.rsplit("-", 1)
+ cid = _fmt_clock_identity(parts[0]) if parts else raw
+ pnum = int(parts[1]) if len(parts) == 2 else 0
+ return {"clock-identity": cid, "port-number": pnum}
+
+
+# ---------------------------------------------------------------------------
+# clockAccuracy identity mapping
+# ---------------------------------------------------------------------------
+
+# Map clockClass decimal values to ieee1588-ptp-tt identity names (identityref, not uint8).
+_CLOCK_CLASS_MAP = {
+ 6: "ieee1588-ptp-tt:cc-primary-sync",
+ 7: "ieee1588-ptp-tt:cc-primary-sync-lost",
+ 13: "ieee1588-ptp-tt:cc-application-specific-sync",
+ 14: "ieee1588-ptp-tt:cc-application-specific-sync-lost",
+ 52: "ieee1588-ptp-tt:cc-primary-sync-alternative-a",
+ 58: "ieee1588-ptp-tt:cc-application-specific-alternative-a",
+ 187: "ieee1588-ptp-tt:cc-primary-sync-alternative-b",
+ 193: "ieee1588-ptp-tt:cc-application-specific-alternative-b",
+ 248: "ieee1588-ptp-tt:cc-default",
+ 255: "ieee1588-ptp-tt:cc-time-receiver-only",
+}
+
+
+def _clock_class_identity(raw):
+ """Return the YANG identity string for a pmc clockClass decimal value, or None."""
+ try:
+ return _CLOCK_CLASS_MAP.get(int(raw))
+ except (ValueError, TypeError):
+ return None
+
+
+# Map clockAccuracy hex values to ieee1588-ptp-tt identity names (identityref, not uint8).
+# Identity names use the 'ca-' prefix as defined in ieee1588-ptp-tt@2023-08-14.yang.
+# 0xfe (unknown) has no corresponding identity and is omitted by returning None.
+_CLOCK_ACCURACY_MAP = {
+ 0x17: "ieee1588-ptp-tt:ca-time-accurate-to-1000-fs",
+ 0x18: "ieee1588-ptp-tt:ca-time-accurate-to-2500-fs",
+ 0x19: "ieee1588-ptp-tt:ca-time-accurate-to-10-ps",
+ 0x1a: "ieee1588-ptp-tt:ca-time-accurate-to-25ps",
+ 0x1b: "ieee1588-ptp-tt:ca-time-accurate-to-100-ps",
+ 0x1c: "ieee1588-ptp-tt:ca-time-accurate-to-250-ps",
+ 0x1d: "ieee1588-ptp-tt:ca-time-accurate-to-1000-ps",
+ 0x1e: "ieee1588-ptp-tt:ca-time-accurate-to-2500-ps",
+ 0x1f: "ieee1588-ptp-tt:ca-time-accurate-to-10-ns",
+ 0x20: "ieee1588-ptp-tt:ca-time-accurate-to-25-ns",
+ 0x21: "ieee1588-ptp-tt:ca-time-accurate-to-100-ns",
+ 0x22: "ieee1588-ptp-tt:ca-time-accurate-to-250-ns",
+ 0x23: "ieee1588-ptp-tt:ca-time-accurate-to-1000-ns",
+ 0x24: "ieee1588-ptp-tt:ca-time-accurate-to-2500-ns",
+ 0x25: "ieee1588-ptp-tt:ca-time-accurate-to-10-us",
+ 0x26: "ieee1588-ptp-tt:ca-time-accurate-to-25-us",
+ 0x27: "ieee1588-ptp-tt:ca-time-accurate-to-100-us",
+ 0x28: "ieee1588-ptp-tt:ca-time-accurate-to-250-us",
+ 0x29: "ieee1588-ptp-tt:ca-time-accurate-to-1000-us",
+ 0x2a: "ieee1588-ptp-tt:ca-time-accurate-to-2500-us",
+ 0x2b: "ieee1588-ptp-tt:ca-time-accurate-to-10-ms",
+ 0x2c: "ieee1588-ptp-tt:ca-time-accurate-to-25-ms",
+ 0x2d: "ieee1588-ptp-tt:ca-time-accurate-to-100-ms",
+ 0x2e: "ieee1588-ptp-tt:ca-time-accurate-to-250-ms",
+ 0x2f: "ieee1588-ptp-tt:ca-time-accurate-to-1-s",
+ 0x30: "ieee1588-ptp-tt:ca-time-accurate-to-10-s",
+ 0x31: "ieee1588-ptp-tt:ca-time-accurate-to-gt-10-s",
+}
+
+
+def _clock_accuracy_identity(raw):
+ """Return the YANG identity string for a pmc clockAccuracy hex value, or None."""
+ try:
+ return _CLOCK_ACCURACY_MAP.get(int(raw, 16))
+ except (ValueError, TypeError):
+ return None
+
+
+# ---------------------------------------------------------------------------
+# time-source identity mapping
+# ---------------------------------------------------------------------------
+
+_TIME_SOURCE_MAP = {
+ "0x10": "ieee1588-ptp-tt:atomic-clock",
+ "0x20": "ieee1588-ptp-tt:gnss",
+ "0x30": "ieee1588-ptp-tt:terrestrial-radio",
+ "0x39": "ieee1588-ptp-tt:serial-time-code",
+ "0x40": "ieee1588-ptp-tt:ptp",
+ "0x50": "ieee1588-ptp-tt:ntp",
+ "0x60": "ieee1588-ptp-tt:hand-set",
+ "0x90": "ieee1588-ptp-tt:other",
+ "0xa0": "ieee1588-ptp-tt:internal-oscillator",
+}
+
+
+def _time_source_identity(raw):
+ return _TIME_SOURCE_MAP.get(raw.lower(),
+ "ieee1588-ptp-tt:internal-oscillator")
+
+
+# ---------------------------------------------------------------------------
+# delay-mechanism and port-state mapping
+# ---------------------------------------------------------------------------
+
+_DELAY_MECH_MAP = {
+ "E2E": "e2e",
+ "P2P": "p2p",
+ "AUTO": "no-mechanism",
+}
+
+_PORT_STATE_MAP = {
+ "INITIALIZING": "initializing",
+ "FAULTY": "faulty",
+ "DISABLED": "disabled",
+ "LISTENING": "listening",
+ "PRE_MASTER": "pre-time-transmitter",
+ "MASTER": "time-transmitter",
+ "PASSIVE": "passive",
+ "UNCALIBRATED": "uncalibrated",
+ "SLAVE": "time-receiver",
+ "GRAND_MASTER": "time-transmitter",
+}
+
+
+# ---------------------------------------------------------------------------
+# Per-dataset builders
+# ---------------------------------------------------------------------------
+
+def _build_default_ds(d):
+ """Map pmc DEFAULT_DATA_SET response to YANG default-ds."""
+ ds = {}
+
+ cid = d.get("clockIdentity")
+ if cid:
+ ds["clock-identity"] = _fmt_clock_identity(cid)
+
+ v = d.get("numberPorts")
+ if v:
+ ds["number-ports"] = int(v)
+
+ cq = {}
+ v = d.get("clockClass")
+ if v:
+ cc = _clock_class_identity(v)
+ if cc:
+ cq["clock-class"] = cc
+ v = d.get("clockAccuracy")
+ if v:
+ ca = _clock_accuracy_identity(v)
+ if ca:
+ cq["clock-accuracy"] = ca
+ v = d.get("offsetScaledLogVariance")
+ if v:
+ cq["offset-scaled-log-variance"] = int(v, 16)
+ if cq:
+ ds["clock-quality"] = cq
+
+ v = d.get("priority1")
+ if v:
+ ds["priority1"] = int(v)
+ v = d.get("priority2")
+ if v:
+ ds["priority2"] = int(v)
+
+ v = d.get("domainNumber")
+ if v:
+ ds["domain-number"] = int(v)
+
+ v = d.get("clientOnly") or d.get("slaveOnly") # renamed in ptp4l 4.x
+ if v is not None:
+ ds["time-receiver-only"] = (v == "1")
+
+ # instance-type: derive from ptp4l GM/time-receiver state (read-only, operational)
+ # pmc doesn't directly expose clockType in DEFAULT_DATA_SET
+ # We'll fill instance-type later from the instance's config if possible
+
+ return ds
+
+
+def _build_current_ds(d):
+ """Map pmc CURRENT_DATA_SET response to YANG current-ds."""
+ ds = {}
+
+ v = d.get("stepsRemoved")
+ if v:
+ ds["steps-removed"] = int(v)
+
+ v = d.get("offsetFromMaster")
+ if v:
+ # ptp4l reports nanoseconds as float; YANG time-interval is ns * 2^16.
+ # RFC 7951: int64 must be JSON-encoded as a string.
+ try:
+ ds["offset-from-time-transmitter"] = str(int(float(v) * 65536))
+ except ValueError:
+ pass
+
+ v = d.get("meanPathDelay")
+ if v:
+ try:
+ ds["mean-delay"] = str(int(float(v) * 65536))
+ except ValueError:
+ pass
+
+ return ds
+
+
+def _build_parent_ds(d):
+ """Map pmc PARENT_DATA_SET response to YANG parent-ds."""
+ ds = {}
+
+ v = d.get("parentPortIdentity")
+ if v:
+ ds["parent-port-identity"] = _fmt_port_identity(v)
+
+ v = d.get("parentStats")
+ if v:
+ ds["parent-stats"] = (v == "1")
+
+ v = d.get("observedParentOffsetScaledLogVariance")
+ if v:
+ try:
+ ds["observed-parent-offset-scaled-log-variance"] = int(v, 16)
+ except ValueError:
+ pass
+
+ v = d.get("observedParentClockPhaseChangeRate")
+ if v:
+ try:
+ ds["observed-parent-clock-phase-change-rate"] = int(v)
+ except ValueError:
+ pass
+
+ v = d.get("grandmasterIdentity")
+ if v:
+ ds["grandmaster-identity"] = _fmt_clock_identity(v)
+
+ gcq = {}
+ v = d.get("gm.ClockClass")
+ if v:
+ cc = _clock_class_identity(v)
+ if cc:
+ gcq["clock-class"] = cc
+ v = d.get("gm.ClockAccuracy")
+ if v:
+ ca = _clock_accuracy_identity(v)
+ if ca:
+ gcq["clock-accuracy"] = ca
+ v = d.get("gm.OffsetScaledLogVariance")
+ if v:
+ try:
+ gcq["offset-scaled-log-variance"] = int(v, 16)
+ except ValueError:
+ pass
+ if gcq:
+ ds["grandmaster-clock-quality"] = gcq
+
+ v = d.get("grandmasterPriority1")
+ if v:
+ ds["grandmaster-priority1"] = int(v)
+ v = d.get("grandmasterPriority2")
+ if v:
+ ds["grandmaster-priority2"] = int(v)
+
+ return ds
+
+
+def _build_time_properties_ds(d):
+ """Map pmc TIME_PROPERTIES_DATA_SET response to YANG time-properties-ds."""
+ ds = {}
+
+ # current-utc-offset has a when condition requiring current-utc-offset-valid='true'
+ if d.get("currentUtcOffsetValid") in ("1", "true"):
+ v = d.get("currentUtcOffset")
+ if v:
+ ds["current-utc-offset"] = int(v)
+
+ v = d.get("leap61")
+ if v is not None:
+ ds["leap61"] = (v == "1")
+ v = d.get("leap59")
+ if v is not None:
+ ds["leap59"] = (v == "1")
+ v = d.get("currentUtcOffsetValid")
+ if v is not None:
+ ds["current-utc-offset-valid"] = (v == "1")
+ v = d.get("ptpTimescale")
+ if v is not None:
+ ds["ptp-timescale"] = (v == "1")
+ v = d.get("timeTraceable")
+ if v is not None:
+ ds["time-traceable"] = (v == "1")
+ v = d.get("frequencyTraceable")
+ if v is not None:
+ ds["frequency-traceable"] = (v == "1")
+
+ v = d.get("timeSource")
+ if v:
+ ds["time-source"] = _time_source_identity(v)
+
+ return ds
+
+
+def _build_port_ds(d):
+ """Map pmc PORT_DATA_SET response to YANG port-ds."""
+ ds = {}
+
+ v = d.get("portIdentity")
+ if v:
+ ds["port-identity"] = _fmt_port_identity(v)
+
+ v = d.get("portState")
+ if v:
+ ds["port-state"] = _PORT_STATE_MAP.get(v, "disabled")
+
+ v = d.get("logMinDelayReqInterval")
+ if v:
+ try:
+ ds["log-min-delay-req-interval"] = int(v)
+ except ValueError:
+ pass
+
+ v = d.get("peerMeanPathDelay")
+ if v:
+ try:
+ # RFC 7951: int64 must be JSON-encoded as a string.
+ ds["mean-link-delay"] = str(int(float(v) * 65536))
+ except ValueError:
+ pass
+
+ v = d.get("logAnnounceInterval")
+ if v:
+ try:
+ ds["log-announce-interval"] = int(v)
+ except ValueError:
+ pass
+
+ v = d.get("announceReceiptTimeout")
+ if v:
+ try:
+ ds["announce-receipt-timeout"] = int(v)
+ except ValueError:
+ pass
+
+ v = d.get("logSyncInterval")
+ if v:
+ try:
+ ds["log-sync-interval"] = int(v)
+ except ValueError:
+ pass
+
+ v = d.get("delayMechanism")
+ if v:
+ ds["delay-mechanism"] = _DELAY_MECH_MAP.get(v, "e2e")
+
+ v = d.get("logMinPdelayReqInterval")
+ if v:
+ try:
+ ds["log-min-pdelay-req-interval"] = int(v)
+ except ValueError:
+ pass
+
+ v = d.get("versionNumber")
+ if v:
+ try:
+ ds["version-number"] = int(v)
+ except ValueError:
+ pass
+
+ v = d.get("portEnable")
+ if v is not None:
+ ds["port-enable"] = (v == "1")
+
+ return ds
+
+
+def _build_port_stats(d):
+ """Map pmc PORT_STATS_NP response to ieee802-dot1as-gptp port-statistics-ds."""
+ stats = {}
+ mapping = {
+ "rx_Sync": "rx-sync-count",
+ "rx_Follow_Up": "rx-follow-up-count",
+ "rx_Pdelay_Req": "rx-pdelay-req-count",
+ "rx_Pdelay_Resp": "rx-pdelay-resp-count",
+ "rx_Pdelay_Resp_Follow_Up": "rx-pdelay-resp-follow-up-count",
+ "rx_Announce": "rx-announce-count",
+ "tx_Sync": "tx-sync-count",
+ "tx_Follow_Up": "tx-follow-up-count",
+ "tx_Pdelay_Req": "tx-pdelay-req-count",
+ "tx_Pdelay_Resp": "tx-pdelay-resp-count",
+ "tx_Pdelay_Resp_Follow_Up": "tx-pdelay-resp-follow-up-count",
+ "tx_Announce": "tx-announce-count",
+ }
+ for pmc_key, yang_key in mapping.items():
+ v = d.get(pmc_key)
+ if v is not None:
+ try:
+ stats[yang_key] = int(v)
+ except ValueError:
+ pass
+ return stats
+
+
+# ---------------------------------------------------------------------------
+# Per-instance builder
+# ---------------------------------------------------------------------------
+
+def _port_interfaces(conf_path):
+ """Return ordered list of interface names from ptp4l conf (non-global section headers)."""
+ ifaces = []
+ try:
+ with open(conf_path) as f:
+ for line in f:
+ s = line.strip()
+ if s.startswith('[') and s.endswith(']') and s[1:-1] != 'global':
+ ifaces.append(s[1:-1])
+ except OSError:
+ pass
+ return ifaces
+
+
+def _instance_type_from_config(conf_path):
+ """Read instance-type from a saved config file (best effort)."""
+ try:
+ with open(conf_path, "r") as f:
+ for line in f:
+ m = re.match(r'\s*clockType\s+(\S+)', line)
+ if m:
+ ct = m.group(1).upper()
+ if ct == "P2P_TC":
+ return "p2p-tc"
+ if ct == "E2E_TC":
+ return "e2e-tc"
+ if ct == "BOUNDARY_CLOCK":
+ return "bc"
+ # Default: if more than one port, bc; otherwise oc
+ # (approximation — proper detection requires DEFAULT_DATA_SET numberPorts)
+ except OSError:
+ pass
+ return None
+
+
+def _build_instance(idx, conf_path):
+ """Build one instance dict from pmc queries for instance index idx."""
+ inst = {"instance-index": idx}
+
+ # default-ds
+ dd = _pmc_get_one(conf_path, "DEFAULT_DATA_SET")
+ if dd:
+ dds = _build_default_ds(dd)
+ # Derive instance-type from numberPorts + config file
+ num_ports = int(dd.get("numberPorts", "0") or "0")
+ it = _instance_type_from_config(conf_path)
+ if it is None:
+ it = "bc" if num_ports > 1 else "oc"
+ dds["instance-type"] = it
+ inst["default-ds"] = dds
+
+ # current-ds
+ cd = _pmc_get_one(conf_path, "CURRENT_DATA_SET")
+ if cd:
+ cds = _build_current_ds(cd)
+ if cds:
+ inst["current-ds"] = cds
+
+ # parent-ds
+ pd = _pmc_get_one(conf_path, "PARENT_DATA_SET")
+ if pd:
+ pds = _build_parent_ds(pd)
+ if pds:
+ inst["parent-ds"] = pds
+
+ # time-properties-ds
+ tp = _pmc_get_one(conf_path, "TIME_PROPERTIES_DATA_SET")
+ if tp:
+ tpds = _build_time_properties_ds(tp)
+ if tpds:
+ inst["time-properties-ds"] = tpds
+
+ # ports: PORT_DATA_SET returns one block per port
+ port_blocks = _pmc_get(conf_path, "PORT_DATA_SET")
+ stats_blocks = _pmc_get(conf_path, "PORT_STATS_NP")
+ ifaces = _port_interfaces(conf_path)
+
+ # Build a stats map keyed by portIdentity for quick lookup
+ stats_by_id = {}
+ for sb in stats_blocks:
+ pid = sb.get("portIdentity")
+ if pid:
+ stats_by_id[pid] = _build_port_stats(sb)
+
+ ports = []
+ for i, pb in enumerate(port_blocks, start=1):
+ pid_raw = pb.get("portIdentity", "")
+ port_entry = {}
+
+ # port-index = port number from portIdentity
+ pid_dict = _fmt_port_identity(pid_raw)
+ port_entry["port-index"] = pid_dict.get("port-number", i)
+
+ if i <= len(ifaces):
+ port_entry["underlying-interface"] = ifaces[i - 1]
+
+ pds = _build_port_ds(pb)
+ if pds:
+ port_entry["port-ds"] = pds
+
+ # 802.1AS port-statistics-ds
+ stats = stats_by_id.get(pid_raw)
+ if stats:
+ port_entry["ieee802-dot1as-gptp:port-statistics-ds"] = stats
+
+ ports.append(port_entry)
+
+ if ports:
+ inst["ports"] = {"port": ports}
+
+ return inst
+
+
+# ---------------------------------------------------------------------------
+# Top-level entry point
+# ---------------------------------------------------------------------------
+
+def operational():
+ """Return operational data for ieee1588-ptp-tt."""
+ out = {}
+ instances = []
+
+ conf_files = sorted(glob.glob("/etc/linuxptp/ptp4l-*.conf"))
+ for conf_path in conf_files:
+ m = re.search(r'ptp4l-(\d+)\.conf$', conf_path)
+ if not m:
+ continue
+ idx = int(m.group(1))
+
+ # Only include instances with a live UDS socket (i.e. ptp4l running)
+ uds_path = f"/var/run/ptp4l-{idx}"
+ if not HOST.exists(uds_path):
+ continue
+
+ try:
+ inst = _build_instance(idx, conf_path)
+ instances.append(inst)
+ except Exception as e:
+ LOG.debug("ptp4l-%d: skipping instance: %s", idx, e)
+
+ if instances:
+ insert(out, "ieee1588-ptp-tt:ptp", "instances", "instance", instances)
+
+ return out
diff --git a/src/statd/python/yanger/ietf_interfaces/link.py b/src/statd/python/yanger/ietf_interfaces/link.py
index 60665e0ce..4f99c8769 100644
--- a/src/statd/python/yanger/ietf_interfaces/link.py
+++ b/src/statd/python/yanger/ietf_interfaces/link.py
@@ -117,9 +117,38 @@ def interface_common(iplink, ipaddr):
return interface
-def interface(iplink, ipaddr):
+def ptp_capabilities(ifname, systemjson):
+ """Return infix-interfaces:ptp-capabilities dict for ifname, or None."""
+ caps = systemjson.get("interfaces", {}).get(ifname, {}).get("ptp-capabilities")
+ if not caps:
+ return None
+
+ result = {}
+ if cl := caps.get("capabilities"):
+ result["capabilities"] = cl
+ if (phc := caps.get("phc-index")) is not None:
+ result["phc-index"] = phc
+ if tx := caps.get("tx-types"):
+ result["tx-types"] = tx
+ if rx := caps.get("rx-filters"):
+ result["rx-filters"] = rx
+ if (idx := caps.get("hwtstamp-provider-index")) is not None:
+ result["hwtstamp-provider-index"] = idx
+ if qual := caps.get("hwtstamp-provider-qualifier"):
+ result["hwtstamp-provider-qualifier"] = qual
+
+ return result or None
+
+
+def interface(iplink, ipaddr, systemjson=None):
interface = interface_common(iplink, ipaddr)
+ if systemjson is None:
+ systemjson = {}
+
+ if ptpcap := ptp_capabilities(iplink["ifname"], systemjson):
+ interface["infix-interfaces:ptp-capabilities"] = ptpcap
+
match interface["type"]:
case "infix-if-type:bridge":
if br := bridge.bridge(iplink):
@@ -163,8 +192,11 @@ def interface(iplink, ipaddr):
def interfaces(ifname=None):
+ from ..host import HOST
+
links = common.iplinks(ifname)
addrs = common.ipaddrs(ifname)
+ systemjson = HOST.read_json("/run/system.json", {})
interfaces = []
for ifname, iplink in links.items():
@@ -177,6 +209,6 @@ def interfaces(ifname=None):
ipaddr = addrs.get(ifname, {})
- interfaces.append(interface(iplink, ipaddr))
+ interfaces.append(interface(iplink, ipaddr, systemjson))
return interfaces
diff --git a/src/statd/statd.c b/src/statd/statd.c
index affc90740..dac055836 100644
--- a/src/statd/statd.c
+++ b/src/statd/statd.c
@@ -52,6 +52,7 @@
#define XPATH_LLDP_BASE "/ieee802-dot1ab-lldp:lldp"
#define XPATH_FIREWALL_BASE "/infix-firewall:firewall"
#define XPATH_NTP_BASE "/ietf-ntp:ntp"
+#define XPATH_PTP_BASE "/ieee1588-ptp-tt:ptp"
TAILQ_HEAD(sub_head, sub);
@@ -111,7 +112,7 @@ static int ly_add_yanger_data(const struct ly_ctx *ctx, struct lyd_node **parent
err = lyd_parse_data_fd(ctx, fd, LYD_JSON, LYD_PARSE_ONLY, 0, parent);
if (err)
- ERROR("Error, parsing yanger data (%d)", err);
+ ERROR("Error, parsing yanger data (%d): %s", err, ly_errmsg(ctx));
fclose(stream);
/* Note: fclose() already closes the underlying fd from fdopen() */
@@ -455,6 +456,8 @@ static int subscribe_to_all(struct statd *statd)
return SR_ERR_INTERNAL;
if (subscribe(statd, "ietf-ntp", XPATH_NTP_BASE, sr_generic_cb))
return SR_ERR_INTERNAL;
+ if (subscribe(statd, "ieee1588-ptp-tt", XPATH_PTP_BASE, sr_generic_cb))
+ return SR_ERR_INTERNAL;
INFO("Successfully subscribed to all models");
return SR_ERR_OK;
diff --git a/test/case/all.yaml b/test/case/all.yaml
index 3e75ace0c..1fdbec3d5 100644
--- a/test/case/all.yaml
+++ b/test/case/all.yaml
@@ -39,6 +39,9 @@
- name: "NTP Server"
suite: ntp/all.yaml
+- name: "PTP"
+ suite: ptp/all.yaml
+
- name: "Routing"
suite: routing/all.yaml
diff --git a/test/case/containers/basic/test.adoc b/test/case/containers/basic/test.adoc
index e4e543625..7a55ccf31 100644
--- a/test/case/containers/basic/test.adoc
+++ b/test/case/containers/basic/test.adoc
@@ -1,4 +1,4 @@
-=== Container basic
+=== Container Basic
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/containers/basic]
@@ -12,7 +12,7 @@ The RPC actions: stop + start, and restart are also verified.
==== Topology
-image::topology.svg[Container basic topology, align=center, scaledwidth=75%]
+image::topology.svg[Container Basic topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/containers/bridge/test.adoc b/test/case/containers/bridge/test.adoc
index a99dc0919..28f8e458c 100644
--- a/test/case/containers/bridge/test.adoc
+++ b/test/case/containers/bridge/test.adoc
@@ -1,4 +1,4 @@
-=== Container with bridge network
+=== Container with Bridge Network
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/containers/bridge]
@@ -13,7 +13,7 @@ port accessed from the host.
==== Topology
-image::topology.svg[Container with bridge network topology, align=center, scaledwidth=75%]
+image::topology.svg[Container with Bridge Network topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/containers/enabled/test.adoc b/test/case/containers/enabled/test.adoc
index 574e2d931..d2eb4e80f 100644
--- a/test/case/containers/enabled/test.adoc
+++ b/test/case/containers/enabled/test.adoc
@@ -1,4 +1,4 @@
-=== Container enabled/disabled
+=== Container Enabled/Disabled
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/containers/enabled]
@@ -15,7 +15,7 @@ Uses operational datastore to verify container running status.
==== Topology
-image::topology.svg[Container enabled/disabled topology, align=center, scaledwidth=75%]
+image::topology.svg[Container Enabled/Disabled topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/containers/environment/test.adoc b/test/case/containers/environment/test.adoc
index 54b8378b9..63eba8be9 100644
--- a/test/case/containers/environment/test.adoc
+++ b/test/case/containers/environment/test.adoc
@@ -1,4 +1,4 @@
-=== Container environment variables
+=== Container Environment Variables
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/containers/environment]
@@ -15,7 +15,7 @@ changing an environment variable triggers a container restart.
==== Topology
-image::topology.svg[Container environment variables topology, align=center, scaledwidth=75%]
+image::topology.svg[Container Environment Variables topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/containers/phys/test.adoc b/test/case/containers/phys/test.adoc
index 4a2214542..9f5221500 100644
--- a/test/case/containers/phys/test.adoc
+++ b/test/case/containers/phys/test.adoc
@@ -1,4 +1,4 @@
-=== Container with physical interface
+=== Container with Physical Interface
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/containers/phys]
@@ -9,7 +9,7 @@ given a physical interface instead of an end of a VETH pair.
==== Topology
-image::topology.svg[Container with physical interface topology, align=center, scaledwidth=75%]
+image::topology.svg[Container with Physical Interface topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/containers/veth/test.adoc b/test/case/containers/veth/test.adoc
index dd557d043..7adb242e5 100644
--- a/test/case/containers/veth/test.adoc
+++ b/test/case/containers/veth/test.adoc
@@ -1,4 +1,4 @@
-=== Container with VETH pair
+=== Container with VETH Pair
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/containers/veth]
@@ -19,7 +19,7 @@ regular bridge, a VETH pair connects the container to the bridge.
==== Topology
-image::topology.svg[Container with VETH pair topology, align=center, scaledwidth=75%]
+image::topology.svg[Container with VETH Pair topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/dhcp/client_routes/test.adoc b/test/case/dhcp/client_routes/test.adoc
index 5a35e4f46..2bd6e2d06 100644
--- a/test/case/dhcp/client_routes/test.adoc
+++ b/test/case/dhcp/client_routes/test.adoc
@@ -1,4 +1,4 @@
-=== DHCP option 121 vs option 3
+=== DHCP Option 121 vs Option 3
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/dhcp/client_routes]
@@ -20,7 +20,7 @@ via 192.168.0.1 and a default route (option 121) via 192.168.0.254.
==== Topology
-image::topology.svg[DHCP option 121 vs option 3 topology, align=center, scaledwidth=75%]
+image::topology.svg[DHCP Option 121 vs Option 3 topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/hardware/gps_simple/test.adoc b/test/case/hardware/gps_simple/test.adoc
index 2715ce0f7..ab3a8d40a 100644
--- a/test/case/hardware/gps_simple/test.adoc
+++ b/test/case/hardware/gps_simple/test.adoc
@@ -1,4 +1,4 @@
-=== GPS receiver basic test
+=== GPS Receiver Basic Test
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/hardware/gps_simple]
@@ -12,7 +12,7 @@ which appear as virtio serial ports inside the guest.
==== Topology
-image::topology.svg[GPS receiver basic test topology, align=center, scaledwidth=75%]
+image::topology.svg[GPS Receiver Basic Test topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/hardware/usb/test.adoc b/test/case/hardware/usb/test.adoc
index 69d0c01f9..5ce510abf 100644
--- a/test/case/hardware/usb/test.adoc
+++ b/test/case/hardware/usb/test.adoc
@@ -1,4 +1,4 @@
-=== USB configuration
+=== USB Configuration
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/hardware/usb]
@@ -16,7 +16,7 @@ port is handled correctly.
==== Topology
-image::topology.svg[USB configuration topology, align=center, scaledwidth=75%]
+image::topology.svg[USB Configuration topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/hardware/usb_two_ports/test.adoc b/test/case/hardware/usb_two_ports/test.adoc
index 98d10f66b..cb9e1f6bc 100644
--- a/test/case/hardware/usb_two_ports/test.adoc
+++ b/test/case/hardware/usb_two_ports/test.adoc
@@ -1,4 +1,4 @@
-=== USB configuration with two USB ports
+=== USB Configuration with Two USB Ports
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/hardware/usb_two_ports]
@@ -9,7 +9,7 @@ when having two USB ports.
==== Topology
-image::topology.svg[USB configuration with two USB ports topology, align=center, scaledwidth=75%]
+image::topology.svg[USB Configuration with Two USB Ports topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/hardware/watchdog/test.adoc b/test/case/hardware/watchdog/test.adoc
index ca904cd89..7f2fd15f6 100644
--- a/test/case/hardware/watchdog/test.adoc
+++ b/test/case/hardware/watchdog/test.adoc
@@ -1,4 +1,4 @@
-=== Watchdog reset on system lockup
+=== Watchdog Reset on System Lockup
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/hardware/watchdog]
@@ -14,7 +14,7 @@ timeout.
==== Topology
-image::topology.svg[Watchdog reset on system lockup topology, align=center, scaledwidth=75%]
+image::topology.svg[Watchdog Reset on System Lockup topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/interfaces/bridge_basic/test.adoc b/test/case/interfaces/bridge_basic/test.adoc
index 58ee1c23a..f943917ab 100644
--- a/test/case/interfaces/bridge_basic/test.adoc
+++ b/test/case/interfaces/bridge_basic/test.adoc
@@ -1,4 +1,4 @@
-=== Bridge basic
+=== Bridge Basic
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/bridge_basic]
@@ -16,7 +16,7 @@ Test basic connectivity to a bridge
==== Topology
-image::topology.svg[Bridge basic topology, align=center, scaledwidth=75%]
+image::topology.svg[Bridge Basic topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/interfaces/bridge_fwd_dual_dut/test.adoc b/test/case/interfaces/bridge_fwd_dual_dut/test.adoc
index aa6f1e771..c5fa45d18 100644
--- a/test/case/interfaces/bridge_fwd_dual_dut/test.adoc
+++ b/test/case/interfaces/bridge_fwd_dual_dut/test.adoc
@@ -1,4 +1,4 @@
-=== Bridge forwarding dual DUTs
+=== Bridge Forwarding Dual DUTs
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/bridge_fwd_dual_dut]
@@ -28,7 +28,7 @@ Ping through two bridges on two different DUTs.
==== Topology
-image::topology.svg[Bridge forwarding dual DUTs topology, align=center, scaledwidth=75%]
+image::topology.svg[Bridge Forwarding Dual DUTs topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/interfaces/bridge_fwd_sgl_dut/test.adoc b/test/case/interfaces/bridge_fwd_sgl_dut/test.adoc
index 3469e71e3..496d40f1b 100644
--- a/test/case/interfaces/bridge_fwd_sgl_dut/test.adoc
+++ b/test/case/interfaces/bridge_fwd_sgl_dut/test.adoc
@@ -1,4 +1,4 @@
-=== Bridge forwarding single DUTs
+=== Bridge Forwarding Single DUTs
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/bridge_fwd_sgl_dut]
@@ -28,7 +28,7 @@ Tests forwarding through a DUT with two bridged interfaces on one DUT.
==== Topology
-image::topology.svg[Bridge forwarding single DUTs topology, align=center, scaledwidth=75%]
+image::topology.svg[Bridge Forwarding Single DUTs topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/interfaces/bridge_veth/test.adoc b/test/case/interfaces/bridge_veth/test.adoc
index b3826e90d..acf0a2be8 100644
--- a/test/case/interfaces/bridge_veth/test.adoc
+++ b/test/case/interfaces/bridge_veth/test.adoc
@@ -1,4 +1,4 @@
-=== Bridge with a physical port and a veth
+=== Bridge with a Physical Port and a Veth
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/bridge_veth]
@@ -18,7 +18,7 @@ PING --> br0
==== Topology
-image::topology.svg[Bridge with a physical port and a veth topology, align=center, scaledwidth=75%]
+image::topology.svg[Bridge with a Physical Port and a Veth topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/interfaces/bridge_vlan_separation/test.adoc b/test/case/interfaces/bridge_vlan_separation/test.adoc
index ae06a6e15..cf3282574 100644
--- a/test/case/interfaces/bridge_vlan_separation/test.adoc
+++ b/test/case/interfaces/bridge_vlan_separation/test.adoc
@@ -1,4 +1,4 @@
-=== Bridge VLAN separation
+=== Bridge VLAN Separation
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/bridge_vlan_separation]
@@ -27,7 +27,7 @@ Test that two VLANs are correctly separated in the bridge
==== Topology
-image::topology.svg[Bridge VLAN separation topology, align=center, scaledwidth=75%]
+image::topology.svg[Bridge VLAN Separation topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/interfaces/dual_bridge/test.adoc b/test/case/interfaces/dual_bridge/test.adoc
index 440e6b119..913a2aff4 100644
--- a/test/case/interfaces/dual_bridge/test.adoc
+++ b/test/case/interfaces/dual_bridge/test.adoc
@@ -1,4 +1,4 @@
-=== Dual bridges on one device
+=== Dual Bridges on One Device
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/dual_bridge]
@@ -15,7 +15,7 @@ PC - target:data veth0a - veth0b
==== Topology
-image::topology.svg[Dual bridges on one device topology, align=center, scaledwidth=75%]
+image::topology.svg[Dual Bridges on One Device topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/interfaces/iface_enable_disable/test.adoc b/test/case/interfaces/iface_enable_disable/test.adoc
index ba0abf80a..05ed3f838 100644
--- a/test/case/interfaces/iface_enable_disable/test.adoc
+++ b/test/case/interfaces/iface_enable_disable/test.adoc
@@ -1,4 +1,4 @@
-=== Interface status
+=== Interface Status
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/iface_enable_disable]
@@ -11,7 +11,7 @@ Both admin-status and oper-status are verified.
==== Topology
-image::topology.svg[Interface status topology, align=center, scaledwidth=75%]
+image::topology.svg[Interface Status topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/interfaces/iface_phys_address/test.adoc b/test/case/interfaces/iface_phys_address/test.adoc
index a78817a95..20751240c 100644
--- a/test/case/interfaces/iface_phys_address/test.adoc
+++ b/test/case/interfaces/iface_phys_address/test.adoc
@@ -1,4 +1,4 @@
-=== Custom MAC address on interface
+=== Custom MAC Address on Interface
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/iface_phys_address]
@@ -10,7 +10,7 @@ an offset applied.
==== Topology
-image::topology.svg[Custom MAC address on interface topology, align=center, scaledwidth=75%]
+image::topology.svg[Custom MAC Address on Interface topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/interfaces/igmp_basic/test.adoc b/test/case/interfaces/igmp_basic/test.adoc
index 6a7c51d5b..b1015c5e4 100644
--- a/test/case/interfaces/igmp_basic/test.adoc
+++ b/test/case/interfaces/igmp_basic/test.adoc
@@ -1,4 +1,4 @@
-=== IGMP basic
+=== IGMP Basic
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/igmp_basic]
@@ -33,7 +33,7 @@ the group.
==== Topology
-image::topology.svg[IGMP basic topology, align=center, scaledwidth=75%]
+image::topology.svg[IGMP Basic topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/interfaces/ipv6_address/test.adoc b/test/case/interfaces/ipv6_address/test.adoc
index 048e1c394..810bb0d64 100644
--- a/test/case/interfaces/ipv6_address/test.adoc
+++ b/test/case/interfaces/ipv6_address/test.adoc
@@ -1,4 +1,4 @@
-=== Interface IPv6 autoconf for bridges
+=== Interface IPv6 Autoconf for Bridges
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/ipv6_address]
@@ -9,7 +9,7 @@ See issue #473 for details.
==== Topology
-image::topology.svg[Interface IPv6 autoconf for bridges topology, align=center, scaledwidth=75%]
+image::topology.svg[Interface IPv6 Autoconf for Bridges topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/interfaces/lag_basic/test.adoc b/test/case/interfaces/lag_basic/test.adoc
index 443928d8e..ff34995ce 100644
--- a/test/case/interfaces/lag_basic/test.adoc
+++ b/test/case/interfaces/lag_basic/test.adoc
@@ -1,4 +1,4 @@
-=== Ling Aggregation Basic
+=== Link Aggregation Basic
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/lag_basic]
@@ -15,7 +15,7 @@ each test step using the `mon` interface.
==== Topology
-image::topology.svg[Ling Aggregation Basic topology, align=center, scaledwidth=75%]
+image::topology.svg[Link Aggregation Basic topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/interfaces/routing_basic/test.adoc b/test/case/interfaces/routing_basic/test.adoc
index 1f896230f..f802b81dc 100644
--- a/test/case/interfaces/routing_basic/test.adoc
+++ b/test/case/interfaces/routing_basic/test.adoc
@@ -1,4 +1,4 @@
-=== Routing basic
+=== Routing Basic
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/routing_basic]
@@ -12,7 +12,7 @@ expected to be lost.
==== Topology
-image::topology.svg[Routing basic topology, align=center, scaledwidth=75%]
+image::topology.svg[Routing Basic topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/interfaces/static_multicast_filters/test.adoc b/test/case/interfaces/static_multicast_filters/test.adoc
index e36f9156c..9b297c033 100644
--- a/test/case/interfaces/static_multicast_filters/test.adoc
+++ b/test/case/interfaces/static_multicast_filters/test.adoc
@@ -1,4 +1,4 @@
-=== Static multicast filters
+=== Static Multicast Filters
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/static_multicast_filters]
@@ -24,7 +24,7 @@ enabled when using static multicast filters)
==== Topology
-image::topology.svg[Static multicast filters topology, align=center, scaledwidth=75%]
+image::topology.svg[Static Multicast Filters topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/interfaces/tunnel_basic/gre.adoc b/test/case/interfaces/tunnel_basic/gre.adoc
index 745f8ec9d..83b4cc356 100644
--- a/test/case/interfaces/tunnel_basic/gre.adoc
+++ b/test/case/interfaces/tunnel_basic/gre.adoc
@@ -1,4 +1,4 @@
-=== GRE point-to-point
+=== GRE Point-to-Point
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/tunnel_basic]
@@ -14,7 +14,7 @@ connectivity with the second DUT through the tunnel.
==== Topology
-image::topology.svg[GRE point-to-point topology, align=center, scaledwidth=75%]
+image::topology.svg[GRE Point-to-Point topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/interfaces/tunnel_basic/gretap.adoc b/test/case/interfaces/tunnel_basic/gretap.adoc
index b358f766f..3701a6547 100644
--- a/test/case/interfaces/tunnel_basic/gretap.adoc
+++ b/test/case/interfaces/tunnel_basic/gretap.adoc
@@ -1,4 +1,4 @@
-=== GRETAP point-to-point
+=== GRETAP Point-to-Point
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/tunnel_basic]
@@ -14,7 +14,7 @@ connectivity with the second DUT through the tunnel.
==== Topology
-image::topology.svg[GRETAP point-to-point topology, align=center, scaledwidth=75%]
+image::topology.svg[GRETAP Point-to-Point topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/interfaces/tunnel_basic/vxlan.adoc b/test/case/interfaces/tunnel_basic/vxlan.adoc
index 3b4cba0f7..dab247730 100644
--- a/test/case/interfaces/tunnel_basic/vxlan.adoc
+++ b/test/case/interfaces/tunnel_basic/vxlan.adoc
@@ -1,4 +1,4 @@
-=== VXLAN point-to-point
+=== VXLAN Point-to-Point
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/tunnel_basic]
@@ -14,7 +14,7 @@ connectivity with the second DUT through the tunnel.
==== Topology
-image::topology.svg[VXLAN point-to-point topology, align=center, scaledwidth=75%]
+image::topology.svg[VXLAN Point-to-Point topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/interfaces/tunnel_bridged/gretap.adoc b/test/case/interfaces/tunnel_bridged/gretap.adoc
index e664f3f82..51ab43914 100644
--- a/test/case/interfaces/tunnel_bridged/gretap.adoc
+++ b/test/case/interfaces/tunnel_bridged/gretap.adoc
@@ -1,4 +1,4 @@
-=== GRETAP bridged with physical interface
+=== GRETAP Bridged with Physical Interface
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/tunnel_bridged]
@@ -13,7 +13,7 @@ first DUT. On host, verify connectivity with the second DUT through tunnel.
==== Topology
-image::topology.svg[GRETAP bridged with physical interface topology, align=center, scaledwidth=75%]
+image::topology.svg[GRETAP Bridged with Physical Interface topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/interfaces/tunnel_bridged/vxlan.adoc b/test/case/interfaces/tunnel_bridged/vxlan.adoc
index d6a463d6f..c4531383f 100644
--- a/test/case/interfaces/tunnel_bridged/vxlan.adoc
+++ b/test/case/interfaces/tunnel_bridged/vxlan.adoc
@@ -1,4 +1,4 @@
-=== VXLAN bridged with physical interface
+=== VXLAN Bridged with Physical Interface
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/tunnel_bridged]
@@ -13,7 +13,7 @@ first DUT. On host, verify connectivity with the second DUT through tunnel.
==== Topology
-image::topology.svg[VXLAN bridged with physical interface topology, align=center, scaledwidth=75%]
+image::topology.svg[VXLAN Bridged with Physical Interface topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/interfaces/tunnel_ttl/gre.adoc b/test/case/interfaces/tunnel_ttl/gre.adoc
index fcbfa2961..10f47106d 100644
--- a/test/case/interfaces/tunnel_ttl/gre.adoc
+++ b/test/case/interfaces/tunnel_ttl/gre.adoc
@@ -1,4 +1,4 @@
-=== GRE Tunnel TTL verification
+=== GRE Tunnel TTL Verification
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/tunnel_ttl]
@@ -18,7 +18,7 @@ many hops and the TTL would reach zero before the last routing step.)
==== Topology
-image::topology.svg[GRE Tunnel TTL verification topology, align=center, scaledwidth=75%]
+image::topology.svg[GRE Tunnel TTL Verification topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/interfaces/tunnel_ttl/vxlan.adoc b/test/case/interfaces/tunnel_ttl/vxlan.adoc
index 022df7c76..ca2f5a6f8 100644
--- a/test/case/interfaces/tunnel_ttl/vxlan.adoc
+++ b/test/case/interfaces/tunnel_ttl/vxlan.adoc
@@ -1,4 +1,4 @@
-=== VXLAN Tunnel TTL verification
+=== VXLAN Tunnel TTL Verification
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/tunnel_ttl]
@@ -18,7 +18,7 @@ many hops and the TTL would reach zero before the last routing step.)
==== Topology
-image::topology.svg[VXLAN Tunnel TTL verification topology, align=center, scaledwidth=75%]
+image::topology.svg[VXLAN Tunnel TTL Verification topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/interfaces/verify_all_interface_types/test.adoc b/test/case/interfaces/verify_all_interface_types/test.adoc
index fc15c7d08..e82bbbaf2 100644
--- a/test/case/interfaces/verify_all_interface_types/test.adoc
+++ b/test/case/interfaces/verify_all_interface_types/test.adoc
@@ -1,4 +1,4 @@
-=== Verify that all interface types can be created
+=== Verify that All Interface Types Can Be Created
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/verify_all_interface_types]
@@ -20,7 +20,7 @@ slightly longer than sending the entire configuration at once.
==== Topology
-image::topology.svg[Verify that all interface types can be created topology, align=center, scaledwidth=75%]
+image::topology.svg[Verify that All Interface Types Can Be Created topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/interfaces/veth_delete/test.adoc b/test/case/interfaces/veth_delete/test.adoc
index 95b9992ef..372faa71a 100644
--- a/test/case/interfaces/veth_delete/test.adoc
+++ b/test/case/interfaces/veth_delete/test.adoc
@@ -1,4 +1,4 @@
-=== Verify that VETH pairs can be deleted
+=== Verify that VETH Pairs Can Be Deleted
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/veth_delete]
@@ -14,7 +14,7 @@ from any other step. This to trigger a new configuration "generation".
==== Topology
-image::topology.svg[Verify that VETH pairs can be deleted topology, align=center, scaledwidth=75%]
+image::topology.svg[Verify that VETH Pairs Can Be Deleted topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/interfaces/vlan_ping/test.adoc b/test/case/interfaces/vlan_ping/test.adoc
index 2b4a211b7..87d91df0b 100644
--- a/test/case/interfaces/vlan_ping/test.adoc
+++ b/test/case/interfaces/vlan_ping/test.adoc
@@ -1,4 +1,4 @@
-=== VLAN ping connectivity
+=== VLAN Ping Connectivity
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/vlan_ping]
@@ -8,7 +8,7 @@ Very basic test if the VLAN interface configuration works.
==== Topology
-image::topology.svg[VLAN ping connectivity topology, align=center, scaledwidth=75%]
+image::topology.svg[VLAN Ping Connectivity topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/interfaces/wireguard_multipoint/test.adoc b/test/case/interfaces/wireguard_multipoint/test.adoc
index 265ac3fe3..14e60dbc0 100644
--- a/test/case/interfaces/wireguard_multipoint/test.adoc
+++ b/test/case/interfaces/wireguard_multipoint/test.adoc
@@ -1,4 +1,4 @@
-=== WireGuard multipoint
+=== WireGuard Multipoint
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/wireguard_multipoint]
@@ -41,7 +41,7 @@ Security boundaries:
==== Topology
-image::topology.svg[WireGuard multipoint topology, align=center, scaledwidth=75%]
+image::topology.svg[WireGuard Multipoint topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/misc/operational_all/test.adoc b/test/case/misc/operational_all/test.adoc
index c4e263966..a50ce7dfb 100644
--- a/test/case/misc/operational_all/test.adoc
+++ b/test/case/misc/operational_all/test.adoc
@@ -1,4 +1,4 @@
-=== Get operational
+=== Get Operational
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/misc/operational_all]
@@ -8,7 +8,7 @@ Basic test just to get operational from test-config without errors.
==== Topology
-image::topology.svg[Get operational topology, align=center, scaledwidth=75%]
+image::topology.svg[Get Operational topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/misc/support_collect/test.adoc b/test/case/misc/support_collect/test.adoc
index c3d4f6a64..70dd100f1 100644
--- a/test/case/misc/support_collect/test.adoc
+++ b/test/case/misc/support_collect/test.adoc
@@ -1,4 +1,4 @@
-=== Support data collection
+=== Support Data Collection
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/misc/support_collect]
@@ -10,7 +10,7 @@ encryption (when available on target).
==== Topology
-image::topology.svg[Support data collection topology, align=center, scaledwidth=75%]
+image::topology.svg[Support Data Collection topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/ntp/client_stratum_selection/test.adoc b/test/case/ntp/client_stratum_selection/test.adoc
index 56f5ebc52..07a9ecc3a 100644
--- a/test/case/ntp/client_stratum_selection/test.adoc
+++ b/test/case/ntp/client_stratum_selection/test.adoc
@@ -1,4 +1,4 @@
-=== NTP client stratum selection
+=== NTP Client Stratum Selection
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ntp/client_stratum_selection]
@@ -20,7 +20,7 @@ should then select srv1 (lower stratum) as its sync source.
==== Topology
-image::topology.svg[NTP client stratum selection topology, align=center, scaledwidth=75%]
+image::topology.svg[NTP Client Stratum Selection topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/ntp/server_client/test.adoc b/test/case/ntp/server_client/test.adoc
index abeb0a336..0f271de6b 100644
--- a/test/case/ntp/server_client/test.adoc
+++ b/test/case/ntp/server_client/test.adoc
@@ -1,4 +1,4 @@
-=== NTP server and client interoperability
+=== NTP Server and Client Interoperability
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ntp/server_client]
@@ -14,7 +14,7 @@ Verify NTP server and client work together:
==== Topology
-image::topology.svg[NTP server and client interoperability topology, align=center, scaledwidth=75%]
+image::topology.svg[NTP Server and Client Interoperability topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/ntp/server_mode_peer/test.adoc b/test/case/ntp/server_mode_peer/test.adoc
index 2469b6717..87c02db13 100644
--- a/test/case/ntp/server_mode_peer/test.adoc
+++ b/test/case/ntp/server_mode_peer/test.adoc
@@ -1,4 +1,4 @@
-=== NTP peer mode
+=== NTP Peer Mode
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ntp/server_mode_peer]
@@ -22,7 +22,7 @@ selected as sync source by the other peer.
==== Topology
-image::topology.svg[NTP peer mode topology, align=center, scaledwidth=75%]
+image::topology.svg[NTP Peer Mode topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/ntp/server_mode_server/test.adoc b/test/case/ntp/server_mode_server/test.adoc
index e4cf34eb8..0169a2e82 100644
--- a/test/case/ntp/server_mode_server/test.adoc
+++ b/test/case/ntp/server_mode_server/test.adoc
@@ -1,4 +1,4 @@
-=== NTP server mode
+=== NTP Server Mode
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ntp/server_mode_server]
@@ -18,7 +18,7 @@ The test verifies both servers operate correctly and serve accurate time.
==== Topology
-image::topology.svg[NTP server mode topology, align=center, scaledwidth=75%]
+image::topology.svg[NTP Server Mode topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/ntp/server_mode_standalone/test.adoc b/test/case/ntp/server_mode_standalone/test.adoc
index 4f7cef5c7..8255e113c 100644
--- a/test/case/ntp/server_mode_standalone/test.adoc
+++ b/test/case/ntp/server_mode_standalone/test.adoc
@@ -1,4 +1,4 @@
-=== NTP server standalone mode
+=== NTP Server Standalone Mode
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ntp/server_mode_standalone]
@@ -12,7 +12,7 @@ syncing from any upstream sources.
==== Topology
-image::topology.svg[NTP server standalone mode topology, align=center, scaledwidth=75%]
+image::topology.svg[NTP Server Standalone Mode topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/ptp/Readme.adoc b/test/case/ptp/Readme.adoc
new file mode 100644
index 000000000..f47f2f3ae
--- /dev/null
+++ b/test/case/ptp/Readme.adoc
@@ -0,0 +1,46 @@
+:testgroup:
+== PTP Tests
+
+Tests for PTP (IEEE 1588 Precision Time Protocol) functionality across
+different clock types and operational scenarios:
+
+ - Basic smoke test: full stack end-to-end
+ - BTCA: grandmaster election and runtime re-election via `priority1`
+ - Boundary Clock: two-port BC forwarding time with `steps-removed` accounting
+ - Transparent Clock: E2E and P2P TC on hardware-timestamping nodes
+ - Port fault recovery: link-down/link-up state machine and re-convergence
+
+The offset convergence threshold in the tests varies with the timestamping
+capability of the nodes under test:
+
+[cols="1,1,3",options="header"]
+|===
+| Timestamping | Default Threshold | Typical scenario
+| Software | 100 000 ns | Virtual machines, QEMU tap interfaces
+| Hardware | 1 000 ns | Nodes with PHC hardware timestamping
+|===
+
+Tests that accept a `--threshold-ns` option may use that value instead.
+When no option is given, the threshold is selected automatically based
+on the `ptp-hwts` capability of the relevant node in the physical test
+system's topology file.
+
+<<<
+
+include::basic/Readme.adoc[]
+
+<<<
+
+include::bmca/Readme.adoc[]
+
+<<<
+
+include::boundary_clock/Readme.adoc[]
+
+<<<
+
+include::transparent_clock/Readme.adoc[]
+
+<<<
+
+include::port_recovery/Readme.adoc[]
diff --git a/test/case/ptp/all.yaml b/test/case/ptp/all.yaml
new file mode 100644
index 000000000..a86f28853
--- /dev/null
+++ b/test/case/ptp/all.yaml
@@ -0,0 +1,18 @@
+---
+- name: PTP basic
+ suite: basic/test.yaml
+
+- name: PTP BTCA grandmaster election
+ suite: bmca/test.yaml
+
+- name: PTP boundary clock
+ suite: boundary_clock/test.yaml
+
+- name: PTP transparent clock
+ suite: transparent_clock/test.yaml
+
+- name: PTP port fault recovery
+ case: port_recovery/test.py
+
+- name: PTP servo step-threshold
+ case: servo/test.py
diff --git a/test/case/ptp/basic/Readme.adoc b/test/case/ptp/basic/Readme.adoc
new file mode 100644
index 000000000..cb3079ad3
--- /dev/null
+++ b/test/case/ptp/basic/Readme.adoc
@@ -0,0 +1,6 @@
+include::ieee1588.adoc[]
+
+<<<
+
+include::ieee802dot1as.adoc[]
+
diff --git a/test/case/ptp/basic/ieee1588.adoc b/test/case/ptp/basic/ieee1588.adoc
new file mode 100644
index 000000000..bfd582fee
--- /dev/null
+++ b/test/case/ptp/basic/ieee1588.adoc
@@ -0,0 +1,28 @@
+=== PTP basic (IEEE 1588)
+
+ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ptp/basic]
+
+==== Description
+
+Verify basic PTP operation end-to-end: clock configuration, port state
+transitions, and clock servo convergence.
+
+Two Ordinary Clocks are connected back-to-back. The grandmaster is
+configured with `priority1=1` so it always wins the BTCA election; the
+time receiver is configured with `time-receiver-only` so it never
+attempts to become grandmaster. The test is run once per supported
+profile, covering both IEEE 1588-2019 (UDP/IPv4, E2E) and IEEE 802.1AS
+(Layer 2, P2P).
+
+==== Topology
+
+image::topology.svg[PTP basic (IEEE 1588) topology, align=center, scaledwidth=75%]
+
+==== Sequence
+
+. Set up topology and attach to DUTs
+. Configure grandmaster (OC, ieee1588, priority1=1) and time receiver (ieee1588, priority1=128, client-only)
+. Wait for grandmaster and time receiver ports to reach active states
+. Wait for time receiver offset to converge
+
+
diff --git a/test/case/ptp/basic/ieee1588.py b/test/case/ptp/basic/ieee1588.py
new file mode 120000
index 000000000..946566431
--- /dev/null
+++ b/test/case/ptp/basic/ieee1588.py
@@ -0,0 +1 @@
+test.py
\ No newline at end of file
diff --git a/test/case/ptp/basic/ieee802dot1as.adoc b/test/case/ptp/basic/ieee802dot1as.adoc
new file mode 100644
index 000000000..3fbc7a1bf
--- /dev/null
+++ b/test/case/ptp/basic/ieee802dot1as.adoc
@@ -0,0 +1,28 @@
+=== PTP basic (IEEE 802.1AS)
+
+ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ptp/basic]
+
+==== Description
+
+Verify basic PTP operation end-to-end: clock configuration, port state
+transitions, and clock servo convergence.
+
+Two Ordinary Clocks are connected back-to-back. The grandmaster is
+configured with `priority1=1` so it always wins the BTCA election; the
+time receiver is configured with `time-receiver-only` so it never
+attempts to become grandmaster. The test is run once per supported
+profile, covering both IEEE 1588-2019 (UDP/IPv4, E2E) and IEEE 802.1AS
+(Layer 2, P2P).
+
+==== Topology
+
+image::topology.svg[PTP basic (IEEE 802.1AS) topology, align=center, scaledwidth=75%]
+
+==== Sequence
+
+. Set up topology and attach to DUTs
+. Configure grandmaster (OC, ieee802-dot1as, priority1=1) and time receiver (ieee802-dot1as, priority1=128, client-only)
+. Wait for grandmaster and time receiver ports to reach active states
+. Wait for time receiver offset to converge
+
+
diff --git a/test/case/ptp/basic/ieee802dot1as.py b/test/case/ptp/basic/ieee802dot1as.py
new file mode 120000
index 000000000..946566431
--- /dev/null
+++ b/test/case/ptp/basic/ieee802dot1as.py
@@ -0,0 +1 @@
+test.py
\ No newline at end of file
diff --git a/test/case/ptp/basic/test.adoc b/test/case/ptp/basic/test.adoc
new file mode 100644
index 000000000..3b02110b7
--- /dev/null
+++ b/test/case/ptp/basic/test.adoc
@@ -0,0 +1,29 @@
+=== PTP basic
+
+ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ptp/basic]
+
+==== Description
+
+Verify basic PTP operation end-to-end: clock configuration, port state
+transitions, and clock servo convergence.
+
+Two Ordinary Clocks are connected back-to-back. The grandmaster is
+configured with `priority1=1` so it always wins the BTCA election; the
+time receiver is configured with `time-receiver-only` so it never
+attempts to become grandmaster. Software timestamping is used, making
+the test suitable for virtual machines as well as real hardware.
+
+This is the smoke test: if it passes, the full PTP stack is working.
+
+==== Topology
+
+image::topology.svg[PTP basic topology, align=center, scaledwidth=75%]
+
+==== Sequence
+
+. Set up topology and attach to DUTs
+. Configure grandmaster (priority1=1) and time receiver (priority1=128, client-only)
+. Wait for grandmaster and time receiver ports to reach active states
+. Wait for time receiver offset to converge
+
+
diff --git a/test/case/ptp/basic/test.py b/test/case/ptp/basic/test.py
new file mode 100755
index 000000000..6d8b50b41
--- /dev/null
+++ b/test/case/ptp/basic/test.py
@@ -0,0 +1,107 @@
+#!/usr/bin/env python3
+"""PTP basic
+
+Verify basic PTP operation end-to-end: clock configuration, port state
+transitions, and clock servo convergence.
+
+Two Ordinary Clocks are connected back-to-back. The grandmaster is
+configured with `priority1=1` so it always wins the BTCA election; the
+time receiver is configured with `time-receiver-only` so it never
+attempts to become grandmaster. The test is run once per supported
+profile, covering both IEEE 1588-2019 (UDP/IPv4, E2E) and IEEE 802.1AS
+(Layer 2, P2P).
+"""
+
+import infamy
+import infamy.ptp as ptp
+from infamy import until
+from infamy.util import parallel
+
+
+class ArgumentParser(infamy.ArgumentParser):
+ def __init__(self):
+ super().__init__()
+ self.args.add_argument("--profile", default="ieee1588",
+ choices=["ieee1588", "ieee802-dot1as"])
+ self.args.add_argument("--threshold-ns", type=int, default=None)
+
+
+def configure_oc(iface, priority1, client_only, profile, ip=None):
+ config = {
+ "ieee1588-ptp-tt": {
+ "ptp": {
+ "instances": {
+ "instance": [{
+ "instance-index": 0,
+ "default-ds": {
+ "instance-type": "oc",
+ "domain-number": 0,
+ "priority1": priority1,
+ "priority2": 128,
+ "infix-ptp:profile": profile,
+ "time-receiver-only": client_only,
+ },
+ "ports": {
+ "port": [{
+ "port-index": 1,
+ "underlying-interface": iface,
+ }]
+ }
+ }]
+ }
+ }
+ }
+ }
+
+ # Always enable the underlying interface — ptp4l needs it up regardless
+ # of profile. IEEE 1588 also needs an IPv4 address for UDP transport.
+ iface_cfg = {"name": iface, "enabled": True}
+ if profile == "ieee1588":
+ iface_cfg["ipv4"] = {"address": [{"ip": ip, "prefix-length": 30}]}
+ config["ietf-interfaces"] = {
+ "interfaces": {"interface": [iface_cfg]}
+ }
+
+ # Fast timers: 250 ms announce/sync intervals speed up port state transitions
+ # and convergence compared to the 1 s defaults.
+ port_ds = {
+ "log-announce-interval": -2,
+ "announce-receipt-timeout": 2,
+ "log-sync-interval": -2,
+ }
+ if profile == "ieee1588":
+ port_ds["delay-mechanism"] = "e2e"
+ config["ieee1588-ptp-tt"]["ptp"]["instances"]["instance"][0] \
+ ["ports"]["port"][0]["port-ds"] = port_ds
+
+ return config
+
+
+with infamy.Test() as test:
+ with test.step("Set up topology and attach to DUTs"):
+ arg = ArgumentParser()
+ env = infamy.Env(args=arg)
+ profile = env.args.profile
+ gm = env.attach("gm", "mgmt")
+ receiver = env.attach("receiver", "mgmt")
+
+ _, gm_iface = env.ltop.xlate("gm", "data")
+ _, receiver_iface = env.ltop.xlate("receiver", "data")
+ threshold_ns = env.args.threshold_ns or ptp.default_threshold(env, "receiver")
+
+ with test.step(f"Configure grandmaster (OC, {profile}, priority1=1) and time receiver ({profile}, priority1=128, client-only)"):
+ gm.put_config_dicts(configure_oc(gm_iface, priority1=1,
+ client_only=False, profile=profile,
+ ip="192.168.100.1"))
+ receiver.put_config_dicts(configure_oc(receiver_iface, priority1=128,
+ client_only=True, profile=profile,
+ ip="192.168.100.2"))
+
+ with test.step("Wait for grandmaster and time receiver ports to reach active states"):
+ parallel(lambda: until(lambda: ptp.is_time_transmitter(gm), attempts=60),
+ lambda: until(lambda: ptp.is_time_receiver(receiver), attempts=60))
+
+ with test.step("Wait for time receiver offset to converge"):
+ until(lambda: ptp.has_converged(receiver, threshold_ns), attempts=120)
+
+ test.succeed()
diff --git a/test/case/ptp/basic/test.yaml b/test/case/ptp/basic/test.yaml
new file mode 100644
index 000000000..4a4424f8b
--- /dev/null
+++ b/test/case/ptp/basic/test.yaml
@@ -0,0 +1,11 @@
+---
+- settings:
+ test-spec: .adoc
+
+- name: PTP basic (IEEE 1588)
+ case: ieee1588.py
+ opts: ["--profile", "ieee1588"]
+
+- name: PTP basic (IEEE 802.1AS)
+ case: ieee802dot1as.py
+ opts: ["--profile", "ieee802-dot1as"]
diff --git a/test/case/ptp/basic/topology.dot b/test/case/ptp/basic/topology.dot
new file mode 100644
index 000000000..efa656b63
--- /dev/null
+++ b/test/case/ptp/basic/topology.dot
@@ -0,0 +1,33 @@
+graph "ptp-basic" {
+ layout="neato";
+ overlap="false";
+ esep="+22";
+
+ node [shape=record, fontname="DejaVu Sans Mono, Book"];
+ edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
+
+ host [
+ label="{ mgmt1 | \n\nhost\n\n\n | mgmt2 }",
+ pos="0,15!",
+ requires="controller",
+ ];
+
+ gm [
+ label="{ mgmt | data } | { gm\npriority1=1 }",
+ pos="2,15.25!",
+ fontsize=12,
+ requires="infix",
+ ];
+
+ receiver [
+ label="{ data | mgmt } | { receiver\npriority1=128 }",
+ pos="2,14.75!",
+ fontsize=12,
+ requires="infix",
+ ];
+
+ host:mgmt1 -- gm:mgmt [requires="mgmt", color="lightgray"]
+ host:mgmt2 -- receiver:mgmt [requires="mgmt", color="lightgray"]
+
+ gm:data -- receiver:data [label="\n\n192.168.100.0/30 ", dir="both"]
+}
diff --git a/test/case/ptp/basic/topology.svg b/test/case/ptp/basic/topology.svg
new file mode 100644
index 000000000..92ee2de21
--- /dev/null
+++ b/test/case/ptp/basic/topology.svg
@@ -0,0 +1,62 @@
+
+
+
+
+
diff --git a/test/case/ptp/bmca/Readme.adoc b/test/case/ptp/bmca/Readme.adoc
new file mode 100644
index 000000000..cb3079ad3
--- /dev/null
+++ b/test/case/ptp/bmca/Readme.adoc
@@ -0,0 +1,6 @@
+include::ieee1588.adoc[]
+
+<<<
+
+include::ieee802dot1as.adoc[]
+
diff --git a/test/case/ptp/bmca/ieee1588.adoc b/test/case/ptp/bmca/ieee1588.adoc
new file mode 100644
index 000000000..8ec36caf7
--- /dev/null
+++ b/test/case/ptp/bmca/ieee1588.adoc
@@ -0,0 +1,39 @@
+=== PTP BTCA grandmaster election (IEEE 1588)
+
+ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ptp/bmca]
+
+==== Description
+
+Verify that the Best TimeTransmitter Clock Algorithm (BTCA) selects the clock
+with the lowest `priority1` as grandmaster, and that a change of `priority1`
+at runtime triggers a new election with the correct result.
+
+Two Ordinary Clocks are connected back-to-back. Both announce themselves as
+potential grandmasters. In round one, *alpha* holds `priority1=1` and wins
+the election; *beta* (`priority1=128`) becomes the time receiver. In round
+two, *alpha* is reconfigured to priority1=200 without restarting; the BTCA
+re-runs and beta wins, becoming the new grandmaster. The test verifies that
+alpha's `parent-ds` `grandmaster-identity` changes to beta's `clock-identity`,
+confirming that the re-election is reflected in the operational datastore.
+
+Announce intervals are reduced to 250 ms (`log-announce-interval -2`) and the
+announce receipt timeout to 2 intervals (500 ms) to make re-election complete
+in roughly one second rather than the default three.
+
+The test is run for both IEEE 1588-2019 (UDP/IPv4, E2E) and IEEE 802.1AS
+(Layer 2, P2P) profiles.
+
+==== Topology
+
+image::topology.svg[PTP BTCA grandmaster election (IEEE 1588) topology, align=center, scaledwidth=75%]
+
+==== Sequence
+
+. Set up topology and attach to DUTs
+. Configure both DUTs (ieee1588); alpha has lower priority1
+. Verify initial election: alpha is grandmaster, beta is time receiver
+. Reconfigure alpha with worse priority1=200
+. Verify beta wins re-election (is own grandmaster)
+. Verify alpha tracks beta as grandmaster
+
+
diff --git a/test/case/ptp/bmca/ieee1588.py b/test/case/ptp/bmca/ieee1588.py
new file mode 120000
index 000000000..946566431
--- /dev/null
+++ b/test/case/ptp/bmca/ieee1588.py
@@ -0,0 +1 @@
+test.py
\ No newline at end of file
diff --git a/test/case/ptp/bmca/ieee802dot1as.adoc b/test/case/ptp/bmca/ieee802dot1as.adoc
new file mode 100644
index 000000000..625efadb4
--- /dev/null
+++ b/test/case/ptp/bmca/ieee802dot1as.adoc
@@ -0,0 +1,39 @@
+=== PTP BTCA grandmaster election (IEEE 802.1AS)
+
+ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ptp/bmca]
+
+==== Description
+
+Verify that the Best TimeTransmitter Clock Algorithm (BTCA) selects the clock
+with the lowest `priority1` as grandmaster, and that a change of `priority1`
+at runtime triggers a new election with the correct result.
+
+Two Ordinary Clocks are connected back-to-back. Both announce themselves as
+potential grandmasters. In round one, *alpha* holds `priority1=1` and wins
+the election; *beta* (`priority1=128`) becomes the time receiver. In round
+two, *alpha* is reconfigured to priority1=200 without restarting; the BTCA
+re-runs and beta wins, becoming the new grandmaster. The test verifies that
+alpha's `parent-ds` `grandmaster-identity` changes to beta's `clock-identity`,
+confirming that the re-election is reflected in the operational datastore.
+
+Announce intervals are reduced to 250 ms (`log-announce-interval -2`) and the
+announce receipt timeout to 2 intervals (500 ms) to make re-election complete
+in roughly one second rather than the default three.
+
+The test is run for both IEEE 1588-2019 (UDP/IPv4, E2E) and IEEE 802.1AS
+(Layer 2, P2P) profiles.
+
+==== Topology
+
+image::topology.svg[PTP BTCA grandmaster election (IEEE 802.1AS) topology, align=center, scaledwidth=75%]
+
+==== Sequence
+
+. Set up topology and attach to DUTs
+. Configure both DUTs (ieee802-dot1as); alpha has lower priority1
+. Verify initial election: alpha is grandmaster, beta is time receiver
+. Reconfigure alpha with worse priority1=200
+. Verify beta wins re-election (is own grandmaster)
+. Verify alpha tracks beta as grandmaster
+
+
diff --git a/test/case/ptp/bmca/ieee802dot1as.py b/test/case/ptp/bmca/ieee802dot1as.py
new file mode 120000
index 000000000..946566431
--- /dev/null
+++ b/test/case/ptp/bmca/ieee802dot1as.py
@@ -0,0 +1 @@
+test.py
\ No newline at end of file
diff --git a/test/case/ptp/bmca/test.adoc b/test/case/ptp/bmca/test.adoc
new file mode 100644
index 000000000..1080fefc5
--- /dev/null
+++ b/test/case/ptp/bmca/test.adoc
@@ -0,0 +1,5 @@
+include::ieee1588.adoc[]
+
+<<<
+
+include::ieee802dot1as.adoc[]
diff --git a/test/case/ptp/bmca/test.py b/test/case/ptp/bmca/test.py
new file mode 100755
index 000000000..615ee60f4
--- /dev/null
+++ b/test/case/ptp/bmca/test.py
@@ -0,0 +1,113 @@
+#!/usr/bin/env python3
+"""PTP BTCA grandmaster election
+
+Verify that the Best TimeTransmitter Clock Algorithm (BTCA) selects the clock
+with the lowest `priority1` as grandmaster, and that a change of `priority1`
+at runtime triggers a new election with the correct result.
+
+Two Ordinary Clocks are connected back-to-back. Both announce themselves as
+potential grandmasters. In round one, *alpha* holds `priority1=1` and wins
+the election; *beta* (`priority1=128`) becomes the time receiver. In round
+two, *alpha* is reconfigured to priority1=200 without restarting; the BTCA
+re-runs and beta wins, becoming the new grandmaster. The test verifies that
+alpha's `parent-ds` `grandmaster-identity` changes to beta's `clock-identity`,
+confirming that the re-election is reflected in the operational datastore.
+
+Announce intervals are reduced to 250 ms (`log-announce-interval -2`) and the
+announce receipt timeout to 2 intervals (500 ms) to make re-election complete
+in roughly one second rather than the default three.
+
+The test is run for both IEEE 1588-2019 (UDP/IPv4, E2E) and IEEE 802.1AS
+(Layer 2, P2P) profiles.
+"""
+
+import infamy
+import infamy.ptp as ptp
+from infamy import until
+from infamy.util import parallel
+
+
+class ArgumentParser(infamy.ArgumentParser):
+ def __init__(self):
+ super().__init__()
+ self.args.add_argument("--profile", default="ieee1588",
+ choices=["ieee1588", "ieee802-dot1as"])
+
+
+def configure_oc(iface, priority1, profile, ip=None):
+ iface_cfg = {"name": iface, "enabled": True}
+ if profile == "ieee1588":
+ iface_cfg["ipv4"] = {"address": [{"ip": ip, "prefix-length": 30}]}
+
+ port_ds = {
+ "log-announce-interval": -2,
+ "announce-receipt-timeout": 2,
+ "log-sync-interval": -2,
+ }
+ if profile == "ieee1588":
+ port_ds["delay-mechanism"] = "e2e"
+
+ return {
+ "ietf-interfaces": {
+ "interfaces": {"interface": [iface_cfg]}
+ },
+ "ieee1588-ptp-tt": {
+ "ptp": {
+ "instances": {
+ "instance": [{
+ "instance-index": 0,
+ "default-ds": {
+ "instance-type": "oc",
+ "domain-number": 0,
+ "priority1": priority1,
+ "priority2": 128,
+ "infix-ptp:profile": profile,
+ "time-receiver-only": False,
+ },
+ "ports": {
+ "port": [{
+ "port-index": 1,
+ "underlying-interface": iface,
+ "port-ds": port_ds,
+ }]
+ }
+ }]
+ }
+ }
+ }
+ }
+
+
+with infamy.Test() as test:
+ with test.step("Set up topology and attach to DUTs"):
+ arg = ArgumentParser()
+ env = infamy.Env(args=arg)
+ profile = env.args.profile
+ alpha = env.attach("alpha", "mgmt")
+ beta = env.attach("beta", "mgmt")
+
+ _, if_alpha = env.ltop.xlate("alpha", "data")
+ _, if_beta = env.ltop.xlate("beta", "data")
+
+ with test.step(f"Configure both DUTs ({profile}); alpha has lower priority1"):
+ alpha.put_config_dicts(configure_oc(if_alpha, priority1=1,
+ profile=profile, ip="192.168.100.1"))
+ beta.put_config_dicts(configure_oc(if_beta, priority1=128,
+ profile=profile, ip="192.168.100.2"))
+
+ with test.step("Verify initial election: alpha is grandmaster, beta is time receiver"):
+ parallel(lambda: until(lambda: ptp.is_own_gm(alpha), attempts=60),
+ lambda: until(lambda: ptp.is_time_receiver(beta), attempts=60))
+
+ with test.step("Reconfigure alpha with worse priority1=200"):
+ alpha.put_config_dicts(configure_oc(if_alpha, priority1=200,
+ profile=profile, ip="192.168.100.1"))
+
+ with test.step("Verify beta wins re-election (is own grandmaster)"):
+ until(lambda: ptp.is_own_gm(beta), attempts=30)
+
+ with test.step("Verify alpha tracks beta as grandmaster"):
+ until(lambda: ptp.grandmaster_identity(alpha) == ptp.clock_identity(beta),
+ attempts=30)
+
+ test.succeed()
diff --git a/test/case/ptp/bmca/test.yaml b/test/case/ptp/bmca/test.yaml
new file mode 100644
index 000000000..3246043c7
--- /dev/null
+++ b/test/case/ptp/bmca/test.yaml
@@ -0,0 +1,11 @@
+---
+- settings:
+ test-spec: .adoc
+
+- name: PTP BTCA grandmaster election (IEEE 1588)
+ case: ieee1588.py
+ opts: ["--profile", "ieee1588"]
+
+- name: PTP BTCA grandmaster election (IEEE 802.1AS)
+ case: ieee802dot1as.py
+ opts: ["--profile", "ieee802-dot1as"]
diff --git a/test/case/ptp/bmca/topology.dot b/test/case/ptp/bmca/topology.dot
new file mode 100644
index 000000000..1d9dcc79b
--- /dev/null
+++ b/test/case/ptp/bmca/topology.dot
@@ -0,0 +1,33 @@
+graph "ptp-bmca" {
+ layout="neato";
+ overlap="false";
+ esep="+22";
+
+ node [shape=record, fontname="DejaVu Sans Mono, Book"];
+ edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
+
+ host [
+ label="{ mgmt1 | \n\nhost\n\n\n | mgmt2 }",
+ pos="0,15!",
+ requires="controller",
+ ];
+
+ alpha [
+ label="{ mgmt | data } | { alpha }",
+ pos="2,15.25!",
+ fontsize=12,
+ requires="infix",
+ ];
+
+ beta [
+ label="{ data | mgmt } | { beta }",
+ pos="2,14.75!",
+ fontsize=12,
+ requires="infix",
+ ];
+
+ host:mgmt1 -- alpha:mgmt [requires="mgmt", color="lightgray"]
+ host:mgmt2 -- beta:mgmt [requires="mgmt", color="lightgray"]
+
+ alpha:data -- beta:data [label="192.168.101.0/30 ", dir="both"]
+}
diff --git a/test/case/ptp/bmca/topology.svg b/test/case/ptp/bmca/topology.svg
new file mode 100644
index 000000000..04f715050
--- /dev/null
+++ b/test/case/ptp/bmca/topology.svg
@@ -0,0 +1,60 @@
+
+
+
+
+
diff --git a/test/case/ptp/boundary_clock/Readme.adoc b/test/case/ptp/boundary_clock/Readme.adoc
new file mode 100644
index 000000000..cb3079ad3
--- /dev/null
+++ b/test/case/ptp/boundary_clock/Readme.adoc
@@ -0,0 +1,6 @@
+include::ieee1588.adoc[]
+
+<<<
+
+include::ieee802dot1as.adoc[]
+
diff --git a/test/case/ptp/boundary_clock/ieee1588.adoc b/test/case/ptp/boundary_clock/ieee1588.adoc
new file mode 100644
index 000000000..6aa262fd0
--- /dev/null
+++ b/test/case/ptp/boundary_clock/ieee1588.adoc
@@ -0,0 +1,41 @@
+=== PTP boundary clock (IEEE 1588)
+
+ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ptp/boundary_clock]
+
+==== Description
+
+Verify that a Boundary Clock (BC) correctly receives time on one port and
+distributes it on another, and that the downstream time receiver sees exactly
+one additional hop (`steps-removed=2`).
+
+Three nodes are connected in a chain: a grandmaster Ordinary Clock (OC,
+`priority1=1`), a Boundary Clock (BC, `priority1=64`) with two ports, and a
+time-receiver Ordinary Clock (OC, `priority1=128`).
+
+The BC's upstream port (toward the GM) must reach time-receiver state; the
+downstream port (toward the time receiver) must reach time-transmitter state.
+The time receiver's `steps-removed` counter must equal 2: the BC increments
+`steps-removed` to 1 in the ANNOUNCE messages it forwards, and the time
+receiver adds 1 more when it stores the value in its `currentDS`. An OC
+directly connected to the GM shows 1, so the BC adds exactly one extra hop.
+
+The test is run for both IEEE 1588-2019 (UDP/IPv4, E2E) and IEEE 802.1AS
+(Layer 2, P2P) profiles.
+
+==== Topology
+
+image::topology.svg[PTP boundary clock (IEEE 1588) topology, align=center, scaledwidth=75%]
+
+==== Sequence
+
+. Set up topology and attach to DUTs
+. Configure grandmaster (OC, ieee1588, priority1=1) and boundary clock (BC, ieee1588, priority1=64, two ports)
+. Wait for BC uplink port to become time-receiver
+. Wait for BC dnlink port to become time-transmitter
+. Wait for boundary clock offset to converge
+. Configure time receiver (OC, ieee1588, priority1=128, client-only)
+. Wait for time receiver to reach time-receiver state
+. Verify time receiver steps-removed equals 2 (one BC hop)
+. Wait for time receiver offset to converge
+
+
diff --git a/test/case/ptp/boundary_clock/ieee1588.py b/test/case/ptp/boundary_clock/ieee1588.py
new file mode 120000
index 000000000..946566431
--- /dev/null
+++ b/test/case/ptp/boundary_clock/ieee1588.py
@@ -0,0 +1 @@
+test.py
\ No newline at end of file
diff --git a/test/case/ptp/boundary_clock/ieee802dot1as.adoc b/test/case/ptp/boundary_clock/ieee802dot1as.adoc
new file mode 100644
index 000000000..9293a8463
--- /dev/null
+++ b/test/case/ptp/boundary_clock/ieee802dot1as.adoc
@@ -0,0 +1,41 @@
+=== PTP boundary clock (IEEE 802.1AS)
+
+ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ptp/boundary_clock]
+
+==== Description
+
+Verify that a Boundary Clock (BC) correctly receives time on one port and
+distributes it on another, and that the downstream time receiver sees exactly
+one additional hop (`steps-removed=2`).
+
+Three nodes are connected in a chain: a grandmaster Ordinary Clock (OC,
+`priority1=1`), a Boundary Clock (BC, `priority1=64`) with two ports, and a
+time-receiver Ordinary Clock (OC, `priority1=128`).
+
+The BC's upstream port (toward the GM) must reach time-receiver state; the
+downstream port (toward the time receiver) must reach time-transmitter state.
+The time receiver's `steps-removed` counter must equal 2: the BC increments
+`steps-removed` to 1 in the ANNOUNCE messages it forwards, and the time
+receiver adds 1 more when it stores the value in its `currentDS`. An OC
+directly connected to the GM shows 1, so the BC adds exactly one extra hop.
+
+The test is run for both IEEE 1588-2019 (UDP/IPv4, E2E) and IEEE 802.1AS
+(Layer 2, P2P) profiles.
+
+==== Topology
+
+image::topology.svg[PTP boundary clock (IEEE 802.1AS) topology, align=center, scaledwidth=75%]
+
+==== Sequence
+
+. Set up topology and attach to DUTs
+. Configure grandmaster (OC, ieee802-dot1as, priority1=1) and boundary clock (BC, ieee802-dot1as, priority1=64, two ports)
+. Wait for BC uplink port to become time-receiver
+. Wait for BC dnlink port to become time-transmitter
+. Wait for boundary clock offset to converge
+. Configure time receiver (OC, ieee802-dot1as, priority1=128, client-only)
+. Wait for time receiver to reach time-receiver state
+. Verify time receiver steps-removed equals 2 (one BC hop)
+. Wait for time receiver offset to converge
+
+
diff --git a/test/case/ptp/boundary_clock/ieee802dot1as.py b/test/case/ptp/boundary_clock/ieee802dot1as.py
new file mode 120000
index 000000000..946566431
--- /dev/null
+++ b/test/case/ptp/boundary_clock/ieee802dot1as.py
@@ -0,0 +1 @@
+test.py
\ No newline at end of file
diff --git a/test/case/ptp/boundary_clock/test.adoc b/test/case/ptp/boundary_clock/test.adoc
new file mode 100644
index 000000000..1080fefc5
--- /dev/null
+++ b/test/case/ptp/boundary_clock/test.adoc
@@ -0,0 +1,5 @@
+include::ieee1588.adoc[]
+
+<<<
+
+include::ieee802dot1as.adoc[]
diff --git a/test/case/ptp/boundary_clock/test.py b/test/case/ptp/boundary_clock/test.py
new file mode 100755
index 000000000..552051a3e
--- /dev/null
+++ b/test/case/ptp/boundary_clock/test.py
@@ -0,0 +1,179 @@
+#!/usr/bin/env python3
+"""PTP boundary clock
+
+Verify that a Boundary Clock (BC) correctly receives time on one port and
+distributes it on another, and that the downstream time receiver sees exactly
+one additional hop (`steps-removed=2`).
+
+Three nodes are connected in a chain: a grandmaster Ordinary Clock (OC,
+`priority1=1`), a Boundary Clock (BC, `priority1=64`) with two ports, and a
+time-receiver Ordinary Clock (OC, `priority1=128`).
+
+The BC's upstream port (toward the GM) must reach time-receiver state; the
+downstream port (toward the time receiver) must reach time-transmitter state.
+The time receiver's `steps-removed` counter must equal 2: the BC increments
+`steps-removed` to 1 in the ANNOUNCE messages it forwards, and the time
+receiver adds 1 more when it stores the value in its `currentDS`. An OC
+directly connected to the GM shows 1, so the BC adds exactly one extra hop.
+
+The test is run for both IEEE 1588-2019 (UDP/IPv4, E2E) and IEEE 802.1AS
+(Layer 2, P2P) profiles.
+"""
+
+import infamy
+import infamy.ptp as ptp
+from infamy import until
+
+
+class ArgumentParser(infamy.ArgumentParser):
+ def __init__(self):
+ super().__init__()
+ self.args.add_argument("--profile", default="ieee1588",
+ choices=["ieee1588", "ieee802-dot1as"])
+ self.args.add_argument("--threshold-ns", type=int, default=None)
+
+
+def configure_oc(iface, priority1, profile, client_only=False, ip=None):
+ iface_cfg = {"name": iface, "enabled": True}
+ if profile == "ieee1588":
+ iface_cfg["ipv4"] = {"address": [{"ip": ip, "prefix-length": 30}]}
+
+ port_ds = {
+ "log-announce-interval": -2,
+ "announce-receipt-timeout": 2,
+ "log-sync-interval": -2,
+ }
+ if profile == "ieee1588":
+ port_ds["delay-mechanism"] = "e2e"
+
+ return {
+ "ietf-interfaces": {
+ "interfaces": {"interface": [iface_cfg]}
+ },
+ "ieee1588-ptp-tt": {
+ "ptp": {
+ "instances": {
+ "instance": [{
+ "instance-index": 0,
+ "default-ds": {
+ "instance-type": "oc",
+ "domain-number": 0,
+ "priority1": priority1,
+ "priority2": 128,
+ "infix-ptp:profile": profile,
+ "time-receiver-only": client_only,
+ },
+ "ports": {
+ "port": [{
+ "port-index": 1,
+ "underlying-interface": iface,
+ "port-ds": port_ds,
+ }]
+ }
+ }]
+ }
+ }
+ }
+ }
+
+
+def configure_bc(uplink_iface, dnlink_iface, profile,
+ uplink_ip=None, dnlink_ip=None, priority1=64):
+ ifaces = [{"name": uplink_iface, "enabled": True},
+ {"name": dnlink_iface, "enabled": True}]
+ if profile == "ieee1588":
+ ifaces[0]["ipv4"] = {"address": [{"ip": uplink_ip, "prefix-length": 30}]}
+ ifaces[1]["ipv4"] = {"address": [{"ip": dnlink_ip, "prefix-length": 30}]}
+
+ port_ds = {
+ "log-announce-interval": -2,
+ "announce-receipt-timeout": 2,
+ "log-sync-interval": -2,
+ }
+ if profile == "ieee1588":
+ port_ds["delay-mechanism"] = "e2e"
+
+ return {
+ "ietf-interfaces": {
+ "interfaces": {"interface": ifaces}
+ },
+ "ieee1588-ptp-tt": {
+ "ptp": {
+ "instances": {
+ "instance": [{
+ "instance-index": 0,
+ "default-ds": {
+ "instance-type": "bc",
+ "domain-number": 0,
+ "priority1": priority1,
+ "priority2": 128,
+ "infix-ptp:profile": profile,
+ },
+ "ports": {
+ "port": [
+ {
+ "port-index": 1,
+ "underlying-interface": uplink_iface,
+ "port-ds": port_ds,
+ },
+ {
+ "port-index": 2,
+ "underlying-interface": dnlink_iface,
+ "port-ds": port_ds,
+ }
+ ]
+ }
+ }]
+ }
+ }
+ }
+ }
+
+
+with infamy.Test() as test:
+ with test.step("Set up topology and attach to DUTs"):
+ arg = ArgumentParser()
+ env = infamy.Env(args=arg)
+ profile = env.args.profile
+ gm = env.attach("gm", "mgmt")
+ bc = env.attach("bc", "mgmt")
+ receiver = env.attach("receiver", "mgmt")
+
+ _, gm_iface = env.ltop.xlate("gm", "data")
+ _, bc_uplink = env.ltop.xlate("bc", "uplink")
+ _, bc_dnlink = env.ltop.xlate("bc", "dnlink")
+ _, recv_iface = env.ltop.xlate("receiver", "data")
+ threshold_ns = env.args.threshold_ns or ptp.default_threshold(env, "receiver", hops=2)
+
+ with test.step(f"Configure grandmaster (OC, {profile}, priority1=1) and boundary clock (BC, {profile}, priority1=64, two ports)"):
+ gm.put_config_dicts(configure_oc(gm_iface, priority1=1,
+ profile=profile, ip="192.168.100.1"))
+ bc.put_config_dicts(configure_bc(bc_uplink, bc_dnlink, profile=profile,
+ uplink_ip="192.168.100.2",
+ dnlink_ip="192.168.101.1"))
+
+ with test.step("Wait for BC uplink port to become time-receiver"):
+ until(lambda: ptp.is_time_receiver(bc, port_idx=1), attempts=60)
+
+ with test.step("Wait for BC dnlink port to become time-transmitter"):
+ until(lambda: ptp.is_time_transmitter(bc, port_idx=2), attempts=60)
+
+ with test.step("Wait for boundary clock offset to converge"):
+ bc_threshold_ns = env.args.threshold_ns or ptp.default_threshold(env, "bc", hops=1)
+ until(lambda: ptp.has_converged(bc, bc_threshold_ns), attempts=120)
+
+ with test.step(f"Configure time receiver (OC, {profile}, priority1=128, client-only)"):
+ receiver.put_config_dicts(configure_oc(recv_iface, priority1=128,
+ profile=profile, client_only=True,
+ ip="192.168.101.2"))
+
+ with test.step("Wait for time receiver to reach time-receiver state"):
+ until(lambda: ptp.is_time_receiver(receiver), attempts=60)
+
+ with test.step("Verify time receiver steps-removed equals 2 (one BC hop)"):
+ until(lambda: ptp.steps_removed(receiver) == 2, attempts=30)
+
+ with test.step("Wait for time receiver offset to converge"):
+ until(lambda: ptp.has_converged(receiver, threshold_ns), attempts=120)
+
+ test.succeed()
diff --git a/test/case/ptp/boundary_clock/test.yaml b/test/case/ptp/boundary_clock/test.yaml
new file mode 100644
index 000000000..5277fe19d
--- /dev/null
+++ b/test/case/ptp/boundary_clock/test.yaml
@@ -0,0 +1,11 @@
+---
+- settings:
+ test-spec: .adoc
+
+- name: PTP boundary clock (IEEE 1588)
+ case: ieee1588.py
+ opts: ["--profile", "ieee1588"]
+
+- name: PTP boundary clock (IEEE 802.1AS)
+ case: ieee802dot1as.py
+ opts: ["--profile", "ieee802-dot1as"]
diff --git a/test/case/ptp/boundary_clock/topology.dot b/test/case/ptp/boundary_clock/topology.dot
new file mode 100644
index 000000000..66a895d14
--- /dev/null
+++ b/test/case/ptp/boundary_clock/topology.dot
@@ -0,0 +1,42 @@
+graph "ptp-boundary-clock" {
+ layout="neato";
+ overlap="false";
+ esep="+22";
+
+ node [shape=record, fontname="DejaVu Sans Mono, Book"];
+ edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
+
+ host [
+ label="{ mgmt1 | \n\nhost\n\n | mgmt2 | mgmt3 }",
+ pos="0,15!",
+ requires="controller",
+ ];
+
+ gm [
+ label="{ mgmt | data } | { gm\npriority1=1 }",
+ pos="2,15.5!",
+ fontsize=12,
+ requires="infix",
+ ];
+
+ bc [
+ label="{ uplink | mgmt | dnlink } | { bc\npriority1=64 }",
+ pos="2,15!",
+ fontsize=12,
+ requires="infix",
+ ];
+
+ receiver [
+ label="{ data | mgmt } | { receiver\npriority1=128 }",
+ pos="2,14.5!",
+ fontsize=12,
+ requires="infix",
+ ];
+
+ host:mgmt1 -- gm:mgmt [requires="mgmt", color="lightgray"]
+ host:mgmt2 -- bc:mgmt [requires="mgmt", color="lightgray"]
+ host:mgmt3 -- receiver:mgmt [requires="mgmt", color="lightgray"]
+
+ gm:data -- bc:uplink [label="192.168.102.0/30 ", dir="both"]
+ bc:dnlink -- receiver:data [label="192.168.103.0/30 ", dir="both"]
+}
diff --git a/test/case/ptp/boundary_clock/topology.svg b/test/case/ptp/boundary_clock/topology.svg
new file mode 100644
index 000000000..411c29ae0
--- /dev/null
+++ b/test/case/ptp/boundary_clock/topology.svg
@@ -0,0 +1,90 @@
+
+
+
+
+
diff --git a/test/case/ptp/port_recovery/Readme.adoc b/test/case/ptp/port_recovery/Readme.adoc
new file mode 120000
index 000000000..ae32c8412
--- /dev/null
+++ b/test/case/ptp/port_recovery/Readme.adoc
@@ -0,0 +1 @@
+test.adoc
\ No newline at end of file
diff --git a/test/case/ptp/port_recovery/test.adoc b/test/case/ptp/port_recovery/test.adoc
new file mode 100644
index 000000000..e58d2f0c2
--- /dev/null
+++ b/test/case/ptp/port_recovery/test.adoc
@@ -0,0 +1,32 @@
+=== PTP port fault recovery
+
+ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ptp/port_recovery]
+
+==== Description
+
+Verify that the PTP port state machine correctly detects a link fault
+and recovers to time-receiver state once the link is restored.
+
+Two Ordinary Clocks are connected back-to-back. Once the time receiver
+has converged, the grandmaster's data interface is disabled. The time
+receiver must leave time-receiver state within a short timeout. When
+the interface is re-enabled, the time receiver must return to
+time-receiver state and its offset must converge again to within the
+configured threshold.
+
+==== Topology
+
+image::topology.svg[PTP port fault recovery topology, align=center, scaledwidth=75%]
+
+==== Sequence
+
+. Set up topology and attach to DUTs
+. Configure grandmaster (priority1=1) and time receiver (client-only)
+. Wait for initial convergence
+. Disable grandmaster data interface to trigger fault
+. Verify time receiver leaves time-receiver state
+. Re-enable grandmaster data interface
+. Wait for time receiver to return to time-receiver state after recovery
+. Wait for offset to re-converge
+
+
diff --git a/test/case/ptp/port_recovery/test.py b/test/case/ptp/port_recovery/test.py
new file mode 100755
index 000000000..740d907ff
--- /dev/null
+++ b/test/case/ptp/port_recovery/test.py
@@ -0,0 +1,118 @@
+#!/usr/bin/env python3
+"""PTP port fault recovery
+
+Verify that the PTP port state machine correctly detects a link fault
+and recovers to time-receiver state once the link is restored.
+
+Two Ordinary Clocks are connected back-to-back. Once the time receiver
+has converged, the grandmaster's data interface is disabled. The time
+receiver must leave time-receiver state within a short timeout. When
+the interface is re-enabled, the time receiver must return to
+time-receiver state and its offset must converge again to within the
+configured threshold.
+"""
+
+import infamy
+import infamy.ptp as ptp
+from infamy import until
+
+
+def configure_oc(iface, ip, priority1, client_only, dm="e2e"):
+ return {
+ "ietf-interfaces": {
+ "interfaces": {
+ "interface": [{
+ "name": iface,
+ "enabled": True,
+ "ipv4": {
+ "address": [{"ip": ip, "prefix-length": 30}]
+ }
+ }]
+ }
+ },
+ "ieee1588-ptp-tt": {
+ "ptp": {
+ "instances": {
+ "instance": [{
+ "instance-index": 0,
+ "default-ds": {
+ "instance-type": "oc",
+ "domain-number": 0,
+ "priority1": priority1,
+ "priority2": 128,
+ "infix-ptp:profile": "ieee1588",
+ "time-receiver-only": client_only,
+ },
+ "ports": {
+ "port": [{
+ "port-index": 1,
+ "underlying-interface": iface,
+ "port-ds": {
+ "delay-mechanism": dm,
+ "log-announce-interval": -2,
+ "announce-receipt-timeout": 2,
+ "log-sync-interval": -2,
+ }
+ }]
+ }
+ }]
+ }
+ }
+ }
+ }
+
+
+def set_iface_enabled(target, iface, enabled):
+ target.put_config_dict("ietf-interfaces", {
+ "interfaces": {
+ "interface": [{
+ "name": iface,
+ "enabled": enabled,
+ }]
+ }
+ })
+
+
+class ArgumentParser(infamy.ArgumentParser):
+ def __init__(self):
+ super().__init__()
+ self.args.add_argument("--threshold-ns", type=int, default=None)
+
+
+with infamy.Test() as test:
+ with test.step("Set up topology and attach to DUTs"):
+ arg = ArgumentParser()
+ env = infamy.Env(args=arg)
+ gm = env.attach("gm", "mgmt")
+ receiver = env.attach("receiver", "mgmt")
+
+ _, gm_iface = env.ltop.xlate("gm", "data")
+ _, receiver_iface = env.ltop.xlate("receiver", "data")
+ threshold_ns = env.args.threshold_ns or ptp.default_threshold(env, "receiver")
+
+ with test.step("Configure grandmaster (priority1=1) and time receiver (client-only)"):
+ gm.put_config_dicts(configure_oc(gm_iface, "192.168.100.1",
+ priority1=1, client_only=False))
+ receiver.put_config_dicts(configure_oc(receiver_iface, "192.168.100.2",
+ priority1=128, client_only=True))
+
+ with test.step("Wait for initial convergence"):
+ until(lambda: ptp.is_time_receiver(receiver) and ptp.has_converged(receiver, threshold_ns),
+ attempts=120)
+
+ with test.step("Disable grandmaster data interface to trigger fault"):
+ set_iface_enabled(gm, gm_iface, False)
+
+ with test.step("Verify time receiver leaves time-receiver state"):
+ until(lambda: not ptp.is_time_receiver(receiver), attempts=30)
+
+ with test.step("Re-enable grandmaster data interface"):
+ set_iface_enabled(gm, gm_iface, True)
+
+ with test.step("Wait for time receiver to return to time-receiver state after recovery"):
+ until(lambda: ptp.is_time_receiver(receiver), attempts=120)
+
+ with test.step("Wait for offset to re-converge"):
+ until(lambda: ptp.has_converged(receiver, threshold_ns), attempts=120)
+
+ test.succeed()
diff --git a/test/case/ptp/port_recovery/topology.dot b/test/case/ptp/port_recovery/topology.dot
new file mode 100644
index 000000000..4544ca5ad
--- /dev/null
+++ b/test/case/ptp/port_recovery/topology.dot
@@ -0,0 +1,33 @@
+graph "ptp-port-recovery" {
+ layout="neato";
+ overlap="false";
+ esep="+22";
+
+ node [shape=record, fontname="DejaVu Sans Mono, Book"];
+ edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
+
+ host [
+ label="{ mgmt1 | \n\nhost\n\n\n | mgmt2 }",
+ pos="0,15!",
+ requires="controller",
+ ];
+
+ gm [
+ label="{ mgmt | data } | { gm\npriority1=1 }",
+ pos="2,15.25!",
+ fontsize=12,
+ requires="infix",
+ ];
+
+ receiver [
+ label="{ data | mgmt } | { receiver\npriority1=128 }",
+ pos="2,14.75!",
+ fontsize=12,
+ requires="infix",
+ ];
+
+ host:mgmt1 -- gm:mgmt [requires="mgmt", color="lightgray"]
+ host:mgmt2 -- receiver:mgmt [requires="mgmt", color="lightgray"]
+
+ gm:data -- receiver:data [label="\n\n192.168.106.0/30 ", dir="both"]
+}
diff --git a/test/case/ptp/port_recovery/topology.svg b/test/case/ptp/port_recovery/topology.svg
new file mode 100644
index 000000000..8c5b1311d
--- /dev/null
+++ b/test/case/ptp/port_recovery/topology.svg
@@ -0,0 +1,62 @@
+
+
+
+
+
diff --git a/test/case/ptp/servo/Readme.adoc b/test/case/ptp/servo/Readme.adoc
new file mode 120000
index 000000000..ae32c8412
--- /dev/null
+++ b/test/case/ptp/servo/Readme.adoc
@@ -0,0 +1 @@
+test.adoc
\ No newline at end of file
diff --git a/test/case/ptp/servo/test.adoc b/test/case/ptp/servo/test.adoc
new file mode 100644
index 000000000..2e297f60e
--- /dev/null
+++ b/test/case/ptp/servo/test.adoc
@@ -0,0 +1,44 @@
+=== PTP servo step-threshold
+
+ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ptp/servo]
+
+==== Description
+
+Verify that configuring a non-zero `step-threshold` allows the clock servo
+to correct a large time offset by stepping rather than slewing.
+
+Two Ordinary Clocks are connected back-to-back using the IEEE 1588 profile.
+After initial convergence the receiver is reconfigured with
+`step-threshold=1.0 s` and ptp4l restarts. Because the offset at restart
+is near zero, `first_step_threshold` (ptp4l's per-startup step gate) does
+not trigger, so the restart itself is convergence-neutral.
+
+Once the receiver has re-locked, the grandmaster clock is stepped forward by
+10 seconds using phc_ctl (hardware PHC) or the system clock (software
+timestamping). The 10-second offset exceeds the 1-second step-threshold, so
+the servo steps the clock immediately and the receiver converges within a
+few seconds.
+
+Note: a negative test (verify that offset=10 s does *not* converge without
+step-threshold) is not included here because it is unreliable across
+platforms. On physical hardware the kernel caps clock frequency adjustment
+at ~500 ppm, making a 10-second slew take ~5.5 hours; on virtual clocks
+(QEMU) no such limit applies and the servo can slew the offset away in
+seconds. Full negative coverage requires exposing first_step_threshold and
+max_frequency in the YANG model — see TODO.org.
+
+==== Topology
+
+image::topology.svg[PTP servo step-threshold topology, align=center, scaledwidth=75%]
+
+==== Sequence
+
+. Set up topology and attach to DUTs
+. Configure grandmaster (OC, IEEE 1588, priority1=1) and time receiver
+. Wait for grandmaster and time receiver ports to reach active states
+. Wait for initial convergence
+. Reconfigure receiver with step-threshold=1.0 s
+. Inject {STEP_SEC}-second offset on grandmaster clock
+. Verify receiver converges by stepping (step-threshold=1.0 s)
+
+
diff --git a/test/case/ptp/servo/test.py b/test/case/ptp/servo/test.py
new file mode 100755
index 000000000..27576fb2d
--- /dev/null
+++ b/test/case/ptp/servo/test.py
@@ -0,0 +1,154 @@
+#!/usr/bin/env python3
+"""PTP servo step-threshold
+
+Verify that configuring a non-zero `step-threshold` allows the clock servo
+to correct a large time offset by stepping rather than slewing.
+
+Two Ordinary Clocks are connected back-to-back using the IEEE 1588 profile.
+After initial convergence the receiver is reconfigured with
+`step-threshold=1.0 s` and ptp4l restarts. Because the offset at restart
+is near zero, `first_step_threshold` (ptp4l's per-startup step gate) does
+not trigger, so the restart itself is convergence-neutral.
+
+Once the receiver has re-locked, the grandmaster clock is stepped forward by
+10 seconds using phc_ctl (hardware PHC) or the system clock (software
+timestamping). The 10-second offset exceeds the 1-second step-threshold, so
+the servo steps the clock immediately and the receiver converges within a
+few seconds.
+
+Note: a negative test (verify that offset=10 s does *not* converge without
+step-threshold) is not included here because it is unreliable across
+platforms. On physical hardware the kernel caps clock frequency adjustment
+at ~500 ppm, making a 10-second slew take ~5.5 hours; on virtual clocks
+(QEMU) no such limit applies and the servo can slew the offset away in
+seconds. Full negative coverage requires exposing first_step_threshold and
+max_frequency in the YANG model — see TODO.org.
+"""
+
+import infamy
+import infamy.ptp as ptp
+from infamy import until
+from infamy.util import parallel
+
+STEP_SEC = 10
+
+
+class ArgumentParser(infamy.ArgumentParser):
+ def __init__(self):
+ super().__init__()
+ self.args.add_argument("--threshold-ns", type=int, default=None)
+
+
+def configure_oc(iface, priority1, client, ip, step_threshold=None):
+ config = {
+ "ieee1588-ptp-tt": {
+ "ptp": {
+ "instances": {
+ "instance": [{
+ "instance-index": 0,
+ "default-ds": {
+ "instance-type": "oc",
+ "domain-number": 0,
+ "priority1": priority1,
+ "priority2": 128,
+ "infix-ptp:profile": "ieee1588",
+ "time-receiver-only": client,
+ },
+ "ports": {
+ "port": [{
+ "port-index": 1,
+ "underlying-interface": iface,
+ }]
+ }
+ }]
+ }
+ }
+ }
+ }
+
+ iface_cfg = {"name": iface, "enabled": True,
+ "ipv4": {"address": [{"ip": ip, "prefix-length": 30}]}}
+ config["ietf-interfaces"] = {"interfaces": {"interface": [iface_cfg]}}
+
+ port_ds = {
+ "log-announce-interval": -2,
+ "announce-receipt-timeout": 2,
+ "log-sync-interval": -2,
+ "delay-mechanism": "e2e",
+ }
+ inst = config["ieee1588-ptp-tt"]["ptp"]["instances"]["instance"][0]
+ inst["ports"]["port"][0]["port-ds"] = port_ds
+
+ if step_threshold is not None:
+ inst["infix-ptp:servo"] = {"step-threshold": str(step_threshold)}
+
+ return config
+
+
+def step_clock(ssh, iface, seconds):
+ """Step the PTP clock on the node owning iface forward by seconds.
+
+ Uses phc_ctl on the hardware PHC when available (sysfs discovery);
+ falls back to date(1) for software-timestamping nodes where ptp4l
+ disciplines CLOCK_REALTIME directly.
+ """
+ rc = ssh.runsh(f"ls /sys/class/net/{iface}/device/ptp/ 2>/dev/null")
+ phc = rc.stdout.strip().split()[0] if rc.returncode == 0 and rc.stdout.strip() else None
+ if phc:
+ ssh.runsh(f"phc_ctl /dev/{phc} adj {float(seconds)}")
+ else:
+ ssh.runsh(f"date -s @$(( $(date +%s) + {seconds} ))")
+
+
+with infamy.Test() as test:
+ with test.step("Set up topology and attach to DUTs"):
+ arg = ArgumentParser()
+ env = infamy.Env(args=arg)
+ gm = env.attach("gm", "mgmt")
+ receiver = env.attach("receiver", "mgmt")
+
+ _, gm_iface = env.ltop.xlate("gm", "data")
+ _, receiver_iface = env.ltop.xlate("receiver", "data")
+ threshold_ns = env.args.threshold_ns or ptp.default_threshold(env, "receiver")
+
+ with test.step("Configure grandmaster (OC, IEEE 1588, priority1=1) and time receiver"):
+ gm.put_config_dicts(configure_oc(gm_iface, priority1=1,
+ client=False, ip="192.168.100.1"))
+ receiver.put_config_dicts(configure_oc(receiver_iface, priority1=128,
+ client=True, ip="192.168.100.2"))
+
+ with test.step("Wait for grandmaster and time receiver ports to reach active states"):
+ def gm_ready():
+ if not ptp.is_time_transmitter(gm):
+ # print(f"{ptp.port_state_dbg(gm)}")
+ return False
+ return True
+
+ def receiver_ready():
+ if not ptp.is_time_receiver(receiver):
+ # print(f"{ptp.port_state_dbg(receiver)}")
+ return False
+ return True
+
+ parallel(lambda: until(gm_ready, attempts=60),
+ lambda: until(receiver_ready, attempts=60))
+
+ with test.step("Wait for initial convergence"):
+ until(lambda: ptp.has_converged(receiver, threshold_ns), attempts=120)
+
+ with test.step("Reconfigure receiver with step-threshold=1.0 s"):
+ # ptp4l restarts while the offset is near zero so first_step_threshold
+ # does not trigger; the restart itself is convergence-neutral.
+ receiver.put_config_dicts(configure_oc(receiver_iface, priority1=128,
+ client=True, ip="192.168.100.2",
+ step_threshold=1.0))
+ until(lambda: ptp.has_converged(receiver, threshold_ns), attempts=60)
+
+ with test.step(f"Inject {STEP_SEC}-second offset on grandmaster clock"):
+ gmssh = env.attach("gm", "mgmt", "ssh")
+ step_clock(gmssh, gm_iface, STEP_SEC)
+
+ with test.step("Verify receiver converges by stepping (step-threshold=1.0 s)"):
+ until(lambda: ptp.has_converged(receiver, threshold_ns), attempts=60)
+
+ test.succeed()
diff --git a/test/case/ptp/servo/topology.dot b/test/case/ptp/servo/topology.dot
new file mode 100644
index 000000000..cb32dd36a
--- /dev/null
+++ b/test/case/ptp/servo/topology.dot
@@ -0,0 +1,33 @@
+graph "ptp-servo" {
+ layout="neato";
+ overlap="false";
+ esep="+22";
+
+ node [shape=record, fontname="DejaVu Sans Mono, Book"];
+ edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
+
+ host [
+ label="{ mgmt1 | \n\nhost\n\n\n | mgmt2 }",
+ pos="0,15!",
+ requires="controller",
+ ];
+
+ gm [
+ label="{ mgmt | data } | { gm\npriority1=1 }",
+ pos="2,15.25!",
+ fontsize=12,
+ requires="infix",
+ ];
+
+ receiver [
+ label="{ data | mgmt } | { receiver\npriority1=128 }",
+ pos="2,14.75!",
+ fontsize=12,
+ requires="infix",
+ ];
+
+ host:mgmt1 -- gm:mgmt [requires="mgmt", color="lightgray"]
+ host:mgmt2 -- receiver:mgmt [requires="mgmt", color="lightgray"]
+
+ gm:data -- receiver:data [label="\n\n192.168.100.0/30 ", dir="both"]
+}
diff --git a/test/case/ptp/servo/topology.svg b/test/case/ptp/servo/topology.svg
new file mode 100644
index 000000000..3a9164474
--- /dev/null
+++ b/test/case/ptp/servo/topology.svg
@@ -0,0 +1,62 @@
+
+
+
+
+
diff --git a/test/case/ptp/transparent_clock/Readme.adoc b/test/case/ptp/transparent_clock/Readme.adoc
new file mode 100644
index 000000000..164bfb7f6
--- /dev/null
+++ b/test/case/ptp/transparent_clock/Readme.adoc
@@ -0,0 +1,10 @@
+include::e2e.adoc[]
+
+<<<
+
+include::p2p.adoc[]
+
+<<<
+
+include::ieee802dot1as.adoc[]
+
diff --git a/test/case/ptp/transparent_clock/e2e.adoc b/test/case/ptp/transparent_clock/e2e.adoc
new file mode 100644
index 000000000..5346f9070
--- /dev/null
+++ b/test/case/ptp/transparent_clock/e2e.adoc
@@ -0,0 +1,43 @@
+=== PTP transparent clock (E2E)
+
+ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ptp/transparent_clock]
+
+==== Description
+
+Verify that an E2E or P2P Transparent Clock (TC) passes timing transparently
+through a hardware switch without adding a boundary-clock hop, and that the
+downstream time receiver converges to the grandmaster's time.
+
+Three nodes are connected in a chain: a grandmaster Ordinary Clock
+(`priority1=1`), a Transparent Clock, and a time-receiver Ordinary Clock
+(`priority1=128`).
+
+The TC updates the correction field in each Sync and Delay_Req message to
+account for its own residence time. Because a TC is transparent, the time
+receiver's `steps-removed` counter must equal 1 — unlike a Boundary Clock,
+which would give 2. A TC passes ANNOUNCE messages unchanged (`stepsRemoved=0`
+from the GM), and the time receiver adds 1 when it stores the value in
+`currentDS`, giving a total of 1. A BC increments `stepsRemoved` to 1 before
+forwarding, and the receiver adds 1 more, giving 2. The time receiver's offset must converge within the configured threshold
+(default is tighter when the topology provides hardware timestamping links).
+
+The delay mechanism (E2E or P2P) is controlled by the test suite for
+IEEE 1588 runs. When the profile is IEEE 802.1AS the delay mechanism is
+always P2P (mandated by the standard) and Layer 2 transport is used.
+
+==== Topology
+
+image::topology.svg[PTP transparent clock (E2E) topology, align=center, scaledwidth=75%]
+
+==== Sequence
+
+. Set up topology and attach to DUTs
+. Configure grandmaster (OC, priority1=1, {dm})
+. Configure transparent clock ({dm}-tc, {profile})
+. Configure time receiver (OC, priority1=128, client-only)
+. Wait for grandmaster port to become time-transmitter
+. Wait for time receiver to reach time-receiver state
+. Verify time receiver steps-removed equals 1
+. Wait for time receiver offset to converge
+
+
diff --git a/test/case/ptp/transparent_clock/e2e.py b/test/case/ptp/transparent_clock/e2e.py
new file mode 120000
index 000000000..946566431
--- /dev/null
+++ b/test/case/ptp/transparent_clock/e2e.py
@@ -0,0 +1 @@
+test.py
\ No newline at end of file
diff --git a/test/case/ptp/transparent_clock/ieee802dot1as.adoc b/test/case/ptp/transparent_clock/ieee802dot1as.adoc
new file mode 100644
index 000000000..68d64b802
--- /dev/null
+++ b/test/case/ptp/transparent_clock/ieee802dot1as.adoc
@@ -0,0 +1,43 @@
+=== PTP transparent clock (IEEE 802.1AS)
+
+ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ptp/transparent_clock]
+
+==== Description
+
+Verify that an E2E or P2P Transparent Clock (TC) passes timing transparently
+through a hardware switch without adding a boundary-clock hop, and that the
+downstream time receiver converges to the grandmaster's time.
+
+Three nodes are connected in a chain: a grandmaster Ordinary Clock
+(`priority1=1`), a Transparent Clock, and a time-receiver Ordinary Clock
+(`priority1=128`).
+
+The TC updates the correction field in each Sync and Delay_Req message to
+account for its own residence time. Because a TC is transparent, the time
+receiver's `steps-removed` counter must equal 1 — unlike a Boundary Clock,
+which would give 2. A TC passes ANNOUNCE messages unchanged (`stepsRemoved=0`
+from the GM), and the time receiver adds 1 when it stores the value in
+`currentDS`, giving a total of 1. A BC increments `stepsRemoved` to 1 before
+forwarding, and the receiver adds 1 more, giving 2. The time receiver's offset must converge within the configured threshold
+(default is tighter when the topology provides hardware timestamping links).
+
+The delay mechanism (E2E or P2P) is controlled by the test suite for
+IEEE 1588 runs. When the profile is IEEE 802.1AS the delay mechanism is
+always P2P (mandated by the standard) and Layer 2 transport is used.
+
+==== Topology
+
+image::topology.svg[PTP transparent clock (IEEE 802.1AS) topology, align=center, scaledwidth=75%]
+
+==== Sequence
+
+. Set up topology and attach to DUTs
+. Configure grandmaster (OC, priority1=1, {dm})
+. Configure transparent clock ({dm}-tc, ieee802-dot1as)
+. Configure time receiver (OC, priority1=128, client-only)
+. Wait for grandmaster port to become time-transmitter
+. Wait for time receiver to reach time-receiver state
+. Verify time receiver steps-removed equals 1
+. Wait for time receiver offset to converge
+
+
diff --git a/test/case/ptp/transparent_clock/ieee802dot1as.py b/test/case/ptp/transparent_clock/ieee802dot1as.py
new file mode 120000
index 000000000..946566431
--- /dev/null
+++ b/test/case/ptp/transparent_clock/ieee802dot1as.py
@@ -0,0 +1 @@
+test.py
\ No newline at end of file
diff --git a/test/case/ptp/transparent_clock/p2p.adoc b/test/case/ptp/transparent_clock/p2p.adoc
new file mode 100644
index 000000000..cacdb4499
--- /dev/null
+++ b/test/case/ptp/transparent_clock/p2p.adoc
@@ -0,0 +1,43 @@
+=== PTP transparent clock (P2P)
+
+ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ptp/transparent_clock]
+
+==== Description
+
+Verify that an E2E or P2P Transparent Clock (TC) passes timing transparently
+through a hardware switch without adding a boundary-clock hop, and that the
+downstream time receiver converges to the grandmaster's time.
+
+Three nodes are connected in a chain: a grandmaster Ordinary Clock
+(`priority1=1`), a Transparent Clock, and a time-receiver Ordinary Clock
+(`priority1=128`).
+
+The TC updates the correction field in each Sync and Delay_Req message to
+account for its own residence time. Because a TC is transparent, the time
+receiver's `steps-removed` counter must equal 1 — unlike a Boundary Clock,
+which would give 2. A TC passes ANNOUNCE messages unchanged (`stepsRemoved=0`
+from the GM), and the time receiver adds 1 when it stores the value in
+`currentDS`, giving a total of 1. A BC increments `stepsRemoved` to 1 before
+forwarding, and the receiver adds 1 more, giving 2. The time receiver's offset must converge within the configured threshold
+(default is tighter when the topology provides hardware timestamping links).
+
+The delay mechanism (E2E or P2P) is controlled by the test suite for
+IEEE 1588 runs. When the profile is IEEE 802.1AS the delay mechanism is
+always P2P (mandated by the standard) and Layer 2 transport is used.
+
+==== Topology
+
+image::topology.svg[PTP transparent clock (P2P) topology, align=center, scaledwidth=75%]
+
+==== Sequence
+
+. Set up topology and attach to DUTs
+. Configure grandmaster (OC, priority1=1, {dm})
+. Configure transparent clock ({dm}-tc, {profile})
+. Configure time receiver (OC, priority1=128, client-only)
+. Wait for grandmaster port to become time-transmitter
+. Wait for time receiver to reach time-receiver state
+. Verify time receiver steps-removed equals 1
+. Wait for time receiver offset to converge
+
+
diff --git a/test/case/ptp/transparent_clock/p2p.py b/test/case/ptp/transparent_clock/p2p.py
new file mode 120000
index 000000000..946566431
--- /dev/null
+++ b/test/case/ptp/transparent_clock/p2p.py
@@ -0,0 +1 @@
+test.py
\ No newline at end of file
diff --git a/test/case/ptp/transparent_clock/test.adoc b/test/case/ptp/transparent_clock/test.adoc
new file mode 100644
index 000000000..a07dcf860
--- /dev/null
+++ b/test/case/ptp/transparent_clock/test.adoc
@@ -0,0 +1,46 @@
+=== PTP transparent clock (P2P)
+
+ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ptp/transparent_clock]
+
+==== Description
+
+Verify that an E2E or P2P Transparent Clock (TC) passes timing
+transparently through a hardware switch without adding a boundary-clock
+hop, and that the downstream time receiver converges to the
+grandmaster's time.
+
+Three nodes are connected in a chain: a grandmaster Ordinary Clock
+(priority1=1), a Transparent Clock with hardware timestamping, and a
+time-receiver Ordinary Clock (priority1=128).
+
+The TC updates the correction field in each Sync and Delay_Req message
+to account for its own residence time. Because a TC is transparent,
+the time receiver's steps-removed counter must equal 1 — unlike a
+Boundary Clock, which would give 2. A TC passes ANNOUNCE messages
+unchanged (stepsRemoved=0 from the GM), and the time receiver adds 1
+when it stores the value in currentDS, giving a total of 1. A BC
+increments stepsRemoved to 1 before forwarding, and the receiver adds
+1 more, giving 2. The time receiver's offset must converge within the
+tight threshold appropriate for hardware timestamping.
+
+The delay mechanism (E2E or P2P) is injected via the --delay-mechanism
+argument from the test suite YAML, allowing both variants to run from
+the same test script. The TC node requires hardware PTP timestamping
+support (capability: ptp-hwts).
+
+==== Topology
+
+image::topology.svg[PTP transparent clock (P2P) topology, align=center, scaledwidth=75%]
+
+==== Sequence
+
+. Set up topology and attach to DUTs
+. Configure grandmaster (OC, priority1=1, {dm})
+. Configure transparent clock ({dm.upper()}-TC)
+. Configure time receiver (OC, priority1=128, client-only)
+. Wait for grandmaster port to become time-transmitter
+. Wait for time receiver to reach time-receiver state
+. Verify time receiver steps-removed equals 1 (TC adds no boundary-clock hop)
+. Wait for time receiver offset to converge
+
+
diff --git a/test/case/ptp/transparent_clock/test.py b/test/case/ptp/transparent_clock/test.py
new file mode 100755
index 000000000..f309e89db
--- /dev/null
+++ b/test/case/ptp/transparent_clock/test.py
@@ -0,0 +1,175 @@
+#!/usr/bin/env python3
+"""PTP transparent clock
+
+Verify that an E2E or P2P Transparent Clock (TC) passes timing transparently
+through a hardware switch without adding a boundary-clock hop, and that the
+downstream time receiver converges to the grandmaster's time.
+
+Three nodes are connected in a chain: a grandmaster Ordinary Clock
+(`priority1=1`), a Transparent Clock, and a time-receiver Ordinary Clock
+(`priority1=128`).
+
+The TC updates the correction field in each Sync and Delay_Req message to
+account for its own residence time. Because a TC is transparent, the time
+receiver's `steps-removed` counter must equal 1 — unlike a Boundary Clock,
+which would give 2. A TC passes ANNOUNCE messages unchanged (`stepsRemoved=0`
+from the GM), and the time receiver adds 1 when it stores the value in
+`currentDS`, giving a total of 1. A BC increments `stepsRemoved` to 1 before
+forwarding, and the receiver adds 1 more, giving 2. The time receiver's offset must converge within the configured threshold
+(default is tighter when the topology provides hardware timestamping links).
+
+The delay mechanism (E2E or P2P) is controlled by the test suite for
+IEEE 1588 runs. When the profile is IEEE 802.1AS the delay mechanism is
+always P2P (mandated by the standard) and Layer 2 transport is used.
+"""
+
+import infamy
+import infamy.ptp as ptp
+from infamy import until
+
+
+class ArgumentParser(infamy.ArgumentParser):
+ def __init__(self):
+ super().__init__()
+ self.args.add_argument("--profile", default="ieee1588",
+ choices=["ieee1588", "ieee802-dot1as"])
+ self.args.add_argument("--delay-mechanism", default="e2e",
+ choices=["e2e", "p2p"])
+ self.args.add_argument("--threshold-ns", type=int, default=None)
+
+
+def configure_oc(iface, priority1, profile, client_only=False, ip=None, dm="e2e"):
+ iface_cfg = {"name": iface, "enabled": True}
+ if profile == "ieee1588":
+ iface_cfg["ipv4"] = {"address": [{"ip": ip, "prefix-length": 30}]}
+
+ port_ds = {
+ "log-announce-interval": -2,
+ "announce-receipt-timeout": 2,
+ "log-sync-interval": -2,
+ }
+ if profile == "ieee1588":
+ port_ds["delay-mechanism"] = dm
+
+ return {
+ "ietf-interfaces": {
+ "interfaces": {"interface": [iface_cfg]}
+ },
+ "ieee1588-ptp-tt": {
+ "ptp": {
+ "instances": {
+ "instance": [{
+ "instance-index": 0,
+ "default-ds": {
+ "instance-type": "oc",
+ "domain-number": 0,
+ "priority1": priority1,
+ "priority2": 128,
+ "infix-ptp:profile": profile,
+ "time-receiver-only": client_only,
+ },
+ "ports": {
+ "port": [{
+ "port-index": 1,
+ "underlying-interface": iface,
+ "port-ds": port_ds,
+ }]
+ }
+ }]
+ }
+ }
+ }
+ }
+
+
+def configure_tc(uplink_iface, dnlink_iface, profile, dm="e2e",
+ uplink_ip=None, dnlink_ip=None):
+ if profile == "ieee802-dot1as":
+ instance_type = "p2p-tc"
+ else:
+ instance_type = "p2p-tc" if dm == "p2p" else "e2e-tc"
+
+ ifaces = [{"name": uplink_iface, "enabled": True},
+ {"name": dnlink_iface, "enabled": True}]
+ if profile == "ieee1588":
+ ifaces[0]["ipv4"] = {"address": [{"ip": uplink_ip, "prefix-length": 30}]}
+ ifaces[1]["ipv4"] = {"address": [{"ip": dnlink_ip, "prefix-length": 30}]}
+
+ return {
+ "ietf-interfaces": {
+ "interfaces": {"interface": ifaces}
+ },
+ "ieee1588-ptp-tt": {
+ "ptp": {
+ "instances": {
+ "instance": [{
+ "instance-index": 0,
+ "default-ds": {
+ "instance-type": instance_type,
+ "domain-number": 0,
+ "infix-ptp:profile": profile,
+ },
+ "ports": {
+ "port": [
+ {
+ "port-index": 1,
+ "underlying-interface": uplink_iface,
+ "port-ds": {"log-sync-interval": -2},
+ },
+ {
+ "port-index": 2,
+ "underlying-interface": dnlink_iface,
+ "port-ds": {"log-sync-interval": -2},
+ }
+ ]
+ }
+ }]
+ }
+ }
+ }
+ }
+
+
+with infamy.Test() as test:
+ with test.step("Set up topology and attach to DUTs"):
+ arg = ArgumentParser()
+ env = infamy.Env(args=arg)
+ profile = env.args.profile
+ dm = "p2p" if profile == "ieee802-dot1as" else env.args.delay_mechanism
+ gm = env.attach("gm", "mgmt")
+ tc = env.attach("tc", "mgmt")
+ receiver = env.attach("receiver", "mgmt")
+
+ gm_iface = gm["data"]
+ tc_uplink = tc["uplink"]
+ tc_dnlink = tc["dnlink"]
+ receiver_iface = receiver["data"]
+ threshold_ns = env.args.threshold_ns or ptp.default_threshold(env, "tc")
+
+ with test.step(f"Configure grandmaster (OC, priority1=1, {dm})"):
+ gm.put_config_dicts(configure_oc(gm_iface, priority1=1,
+ profile=profile, ip="192.168.100.1", dm=dm))
+
+ with test.step(f"Configure transparent clock ({dm}-tc, {profile})"):
+ tc.put_config_dicts(configure_tc(tc_uplink, tc_dnlink, profile=profile, dm=dm,
+ uplink_ip="192.168.100.2",
+ dnlink_ip="192.168.101.1"))
+
+ with test.step("Configure time receiver (OC, priority1=128, client-only)"):
+ receiver.put_config_dicts(configure_oc(receiver_iface, priority1=128,
+ profile=profile, client_only=True,
+ ip="192.168.101.2", dm=dm))
+
+ with test.step("Wait for grandmaster port to become time-transmitter"):
+ until(lambda: ptp.is_time_transmitter(gm), attempts=60)
+
+ with test.step("Wait for time receiver to reach time-receiver state"):
+ until(lambda: ptp.is_time_receiver(receiver), attempts=60)
+
+ with test.step("Verify time receiver steps-removed equals 1"):
+ until(lambda: ptp.steps_removed(receiver) == 1, attempts=60)
+
+ with test.step("Wait for time receiver offset to converge"):
+ until(lambda: ptp.has_converged(receiver, threshold_ns), attempts=180)
+
+ test.succeed()
diff --git a/test/case/ptp/transparent_clock/test.yaml b/test/case/ptp/transparent_clock/test.yaml
new file mode 100644
index 000000000..4bbc87daf
--- /dev/null
+++ b/test/case/ptp/transparent_clock/test.yaml
@@ -0,0 +1,15 @@
+---
+- settings:
+ test-spec: .adoc
+
+- name: PTP transparent clock (E2E)
+ case: e2e.py
+ opts: ["--delay-mechanism", "e2e"]
+
+- name: PTP transparent clock (P2P)
+ case: p2p.py
+ opts: ["--delay-mechanism", "p2p"]
+
+- name: PTP transparent clock (IEEE 802.1AS)
+ case: ieee802dot1as.py
+ opts: ["--profile", "ieee802-dot1as"]
diff --git a/test/case/ptp/transparent_clock/topology.dot b/test/case/ptp/transparent_clock/topology.dot
new file mode 100644
index 000000000..3009cf821
--- /dev/null
+++ b/test/case/ptp/transparent_clock/topology.dot
@@ -0,0 +1,42 @@
+graph "ptp-transparent-clock" {
+ layout="neato";
+ overlap="false";
+ esep="+22";
+
+ node [shape=record, fontname="DejaVu Sans Mono, Book"];
+ edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
+
+ host [
+ label="{ mgmt1 | \n\nhost\n\n | mgmt2 | mgmt3 }",
+ pos="0,15!",
+ requires="controller",
+ ];
+
+ gm [
+ label="{ mgmt | data } | { gm\npriority1=1 }",
+ pos="2,15.5!",
+ fontsize=12,
+ requires="infix",
+ ];
+
+ tc [
+ label="{ uplink | mgmt | dnlink } | { tc\nE2E-TC or P2P-TC }",
+ pos="2,15!",
+ fontsize=12,
+ requires="infix",
+ ];
+
+ receiver [
+ label="{ data | mgmt } | { receiver\npriority1=128 }",
+ pos="2,14.5!",
+ fontsize=12,
+ requires="infix",
+ ];
+
+ host:mgmt1 -- gm:mgmt [requires="mgmt", color="lightgray"]
+ host:mgmt2 -- tc:mgmt [requires="mgmt", color="lightgray"]
+ host:mgmt3 -- receiver:mgmt [requires="mgmt", color="lightgray"]
+
+ gm:data -- tc:uplink [label="192.168.104.0/30 ", dir="both"]
+ tc:dnlink -- receiver:data [label="192.168.105.0/30 ", dir="both"]
+}
diff --git a/test/case/ptp/transparent_clock/topology.svg b/test/case/ptp/transparent_clock/topology.svg
new file mode 100644
index 000000000..cfa0ceb65
--- /dev/null
+++ b/test/case/ptp/transparent_clock/topology.svg
@@ -0,0 +1,90 @@
+
+
+
+
+
diff --git a/test/case/routing/ospf_default_route_advertise/test.adoc b/test/case/routing/ospf_default_route_advertise/test.adoc
index c3d24e59e..d0aa18992 100644
--- a/test/case/routing/ospf_default_route_advertise/test.adoc
+++ b/test/case/routing/ospf_default_route_advertise/test.adoc
@@ -1,4 +1,4 @@
-=== OSPF Default route advertise
+=== OSPF Default Route Advertise
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/routing/ospf_default_route_advertise]
@@ -33,7 +33,7 @@ unless _always_ is set for _default-route-advertising_.
==== Topology
-image::topology.svg[OSPF Default route advertise topology, align=center, scaledwidth=75%]
+image::topology.svg[OSPF Default Route Advertise topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/routing/ospf_multiarea/test.adoc b/test/case/routing/ospf_multiarea/test.adoc
index 219191523..c6e80935e 100644
--- a/test/case/routing/ospf_multiarea/test.adoc
+++ b/test/case/routing/ospf_multiarea/test.adoc
@@ -1,4 +1,4 @@
-=== OSPF with multiple areas
+=== OSPF with Multiple Areas
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/routing/ospf_multiarea]
@@ -28,7 +28,7 @@ explicit router-id.
==== Topology
-image::topology.svg[OSPF with multiple areas topology, align=center, scaledwidth=75%]
+image::topology.svg[OSPF with Multiple Areas topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/routing/ospf_unnumbered_interface/test.adoc b/test/case/routing/ospf_unnumbered_interface/test.adoc
index d066026a3..c6014db35 100644
--- a/test/case/routing/ospf_unnumbered_interface/test.adoc
+++ b/test/case/routing/ospf_unnumbered_interface/test.adoc
@@ -1,4 +1,4 @@
-=== OSPF unnumbered interfaces
+=== OSPF Unnumbered Interfaces
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/routing/ospf_unnumbered_interface]
@@ -13,7 +13,7 @@ configuration and passive to function
==== Topology
-image::topology.svg[OSPF unnumbered interfaces topology, align=center, scaledwidth=75%]
+image::topology.svg[OSPF Unnumbered Interfaces topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/routing/static_routing/test.adoc b/test/case/routing/static_routing/test.adoc
index 70782fc72..b16073d60 100644
--- a/test/case/routing/static_routing/test.adoc
+++ b/test/case/routing/static_routing/test.adoc
@@ -1,4 +1,4 @@
-=== Static routing
+=== Static Routing
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/routing/static_routing]
@@ -9,7 +9,7 @@ that data forwarding works as expected via an intermediate device.
==== Topology
-image::topology.svg[Static routing topology, align=center, scaledwidth=75%]
+image::topology.svg[Static Routing topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/services/lldp/lldp_admin_status/test.adoc b/test/case/services/lldp/lldp_admin_status/test.adoc
index 58ad98a05..f87f53c6e 100644
--- a/test/case/services/lldp/lldp_admin_status/test.adoc
+++ b/test/case/services/lldp/lldp_admin_status/test.adoc
@@ -1,4 +1,4 @@
-=== LLDP admin status
+=== LLDP Admin Status
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/services/lldp/lldp_admin_status]
@@ -8,7 +8,7 @@ Verify that LLDP admin status is set properly by lldpd
==== Topology
-image::topology.svg[LLDP admin status topology, align=center, scaledwidth=75%]
+image::topology.svg[LLDP Admin Status topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/services/lldp/lldp_enable_disable/test.adoc b/test/case/services/lldp/lldp_enable_disable/test.adoc
index 5f8a02357..90c726ebe 100644
--- a/test/case/services/lldp/lldp_enable_disable/test.adoc
+++ b/test/case/services/lldp/lldp_enable_disable/test.adoc
@@ -1,4 +1,4 @@
-=== LLDP enable/disable
+=== LLDP Enable/Disable
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/services/lldp/lldp_enable_disable]
@@ -9,7 +9,7 @@ Operation and non-operation are confirmed using tcpdump.
==== Topology
-image::topology.svg[LLDP enable/disable topology, align=center, scaledwidth=75%]
+image::topology.svg[LLDP Enable/Disable topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/services/mdns/mdns_allow_deny/test.adoc b/test/case/services/mdns/mdns_allow_deny/test.adoc
index 2045daca4..c9f736e88 100644
--- a/test/case/services/mdns/mdns_allow_deny/test.adoc
+++ b/test/case/services/mdns/mdns_allow_deny/test.adoc
@@ -1,4 +1,4 @@
-=== mDNS allow/deny interfaces
+=== mDNS Allow/Deny Interfaces
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/services/mdns/mdns_allow_deny]
@@ -14,7 +14,7 @@ with three scenarios:
==== Topology
-image::topology.svg[mDNS allow/deny interfaces topology, align=center, scaledwidth=75%]
+image::topology.svg[mDNS Allow/Deny Interfaces topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/services/mdns/mdns_enable_disable/test.adoc b/test/case/services/mdns/mdns_enable_disable/test.adoc
index aea23bce2..1430ee993 100644
--- a/test/case/services/mdns/mdns_enable_disable/test.adoc
+++ b/test/case/services/mdns/mdns_enable_disable/test.adoc
@@ -1,4 +1,4 @@
-=== mDNS enable/disable
+=== mDNS Enable/Disable
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/services/mdns/mdns_enable_disable]
@@ -9,7 +9,7 @@ Operation and non-operation are confirmed using tcpdump.
==== Topology
-image::topology.svg[mDNS enable/disable topology, align=center, scaledwidth=75%]
+image::topology.svg[mDNS Enable/Disable topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/services/mdns/mdns_reflector/test.adoc b/test/case/services/mdns/mdns_reflector/test.adoc
index 4937cd742..75cce147d 100644
--- a/test/case/services/mdns/mdns_reflector/test.adoc
+++ b/test/case/services/mdns/mdns_reflector/test.adoc
@@ -1,4 +1,4 @@
-=== mDNS reflector
+=== mDNS Reflector
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/services/mdns/mdns_reflector]
@@ -17,7 +17,7 @@ We verify operation with two scenarios:
==== Topology
-image::topology.svg[mDNS reflector topology, align=center, scaledwidth=75%]
+image::topology.svg[mDNS Reflector topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/services/ssh/ssh_key_authentication/test.adoc b/test/case/services/ssh/ssh_key_authentication/test.adoc
index 6d48b5242..93a1fa867 100644
--- a/test/case/services/ssh/ssh_key_authentication/test.adoc
+++ b/test/case/services/ssh/ssh_key_authentication/test.adoc
@@ -1,4 +1,4 @@
-=== Generate ssh key pair
+=== Generate SSH Key Pair
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/services/ssh/ssh_key_authentication]
@@ -8,7 +8,7 @@ Verify that 'guest' user can fetch data using only the 'public' key
==== Topology
-image::topology.svg[Generate ssh key pair topology, align=center, scaledwidth=75%]
+image::topology.svg[Generate SSH Key Pair topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/services/ssh/ssh_server_config/test.adoc b/test/case/services/ssh/ssh_server_config/test.adoc
index 801a16b97..37e37a27f 100644
--- a/test/case/services/ssh/ssh_server_config/test.adoc
+++ b/test/case/services/ssh/ssh_server_config/test.adoc
@@ -1,4 +1,4 @@
-=== SSH server configuration
+=== SSH Server Configuration
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/services/ssh/ssh_server_config]
@@ -11,7 +11,7 @@ Test SSH server functionality with pre-defined key pair:
==== Topology
-image::topology.svg[SSH server configuration topology, align=center, scaledwidth=75%]
+image::topology.svg[SSH Server Configuration topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/syslog/remote/test.adoc b/test/case/syslog/remote/test.adoc
index 5535ac10f..a37b9844b 100644
--- a/test/case/syslog/remote/test.adoc
+++ b/test/case/syslog/remote/test.adoc
@@ -1,4 +1,4 @@
-=== Remote syslog
+=== Remote Syslog
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/syslog/remote]
@@ -8,7 +8,7 @@ Verify logging to remote, acting as a remote, and RFC5424 log format.
==== Topology
-image::topology.svg[Remote syslog topology, align=center, scaledwidth=75%]
+image::topology.svg[Remote Syslog topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/system/add_delete_user/test.adoc b/test/case/system/add_delete_user/test.adoc
index ebbed34ab..23335e5ad 100644
--- a/test/case/system/add_delete_user/test.adoc
+++ b/test/case/system/add_delete_user/test.adoc
@@ -1,4 +1,4 @@
-=== Add/delete user
+=== Add/Delete User
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/system/add_delete_user]
@@ -9,7 +9,7 @@ with yescrypt.
==== Topology
-image::topology.svg[Add/delete user topology, align=center, scaledwidth=75%]
+image::topology.svg[Add/Delete User topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/system/hostname/test.adoc b/test/case/system/hostname/test.adoc
index 7a71726bd..a8a2d1c51 100644
--- a/test/case/system/hostname/test.adoc
+++ b/test/case/system/hostname/test.adoc
@@ -1,4 +1,4 @@
-=== Set hostname
+=== Set Hostname
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/system/hostname]
@@ -11,7 +11,7 @@ base MAC address. E.g., ix-01-01-01.
==== Topology
-image::topology.svg[Set hostname topology, align=center, scaledwidth=75%]
+image::topology.svg[Set Hostname topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/system/nacm-basic/test.adoc b/test/case/system/nacm-basic/test.adoc
index d18b8d16b..607aef296 100644
--- a/test/case/system/nacm-basic/test.adoc
+++ b/test/case/system/nacm-basic/test.adoc
@@ -1,4 +1,4 @@
-=== Basic NACM permissions
+=== Basic NACM Permissions
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/system/nacm-basic]
@@ -22,7 +22,7 @@ Verifies that:
==== Topology
-image::topology.svg[Basic NACM permissions topology, align=center, scaledwidth=75%]
+image::topology.svg[Basic NACM Permissions topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/system/ntp_client/test.adoc b/test/case/system/ntp_client/test.adoc
index 6bd64e3e7..fa3315e25 100644
--- a/test/case/system/ntp_client/test.adoc
+++ b/test/case/system/ntp_client/test.adoc
@@ -1,4 +1,4 @@
-=== Basic NTP client test
+=== Basic NTP Client Test
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/system/ntp_client]
@@ -8,7 +8,7 @@ Verify NTP client with multiple servers, ensure one get selected.
==== Topology
-image::topology.svg[Basic NTP client test topology, align=center, scaledwidth=75%]
+image::topology.svg[Basic NTP Client Test topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/system/timezone/test.adoc b/test/case/system/timezone/test.adoc
index d266feabe..056342c02 100644
--- a/test/case/system/timezone/test.adoc
+++ b/test/case/system/timezone/test.adoc
@@ -1,4 +1,4 @@
-=== Set timezone using timezone name
+=== Set Timezone Using Timezone Name
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/system/timezone]
@@ -8,7 +8,7 @@ Verify that it is possible to set timezone using timezone names.
==== Topology
-image::topology.svg[Set timezone using timezone name topology, align=center, scaledwidth=75%]
+image::topology.svg[Set Timezone Using Timezone Name topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/system/timezone_utc_offset/test.adoc b/test/case/system/timezone_utc_offset/test.adoc
index a043e0ae8..c3493680b 100644
--- a/test/case/system/timezone_utc_offset/test.adoc
+++ b/test/case/system/timezone_utc_offset/test.adoc
@@ -1,4 +1,4 @@
-=== Set timezone with UTC offset
+=== Set Timezone with UTC Offset
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/system/timezone_utc_offset]
@@ -8,7 +8,7 @@ Verify that it is possible to set timezone using UTC offset
==== Topology
-image::topology.svg[Set timezone with UTC offset topology, align=center, scaledwidth=75%]
+image::topology.svg[Set Timezone with UTC Offset topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/system/upgrade/test.adoc b/test/case/system/upgrade/test.adoc
index de3058e71..7bbb6ca21 100644
--- a/test/case/system/upgrade/test.adoc
+++ b/test/case/system/upgrade/test.adoc
@@ -1,4 +1,4 @@
-=== System upgrade
+=== System Upgrade
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/system/upgrade]
@@ -8,7 +8,7 @@ Verify system upgrade functionality.
==== Topology
-image::topology.svg[System upgrade topology, align=center, scaledwidth=75%]
+image::topology.svg[System Upgrade topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/system/user_admin/test.adoc b/test/case/system/user_admin/test.adoc
index d8ffc3708..a271ce281 100644
--- a/test/case/system/user_admin/test.adoc
+++ b/test/case/system/user_admin/test.adoc
@@ -1,4 +1,4 @@
-=== Add admin user
+=== Add Admin User
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/system/user_admin]
@@ -9,7 +9,7 @@ check that it when added as admin it is also the case in Linux.
==== Topology
-image::topology.svg[Add admin user topology, align=center, scaledwidth=75%]
+image::topology.svg[Add Admin User topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/case/use_case/dhcp_ntp_dns_combination/test.adoc b/test/case/use_case/dhcp_ntp_dns_combination/test.adoc
index 4d32bc6b7..007ffa766 100644
--- a/test/case/use_case/dhcp_ntp_dns_combination/test.adoc
+++ b/test/case/use_case/dhcp_ntp_dns_combination/test.adoc
@@ -1,4 +1,4 @@
-=== DHCP NTP DNS combination
+=== DHCP NTP DNS Combination
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/use_case/dhcp_ntp_dns_combination]
@@ -9,7 +9,7 @@ servers from a DHCP server.
==== Topology
-image::topology.svg[DHCP NTP DNS combination topology, align=center, scaledwidth=75%]
+image::topology.svg[DHCP NTP DNS Combination topology, align=center, scaledwidth=75%]
==== Sequence
diff --git a/test/infamy/ptp.py b/test/infamy/ptp.py
new file mode 100644
index 000000000..a2b8826c0
--- /dev/null
+++ b/test/infamy/ptp.py
@@ -0,0 +1,142 @@
+"""PTP (IEEE 1588) test helpers
+
+Query PTP operational data from the ieee1588-ptp-tt YANG model.
+All functions are None-safe and intended for use with until():
+
+ until(lambda: ptp.is_time_receiver(target), attempts=60)
+"""
+
+
+def _get_instance(target, idx=0):
+ data = target.get_data("/ieee1588-ptp-tt:ptp") or {}
+ instances = (data.get("ptp", {})
+ .get("instances", {})
+ .get("instance", []))
+ for inst in instances:
+ if inst.get("instance-index") == idx:
+ return inst
+ return None
+
+
+def port_state(target, port_idx=1, inst_idx=0):
+ """Return port-state string for given port, or None."""
+ inst = _get_instance(target, inst_idx)
+ if not inst:
+ return None
+ for port in inst.get("ports", {}).get("port", []):
+ if port.get("port-index") == port_idx:
+ return port.get("port-ds", {}).get("port-state")
+ return None
+
+
+def is_time_receiver(target, port_idx=1, inst_idx=0):
+ """True when port is in time-receiver state."""
+ return port_state(target, port_idx, inst_idx) == "time-receiver"
+
+
+def is_time_transmitter(target, port_idx=1, inst_idx=0):
+ """True when port is in time-transmitter state."""
+ return port_state(target, port_idx, inst_idx) == "time-transmitter"
+
+
+def offset_ns(target, inst_idx=0):
+ """Return offset-from-time-transmitter in nanoseconds, or None.
+
+ The YANG value is scaled nanoseconds (int64 × 2^16 stored as string).
+ """
+ inst = _get_instance(target, inst_idx)
+ if not inst:
+ return None
+ raw = inst.get("current-ds", {}).get("offset-from-time-transmitter")
+ try:
+ return int(raw) // 65536
+ except (TypeError, ValueError):
+ return None
+
+
+def steps_removed(target, inst_idx=0):
+ """Return steps-removed count, or None."""
+ inst = _get_instance(target, inst_idx)
+ return inst.get("current-ds", {}).get("steps-removed") if inst else None
+
+
+def grandmaster_identity(target, inst_idx=0):
+ """Return grandmaster-identity string from parent-ds, or None."""
+ inst = _get_instance(target, inst_idx)
+ return inst.get("parent-ds", {}).get("grandmaster-identity") if inst else None
+
+
+def clock_identity(target, inst_idx=0):
+ """Return this device's clock-identity string from default-ds, or None."""
+ inst = _get_instance(target, inst_idx)
+ return inst.get("default-ds", {}).get("clock-identity") if inst else None
+
+
+def is_own_gm(target, inst_idx=0):
+ """True when device is its own grandmaster (acting as GM).
+
+ Compares clock-identity to grandmaster-identity; equal means the
+ device won the BTCA election and is distributing its own time.
+ """
+ cid = clock_identity(target, inst_idx)
+ gm = grandmaster_identity(target, inst_idx)
+ return cid is not None and cid == gm
+
+
+def has_converged(target, threshold_ns=100_000, inst_idx=0):
+ """True when |offset-from-time-transmitter| < threshold_ns."""
+ off = offset_ns(target, inst_idx)
+ if off is None:
+ return False
+ return abs(off) < threshold_ns
+
+
+def port_state_dbg(target, port_idx=1, inst_idx=0):
+ """Return a diagnostic string with instance/port state, or an error hint.
+
+ Useful in until() lambdas and test step output to show what is actually
+ being observed when a state check does not converge::
+
+ until(lambda: is_time_transmitter(gm) or not print(port_state_dbg(gm)),
+ attempts=60)
+ """
+ data = target.get_data("/ieee1588-ptp-tt:ptp") or {}
+ if not data:
+ return f"{target.name}: no PTP operational data (ptp4l not running?)"
+
+ instances = (data.get("ptp", {})
+ .get("instances", {})
+ .get("instance", []))
+ if not instances:
+ return f"{target.name}: PTP data present but no instances"
+
+ parts = []
+ for inst in instances:
+ idx = inst.get("instance-index", "?")
+ for port in inst.get("ports", {}).get("port", []):
+ pidx = port.get("port-index", "?")
+ state = port.get("port-ds", {}).get("port-state", "?")
+ parts.append(f"inst={idx} port={pidx} state={state}")
+
+ return f"{target.name}: " + (", ".join(parts) if parts else "no ports")
+
+
+def default_threshold(env, logical_node, hops=1):
+ """Return a convergence threshold suited to the node's timestamping capability.
+
+ Queries the physical topology for ptp-hwts on any link connected to the
+ physical node matched to logical_node. Returns 1000 ns (1 µs) per hop for
+ hardware-timestamping nodes or 100000 ns (100 µs) for software timestamping.
+
+ Use hops=2 for a receiver behind a Boundary Clock — each BC hop adds
+ phc2sys relay jitter on multi-chip hardware.
+
+ Pass --threshold-ns on the command line to override.
+ """
+ phys = env.ltop.xlate(logical_node)
+ g = env.ptop.g
+ has_hwts = any(
+ "ptp-hwts" in data.get("provides", set())
+ for _, _, data in g.edges(phys, data=True)
+ )
+ return 1_000 * hops if has_hwts else 100_000
diff --git a/test/spec/Readme.adoc.in b/test/spec/Readme.adoc.in
index 0e6a1dbe4..524941533 100644
--- a/test/spec/Readme.adoc.in
+++ b/test/spec/Readme.adoc.in
@@ -56,6 +56,10 @@ include::../case/ntp/Readme.adoc[]
<<<
+include::../case/ptp/Readme.adoc[]
+
+<<<
+
include::../case/hardware/Readme.adoc[]
<<<
diff --git a/utils/mkimage.sh b/utils/mkimage.sh
index 1b01e19e3..4bc8896b4 100755
--- a/utils/mkimage.sh
+++ b/utils/mkimage.sh
@@ -18,7 +18,7 @@ Usage:
$0 [OPTIONS]
Options:
- -b boot-dir Path to bootloader build directory (default: O= or output/)
+ -b boot-dir Path to bootloader build directory (default: same as -r, or O= or output/)
-B Boot-only image (no rootfs, for bootloader testing)
-d Download bootloader files from latest-boot release
-f Force re-download of bootloader even if cached
@@ -48,6 +48,9 @@ Examples:
# Standalone with separate boot/rootfs builds:
$0 -b x-boot -r output raspberrypi-rpi64
+ # Rootfs-only (no separate bootloader build, e.g. EspressoBIN):
+ $0 -r x-aarch64 marvell-espressobin
+
# With downloaded rootfs and bootloader:
$0 -d -r ~/Downloads/rootfs.squashfs friendlyarm-nanopi-r2s
@@ -457,13 +460,20 @@ if [ -n "$STANDALONE" ]; then
BOOT_DIR=$(find_build_dir) || die "Could not find boot directory. Use -b option"
fi
else
- if [ -z "$BOOT_DIR" ]; then
- BOOT_DIR=$(find_build_dir) || die "Could not find boot directory. Use -b option"
- fi
-
if [ -z "$ROOT_DIR" ]; then
ROOT_DIR=$(find_build_dir) || die "Could not find rootfs directory. Set O= or use -r option"
fi
+
+ if [ -z "$BOOT_DIR" ]; then
+ # For boards without a separate bootloader build (e.g. EspressoBIN,
+ # where U-Boot lives in SPI NOR), -r alone is sufficient: default
+ # the boot directory to the rootfs directory.
+ if [ -n "$ROOT_DIR" ]; then
+ BOOT_DIR="$ROOT_DIR"
+ else
+ BOOT_DIR=$(find_build_dir) || die "Could not find boot directory. Use -b option"
+ fi
+ fi
fi
# Set up environment variables, some required by genimage.sh
@@ -501,8 +511,8 @@ if [ -n "$STANDALONE" ]; then
# Build directory with images/ - copy rootfs and partition images
log "Copying artifacts from $ROOT_DIR/images/ to $BINARIES_DIR/"
cp "$ROOT_DIR/images/rootfs.squashfs" "$BINARIES_DIR/"
- # Copy partition images if they exist
- for img in aux.ext4 cfg.ext4 var.ext4; do
+ # Copy partition images and rootfs variants if they exist
+ for img in aux.ext4 cfg.ext4 var.ext4 rootfs.ext2; do
if [ -f "$ROOT_DIR/images/$img" ]; then
cp "$ROOT_DIR/images/$img" "$BINARIES_DIR/"
fi
@@ -514,8 +524,8 @@ if [ -n "$STANDALONE" ]; then
# Directory directly containing rootfs.squashfs
log "Copying rootfs from $ROOT_DIR/rootfs.squashfs"
cp "$ROOT_DIR/rootfs.squashfs" "$BINARIES_DIR/"
- # Copy partition images if they exist
- for img in aux.ext4 cfg.ext4 var.ext4; do
+ # Copy partition images and rootfs variants if they exist
+ for img in aux.ext4 cfg.ext4 var.ext4 rootfs.ext2; do
if [ -f "$ROOT_DIR/$img" ]; then
cp "$ROOT_DIR/$img" "$BINARIES_DIR/"
fi
@@ -557,16 +567,16 @@ if [ -n "$DOWNLOAD_BOOT" ]; then
ln -sf "$(realpath "$ROOT_DIR")" "$BINARIES_DIR/rootfs.squashfs"
elif [ -f "$ROOT_DIR/images/rootfs.squashfs" ]; then
ln -sf "$(realpath "$ROOT_DIR/images/rootfs.squashfs")" "$BINARIES_DIR/rootfs.squashfs"
- # Link partition images if they exist
- for img in aux.ext4 cfg.ext4 var.ext4; do
+ # Link partition images and rootfs variants if they exist
+ for img in aux.ext4 cfg.ext4 var.ext4 rootfs.ext2; do
if [ -f "$ROOT_DIR/images/$img" ]; then
ln -sf "$(realpath "$ROOT_DIR/images/$img")" "$BINARIES_DIR/$img"
fi
done
elif [ -f "$ROOT_DIR/rootfs.squashfs" ]; then
ln -sf "$(realpath "$ROOT_DIR/rootfs.squashfs")" "$BINARIES_DIR/rootfs.squashfs"
- # Link partition images if they exist
- for img in aux.ext4 cfg.ext4 var.ext4; do
+ # Link partition images and rootfs variants if they exist
+ for img in aux.ext4 cfg.ext4 var.ext4 rootfs.ext2; do
if [ -f "$ROOT_DIR/$img" ]; then
ln -sf "$(realpath "$ROOT_DIR/$img")" "$BINARIES_DIR/$img"
fi