[PATCH net-next v2 3/6] net: mdio: add timestamped write operation
flat view
HOTtoday
From: James Clark <hidden>
Date: 2026-10-18 10:03:33
Also in:
lkml
Subsystem:
ethernet phy library, networking drivers, the rest · Maintainers:
Andrew Lunn, Heiner Kallweit, Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
Revision v2 of 2 in this series.
Revisions (2)
- v1 [diff vs current]
- v2 current
Add an optional write_sts bus operation returning system timestamp
bounds for completion of a clause 22 MDIO write. This allows PHY
drivers to obtain tighter bounds when implementing gettimex64. SPI
introduced a similar facility to allow a device driver to obtain
timestamp bounds from the bus controller in commit 79591b7db21d ("spi:
Add a PTP system timestamp to the transfer structure").
The core provides no fallback, because a bus driver's write may return
before the transfer has completed.
An aux clock that is disabled and re-enabled restarts its raw time
with the same clock_was_set_seq, so the MDIO core cannot always tell
that the clock was reset during a write. The same problem can occur
within any PTP_SYS_OFFSET_EXTENDED sample, and the PTP core does not
yet detect it. This patch leaves this problem to be fixed by the PTP
core.
__mdiobus_write_sts() takes the longest delay from completion of the
write to the event being timed, such as a PHY capturing its time, and
adds it to the upper bound before checking that the clock was not
stepped within the bounds.
Also add MDIO_C22_FRAME_BITS and MDIO_PREAMBLE_BITS, the number of
bits in a clause 22 frame and in its preamble. A bus driver can use
them with the MDC period to work out when a write completes.
Signed-off-by: James Clark <redacted>
Assisted-by: LLM
---
Changes in v2:
- Require @sts (Andrew)
- Say that write_sts and __mdiobus_write_sts() do clause 22 writes
(Andrew)
- Wrap __phy_write_sts() to 80 columns (checkpatch)
- Add MDIO_C22_FRAME_BITS and MDIO_PREAMBLE_BITS for use by bus
drivers (Andrew)
- Drop "callers must retry anyway" from the comment on the
clock-step check (Sashiko)
- Explain why the core waits after write_sts() returns, and the 1 ms
cap on that wait (Andrew, Sashiko)
- Say that the MDIO core cannot detect every aux clock reset
(Sashiko), and that this is an existing gap, to be fixed in the PTP
core
- Say that -EBUSY and -EINVAL may be returned after the write has
taken effect (Sashiko)
- Read the time before each snapshot in the wait, so that a long
preemption can't fail a valid write
- Add an event delay argument to __mdiobus_write_sts() and
__phy_write_sts(), added to the upper bound
- Move after the ptp patch, whose helper it now uses
drivers/net/mdio/mdio-mux.c | 27 +++++++++
drivers/net/phy/mdio_bus.c | 107 ++++++++++++++++++++++++++++++++++++
include/linux/mdio.h | 10 ++++
include/linux/phy.h | 39 +++++++++++++
4 files changed, 183 insertions(+)
diff --git a/drivers/net/mdio/mdio-mux.c b/drivers/net/mdio/mdio-mux.c
index fe0e46bd796..ed463a087ce 100644
--- a/drivers/net/mdio/mdio-mux.c
+++ b/drivers/net/mdio/mdio-mux.c@@ -123,6 +123,31 @@ static int mdio_mux_write_c45(struct mii_bus *bus, int phy_id, int dev_addr, return r; } +static int mdio_mux_write_sts(struct mii_bus *bus, int phy_id, int regnum, + u16 val, struct ptp_system_timestamp *sts) +{ + struct mdio_mux_child_bus *cb = bus->priv; + struct mdio_mux_parent_bus *pb = cb->parent; + + int r; + + mutex_lock_nested(&pb->mii_bus->mdio_lock, MDIO_MUTEX_MUX); + r = pb->switch_fn(pb->current_child, cb->bus_number, pb->switch_data); + /* write_sts must not return -EOPNOTSUPP. */ + if (r == -EOPNOTSUPP) + r = -EIO; + if (r) + goto out; + + pb->current_child = cb->bus_number; + + r = pb->mii_bus->write_sts(pb->mii_bus, phy_id, regnum, val, sts); +out: + mutex_unlock(&pb->mii_bus->mdio_lock); + + return r; +} + static int parent_count; static void mdio_mux_uninit_children(struct mdio_mux_parent_bus *pb)
@@ -222,6 +247,8 @@ int mdio_mux_init(struct device *dev, cb->mii_bus->read_c45 = mdio_mux_read_c45; if (parent_bus->write_c45) cb->mii_bus->write_c45 = mdio_mux_write_c45; + if (parent_bus->write_sts) + cb->mii_bus->write_sts = mdio_mux_write_sts; r = of_mdiobus_register(cb->mii_bus, child_bus_node); if (r) { mdiobus_free(cb->mii_bus);
diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c
index 00d0e4159e9..493eb4ae030 100644
--- a/drivers/net/phy/mdio_bus.c
+++ b/drivers/net/phy/mdio_bus.c@@ -18,6 +18,7 @@ #include <linux/mm.h> #include <linux/module.h> #include <linux/phy.h> +#include <linux/ptp_clock_kernel.h> #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/string.h>
@@ -145,6 +146,112 @@ int __mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val) } EXPORT_SYMBOL(__mdiobus_write); +/** + * __mdiobus_write_sts - Timestamped version of the __mdiobus_write function + * @bus: the mii_bus struct + * @addr: the phy address + * @regnum: register number to write + * @val: value to write to @regnum + * @event_delay_ns: the longest the event being timed can follow + * completion of the transfer + * @sts: system timestamps bounding an event occurring at most + * @event_delay_ns after completion of the transfer + * + * Return: Zero if successful, negative error code on failure. Returns + * -EBUSY or -EINVAL if the system timestamps are not valid; these + * codes may be returned after the write has taken effect. + * -EOPNOTSUPP is returned only if mdiobus_supports_write_sts() is + * false. + * + * Write a MDIO bus register with a clause 22 write transfer, with system + * timestamps bounding completion; a transfer is considered complete on + * the rising edge of the MDC that clocks the last data bit. The upper + * bound is moved @event_delay_ns later, so that the bounds also contain + * an event that follows completion. Caller must hold the mdio bus lock. + * + * For clocks that can be stepped, validate the clock generation through + * the raw time of the upper bound. + * + * NOTE: MUST NOT be called from interrupt context. + */ +int __mdiobus_write_sts(struct mii_bus *bus, int addr, u32 regnum, u16 val, + u32 event_delay_ns, struct ptp_system_timestamp *sts) +{ + struct system_time_snapshot now; + ktime_t deadline; + int err; + + lockdep_assert_held_once(&bus->mdio_lock); + + if (addr >= PHY_MAX_ADDR) + return -ENXIO; + + if (bus->write_sts) + err = bus->write_sts(bus, addr, regnum, val, sts); + else + err = -EOPNOTSUPP; + + trace_mdio_access(bus, 0, addr, regnum, val, err); + mdiobus_stats_acct(&bus->stats[addr], false, err); + + if (err) + return err; + + if (!sts->pre_sts.valid || !sts->post_sts.valid) + return -EINVAL; + + ptp_adjust_system_postts(sts, event_delay_ns); + + if (sts->clockid == CLOCK_MONOTONIC || + sts->clockid == CLOCK_MONOTONIC_RAW) + return 0; + + /* Fail if the clock was stepped. */ + if (sts->pre_sts.clock_was_set_seq != sts->post_sts.clock_was_set_seq) + return -EBUSY; + + /* Fail if the shift in the upper bound crosses a clock step. Since + * write_sts() can return before the transfer completes, we need to wait + * until the shifted upper bound has passed. This wait needs a deadline + * in case an aux clock is disabled and reenabled, which restarts its + * raw time. 1 ms is enough for a frame with MDC down to about 65 kHz. + */ + deadline = ktime_add_ns(ktime_get_raw(), + (u64)event_delay_ns + NSEC_PER_MSEC); + for (;;) { + bool late = !ktime_before(ktime_get_raw(), deadline); + + ktime_get_snapshot_id(sts->clockid, &now); + if (!now.valid) + return -EINVAL; + + if (now.clock_was_set_seq != sts->pre_sts.clock_was_set_seq) + return -EBUSY; + + if (!ktime_before(now.monoraw, sts->post_sts.monoraw)) + return 0; + + if (late) + return -EBUSY; + + cpu_relax(); + } +} +EXPORT_SYMBOL_GPL(__mdiobus_write_sts); + +/** + * mdiobus_supports_write_sts - Check for timestamped write support + * @bus: the mii_bus struct + * + * Return: true if __mdiobus_write_sts() can return system timestamps + * for writes on @bus. + */ +bool mdiobus_supports_write_sts(struct mii_bus *bus) +{ + return bus->write_sts; +} +EXPORT_SYMBOL_GPL(mdiobus_supports_write_sts); + /** * __mdiobus_modify_changed - Unlocked version of the mdiobus_modify function * @bus: the mii_bus struct
diff --git a/include/linux/mdio.h b/include/linux/mdio.h
index a7d9e3ae362..204e71b8bfe 100644
--- a/include/linux/mdio.h
+++ b/include/linux/mdio.h@@ -11,6 +11,7 @@ struct gpio_desc; struct mii_bus; +struct ptp_system_timestamp; struct reset_control; /* Multiple levels of nesting are possible. However typically this is
@@ -156,6 +157,12 @@ struct mdio_if_info { #define MDIO_SUPPORTS_C45 2 #define MDIO_EMULATE_C22 4 +/* Bits in a clause 22 frame, from the start of its preamble to the end + * of its data, and bits in the preamble (IEEE 802.3 22.2.4.5). + */ +#define MDIO_C22_FRAME_BITS 64 +#define MDIO_PREAMBLE_BITS 32 + struct ethtool_cmd; struct ethtool_pauseparam; extern int mdio45_probe(struct mdio_if_info *mdio, int prtad);
@@ -574,6 +581,9 @@ static inline void mii_c73_mod_linkmode(unsigned long *adv, u16 *lpa) int __mdiobus_read(struct mii_bus *bus, int addr, u32 regnum); int __mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val); +int __mdiobus_write_sts(struct mii_bus *bus, int addr, u32 regnum, u16 val, + u32 event_delay_ns, struct ptp_system_timestamp *sts); +bool mdiobus_supports_write_sts(struct mii_bus *bus); int __mdiobus_modify(struct mii_bus *bus, int addr, u32 regnum, u16 mask, u16 set); int __mdiobus_modify_changed(struct mii_bus *bus, int addr, u32 regnum,
diff --git a/include/linux/phy.h b/include/linux/phy.h
index a5a419bc400..02c256a7a33 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h@@ -374,6 +374,14 @@ struct mii_bus { /** @write_c45: Perform a C45 write transfer on the bus */ int (*write_c45)(struct mii_bus *bus, int addr, int devnum, int regnum, u16 val); + /** + * @write_sts: Perform a C22 write transfer on the bus, + * with system timestamps bounding its completion. Set only + * if timestamps can always be provided. Must not return + * -EOPNOTSUPP. + */ + int (*write_sts)(struct mii_bus *bus, int addr, int regnum, u16 val, + struct ptp_system_timestamp *sts); /** @reset: Perform a reset of the bus */ int (*reset)(struct mii_bus *bus); /**
@@ -1794,6 +1802,37 @@ static inline int __phy_write(struct phy_device *phydev, u32 regnum, u16 val) val); } +/** + * phy_supports_write_sts - Check for timestamped PHY register writes + * @phydev: the phy_device struct + * + * Return: true if __phy_write_sts() can return system timestamps. + */ +static inline bool phy_supports_write_sts(struct phy_device *phydev) +{ + return mdiobus_supports_write_sts(phydev->mdio.bus); +} + +/** + * __phy_write_sts - Write a PHY register with timestamps bounding completion + * @phydev: the phy_device struct + * @regnum: clause 22 register number + * @val: value to write + * @event_delay_ns: as for __mdiobus_write_sts() + * @sts: system timestamp bounds + * + * Return: As for __mdiobus_write_sts(). + * + * The caller must hold the MDIO bus lock. + */ +static inline int __phy_write_sts(struct phy_device *phydev, u32 regnum, + u16 val, u32 event_delay_ns, + struct ptp_system_timestamp *sts) +{ + return __mdiobus_write_sts(phydev->mdio.bus, phydev->mdio.addr, + regnum, val, event_delay_ns, sts); +} + /** * __phy_modify_changed() - Convenience function for modifying a PHY register * @phydev: a pointer to a &struct phy_device
--
2.56.0