diff --git a/.github/workflows/bitsign.yml b/.github/workflows/bitsign.yml index 7af7e6ffe..836ccb2ba 100644 --- a/.github/workflows/bitsign.yml +++ b/.github/workflows/bitsign.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Validate release version format id: validate @@ -170,7 +170,7 @@ jobs: fi - name: Upload signed artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: signed-infix-${{ steps.validate.outputs.file_version }} path: ${{ steps.download_signed.outputs.signed_filename }} @@ -212,4 +212,4 @@ jobs: --- **Next Steps:** The signed release can now be distributed with cryptographic verification of authenticity. - EOF \ No newline at end of file + EOF diff --git a/.github/workflows/build-boot.yml b/.github/workflows/build-boot.yml index 4cd741e21..cf983c079 100644 --- a/.github/workflows/build-boot.yml +++ b/.github/workflows/build-boot.yml @@ -41,7 +41,7 @@ jobs: ls -la ./ - name: Checkout infix repo - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.branch || github.ref }} clean: true @@ -96,7 +96,7 @@ jobs: mv images ${{ steps.vars.outputs.dirname }} tar cfz ${{ steps.vars.outputs.archive }} ${{ steps.vars.outputs.dirname }}/ - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: path: output/${{ steps.vars.outputs.archive }} name: artifact-${{ matrix.defconfig }} @@ -108,7 +108,7 @@ jobs: permissions: contents: write steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v7 with: pattern: "artifact-*" merge-multiple: true diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml index 16c17b7de..f3409148f 100644 --- a/.github/workflows/build-image.yml +++ b/.github/workflows/build-image.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: clean: true fetch-depth: 0 @@ -277,7 +277,7 @@ jobs: fi - name: Upload images as artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: images-${{ inputs.board }} path: | diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index f8f039fc1..2e6000390 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -23,7 +23,7 @@ jobs: target: [aarch64, arm, x86_64] fail-fast: false steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: clean: true submodules: recursive @@ -86,12 +86,12 @@ jobs: ln -s ${{ steps.vars.outputs.dir }} images tar cfz ${{ steps.vars.outputs.tgz }} ${{ steps.vars.outputs.dir }} - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: artifact-${{ matrix.target }} path: output/*.tar.gz - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: artifact-disk-image-${{ matrix.target }} path: output/images/*.qcow2 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4d60d3353..47cc271d4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -80,7 +80,7 @@ jobs: ls -la ./ - name: Checkout infix repo - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: ${{ env.INFIX_REPO }} ref: ${{ env.INFIX_BRANCH != '' && env.INFIX_BRANCH || github.ref }} @@ -178,7 +178,7 @@ jobs: ln -s ${{ steps.vars.outputs.dir }} images tar cfz ${{ steps.vars.outputs.tgz }} ${{ steps.vars.outputs.dir }} - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: path: output/${{ steps.vars.outputs.tgz }} name: artifact-${{ env.TARGET }} diff --git a/.github/workflows/check-kernel-release.yml b/.github/workflows/check-kernel-release.yml index 4893f0caa..5b91581f3 100644 --- a/.github/workflows/check-kernel-release.yml +++ b/.github/workflows/check-kernel-release.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Check out infix repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 token: ${{ secrets.KERNEL_UPDATE_TOKEN }} @@ -103,7 +103,7 @@ jobs: - name: Create pull request if: steps.check.outputs.new_release == 'true' - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: github-token: ${{ secrets.KERNEL_UPDATE_TOKEN }} script: | diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml index 187d7636e..8f53b2eed 100644 --- a/.github/workflows/coverity.yml +++ b/.github/workflows/coverity.yml @@ -16,7 +16,7 @@ jobs: if: ${{github.repository_owner == 'kernelkit'}} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Fetch latest Coverity Scan MD5 id: var env: @@ -27,7 +27,7 @@ jobs: -O coverity-latest.tar.gz.md5 echo "md5=$(cat coverity-latest.tar.gz.md5)" | tee -a $GITHUB_OUTPUT - - uses: actions/cache@v4 + - uses: actions/cache@v5 id: cache with: path: coverity-latest.tar.gz @@ -98,7 +98,7 @@ jobs: https://scan.coverity.com/builds?project=${PROJECT_NAME} - name: Upload build.log - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: coverity-build.log path: cov-int/build-log.txt diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 0fec0f84b..e080aeb77 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -24,12 +24,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 - name: Setup Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v6 with: python-version: '3.x' diff --git a/.github/workflows/generic-x86-build.yml b/.github/workflows/generic-x86-build.yml index 8dc067050..d6436644f 100644 --- a/.github/workflows/generic-x86-build.yml +++ b/.github/workflows/generic-x86-build.yml @@ -33,7 +33,7 @@ jobs: mtools - name: Checkout infix repo - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: ${{ github.repository }} ref: ${{ github.ref }} @@ -77,7 +77,7 @@ jobs: ln -s Infix-x86_64 images tar cfz Infix-x86_64.tar.gz Infix-x86_64 - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: path: output/Infix-x86_64.tar.gz name: artifact-x86_64 diff --git a/.github/workflows/inventory.yml b/.github/workflows/inventory.yml index 9cf610fd5..205dba2e5 100644 --- a/.github/workflows/inventory.yml +++ b/.github/workflows/inventory.yml @@ -44,7 +44,7 @@ jobs: checkout: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: kernelkit/actions/cache-restore@v1 with: target: x86_64 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 26bb57415..f50a0b59b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -48,7 +48,7 @@ jobs: contents: write discussions: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: 'true' @@ -78,7 +78,7 @@ jobs: echo "pre=${{ steps.rel.outputs.pre }}" echo "latest=${{ steps.rel.outputs.latest }}" - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v7 with: pattern: "artifact-*" merge-multiple: true diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 13cfd09c1..6e6894380 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -61,7 +61,7 @@ jobs: runs-on: [self-hosted, regression] steps: - name: Checkout infix repo - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: ${{ env.INFIX_REPO }} ref: ${{ env.INFIX_BRANCH != '' && env.INFIX_BRANCH || github.ref }} @@ -93,7 +93,7 @@ jobs: run: | make ${{ env.TARGET }}_defconfig - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v7 with: pattern: "artifact-*" merge-multiple: true @@ -131,7 +131,7 @@ jobs: make test-dir="$(pwd)/$TEST_PATH" test-report - name: Upload Test Report as Artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: test-report path: output/images/test-report.pdf diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 4f8febe5f..1c81094d8 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -78,7 +78,7 @@ jobs: ls -la ./ - name: Checkout infix repo - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: ${{ env.INFIX_REPO }} ref: ${{ env.INFIX_BRANCH != '' && env.INFIX_BRANCH || github.ref }} diff --git a/.github/workflows/weekly.yml b/.github/workflows/weekly.yml index e02ef647c..569e921e2 100644 --- a/.github/workflows/weekly.yml +++ b/.github/workflows/weekly.yml @@ -22,7 +22,7 @@ jobs: permissions: contents: write steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v6 with: pattern: "artifact-*" merge-multiple: true diff --git a/Makefile b/Makefile index f2faa5de8..aaaf3c1ff 100644 --- a/Makefile +++ b/Makefile @@ -9,8 +9,10 @@ O ?= output # otherwise treat it as relative to ./buildroot. override O := $(if $(filter /%,$O),$O,$(CURDIR)/$O) -config := $(O)/.config -bmake = $(MAKE) -C buildroot O=$(O) $1 +config := $(O)/.config +bmake = $(MAKE) -C buildroot O=$(O) $1 +SNIPPETS_DIR := $(CURDIR)/configs/snippets +MERGE_CONFIG := $(CURDIR)/buildroot/support/kconfig/merge_config.sh all: $(config) buildroot/Makefile @@ -26,6 +28,23 @@ $(config): @echo "'make _defconfig' before building an image." @exit 1 +apply-%: $(SNIPPETS_DIR)/%.conf | $(config) + @KCONFIG_CONFIG=$(config) $(MERGE_CONFIG) -m $(config) $< + @+$(call bmake,olddefconfig) + @echo "Applied snippet: $<" + +list-snippets: + @echo "Available snippets (use 'make apply-'):" + @ls $(SNIPPETS_DIR)/*.conf 2>/dev/null | sed 's|.*/||; s|\.conf$$||; s|^| |' + +dev: | $(config) + @for s in $(SNIPPETS_DIR)/*.conf; do \ + KCONFIG_CONFIG=$(config) $(MERGE_CONFIG) -m $(config) $$s; \ + echo "Applied snippet: $$s"; \ + done + @+$(call bmake,olddefconfig) + @+$(call bmake,all) + %: | buildroot/Makefile @+$(call bmake,$@) @@ -44,4 +63,4 @@ test: buildroot/Makefile: @git submodule update --init -.PHONY: all check coverity dep test cyclonedx +.PHONY: all check coverity dep test cyclonedx list-snippets dev 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: DIP switches -| 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 board + +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 74a498ed2..e50982d5f 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 8576151c2..bc197cad3 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 045ef04cf..1c108b8d4 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 279c1f5b6..7ef7fd2ca 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/dev.conf b/configs/snippets/dev.conf new file mode 100644 index 000000000..b744ff27a --- /dev/null +++ b/configs/snippets/dev.conf @@ -0,0 +1 @@ +BR2_TARGET_ENABLE_ROOT_LOGIN=y diff --git a/configs/snippets/ext4.conf b/configs/snippets/ext4.conf new file mode 100644 index 000000000..20cfc728d --- /dev/null +++ b/configs/snippets/ext4.conf @@ -0,0 +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 771303313..e9f948dbd 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 5eb98272d..c0c982c5f 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 cfc754c31..8df2caae1 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -9,6 +9,9 @@ All notable changes to the project are documented in this file. ### Changes - Upgrade Linux kernel to 6.18.23 (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/developers-guide.md b/doc/developers-guide.md index d5d5eb721..032fc69d3 100644 --- a/doc/developers-guide.md +++ b/doc/developers-guide.md @@ -7,12 +7,12 @@ account, which is created based on credentials found in the VPD area -- for Qemu devices this is emulated using `qemu_fw_cfg`. For developers this can be quite frustrating to be blocked from logging -in to debug the system. So we recommend enabling the `root` account in -the Buildroot `make menuconfig` system. +in to debug the system. The quickest way to enable root login is to +apply the `dev` configuration snippet: - make menuconfig - -> System configuration - -> [*]Enable root login with password + make apply-dev + +See [Configuration Snippets](#configuration-snippets) for more details. > [!IMPORTANT] > Please see the [Contributing](#contributing) section, below, for @@ -166,6 +166,36 @@ on Buildroot to finalize the target filesystem and generate the images. The final `run` argument is explained below. +### Configuration Snippets + +Infix ships a set of Kconfig fragments in `configs/snippets/` that can +be merged into your active `.config` on demand. This avoids polluting +defconfigs with settings that are only useful during development. + +To see what snippets are available: + + make list-snippets + +To apply a single snippet to the current output directory: + + make apply-dev # enable root login + make apply-ext4 # build an ext4 rootfs (needed for boards + # whose bootloader lacks squashfs support, + # e.g. Marvell ESPRESSObin) + +The `apply-*` targets require an existing `.config` (i.e. you must have +already run a `make _defconfig`). The snippet is merged using +Buildroot's `merge_config.sh`, so it behaves like `make menuconfig`: +unrelated settings are preserved, conflicting ones are overridden. + +To apply **all** snippets at once and then build: + + make dev + +This is the recommended one-shot command for setting up a development +build from a freshly selected defconfig. + + ### YANG Model When making changes to the `confd` and `statd` services, you will often diff --git a/doc/img/windows-ipv6-ping-reply.png b/doc/img/windows-ipv6-ping-reply.png new file mode 100644 index 000000000..2cb07ac37 Binary files /dev/null and b/doc/img/windows-ipv6-ping-reply.png differ 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 + +
admin@example:/> show ptp
+PTP Instance 0                          Ordinary Clock · domain 0
+────────────────────────────────────────────────────────────────────
+  Clock identity          : AA-BB-CC-FF-FE-00-11-22
+  Grandmaster             : DD-EE-FF-FF-FE-33-44-55
+  Priority1/Priority2     : 128 / 128
+  GM Priority1/Priority2  : 1 / 1
+  Clock class             : cc-time-receiver-only
+  GM clock class          : cc-primary-sync
+  Time source             : gnss
+  PTP timescale           : yes
+  UTC offset              : 37 s
+  Time traceable          : yes
+  Freq. traceable         : yes
+  Offset from GM          : -42 ns
+  Mean path delay         : 1250 ns
+  Steps removed           : 1
+
+────────────────────────────────────────────────────────────────────
+Ports
+PORT  INTERFACE          STATE                DELAY  LINK DELAY (ns)
+   1  eth0               time-receiver        E2E                  0
+
+────────────────────────────────────────────────────────────────────
+Message Statistics  (▼ rx  ▲ tx)
+PORT  INTERFACE             SYNC ▼  SYNC ▲  ANN ▼  ANN ▲  PD ▼  PD ▲
+   1  eth0                      42       0     15      0     0     0
+
+
+ +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..1fea58e3f 100644 --- a/doc/testing.md +++ b/doc/testing.md @@ -359,6 +359,51 @@ $ 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 ptp-hwts", +]; + +host:data -- dut:data [requires="ieee-mc"] +``` + +#### Declaring capabilities (physical topology) + +```dot +switch1 [ + provides="infix 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 | +| `infix gps` | Node has a GPS receiver available as a time reference | +| `infix watchdog` | Node has a hardware watchdog device | +| `infix ptp-hwts` | Node has PTP Hardware Clock (PHC) support on its network interfaces, enabling hardware timestamping and sub-microsecond PTP convergence | + +#### 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 | + ### 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..fcf2c47fb --- /dev/null +++ b/src/confd/src/ptp.c @@ -0,0 +1,541 @@ +/* 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; +} + +/* + * 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(struct lyd_node *inst, json_t *root) +{ + struct lyd_node *port; + bool all_hw = true; + bool any_sw = false; + + LYX_LIST_FOR_EACH(lyd_child(lydx_get_child(inst, "ports")), port, "port") { + const char *iface = lydx_get_cattr(port, "underlying-interface"); + if (!iface) + continue; + if (iface_has_hw_timestamp(root, iface)) + continue; + all_hw = false; + any_sw = true; + } + + if (any_sw && !all_hw) + WARN("PTP instance has software-only timestamping port(s), " + "falling back to time_stamping software"); + return all_hw ? "hardware" : "software"; +} + +/* + * 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) +{ + struct lyd_node *default_ds, *port, *port_ds; + const char *instance_type, *clock_type, *profile, *ts; + const char *v; + char path[256]; + uint16_t idx; + bool tc, bc, dot1as; + FILE *fp; + + v = lydx_get_cattr(inst, "instance-index"); + if (!v) + return SR_ERR_INVAL_ARG; + idx = (uint16_t)atoi(v); + + 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(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). + * + * NOTE: for BC on multi-chip hardware, ptp4l only disciplines the PHC + * of the active slave port; the other chips' PHCs will drift unless + * phc2sys(8) -a is also run. That is a separate service (TODO). + */ + 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); + + /* + * 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, *dm; + + iface = lydx_get_cattr(port, "underlying-interface"); + if (!iface) + continue; + + fprintf(fp, "[%s]\n", iface); + + port_ds = lydx_get_child(port, "port-ds"); + if (!port_ds) + goto next_port; + + 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) { + 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); + + /* port-enable */ + v = lydx_get_cattr(port_ds, "port-enable"); + if (v && !strcmp(v, "false")) + fprintf(fp, "masterOnly 0\n"); + + next_port: + 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(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) +{ + struct lyd_node *inst; + struct dirent *ent; + DIR *d; + int idx; + + d = opendir(FINIT_RCSD "/enabled"); + if (!d) + return; + + while ((ent = readdir(d))) { + bool found = false; + + if (sscanf(ent->d_name, "phc2sys@%d.conf", &idx) != 1) + continue; + + LYX_LIST_FOR_EACH(lydx_get_descendant(config, "ptp", "instances", "instance", NULL), + inst, "instance") { + const char *v = lydx_get_cattr(inst, "instance-index"); + if (v && atoi(v) == 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; + } + + (void)rename(live, live); /* no-op, clears stale */ + 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) +{ + struct lyd_node *inst; + struct dirent *ent; + DIR *d; + int idx; + + d = opendir(FINIT_RCSD "/enabled"); + if (!d) + return; + + while ((ent = readdir(d))) { + bool found = false; + + if (sscanf(ent->d_name, "ptp4l@%d.conf", &idx) != 1) + continue; + + /* Is this index still configured? */ + LYX_LIST_FOR_EACH(lydx_get_descendant(config, "ptp", "instances", "instance", NULL), + inst, "instance") { + const char *v = lydx_get_cattr(inst, "instance-index"); + if (v && atoi(v) == 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") { + const char *v = lydx_get_cattr(inst, "instance-index"); + if (v) + remove_staging((uint16_t)atoi(v)); + } + 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") { + const char *v = lydx_get_cattr(inst, "instance-index"); + if (!v) + continue; + uint16_t idx = (uint16_t)atoi(v); + 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); + + if (!instances) + return SR_ERR_OK; + + 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..e0638019c --- /dev/null +++ b/src/confd/yang/confd/infix-ptp.yang @@ -0,0 +1,247 @@ +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."; + } + } + + /* + * 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."; + } + + /* + * /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..8236ffed9 --- /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 +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("slaveOnly") + 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: + pass # ptp4l may have just started; skip silently + + 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 9177fcdf6..7b718cc6b 100644 --- a/test/case/all.yaml +++ b/test/case/all.yaml @@ -35,6 +35,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/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..e63a53d2e --- /dev/null +++ b/test/case/ptp/all.yaml @@ -0,0 +1,15 @@ +--- +- 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 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..848fcd7ab --- /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.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 @@ + + + + + + +ptp-basic + + + +host + +mgmt1 + +host + +mgmt2 + + + +gm + +mgmt + +data + +gm +priority1=1 + + + +host:mgmt1--gm:mgmt + + + + +receiver + +data + +mgmt + +receiver +priority1=128 + + + +host:mgmt2--receiver:mgmt + + + + +gm:data--receiver:data + + + +192.168.100.0/30   + + + 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..fb7a07575 --- /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.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 @@ + + + + + + +ptp-bmca + + + +host + +mgmt1 + +host + +mgmt2 + + + +alpha + +mgmt + +data + +alpha + + + +host:mgmt1--alpha:mgmt + + + + +beta + +data + +mgmt + +beta + + + +host:mgmt2--beta:mgmt + + + + +alpha:data--beta:data + + + +192.168.101.0/30 + + + 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..656af5dd7 --- /dev/null +++ b/test/case/ptp/boundary_clock/ieee1588.adoc @@ -0,0 +1,43 @@ +=== 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. +This distinguishes BC from TC behavior, where `steps-removed` remains 1 (the +TC passes ANNOUNCE messages through without incrementing `steps-removed`). + +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) +. Configure boundary clock (BC, ieee1588, priority1=64, two ports) +. Configure time receiver (OC, ieee1588, priority1=128, client-only) +. Wait for BC uplink port to become time-receiver +. Wait for BC dnlink port to become time-transmitter +. 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..739beafca --- /dev/null +++ b/test/case/ptp/boundary_clock/ieee802dot1as.adoc @@ -0,0 +1,43 @@ +=== 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. +This distinguishes BC from TC behavior, where `steps-removed` remains 1 (the +TC passes ANNOUNCE messages through without incrementing `steps-removed`). + +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) +. Configure boundary clock (BC, ieee802-dot1as, priority1=64, two ports) +. Configure time receiver (OC, ieee802-dot1as, priority1=128, client-only) +. Wait for BC uplink port to become time-receiver +. Wait for BC dnlink port to become time-transmitter +. 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..152331ff4 --- /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. +This distinguishes BC from TC behavior, where `steps-removed` remains 1 (the +TC passes ANNOUNCE messages through without incrementing `steps-removed`). + +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.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") + + with test.step(f"Configure grandmaster (OC, {profile}, priority1=1)"): + gm.put_config_dicts(configure_oc(gm_iface, priority1=1, + profile=profile, ip="192.168.100.1")) + + with test.step(f"Configure boundary clock (BC, {profile}, priority1=64, two ports)"): + 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(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 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 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 @@ + + + + + + +ptp-boundary-clock + + + +host + +mgmt1 + +host + +mgmt2 + +mgmt3 + + + +gm + +mgmt + +data + +gm +priority1=1 + + + +host:mgmt1--gm:mgmt + + + + +bc + +uplink + +mgmt + +dnlink + +bc +priority1=64 + + + +host:mgmt2--bc:mgmt + + + + +receiver + +data + +mgmt + +receiver +priority1=128 + + + +host:mgmt3--receiver:mgmt + + + + +gm:data--bc:uplink + + + +192.168.102.0/30 + + + +bc:dnlink--receiver:data + + + +192.168.103.0/30 + + + 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 @@ + + + + + + +ptp-port-recovery + + + +host + +mgmt1 + +host + +mgmt2 + + + +gm + +mgmt + +data + +gm +priority1=1 + + + +host:mgmt1--gm:mgmt + + + + +receiver + +data + +mgmt + +receiver +priority1=128 + + + +host:mgmt2--receiver:mgmt + + + + +gm:data--receiver:data + + + +192.168.106.0/30   + + + 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..58691a804 --- /dev/null +++ b/test/case/ptp/transparent_clock/e2e.adoc @@ -0,0 +1,44 @@ +=== 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 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 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. +The TC node requires hardware PTP timestamping support. + +==== 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..f5fda6ea6 --- /dev/null +++ b/test/case/ptp/transparent_clock/ieee802dot1as.adoc @@ -0,0 +1,44 @@ +=== 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 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 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. +The TC node requires hardware PTP timestamping support. + +==== 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..aa7fb54e1 --- /dev/null +++ b/test/case/ptp/transparent_clock/p2p.adoc @@ -0,0 +1,44 @@ +=== 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 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. +The TC node requires hardware PTP timestamping support. + +==== 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..03c73d8e7 --- /dev/null +++ b/test/case/ptp/transparent_clock/test.py @@ -0,0 +1,176 @@ +#!/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 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 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. +The TC node requires hardware PTP timestamping support. +""" + +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 = env.ltop.xlate("gm", "data") + _, tc_uplink = env.ltop.xlate("tc", "uplink") + _, tc_dnlink = env.ltop.xlate("tc", "dnlink") + _, receiver_iface = env.ltop.xlate("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..87c8f5eda --- /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 ptp-hwts", + ]; + + 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 @@ + + + + + + +ptp-transparent-clock + + + +host + +mgmt1 + +host + +mgmt2 + +mgmt3 + + + +gm + +mgmt + +data + +gm +priority1=1 + + + +host:mgmt1--gm:mgmt + + + + +tc + +uplink + +mgmt + +dnlink + +tc +E2E-TC or P2P-TC + + + +host:mgmt2--tc:mgmt + + + + +receiver + +data + +mgmt + +receiver +priority1=128 + + + +host:mgmt3--receiver:mgmt + + + + +gm:data--tc:uplink + + + +192.168.104.0/30 + + + +tc:dnlink--receiver:data + + + +192.168.105.0/30 + + + diff --git a/test/case/repo/defconfig.sh b/test/case/repo/defconfig.sh index abd0f16cd..b3acbea16 100755 --- a/test/case/repo/defconfig.sh +++ b/test/case/repo/defconfig.sh @@ -1,4 +1,6 @@ #!/bin/sh +# Verify all config/*_defconfig files, skipping any subdirectories, +# e.g., developer snippets not used in official builds. SCRIPT_PATH="$(dirname "$(readlink -f "$0")")" CONFIGS="$SCRIPT_PATH/../../../configs" @@ -47,6 +49,7 @@ check() done } -check "$CONFIGS"/* || exit 1 +# shellcheck disable=SC2046 +check $(find "$CONFIGS" -maxdepth 1 -type f | LC_ALL=C sort) || exit 1 exit 0 diff --git a/test/infamy/ptp.py b/test/infamy/ptp.py new file mode 100644 index 000000000..2e03877ae --- /dev/null +++ b/test/infamy/ptp.py @@ -0,0 +1,105 @@ +"""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 default_threshold(env, logical_node): + """Return a convergence threshold suited to the node's timestamping capability. + + Queries the physical topology for the ptp-hwts capability on the node + matched to logical_node. Returns 1000 ns (1 µs) for hardware-timestamping + nodes or 100000 ns (100 µs) for software timestamping. + + Pass --threshold-ns on the command line to override. + """ + phys = env.ltop.xlate(logical_node) + provides = env.ptop.g.nodes.get(phys, {}).get("provides", set()) + return 1_000 if "ptp-hwts" in provides 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