mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 01:13:00 +02:00
Initial support for IEEE 1588/802.1AS PTP/gPTP
Remaining work: - phc2sys YANG model (infix-phc2sys.yang, instance-index + servo params) - ts2phc YANG model (GPS/PPS → PHC → ptp4l GM path) - timemaster coordination (Phase 3, after phc2sys YANG is stable) - show ptp network (YANG action or background-polled topology container) - CMLDS (requires upstream linuxptp + 802.1ASdm foundation) - Full 12-bit sdoId, fault log, performance monitoring Backported patches from linuxptp master: - port: fix unicast negotiation recovery after FAULT_DETECTED - udp: fix port-specific ptp/p2p_dst_ipv4 configuration - pmc: avoid race conditions in agent update - phc2sys: wait until pmc agent is subscribed (startup race) - fix MAC driver incorrect SIOCGHWTSTAMP adjustment flags - pmc_agent: longer update interval when not subscribed - phc2sys: don't disable pmc agent with -s/-d/-w options - port_signaling: respect ptp_minor_version in message header - port: refresh link status on faults - uds: copy server socket ownership in pmc clients (non-root pmc) - uds: don't call chmod() on client socket - port: allow mixing wildcard and exact clock identities - Add pidfile support to ptp4l, phc2sys, and timemaster Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
@@ -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
|
||||
@@ -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"][<ifname>]["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" +
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+461
@@ -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:
|
||||
|
||||
<pre class="cli"><code>admin@example:/> <b>configure</b>
|
||||
admin@example:/config/> <b>edit ptp instance 0</b>
|
||||
admin@example:/config/ptp/instance/0/> <b>set default-ds domain-number 0</b>
|
||||
admin@example:/config/ptp/instance/0/> <b>set default-ds time-receiver-only true</b>
|
||||
admin@example:/config/ptp/instance/0/> <b>edit port 1</b>
|
||||
admin@example:/config/ptp/…/0/port/1/> <b>set underlying-interface eth0</b>
|
||||
admin@example:/config/ptp/…/0/port/1/> <b>leave</b>
|
||||
</code></pre>
|
||||
|
||||
## Ordinary Clock (time-transmitter / grandmaster)
|
||||
|
||||
A grandmaster clock with high priority, domain 0:
|
||||
|
||||
<pre class="cli"><code>admin@example:/config/> <b>edit ptp instance 0</b>
|
||||
admin@example:/config/ptp/instance/0/> <b>set default-ds domain-number 0</b>
|
||||
admin@example:/config/ptp/instance/0/> <b>set default-ds priority1 1</b>
|
||||
admin@example:/config/ptp/instance/0/> <b>set default-ds priority2 1</b>
|
||||
admin@example:/config/ptp/instance/0/> <b>edit port 1</b>
|
||||
admin@example:/config/ptp/…/0/port/1/> <b>set underlying-interface eth0</b>
|
||||
admin@example:/config/ptp/…/0/port/1/> <b>leave</b>
|
||||
</code></pre>
|
||||
|
||||
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:
|
||||
|
||||
<pre class="cli"><code>admin@example:/config/> <b>edit ptp instance 0</b>
|
||||
admin@example:/config/ptp/instance/0/> <b>set default-ds instance-type bc</b>
|
||||
admin@example:/config/ptp/instance/0/> <b>set default-ds domain-number 0</b>
|
||||
admin@example:/config/ptp/instance/0/> <b>edit port 1</b>
|
||||
admin@example:/config/ptp/…/0/port/1/> <b>set underlying-interface eth0</b>
|
||||
admin@example:/config/ptp/…/0/port/1/> <b>end</b>
|
||||
admin@example:/config/ptp/instance/0/> <b>edit port 2</b>
|
||||
admin@example:/config/ptp/…/0/port/2/> <b>set underlying-interface eth1</b>
|
||||
admin@example:/config/ptp/…/0/port/2/> <b>leave</b>
|
||||
</code></pre>
|
||||
|
||||
> [!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:
|
||||
|
||||
<pre class="cli"><code>admin@example:/config/> <b>edit ptp instance 0</b>
|
||||
admin@example:/config/ptp/instance/0/> <b>set default-ds instance-type p2p-tc</b>
|
||||
admin@example:/config/ptp/instance/0/> <b>set default-ds domain-number 0</b>
|
||||
admin@example:/config/ptp/instance/0/> <b>edit port 1</b>
|
||||
admin@example:/config/ptp/…/0/port/1/> <b>set underlying-interface eth0</b>
|
||||
admin@example:/config/ptp/…/0/port/1/> <b>end</b>
|
||||
admin@example:/config/ptp/instance/0/> <b>edit port 2</b>
|
||||
admin@example:/config/ptp/…/0/port/2/> <b>set underlying-interface eth1</b>
|
||||
admin@example:/config/ptp/…/0/port/2/> <b>leave</b>
|
||||
</code></pre>
|
||||
|
||||
> [!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.
|
||||
|
||||
<pre class="cli"><code>admin@example:/config/> <b>edit ptp instance 0</b>
|
||||
admin@example:/config/ptp/instance/0/> <b>set default-ds profile ieee802-dot1as</b>
|
||||
admin@example:/config/ptp/instance/0/> <b>set default-ds domain-number 0</b>
|
||||
admin@example:/config/ptp/instance/0/> <b>set default-ds time-receiver-only true</b>
|
||||
admin@example:/config/ptp/instance/0/> <b>edit port 1</b>
|
||||
admin@example:/config/ptp/…/0/port/1/> <b>set underlying-interface eth0</b>
|
||||
admin@example:/config/ptp/…/0/port/1/> <b>leave</b>
|
||||
</code></pre>
|
||||
|
||||
> [!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:
|
||||
|
||||
<pre class="cli"><code>admin@example:/config/> <b>edit ptp instance 0</b>
|
||||
admin@example:/config/ptp/instance/0/> <b>set default-ds domain-number 0</b>
|
||||
admin@example:/config/ptp/instance/0/> <b>set default-ds profile ieee1588</b>
|
||||
admin@example:/config/ptp/instance/0/> <b>edit port 1</b>
|
||||
admin@example:/config/ptp/…/0/port/1/> <b>set underlying-interface eth0</b>
|
||||
admin@example:/config/ptp/…/0/port/1/> <b>end</b>
|
||||
admin@example:/config/ptp/instance/0/> <b>end</b>
|
||||
admin@example:/config/ptp/> <b>edit instance 1</b>
|
||||
admin@example:/config/ptp/instance/1/> <b>set default-ds domain-number 0</b>
|
||||
admin@example:/config/ptp/instance/1/> <b>set default-ds profile ieee802-dot1as</b>
|
||||
admin@example:/config/ptp/instance/1/> <b>edit port 1</b>
|
||||
admin@example:/config/ptp/…/1/port/1/> <b>set underlying-interface eth1</b>
|
||||
admin@example:/config/ptp/…/1/port/1/> <b>leave</b>
|
||||
</code></pre>
|
||||
|
||||
## 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
|
||||
|
||||
<pre class="cli"><code>admin@example:/> <b>show ptp</b>
|
||||
<b>PTP Instance 0</b> 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
|
||||
<span class="header">PORT INTERFACE STATE DELAY LINK DELAY (ns)</span>
|
||||
1 eth0 <span class="ok">time-receiver</span> E2E 0
|
||||
|
||||
────────────────────────────────────────────────────────────────────
|
||||
Message Statistics (▼ rx ▲ tx)
|
||||
<span class="header">PORT INTERFACE SYNC ▼ SYNC ▲ ANN ▼ ANN ▲ PD ▼ PD ▲</span>
|
||||
1 eth0 42 0 15 0 0 0
|
||||
|
||||
</code></pre>
|
||||
|
||||
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
|
||||
|
||||
<pre class="cli"><code>admin@example:/> <b>show ptp 0</b>
|
||||
</code></pre>
|
||||
|
||||
## 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):
|
||||
|
||||
<pre class="cli"><code>admin@example:/config/ptp/…/0/port/1/> <b>set port-ds log-announce-interval 0</b>
|
||||
admin@example:/config/ptp/…/0/port/1/> <b>set port-ds log-sync-interval -3</b>
|
||||
admin@example:/config/ptp/…/0/port/1/> <b>set port-ds log-min-delay-req-interval 0</b>
|
||||
admin@example:/config/ptp/…/0/port/1/> <b>set announce-receipt-timeout 3</b>
|
||||
</code></pre>
|
||||
|
||||
`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 |
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
From d68e6a149340d987ebd19f86a7d792f4aaefdf98 Mon Sep 17 00:00:00 2001
|
||||
From: Vincent Cheng <vscheng@gmail.com>
|
||||
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 <vincent.cheng.xh@renesas.com>
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
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
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
From 4ea423b94264e4eeb0c2706fc3485b1fe283cc11 Mon Sep 17 00:00:00 2001
|
||||
From: Miroslav Lichvar <mlichvar@redhat.com>
|
||||
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 <mlichvar@redhat.com>
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
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
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
From e76bb37019605dea4acd9ccec620a3faec1ba402 Mon Sep 17 00:00:00 2001
|
||||
From: Miroslav Lichvar <mlichvar@redhat.com>
|
||||
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 <mlichvar@redhat.com>
|
||||
Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
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
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
From 1942fde263f40d318d56acf824626641263facd0 Mon Sep 17 00:00:00 2001
|
||||
From: Miroslav Lichvar <mlichvar@redhat.com>
|
||||
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 <mlichvar@redhat.com>
|
||||
Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
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
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
From 9e1b0df61c4dc9bb1a4aac11acb06e4f1f2c68f7 Mon Sep 17 00:00:00 2001
|
||||
From: William Comly <william.comly@nav-timing.safrangroup.com>
|
||||
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 <william.comly@nav-timing.safrangroup.com>
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
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
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
From 0bb9080c1bf631ae0265475d75860b6acd92ed2c Mon Sep 17 00:00:00 2001
|
||||
From: Miroslav Lichvar <mlichvar@redhat.com>
|
||||
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 <mlichvar@redhat.com>
|
||||
Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
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
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
From 5a97466a0ed88bae244566c2a5dba85ce72e4f01 Mon Sep 17 00:00:00 2001
|
||||
From: Miroslav Lichvar <mlichvar@redhat.com>
|
||||
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 <mlichvar@redhat.com>
|
||||
Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
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
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
From 9822b22a07e05eb087cedfbb4a21ceecbb8ec74e Mon Sep 17 00:00:00 2001
|
||||
From: William Comly <william.comly@nav-timing.safrangroup.com>
|
||||
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 <william.comly@nav-timing.safrangroup.com>
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
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
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
From b8a3020e806dd6252e7edd434ae3940706a0b516 Mon Sep 17 00:00:00 2001
|
||||
From: Miroslav Lichvar <mlichvar@redhat.com>
|
||||
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 <mlichvar@redhat.com>
|
||||
Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
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
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
From 3dece8ac8b324e702bf54875b53aee1eb49340e0 Mon Sep 17 00:00:00 2001
|
||||
From: "Miroslav Lichvar (via linuxptp-devel Mailing List)"
|
||||
<linuxptp-devel@lists.nwtime.org>
|
||||
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 <mlichvar@redhat.com>
|
||||
Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
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
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
From 6bfc1de21b64ac17145b119cb636d1c1b3d90a75 Mon Sep 17 00:00:00 2001
|
||||
From: "Miroslav Lichvar (via linuxptp-devel Mailing List)"
|
||||
<linuxptp-devel@lists.nwtime.org>
|
||||
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 <mlichvar@redhat.com>
|
||||
Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
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
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
From 5a192e152f4d19ff5899960154f1e14bbd0c6bd7 Mon Sep 17 00:00:00 2001
|
||||
From: "Maxime Chevallier (via linuxptp-devel Mailing List)"
|
||||
<linuxptp-devel@lists.nwtime.org>
|
||||
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 <maxime.chevallier@bootlin.com>
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
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
|
||||
|
||||
@@ -0,0 +1,386 @@
|
||||
From 4fdb58ce4e052862f0dc7432d84b19febd5256a5 Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
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 <path> on the ptp4l command line). For timemaster,
|
||||
which uses its own config format, the path is given with -u <path>.
|
||||
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
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 <sys/stat.h>
|
||||
+#include <sys/time.h>
|
||||
+#include <sys/types.h>
|
||||
+#include <errno.h>
|
||||
+#include <paths.h>
|
||||
+#include <stdio.h>
|
||||
+#include <stdlib.h>
|
||||
+#include <string.h>
|
||||
+#include <unistd.h>
|
||||
+
|
||||
+#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 <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
+#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
|
||||
|
||||
@@ -711,6 +711,23 @@ def keystore(args: List[str]) -> None:
|
||||
print("Usage: show keystore [symmetric <name> | asymmetric <name>]")
|
||||
|
||||
|
||||
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,
|
||||
|
||||
@@ -49,6 +49,7 @@ confd_plugin_la_SOURCES = \
|
||||
keystore.c \
|
||||
system.c \
|
||||
ntp.c \
|
||||
ptp.c \
|
||||
syslog.c \
|
||||
factory-default.c \
|
||||
routing.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)
|
||||
|
||||
@@ -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_ */
|
||||
|
||||
@@ -0,0 +1,465 @@
|
||||
/* SPDX-License-Identifier: BSD-3-Clause */
|
||||
|
||||
#include <dirent.h>
|
||||
#include <stdbool.h>
|
||||
#include <jansson.h>
|
||||
|
||||
#include <srx/common.h>
|
||||
#include <srx/lyx.h>
|
||||
#include <srx/srx_val.h>
|
||||
|
||||
#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 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");
|
||||
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 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;
|
||||
|
||||
any_sw = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (any_sw) {
|
||||
WARN("PTP instance has software-only timestamping port(s), "
|
||||
"falling back to time_stamping software");
|
||||
|
||||
return "software";
|
||||
}
|
||||
|
||||
return "hardware";
|
||||
}
|
||||
|
||||
/*
|
||||
* Write ptp4l config for one PTP instance.
|
||||
* Config file: /etc/linuxptp/ptp4l-<idx>.conf+ (staging)
|
||||
*
|
||||
* ptp4l key config layout:
|
||||
* [global] — instance-wide settings
|
||||
* [eth0] — per-port interface sections, sorted by port-index
|
||||
*/
|
||||
static int write_instance_conf(struct lyd_node *inst, json_t *root)
|
||||
{
|
||||
const char *instance_type, *clock_type, *profile;
|
||||
struct lyd_node *default_ds, *port, *port_ds, *servo;
|
||||
bool tc, bc, dot1as;
|
||||
char path[256];
|
||||
const char *v;
|
||||
uint16_t idx;
|
||||
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, "infix-ptp: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 */
|
||||
fprintf(fp, "time_stamping %s\n", instance_time_stamping(inst, root));
|
||||
|
||||
/* Profile — sets transportSpecific and all protocol-mandatory options */
|
||||
dot1as = emit_profile_globals(fp, profile);
|
||||
|
||||
/* 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);
|
||||
|
||||
/* servo: step_threshold (0.0 = slew-only, never step) */
|
||||
servo = lydx_get_child(inst, "servo");
|
||||
if (servo) {
|
||||
v = lydx_get_cattr(servo, "step-threshold");
|
||||
if (v)
|
||||
fprintf(fp, "step_threshold %s\n", v);
|
||||
}
|
||||
|
||||
/*
|
||||
* Transparent Clocks set delay_mechanism globally; ptp4l ignores
|
||||
* per-port delay_mechanism for TCs. 802.1AS mandates P2P globally
|
||||
* (already emitted by emit_profile_globals).
|
||||
*/
|
||||
if (tc) {
|
||||
if (!strcmp(clock_type, "P2P_TC"))
|
||||
fprintf(fp, "delay_mechanism P2P\n");
|
||||
else
|
||||
fprintf(fp, "delay_mechanism E2E\n");
|
||||
}
|
||||
|
||||
fprintf(fp, "\n");
|
||||
|
||||
/* Per-port [interface] sections, sorted by port-index */
|
||||
LYX_LIST_FOR_EACH(lyd_child(lydx_get_child(inst, "ports")), port, "port") {
|
||||
const char *iface;
|
||||
|
||||
iface = lydx_get_cattr(port, "underlying-interface");
|
||||
if (!iface)
|
||||
continue;
|
||||
|
||||
port_ds = lydx_get_child(port, "port-ds");
|
||||
if (!port_ds)
|
||||
continue;
|
||||
|
||||
if (!lydx_is_enabled(port_ds, "port-enable"))
|
||||
continue;
|
||||
|
||||
fprintf(fp, "[%s]\n", iface);
|
||||
|
||||
v = lydx_get_cattr(port_ds, "log-announce-interval");
|
||||
if (v)
|
||||
fprintf(fp, "logAnnounceInterval %s\n", v);
|
||||
|
||||
v = lydx_get_cattr(port_ds, "announce-receipt-timeout");
|
||||
if (v)
|
||||
fprintf(fp, "announceReceiptTimeout %s\n", v);
|
||||
|
||||
v = lydx_get_cattr(port_ds, "log-sync-interval");
|
||||
if (v)
|
||||
fprintf(fp, "logSyncInterval %s\n", v);
|
||||
|
||||
v = lydx_get_cattr(port_ds, "log-min-delay-req-interval");
|
||||
if (v)
|
||||
fprintf(fp, "logMinDelayReqInterval %s\n", v);
|
||||
|
||||
v = lydx_get_cattr(port_ds, "log-min-pdelay-req-interval");
|
||||
if (v)
|
||||
fprintf(fp, "logMinPdelayReqInterval %s\n", v);
|
||||
|
||||
/*
|
||||
* delay_mechanism per port — only for OC/BC on ieee1588 profile.
|
||||
* TC and 802.1AS both set it globally; ptp4l ignores per-port
|
||||
* overrides in those cases.
|
||||
*/
|
||||
if (!tc && !dot1as) {
|
||||
const char *dm = lydx_get_cattr(port_ds, "delay-mechanism");
|
||||
|
||||
if (dm) {
|
||||
if (!strcmp(dm, "p2p"))
|
||||
fprintf(fp, "delay_mechanism P2P\n");
|
||||
else if (!strcmp(dm, "e2e"))
|
||||
fprintf(fp, "delay_mechanism E2E\n");
|
||||
}
|
||||
}
|
||||
|
||||
v = lydx_get_cattr(port_ds, "delay-asymmetry");
|
||||
if (v && strcmp(v, "0"))
|
||||
fprintf(fp, "delayAsymmetry %s\n", v);
|
||||
|
||||
if (lydx_is_enabled(port_ds, "time-transmitter-only"))
|
||||
fprintf(fp, "masterOnly 1\n");
|
||||
|
||||
fprintf(fp, "\n");
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
return SR_ERR_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove staging config for one instance.
|
||||
*/
|
||||
static void remove_staging(uint16_t idx)
|
||||
{
|
||||
char path[256];
|
||||
|
||||
snprintf(path, sizeof(path), PTP_CONF_DIR "/ptp4l-%u.conf+", idx);
|
||||
(void)remove(path);
|
||||
}
|
||||
|
||||
/*
|
||||
* Activate one instance: rename staging → live, enable finit service.
|
||||
*/
|
||||
static int activate_instance(uint16_t idx)
|
||||
{
|
||||
char staging[256], live[256];
|
||||
|
||||
snprintf(staging, sizeof(staging), PTP_CONF_DIR "/ptp4l-%u.conf+", idx);
|
||||
snprintf(live, sizeof(live), PTP_CONF_DIR "/ptp4l-%u.conf", idx);
|
||||
|
||||
if (!fexist(staging)) {
|
||||
(void)remove(live);
|
||||
return SR_ERR_OK;
|
||||
}
|
||||
|
||||
if (rename(staging, live)) {
|
||||
ERRNO("Failed renaming %s → %s", staging, live);
|
||||
return SR_ERR_SYS;
|
||||
}
|
||||
|
||||
finit_enablef("ptp4l@%u", idx);
|
||||
return finit_reloadf("ptp4l@%u", idx);
|
||||
}
|
||||
|
||||
/*
|
||||
* Deactivate (disable) one instance and remove its live config.
|
||||
*/
|
||||
static void deactivate_instance(uint16_t idx)
|
||||
{
|
||||
char live[256];
|
||||
|
||||
finit_disablef("ptp4l@%u", idx);
|
||||
|
||||
snprintf(live, sizeof(live), PTP_CONF_DIR "/ptp4l-%u.conf", idx);
|
||||
(void)remove(live);
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable any ptp4l@ services in finit enabled/ whose index is not in the
|
||||
* currently configured set. Called from SR_EV_DONE after enabling active
|
||||
* instances, to clean up stale services from a previous config.
|
||||
*/
|
||||
static void cleanup_stale_instances(struct lyd_node *config)
|
||||
{
|
||||
const struct dirent *ent;
|
||||
struct lyd_node *inst;
|
||||
int idx;
|
||||
DIR *d;
|
||||
|
||||
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;
|
||||
if ((rc = activate_instance((uint16_t)atoi(v))))
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Disable stale services not in current config */
|
||||
cleanup_stale_instances(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);
|
||||
}
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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)'.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
infix-if-ptp.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";
|
||||
|
||||
@@ -0,0 +1,291 @@
|
||||
module infix-ptp {
|
||||
yang-version 1.1;
|
||||
namespace "urn:infix:ptp:ns:yang:1.0";
|
||||
prefix infix-ptp;
|
||||
|
||||
import ieee1588-ptp-tt {
|
||||
prefix ptp-tt;
|
||||
}
|
||||
import ieee802-dot1as-gptp {
|
||||
prefix dot1as-gptp;
|
||||
}
|
||||
|
||||
organization "KernelKit";
|
||||
contact "kernelkit@googlegroups.com";
|
||||
description "Augments and deviations for IEEE 1588-2019 and
|
||||
IEEE 802.1AS-2020 PTP support.
|
||||
|
||||
Profile selection via the profile leaf covers all
|
||||
protocol-mandatory settings for each profile. The
|
||||
standard sdo-id leaf is deviated not-supported; the
|
||||
profile leaf is the authoritative selector.
|
||||
|
||||
Transparent Clock support uses the modern instance-type
|
||||
approach (p2p-tc / e2e-tc) from IEEE 1588-2019. The
|
||||
deprecated IEEE 1588-2008 transparent-clock-default-ds
|
||||
and transparent-clock-ports containers are deviated
|
||||
not-supported.";
|
||||
|
||||
revision 2026-04-07 {
|
||||
description "Initial revision.";
|
||||
reference "internal";
|
||||
}
|
||||
|
||||
/*
|
||||
* Augments
|
||||
*/
|
||||
|
||||
augment "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance"
|
||||
+ "/ptp-tt:default-ds" {
|
||||
description
|
||||
"Profile selection for this PTP instance.
|
||||
|
||||
The profile leaf selects the PTP profile and applies all
|
||||
protocol-mandatory settings for that profile. The standard
|
||||
sdo-id leaf is deviated not-supported; use profile instead.";
|
||||
|
||||
leaf profile {
|
||||
type enumeration {
|
||||
enum ieee1588 {
|
||||
value 0;
|
||||
description
|
||||
"IEEE 1588-2019 default profile. Uses UDP/IPv4 transport
|
||||
and E2E delay measurement by default. Network transport
|
||||
and delay mechanism are user-configurable per port.";
|
||||
}
|
||||
enum ieee802-dot1as {
|
||||
value 1;
|
||||
description
|
||||
"IEEE 802.1AS-2020 gPTP profile. Applies all
|
||||
protocol-mandatory settings: IEEE 802.3 (Layer 2)
|
||||
transport, P2P delay measurement, and the 802.1AS
|
||||
multicast group address. Also enables path trace,
|
||||
follow-up information, and neighbor propagation delay
|
||||
thresholds as required by the standard.
|
||||
|
||||
User-configurable: priority1, priority2, domain-number,
|
||||
time-receiver-only, and timer interval leaves.";
|
||||
}
|
||||
}
|
||||
default ieee1588;
|
||||
description
|
||||
"PTP profile for this instance. Selects the complete set of
|
||||
protocol-mandatory settings for the chosen profile.
|
||||
|
||||
The combination of domain-number and profile must be unique
|
||||
across all PTP instances on this node.";
|
||||
}
|
||||
}
|
||||
|
||||
augment "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance" {
|
||||
description
|
||||
"Clock servo parameters for this PTP instance.";
|
||||
|
||||
container servo {
|
||||
description
|
||||
"Clock servo tuning parameters.";
|
||||
|
||||
leaf step-threshold {
|
||||
type decimal64 {
|
||||
fraction-digits 9;
|
||||
}
|
||||
units "seconds";
|
||||
default "0.0";
|
||||
description
|
||||
"Maximum offset from the time transmitter that the servo
|
||||
corrects by slewing rather than stepping. When the measured
|
||||
offset exceeds this threshold the clock is stepped abruptly
|
||||
to the correct time; below the threshold the servo disciplines
|
||||
the clock by frequency adjustment only.
|
||||
|
||||
The value 0.0 (default) disables stepping: the servo always
|
||||
slews, which guarantees a monotonic clock at the cost of
|
||||
potentially very slow convergence when starting from a large
|
||||
initial offset.
|
||||
|
||||
Setting a non-zero value (for example 0.1 for 100 ms) allows
|
||||
the servo to step the clock on first lock, achieving fast
|
||||
initial convergence while keeping the clock monotonic once
|
||||
it has locked.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Deviations from ieee1588-ptp-tt
|
||||
*/
|
||||
|
||||
/*
|
||||
* /ptp/instances/instance/default-ds
|
||||
*/
|
||||
|
||||
deviation "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance"
|
||||
+ "/ptp-tt:default-ds/ptp-tt:sdo-id" {
|
||||
deviate not-supported;
|
||||
description
|
||||
"Only the upper 4-bit majorSdoId field is configurable.
|
||||
Use the profile leaf to select the correct majorSdoId value.";
|
||||
}
|
||||
|
||||
deviation "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance"
|
||||
+ "/ptp-tt:default-ds/ptp-tt:current-time" {
|
||||
deviate not-supported;
|
||||
description
|
||||
"Setting PTP instance time via YANG is not supported.
|
||||
The current PTP time is only observable via pmc and is
|
||||
presented in operational data.";
|
||||
}
|
||||
|
||||
deviation "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance"
|
||||
+ "/ptp-tt:default-ds/ptp-tt:external-port-config-enable" {
|
||||
deviate not-supported;
|
||||
description
|
||||
"The external-port-config feature is not supported.";
|
||||
}
|
||||
|
||||
/*
|
||||
* /ptp/instances/instance/description-ds
|
||||
*/
|
||||
|
||||
deviation "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance"
|
||||
+ "/ptp-tt:description-ds" {
|
||||
deviate not-supported;
|
||||
description
|
||||
"The description data set is not exposed by pmc and is
|
||||
not supported in this implementation.";
|
||||
}
|
||||
|
||||
/*
|
||||
* /ptp/instances/instance/fault-log-ds
|
||||
* (feature-gated; not enabled)
|
||||
*/
|
||||
|
||||
deviation "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance"
|
||||
+ "/ptp-tt:fault-log-ds" {
|
||||
deviate not-supported;
|
||||
description
|
||||
"Structured fault log is not supported. ptp4l does not
|
||||
expose a fault log via pmc. Faults are observable in
|
||||
syslog (tagged ptp4l).";
|
||||
}
|
||||
|
||||
/*
|
||||
* /ptp/instances/instance/path-trace-ds
|
||||
* (feature-gated; not enabled)
|
||||
*/
|
||||
|
||||
deviation "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance"
|
||||
+ "/ptp-tt:path-trace-ds" {
|
||||
deviate not-supported;
|
||||
description
|
||||
"Path trace mechanism is not supported in this implementation.";
|
||||
}
|
||||
|
||||
/*
|
||||
* /ptp/instances/instance/alternate-timescale-ds
|
||||
* (feature-gated; not enabled)
|
||||
*/
|
||||
|
||||
deviation "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance"
|
||||
+ "/ptp-tt:alternate-timescale-ds" {
|
||||
deviate not-supported;
|
||||
description
|
||||
"Alternate timescale mechanism is not supported.";
|
||||
}
|
||||
|
||||
/*
|
||||
* /ptp/instances/instance/ports/port/port-ds
|
||||
*/
|
||||
|
||||
deviation "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance"
|
||||
+ "/ptp-tt:ports/ptp-tt:port/ptp-tt:port-ds"
|
||||
+ "/ptp-tt:peer-mean-path-delay" {
|
||||
deviate not-supported;
|
||||
description
|
||||
"Deprecated in IEEE 1588-2019; superseded by mean-link-delay.
|
||||
Not supported.";
|
||||
}
|
||||
|
||||
deviation "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance"
|
||||
+ "/ptp-tt:ports/ptp-tt:port/ptp-tt:port-ds"
|
||||
+ "/ptp-tt:version-number" {
|
||||
deviate not-supported;
|
||||
description
|
||||
"PTP version is determined by ptp4l at runtime and is not
|
||||
user-configurable.";
|
||||
}
|
||||
|
||||
deviation "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance"
|
||||
+ "/ptp-tt:ports/ptp-tt:port/ptp-tt:port-ds"
|
||||
+ "/ptp-tt:minor-version-number" {
|
||||
deviate not-supported;
|
||||
description
|
||||
"PTP minor version is determined by ptp4l at runtime and is
|
||||
not user-configurable.";
|
||||
}
|
||||
|
||||
deviation "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance"
|
||||
+ "/ptp-tt:ports/ptp-tt:port/ptp-tt:port-ds"
|
||||
+ "/ptp-tt:port-enable" {
|
||||
deviate add {
|
||||
default "true";
|
||||
}
|
||||
description
|
||||
"Ports are enabled by default.";
|
||||
}
|
||||
|
||||
/*
|
||||
* /ptp/transparent-clock-default-ds (deprecated IEEE 1588-2008)
|
||||
*/
|
||||
|
||||
deviation "/ptp-tt:ptp/ptp-tt:transparent-clock-default-ds" {
|
||||
deviate not-supported;
|
||||
description
|
||||
"Deprecated IEEE 1588-2008 container. Use the modern
|
||||
instance-type = p2p-tc or e2e-tc approach in
|
||||
/ptp/instances/instance/default-ds/instance-type instead.";
|
||||
}
|
||||
|
||||
/*
|
||||
* /ptp/transparent-clock-ports (deprecated IEEE 1588-2008)
|
||||
*/
|
||||
|
||||
deviation "/ptp-tt:ptp/ptp-tt:transparent-clock-ports" {
|
||||
deviate not-supported;
|
||||
description
|
||||
"Deprecated IEEE 1588-2008 container. Transparent Clock
|
||||
ports are managed via /ptp/instances/instance/ports instead.";
|
||||
}
|
||||
|
||||
/*
|
||||
* /ptp/common-services (CMLDS — Phase 2)
|
||||
*/
|
||||
|
||||
deviation "/ptp-tt:ptp/ptp-tt:common-services" {
|
||||
deviate not-supported;
|
||||
description
|
||||
"CMLDS (Common Mean Link Delay Service) is not supported yet.";
|
||||
}
|
||||
|
||||
/*
|
||||
* Deviations from ieee802-dot1as-gptp
|
||||
*/
|
||||
|
||||
deviation "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance"
|
||||
+ "/ptp-tt:ports/ptp-tt:port/ptp-tt:port-ds"
|
||||
+ "/dot1as-gptp:nup" {
|
||||
deviate not-supported;
|
||||
description
|
||||
"EPON upstream refraction index — not applicable to
|
||||
Ethernet/TSN deployments.";
|
||||
}
|
||||
|
||||
deviation "/ptp-tt:ptp/ptp-tt:instances/ptp-tt:instance"
|
||||
+ "/ptp-tt:ports/ptp-tt:port/ptp-tt:port-ds"
|
||||
+ "/dot1as-gptp:ndown" {
|
||||
deviate not-supported;
|
||||
description
|
||||
"EPON downstream refraction index — not applicable to
|
||||
Ethernet/TSN deployments.";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
infix-ptp.yang
|
||||
@@ -422,6 +422,13 @@ echo "Public: $pub"
|
||||
<ACTION sym="script" in="tty" out="tty" interrupt="true">show nacm</ACTION>
|
||||
</COMMAND>
|
||||
|
||||
<COMMAND name="ptp" help="Show PTP (IEEE 1588/802.1AS) status">
|
||||
<SWITCH name="optional" min="0" max="1">
|
||||
<PARAM name="instance" ptype="/INT" help="Show specific PTP instance (instance-index)"/>
|
||||
</SWITCH>
|
||||
<ACTION sym="script">show ptp $KLISH_PARAM_instance</ACTION>
|
||||
</COMMAND>
|
||||
|
||||
<COMMAND name="ntp" help="Show NTP status">
|
||||
<SWITCH name="subcommands" min="0">
|
||||
<COMMAND name="source" help="Show NTP source(s)">
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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-<idx>.conf and its UDS socket at
|
||||
/var/run/ptp4l-<idx>.
|
||||
"""
|
||||
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
|
||||
from .common import insert, LOG
|
||||
from .host import HOST
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# pmc helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _pmc_get(conf, command):
|
||||
"""Run 'pmc -b 0 -f <conf> GET <command>' and return parsed key→value dict.
|
||||
|
||||
pmc output looks like:
|
||||
\t\t<key> <value>
|
||||
Blank lines and lines not starting with whitespace are ignored.
|
||||
Multiple response blocks (one per port for PORT_DATA_SET) each get
|
||||
their own dict; returns a list of dicts in that case.
|
||||
"""
|
||||
lines = HOST.run_multiline(
|
||||
["pmc", "-u", "-b", "0", "-f", conf, f"GET {command}"], default=[])
|
||||
|
||||
blocks = []
|
||||
current = {}
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
if not stripped or stripped.startswith("sending:") or \
|
||||
"RESPONSE MANAGEMENT" in stripped or \
|
||||
"SIGNALING" in stripped:
|
||||
if current:
|
||||
blocks.append(current)
|
||||
current = {}
|
||||
continue
|
||||
m = re.match(r'^\s+(\S+)\s+(.+)$', line)
|
||||
if m:
|
||||
current[m.group(1)] = m.group(2).strip()
|
||||
|
||||
if current:
|
||||
blocks.append(current)
|
||||
|
||||
return blocks
|
||||
|
||||
|
||||
def _pmc_get_one(conf, command):
|
||||
"""Like _pmc_get but return only the first (or only) block."""
|
||||
blocks = _pmc_get(conf, command)
|
||||
return blocks[0] if blocks else {}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# clockIdentity formatting
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _fmt_clock_identity(raw):
|
||||
"""Convert pmc clockIdentity 'aabbcc.fffe.ddeeff' to YANG format 'AA-BB-CC-FF-FE-DD-EE-FF'.
|
||||
|
||||
The YANG typedef clock-identity requires the pattern [0-9A-F]{2}(-[0-9A-F]{2}){7}.
|
||||
pmc outputs in its own dotted notation e.g. '005182.fffe.112202'.
|
||||
"""
|
||||
raw = raw.replace(".", "").replace("-", "").replace(":", "").upper()
|
||||
if len(raw) == 16:
|
||||
return "-".join(raw[i:i+2] for i in range(0, 16, 2))
|
||||
return raw
|
||||
|
||||
|
||||
def _fmt_port_identity(raw):
|
||||
"""Convert 'aabbccfffe001122-1' to dict {clock-identity, port-number}."""
|
||||
parts = raw.rsplit("-", 1)
|
||||
cid = _fmt_clock_identity(parts[0]) if parts else raw
|
||||
pnum = int(parts[1]) if len(parts) == 2 else 0
|
||||
return {"clock-identity": cid, "port-number": pnum}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# clockAccuracy identity mapping
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Map clockClass decimal values to ieee1588-ptp-tt identity names (identityref, not uint8).
|
||||
_CLOCK_CLASS_MAP = {
|
||||
6: "ieee1588-ptp-tt:cc-primary-sync",
|
||||
7: "ieee1588-ptp-tt:cc-primary-sync-lost",
|
||||
13: "ieee1588-ptp-tt:cc-application-specific-sync",
|
||||
14: "ieee1588-ptp-tt:cc-application-specific-sync-lost",
|
||||
52: "ieee1588-ptp-tt:cc-primary-sync-alternative-a",
|
||||
58: "ieee1588-ptp-tt:cc-application-specific-alternative-a",
|
||||
187: "ieee1588-ptp-tt:cc-primary-sync-alternative-b",
|
||||
193: "ieee1588-ptp-tt:cc-application-specific-alternative-b",
|
||||
248: "ieee1588-ptp-tt:cc-default",
|
||||
255: "ieee1588-ptp-tt:cc-time-receiver-only",
|
||||
}
|
||||
|
||||
|
||||
def _clock_class_identity(raw):
|
||||
"""Return the YANG identity string for a pmc clockClass decimal value, or None."""
|
||||
try:
|
||||
return _CLOCK_CLASS_MAP.get(int(raw))
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
|
||||
|
||||
# Map clockAccuracy hex values to ieee1588-ptp-tt identity names (identityref, not uint8).
|
||||
# Identity names use the 'ca-' prefix as defined in ieee1588-ptp-tt@2023-08-14.yang.
|
||||
# 0xfe (unknown) has no corresponding identity and is omitted by returning None.
|
||||
_CLOCK_ACCURACY_MAP = {
|
||||
0x17: "ieee1588-ptp-tt:ca-time-accurate-to-1000-fs",
|
||||
0x18: "ieee1588-ptp-tt:ca-time-accurate-to-2500-fs",
|
||||
0x19: "ieee1588-ptp-tt:ca-time-accurate-to-10-ps",
|
||||
0x1a: "ieee1588-ptp-tt:ca-time-accurate-to-25ps",
|
||||
0x1b: "ieee1588-ptp-tt:ca-time-accurate-to-100-ps",
|
||||
0x1c: "ieee1588-ptp-tt:ca-time-accurate-to-250-ps",
|
||||
0x1d: "ieee1588-ptp-tt:ca-time-accurate-to-1000-ps",
|
||||
0x1e: "ieee1588-ptp-tt:ca-time-accurate-to-2500-ps",
|
||||
0x1f: "ieee1588-ptp-tt:ca-time-accurate-to-10-ns",
|
||||
0x20: "ieee1588-ptp-tt:ca-time-accurate-to-25-ns",
|
||||
0x21: "ieee1588-ptp-tt:ca-time-accurate-to-100-ns",
|
||||
0x22: "ieee1588-ptp-tt:ca-time-accurate-to-250-ns",
|
||||
0x23: "ieee1588-ptp-tt:ca-time-accurate-to-1000-ns",
|
||||
0x24: "ieee1588-ptp-tt:ca-time-accurate-to-2500-ns",
|
||||
0x25: "ieee1588-ptp-tt:ca-time-accurate-to-10-us",
|
||||
0x26: "ieee1588-ptp-tt:ca-time-accurate-to-25-us",
|
||||
0x27: "ieee1588-ptp-tt:ca-time-accurate-to-100-us",
|
||||
0x28: "ieee1588-ptp-tt:ca-time-accurate-to-250-us",
|
||||
0x29: "ieee1588-ptp-tt:ca-time-accurate-to-1000-us",
|
||||
0x2a: "ieee1588-ptp-tt:ca-time-accurate-to-2500-us",
|
||||
0x2b: "ieee1588-ptp-tt:ca-time-accurate-to-10-ms",
|
||||
0x2c: "ieee1588-ptp-tt:ca-time-accurate-to-25-ms",
|
||||
0x2d: "ieee1588-ptp-tt:ca-time-accurate-to-100-ms",
|
||||
0x2e: "ieee1588-ptp-tt:ca-time-accurate-to-250-ms",
|
||||
0x2f: "ieee1588-ptp-tt:ca-time-accurate-to-1-s",
|
||||
0x30: "ieee1588-ptp-tt:ca-time-accurate-to-10-s",
|
||||
0x31: "ieee1588-ptp-tt:ca-time-accurate-to-gt-10-s",
|
||||
}
|
||||
|
||||
|
||||
def _clock_accuracy_identity(raw):
|
||||
"""Return the YANG identity string for a pmc clockAccuracy hex value, or None."""
|
||||
try:
|
||||
return _CLOCK_ACCURACY_MAP.get(int(raw, 16))
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# time-source identity mapping
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_TIME_SOURCE_MAP = {
|
||||
"0x10": "ieee1588-ptp-tt:atomic-clock",
|
||||
"0x20": "ieee1588-ptp-tt:gnss",
|
||||
"0x30": "ieee1588-ptp-tt:terrestrial-radio",
|
||||
"0x39": "ieee1588-ptp-tt:serial-time-code",
|
||||
"0x40": "ieee1588-ptp-tt:ptp",
|
||||
"0x50": "ieee1588-ptp-tt:ntp",
|
||||
"0x60": "ieee1588-ptp-tt:hand-set",
|
||||
"0x90": "ieee1588-ptp-tt:other",
|
||||
"0xa0": "ieee1588-ptp-tt:internal-oscillator",
|
||||
}
|
||||
|
||||
|
||||
def _time_source_identity(raw):
|
||||
return _TIME_SOURCE_MAP.get(raw.lower(),
|
||||
"ieee1588-ptp-tt:internal-oscillator")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# delay-mechanism and port-state mapping
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_DELAY_MECH_MAP = {
|
||||
"E2E": "e2e",
|
||||
"P2P": "p2p",
|
||||
"AUTO": "no-mechanism",
|
||||
}
|
||||
|
||||
_PORT_STATE_MAP = {
|
||||
"INITIALIZING": "initializing",
|
||||
"FAULTY": "faulty",
|
||||
"DISABLED": "disabled",
|
||||
"LISTENING": "listening",
|
||||
"PRE_MASTER": "pre-time-transmitter",
|
||||
"MASTER": "time-transmitter",
|
||||
"PASSIVE": "passive",
|
||||
"UNCALIBRATED": "uncalibrated",
|
||||
"SLAVE": "time-receiver",
|
||||
"GRAND_MASTER": "time-transmitter",
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Per-dataset builders
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _build_default_ds(d):
|
||||
"""Map pmc DEFAULT_DATA_SET response to YANG default-ds."""
|
||||
ds = {}
|
||||
|
||||
cid = d.get("clockIdentity")
|
||||
if cid:
|
||||
ds["clock-identity"] = _fmt_clock_identity(cid)
|
||||
|
||||
v = d.get("numberPorts")
|
||||
if v:
|
||||
ds["number-ports"] = int(v)
|
||||
|
||||
cq = {}
|
||||
v = d.get("clockClass")
|
||||
if v:
|
||||
cc = _clock_class_identity(v)
|
||||
if cc:
|
||||
cq["clock-class"] = cc
|
||||
v = d.get("clockAccuracy")
|
||||
if v:
|
||||
ca = _clock_accuracy_identity(v)
|
||||
if ca:
|
||||
cq["clock-accuracy"] = ca
|
||||
v = d.get("offsetScaledLogVariance")
|
||||
if v:
|
||||
cq["offset-scaled-log-variance"] = int(v, 16)
|
||||
if cq:
|
||||
ds["clock-quality"] = cq
|
||||
|
||||
v = d.get("priority1")
|
||||
if v:
|
||||
ds["priority1"] = int(v)
|
||||
v = d.get("priority2")
|
||||
if v:
|
||||
ds["priority2"] = int(v)
|
||||
|
||||
v = d.get("domainNumber")
|
||||
if v:
|
||||
ds["domain-number"] = int(v)
|
||||
|
||||
v = d.get("clientOnly") or d.get("slaveOnly") # renamed in ptp4l 4.x
|
||||
if v is not None:
|
||||
ds["time-receiver-only"] = (v == "1")
|
||||
|
||||
# instance-type: derive from ptp4l GM/time-receiver state (read-only, operational)
|
||||
# pmc doesn't directly expose clockType in DEFAULT_DATA_SET
|
||||
# We'll fill instance-type later from the instance's config if possible
|
||||
|
||||
return ds
|
||||
|
||||
|
||||
def _build_current_ds(d):
|
||||
"""Map pmc CURRENT_DATA_SET response to YANG current-ds."""
|
||||
ds = {}
|
||||
|
||||
v = d.get("stepsRemoved")
|
||||
if v:
|
||||
ds["steps-removed"] = int(v)
|
||||
|
||||
v = d.get("offsetFromMaster")
|
||||
if v:
|
||||
# ptp4l reports nanoseconds as float; YANG time-interval is ns * 2^16.
|
||||
# RFC 7951: int64 must be JSON-encoded as a string.
|
||||
try:
|
||||
ds["offset-from-time-transmitter"] = str(int(float(v) * 65536))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
v = d.get("meanPathDelay")
|
||||
if v:
|
||||
try:
|
||||
ds["mean-delay"] = str(int(float(v) * 65536))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return ds
|
||||
|
||||
|
||||
def _build_parent_ds(d):
|
||||
"""Map pmc PARENT_DATA_SET response to YANG parent-ds."""
|
||||
ds = {}
|
||||
|
||||
v = d.get("parentPortIdentity")
|
||||
if v:
|
||||
ds["parent-port-identity"] = _fmt_port_identity(v)
|
||||
|
||||
v = d.get("parentStats")
|
||||
if v:
|
||||
ds["parent-stats"] = (v == "1")
|
||||
|
||||
v = d.get("observedParentOffsetScaledLogVariance")
|
||||
if v:
|
||||
try:
|
||||
ds["observed-parent-offset-scaled-log-variance"] = int(v, 16)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
v = d.get("observedParentClockPhaseChangeRate")
|
||||
if v:
|
||||
try:
|
||||
ds["observed-parent-clock-phase-change-rate"] = int(v)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
v = d.get("grandmasterIdentity")
|
||||
if v:
|
||||
ds["grandmaster-identity"] = _fmt_clock_identity(v)
|
||||
|
||||
gcq = {}
|
||||
v = d.get("gm.ClockClass")
|
||||
if v:
|
||||
cc = _clock_class_identity(v)
|
||||
if cc:
|
||||
gcq["clock-class"] = cc
|
||||
v = d.get("gm.ClockAccuracy")
|
||||
if v:
|
||||
ca = _clock_accuracy_identity(v)
|
||||
if ca:
|
||||
gcq["clock-accuracy"] = ca
|
||||
v = d.get("gm.OffsetScaledLogVariance")
|
||||
if v:
|
||||
try:
|
||||
gcq["offset-scaled-log-variance"] = int(v, 16)
|
||||
except ValueError:
|
||||
pass
|
||||
if gcq:
|
||||
ds["grandmaster-clock-quality"] = gcq
|
||||
|
||||
v = d.get("grandmasterPriority1")
|
||||
if v:
|
||||
ds["grandmaster-priority1"] = int(v)
|
||||
v = d.get("grandmasterPriority2")
|
||||
if v:
|
||||
ds["grandmaster-priority2"] = int(v)
|
||||
|
||||
return ds
|
||||
|
||||
|
||||
def _build_time_properties_ds(d):
|
||||
"""Map pmc TIME_PROPERTIES_DATA_SET response to YANG time-properties-ds."""
|
||||
ds = {}
|
||||
|
||||
# current-utc-offset has a when condition requiring current-utc-offset-valid='true'
|
||||
if d.get("currentUtcOffsetValid") in ("1", "true"):
|
||||
v = d.get("currentUtcOffset")
|
||||
if v:
|
||||
ds["current-utc-offset"] = int(v)
|
||||
|
||||
v = d.get("leap61")
|
||||
if v is not None:
|
||||
ds["leap61"] = (v == "1")
|
||||
v = d.get("leap59")
|
||||
if v is not None:
|
||||
ds["leap59"] = (v == "1")
|
||||
v = d.get("currentUtcOffsetValid")
|
||||
if v is not None:
|
||||
ds["current-utc-offset-valid"] = (v == "1")
|
||||
v = d.get("ptpTimescale")
|
||||
if v is not None:
|
||||
ds["ptp-timescale"] = (v == "1")
|
||||
v = d.get("timeTraceable")
|
||||
if v is not None:
|
||||
ds["time-traceable"] = (v == "1")
|
||||
v = d.get("frequencyTraceable")
|
||||
if v is not None:
|
||||
ds["frequency-traceable"] = (v == "1")
|
||||
|
||||
v = d.get("timeSource")
|
||||
if v:
|
||||
ds["time-source"] = _time_source_identity(v)
|
||||
|
||||
return ds
|
||||
|
||||
|
||||
def _build_port_ds(d):
|
||||
"""Map pmc PORT_DATA_SET response to YANG port-ds."""
|
||||
ds = {}
|
||||
|
||||
v = d.get("portIdentity")
|
||||
if v:
|
||||
ds["port-identity"] = _fmt_port_identity(v)
|
||||
|
||||
v = d.get("portState")
|
||||
if v:
|
||||
ds["port-state"] = _PORT_STATE_MAP.get(v, "disabled")
|
||||
|
||||
v = d.get("logMinDelayReqInterval")
|
||||
if v:
|
||||
try:
|
||||
ds["log-min-delay-req-interval"] = int(v)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
v = d.get("peerMeanPathDelay")
|
||||
if v:
|
||||
try:
|
||||
# RFC 7951: int64 must be JSON-encoded as a string.
|
||||
ds["mean-link-delay"] = str(int(float(v) * 65536))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
v = d.get("logAnnounceInterval")
|
||||
if v:
|
||||
try:
|
||||
ds["log-announce-interval"] = int(v)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
v = d.get("announceReceiptTimeout")
|
||||
if v:
|
||||
try:
|
||||
ds["announce-receipt-timeout"] = int(v)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
v = d.get("logSyncInterval")
|
||||
if v:
|
||||
try:
|
||||
ds["log-sync-interval"] = int(v)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
v = d.get("delayMechanism")
|
||||
if v:
|
||||
ds["delay-mechanism"] = _DELAY_MECH_MAP.get(v, "e2e")
|
||||
|
||||
v = d.get("logMinPdelayReqInterval")
|
||||
if v:
|
||||
try:
|
||||
ds["log-min-pdelay-req-interval"] = int(v)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
v = d.get("versionNumber")
|
||||
if v:
|
||||
try:
|
||||
ds["version-number"] = int(v)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
v = d.get("portEnable")
|
||||
if v is not None:
|
||||
ds["port-enable"] = (v == "1")
|
||||
|
||||
return ds
|
||||
|
||||
|
||||
def _build_port_stats(d):
|
||||
"""Map pmc PORT_STATS_NP response to ieee802-dot1as-gptp port-statistics-ds."""
|
||||
stats = {}
|
||||
mapping = {
|
||||
"rx_Sync": "rx-sync-count",
|
||||
"rx_Follow_Up": "rx-follow-up-count",
|
||||
"rx_Pdelay_Req": "rx-pdelay-req-count",
|
||||
"rx_Pdelay_Resp": "rx-pdelay-resp-count",
|
||||
"rx_Pdelay_Resp_Follow_Up": "rx-pdelay-resp-follow-up-count",
|
||||
"rx_Announce": "rx-announce-count",
|
||||
"tx_Sync": "tx-sync-count",
|
||||
"tx_Follow_Up": "tx-follow-up-count",
|
||||
"tx_Pdelay_Req": "tx-pdelay-req-count",
|
||||
"tx_Pdelay_Resp": "tx-pdelay-resp-count",
|
||||
"tx_Pdelay_Resp_Follow_Up": "tx-pdelay-resp-follow-up-count",
|
||||
"tx_Announce": "tx-announce-count",
|
||||
}
|
||||
for pmc_key, yang_key in mapping.items():
|
||||
v = d.get(pmc_key)
|
||||
if v is not None:
|
||||
try:
|
||||
stats[yang_key] = int(v)
|
||||
except ValueError:
|
||||
pass
|
||||
return stats
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Per-instance builder
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _port_interfaces(conf_path):
|
||||
"""Return ordered list of interface names from ptp4l conf (non-global section headers)."""
|
||||
ifaces = []
|
||||
try:
|
||||
with open(conf_path) as f:
|
||||
for line in f:
|
||||
s = line.strip()
|
||||
if s.startswith('[') and s.endswith(']') and s[1:-1] != 'global':
|
||||
ifaces.append(s[1:-1])
|
||||
except OSError:
|
||||
pass
|
||||
return ifaces
|
||||
|
||||
|
||||
def _instance_type_from_config(conf_path):
|
||||
"""Read instance-type from a saved config file (best effort)."""
|
||||
try:
|
||||
with open(conf_path, "r") as f:
|
||||
for line in f:
|
||||
m = re.match(r'\s*clockType\s+(\S+)', line)
|
||||
if m:
|
||||
ct = m.group(1).upper()
|
||||
if ct == "P2P_TC":
|
||||
return "p2p-tc"
|
||||
if ct == "E2E_TC":
|
||||
return "e2e-tc"
|
||||
if ct == "BOUNDARY_CLOCK":
|
||||
return "bc"
|
||||
# Default: if more than one port, bc; otherwise oc
|
||||
# (approximation — proper detection requires DEFAULT_DATA_SET numberPorts)
|
||||
except OSError:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def _build_instance(idx, conf_path):
|
||||
"""Build one instance dict from pmc queries for instance index idx."""
|
||||
inst = {"instance-index": idx}
|
||||
|
||||
# default-ds
|
||||
dd = _pmc_get_one(conf_path, "DEFAULT_DATA_SET")
|
||||
if dd:
|
||||
dds = _build_default_ds(dd)
|
||||
# Derive instance-type from numberPorts + config file
|
||||
num_ports = int(dd.get("numberPorts", "0") or "0")
|
||||
it = _instance_type_from_config(conf_path)
|
||||
if it is None:
|
||||
it = "bc" if num_ports > 1 else "oc"
|
||||
dds["instance-type"] = it
|
||||
inst["default-ds"] = dds
|
||||
|
||||
# current-ds
|
||||
cd = _pmc_get_one(conf_path, "CURRENT_DATA_SET")
|
||||
if cd:
|
||||
cds = _build_current_ds(cd)
|
||||
if cds:
|
||||
inst["current-ds"] = cds
|
||||
|
||||
# parent-ds
|
||||
pd = _pmc_get_one(conf_path, "PARENT_DATA_SET")
|
||||
if pd:
|
||||
pds = _build_parent_ds(pd)
|
||||
if pds:
|
||||
inst["parent-ds"] = pds
|
||||
|
||||
# time-properties-ds
|
||||
tp = _pmc_get_one(conf_path, "TIME_PROPERTIES_DATA_SET")
|
||||
if tp:
|
||||
tpds = _build_time_properties_ds(tp)
|
||||
if tpds:
|
||||
inst["time-properties-ds"] = tpds
|
||||
|
||||
# ports: PORT_DATA_SET returns one block per port
|
||||
port_blocks = _pmc_get(conf_path, "PORT_DATA_SET")
|
||||
stats_blocks = _pmc_get(conf_path, "PORT_STATS_NP")
|
||||
ifaces = _port_interfaces(conf_path)
|
||||
|
||||
# Build a stats map keyed by portIdentity for quick lookup
|
||||
stats_by_id = {}
|
||||
for sb in stats_blocks:
|
||||
pid = sb.get("portIdentity")
|
||||
if pid:
|
||||
stats_by_id[pid] = _build_port_stats(sb)
|
||||
|
||||
ports = []
|
||||
for i, pb in enumerate(port_blocks, start=1):
|
||||
pid_raw = pb.get("portIdentity", "")
|
||||
port_entry = {}
|
||||
|
||||
# port-index = port number from portIdentity
|
||||
pid_dict = _fmt_port_identity(pid_raw)
|
||||
port_entry["port-index"] = pid_dict.get("port-number", i)
|
||||
|
||||
if i <= len(ifaces):
|
||||
port_entry["underlying-interface"] = ifaces[i - 1]
|
||||
|
||||
pds = _build_port_ds(pb)
|
||||
if pds:
|
||||
port_entry["port-ds"] = pds
|
||||
|
||||
# 802.1AS port-statistics-ds
|
||||
stats = stats_by_id.get(pid_raw)
|
||||
if stats:
|
||||
port_entry["ieee802-dot1as-gptp:port-statistics-ds"] = stats
|
||||
|
||||
ports.append(port_entry)
|
||||
|
||||
if ports:
|
||||
inst["ports"] = {"port": ports}
|
||||
|
||||
return inst
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Top-level entry point
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def operational():
|
||||
"""Return operational data for ieee1588-ptp-tt."""
|
||||
out = {}
|
||||
instances = []
|
||||
|
||||
conf_files = sorted(glob.glob("/etc/linuxptp/ptp4l-*.conf"))
|
||||
for conf_path in conf_files:
|
||||
m = re.search(r'ptp4l-(\d+)\.conf$', conf_path)
|
||||
if not m:
|
||||
continue
|
||||
idx = int(m.group(1))
|
||||
|
||||
# Only include instances with a live UDS socket (i.e. ptp4l running)
|
||||
uds_path = f"/var/run/ptp4l-{idx}"
|
||||
if not HOST.exists(uds_path):
|
||||
continue
|
||||
|
||||
try:
|
||||
inst = _build_instance(idx, conf_path)
|
||||
instances.append(inst)
|
||||
except Exception as e:
|
||||
LOG.debug("ptp4l-%d: skipping instance: %s", idx, e)
|
||||
|
||||
if instances:
|
||||
insert(out, "ieee1588-ptp-tt:ptp", "instances", "instance", instances)
|
||||
|
||||
return out
|
||||
@@ -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
|
||||
|
||||
+4
-1
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user