[net,v4,05/15] net: stmmac: serialize and retain PHC configuration across reset
Commit Message
Keep the frequency correction, PEROUT requests and EXTTS selection
independently of timestamp register contents. Cache PPS requests before
converting units so they can be replayed after reset.
Serialize timestamp writers and devlink timestamp-mode updates with a
mutex, and let atomic gettime callers observe a reset gate under
ptp_lock. Provide a common replay helper for later reset transactions,
without changing PHC registration lifetime.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/net/ethernet/stmicro/stmmac/stmmac.h | 7 ++
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 19 +++-
drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c | 131 +++++++++++++++++++---
3 files changed, 141 insertions(+), 16 deletions(-)
Comments
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] net: stmmac: divide by zero in stmmac_ptp_restore() on single-shot PEROUT
- [Medium] net: stmmac: frequency adjustment lost in stmmac_dl_ts_coarse_set()
--
commit 04a85bc09b8c93cccea45ce07b1d943738de8a9b
Author: James Hilliard <james.hilliard1@gmail.com>
Subject: net: stmmac: serialize and retain PHC configuration across reset
This commit retains PHC configuration such as frequency correction, PEROUT
requests, and EXTTS selection across hardware resets. It serializes
timestamp writers and devlink mode updates using a new mutex, and adds a
common replay helper for reset transactions.
> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
[ ... ]
> @@ -7708,6 +7722,8 @@ static int stmmac_dl_ts_coarse_set(struct devlink *dl, u32 id,
> * reconfigure the systime, subsecond increment and addend.
> */
> stmmac_update_subsecond_increment(priv);
> + write_unlock_irqrestore(&priv->ptp_lock, flags);
> + mutex_unlock(&priv->ptp_mutex);
>
> return 0;
> }
[Severity: Medium]
Does this code lose the currently active frequency adjustment when the devlink
coarse mode is changed?
When a user sets the devlink phc_coarse_adj parameter,
stmmac_dl_ts_coarse_set() calls stmmac_update_subsecond_increment().
Looking at stmmac_update_subsecond_increment(), it reconfigures the hardware
with priv->default_addend without accounting for the active adjustment:
stmmac_update_subsecond_increment() {
...
priv->default_addend = div_u64(temp, priv->plat->clk_ptp_rate);
stmmac_config_addend(priv, priv->ptpaddr, priv->default_addend);
}
Since it does not reapply the cached priv->ptp_scaled_ppm, would this cause
the hardware to run unadjusted while the cached ptp_scaled_ppm implies to
the PTP daemon that an active adjustment is still present?
> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c
> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c
[ ... ]
> +int stmmac_ptp_restore(struct stmmac_priv *priv)
> +{
[ ... ]
> + for (i = 0; !ret && i < STMMAC_PPS_MAX; i++) {
> + struct stmmac_pps_cfg cfg = priv->pps[i];
> +
> + if (!(priv->ptp_perout & BIT(i)))
> + continue;
> + stmmac_get_systime(priv, priv->ptpaddr, &ns);
> + period = timespec64_to_ns(&cfg.period);
> + /* Retain phase, but move an expired target into the future. */
> + cfg.start = stmmac_calc_tas_basetime(timespec64_to_ktime(cfg.start),
> + ns + PTP_SAFE_TIME_OFFSET_NS, period);
[Severity: High]
Can this cause a division by zero panic when restoring a single-shot PEROUT
request?
If a user configures a single-shot PTP PEROUT pulse, its period is 0.
When a MAC reset occurs later (such as an MTU change), stmmac_ptp_restore()
attempts to replay the PEROUT request and passes the period (0) as the
cycle_time argument to stmmac_calc_tas_basetime().
Looking at stmmac_calc_tas_basetime() in stmmac_tc.c:
struct timespec64 stmmac_calc_tas_basetime(...) {
...
} else {
s64 n;
ktime_t base_time;
n = div64_s64(ktime_sub_ns(current_time, old_base_time),
cycle_time);
If the target time is in the past, taking this else branch with
cycle_time = 0 will trigger a 64-bit divide error. Should single-shot
requests skip this phase recalculation?
Thank you for your contribution! Sashiko AI review found 7 potential
issue(s) to consider.
Critical: 0 · High: 1 · Medium: 4 · Low: 2
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] While ptp_blocked is set, stmmac_common_interrupt() skips
stmmac_timestamp_interrupt() completely.
- [Medium] System resume re-initializes the PTP block but never replays
or clears the new retained PHC state.
- [Medium] The commit says it serializes timestamp writers and devlink
timestamp-mode updates with ptp_mutex.
- [Medium] While ptp_blocked is set, stmmac_get_time() now returns -EBUSY
and writes a zero timespec.
- [Medium] stmmac_dl_ts_coarse_set() now runs under ptp_mutex/ptp_lock,
but it still calls stmmac_update_subsecond_increment().
- [Low] __stmmac_enable() caches the PEROUT start and period exactly as
userspace supplied them in priv->pps[] whenever flex_pps_config…
- [Low] When an EXTTS request's ATSFC FIFO-clear poll times out,
__stmmac_enable() and dwmac1000_ptp_enable() have already written the…
--
net: stmmac: serialize and retain PHC configuration across reset
This commit caches the PHC frequency correction, PEROUT requests and EXTTS
selection in stmmac_priv, so that a new stmmac_ptp_restore() helper can
replay them after a MAC reset. It adds ptp_mutex and a ptp_blocked gate to
serialize PHC writers with resets, and stmmac_get_time() now returns
-EBUSY while the gate is set.
> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> index 4f3d452c35036..8b0bc7c46df93 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> @@ -941,6 +941,11 @@ static void stmmac_setup_ptp(struct stmmac_priv *priv)
> {
> int ret;
>
> + priv->ptp_scaled_ppm = 0;
> + priv->ptp_perout = 0;
> + priv->ptp_extts = 0;
> + priv->ptp_blocked = false;
> +
[Severity: Medium]
Does the retained state stay consistent across system resume?
These caches are only cleared in stmmac_setup_ptp(). At the end of the
series, stmmac_resume() calls stmmac_hw_setup(ndev, false, false), which
resets the MAC, and then does:
stmmac_resume() {
...
mutex_lock(&priv->ptp_mutex);
stmmac_init_timestamping(priv);
ret = stmmac_tc_restore_est(priv);
if (!ret)
stmmac_block_ptp(priv, false);
...
}
That programs the default addend and loses PEROUT/EXTTS in hardware.
Resume does not call stmmac_ptp_restore(), and it does not clear
ptp_scaled_ppm, ptp_perout, ptp_extts or pps[].
So after resume the hardware has no frequency correction and no periodic
outputs, but the cache still says they are active. A later jumbo MTU
change or XDP reset fallback then goes through stmmac_ptp_restore(). That
re-applies the old frequency correction and restarts PEROUT/EXTTS without
any request from userspace.
Should resume either replay the cache or reset it?
[ ... ]
> @@ -6285,7 +6290,8 @@ static void stmmac_common_interrupt(struct stmmac_priv *priv)
> for (queue = 0; queue < queues_count; queue++)
> stmmac_host_mtl_irq_status(priv, priv->hw, queue);
>
> - stmmac_timestamp_interrupt(priv, priv);
> + if (!READ_ONCE(priv->ptp_blocked))
> + stmmac_timestamp_interrupt(priv, priv);
[Severity: High]
Can this cause an interrupt storm while ptp_blocked is set?
timestamp_interrupt() in stmmac_hwtstamp.c is the only place that reads
the timestamp status register, and that read is what clears TSIS:
ts_status = readl(priv->ioaddr + GMAC_TIMESTAMP_STATUS);
dwmac4_irq_status() does not touch that register, and GMAC_INT_TSIE is
part of GMAC_INT_DEFAULT_ENABLE.
At the end of the series, stmmac_reconfigure_mtu() sets ptp_blocked while
the MAC IRQ is still registered and enabled. Only the DMA channel sources
are masked. stmmac_ptp_restore() also re-arms EXTTS while the gate is
still closed.
Suppose an aux snapshot event (for example a 1 Hz external PPS on an
EXTTS input) or a PPS target time event arrives in that window. TSIS
then stays asserted. With a level-triggered MAC interrupt,
stmmac_interrupt() keeps returning IRQ_HANDLED, so the spurious IRQ
detector never steps in.
If that IRQ fires on the CPU running the MTU transaction, can the reset
thread ever get to stmmac_block_ptp(priv, false)?
Would it help to read and discard the status, or to mask TSIE, while
blocked?
[ ... ]
> @@ -7696,6 +7702,14 @@ static int stmmac_dl_ts_coarse_set(struct devlink *dl, u32 id,
> {
> struct stmmac_devlink_priv *dl_priv = devlink_priv(dl);
> struct stmmac_priv *priv = dl_priv->stmmac_priv;
> + unsigned long flags;
> +
> + mutex_lock(&priv->ptp_mutex);
> + if (priv->ptp_blocked) {
> + mutex_unlock(&priv->ptp_mutex);
> + return -EBUSY;
> + }
> + write_lock_irqsave(&priv->ptp_lock, flags);
>
> priv->tsfupdt_coarse = ctx->val.vbool;
[Severity: Medium]
The commit message says:
Serialize timestamp writers and devlink timestamp-mode updates with a
mutex
Are all of the timestamp writers covered? Two paths write the same state
without taking ptp_mutex or ptp_lock:
stmmac_hwtstamp_set()
stmmac_setup_ptp()->stmmac_init_timestamping()->stmmac_init_tstamp_counter()
The shared state is systime_flags, sub_second_inc, default_addend and
the PTP_TCR/SSIR/addend registers.
stmmac_dl_ts_coarse_set() runs under the devlink instance lock, not RTNL.
stmmac_hwtstamp_set() runs under RTNL and does:
priv->systime_flags = STMMAC_HWTS_ACTIVE;
if (!priv->tsfupdt_coarse)
priv->systime_flags |= PTP_TCR_TSCFUPDT;
...
stmmac_config_hw_tstamping(priv, priv->ptpaddr, priv->systime_flags);
Suppose hwtstamp_set writes TCR with the old TSCFUPDT after devlink has
already computed SSINC and the addend for the new mode. Wouldn't the PHC
then run in one update mode with an increment computed for the other?
The same interleaving looks possible with stmmac_open(), because the
devlink param is registered at probe. This is still the case at the end
of the series.
> @@ -7708,6 +7722,8 @@ static int stmmac_dl_ts_coarse_set(struct devlink *dl, u32 id,
> * reconfigure the systime, subsecond increment and addend.
> */
> stmmac_update_subsecond_increment(priv);
> + write_unlock_irqrestore(&priv->ptp_lock, flags);
> + mutex_unlock(&priv->ptp_mutex);
[Severity: Medium]
Should the retained state be re-applied here?
stmmac_update_subsecond_increment() rewrites SSINC and programs the
uncorrected addend:
priv->default_addend = div_u64(temp, priv->plat->clk_ptp_rate);
stmmac_config_addend(priv, priv->ptpaddr, priv->default_addend);
After this the hardware has lost the frequency correction, but
priv->ptp_scaled_ppm still records it.
Running flex PPS outputs keep INTERVAL/WIDTH values that were computed in
units of the old sub_second_inc. Their real period and width therefore
change, while pps[] and ptp_perout still describe the requested period.
The next reset replays the cache, which abruptly changes frequency and PPS
timing again. This path already holds ptp_mutex. Could the replay done in
stmmac_ptp_restore() also be applied here? This is unchanged at the end of
the series.
[ ... ]
> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c
> index 3bfcc9760dce7..bc6fe0c87e01a 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c
[ ... ]
> @@ -128,14 +150,18 @@ static int stmmac_get_time(struct ptp_clock_info *ptp, struct timespec64 *ts)
> container_of(ptp, struct stmmac_priv, ptp_clock_ops);
> unsigned long flags;
> u64 ns = 0;
> + int ret = 0;
>
> read_lock_irqsave(&priv->ptp_lock, flags);
> - stmmac_get_systime(priv, priv->ptpaddr, &ns);
> + if (priv->ptp_blocked)
> + ret = -EBUSY;
> + else
> + stmmac_get_systime(priv, priv->ptpaddr, &ns);
> read_unlock_irqrestore(&priv->ptp_lock, flags);
>
> *ts = ns_to_timespec64(ns);
>
> - return 0;
> + return ret;
> }
[Severity: Medium]
What happens to in-kernel callers that ignore the gettime64 return value?
While blocked, this returns -EBUSY and also writes a zero timespec.
At this commit, tc_taprio_configure() ignores the result:
priv->ptp_clock_ops.gettime64(&priv->ptp_clock_ops, ¤t_time);
It would then compute the EST base time from 0. A later commit in the
series, "net: stmmac: restore TC offloads before restarting DMA", fixes
that caller: it checks the return value and runs under ptp_mutex.
The virtual clock path still looks affected at the end of the series.
stmmac has no getcycles64, so the core falls back to
ptp_getcycles64()->gettime64, and ptp_vclock_read() drops the error:
ptp->info->getcycles64(ptp->info, &ts);
return timespec64_to_ns(&ts);
The vclock uses a CYCLECOUNTER_MASK(32) cyclecounter. A refresh or read
that lands in a blocked window records a delta of
(0 - cycle_last) & 0xffffffff, and the next real read adds another one.
Can the vclock then jump by about 2^32 ns (4.29 s)?
The window also covers a case where the PHC is never reset:
stmmac_reconfigure_mtu() jumps to its restart label when
stmmac_prepare_rx_buffers() fails. Before this series the PHC was
unregistered across an MTU reopen, so vclocks could not see this state.
[ ... ]
> +static int stmmac_enable(struct ptp_clock_info *ptp,
> + struct ptp_clock_request *rq, int on)
> +{
> + struct stmmac_priv *priv =
> + container_of(ptp, struct stmmac_priv, ptp_clock_ops);
> + int ret;
> +
> + ret = stmmac_ptp_begin(priv);
> + if (ret)
> + return ret;
> + ret = __stmmac_enable(ptp, rq, on);
> + if (!ret) {
> + if (rq->type == PTP_CLK_REQ_PEROUT) {
> + if (on)
> + priv->ptp_perout |= BIT(rq->perout.index);
> + else
> + priv->ptp_perout &= ~BIT(rq->perout.index);
> + } else if (rq->type == PTP_CLK_REQ_EXTTS) {
> + priv->ptp_extts = on ? BIT(rq->extts.index) : 0;
> + }
> + }
[Severity: Low]
Can ptp_extts get out of sync with the hardware when the ATSFC poll
times out? In __stmmac_enable(), the new ATSEN state and
STMMAC_FLAG_EXT_SNAPSHOT_EN are written before the poll that can fail:
writel(acr_value, ptpaddr + PTP_ACR);
mutex_unlock(&priv->aux_ts_lock);
/* wait for auxts fifo clear to finish */
ret = readl_poll_timeout(ptpaddr + PTP_ACR, acr_value,
dwmac1000_ptp_enable() works the same way. It also calls
dwmac1000_timestamp_interrupt_cfg(priv, on) whatever the poll result.
Nothing is rolled back, but ptp_extts is only updated when ret == 0.
If a disable fails, the hardware is off while ptp_extts still holds
BIT(index). The next stmmac_ptp_restore() then re-enables an EXTTS
channel that the user asked to turn off. If an enable fails, the opposite
happens: the hardware keeps capturing, but replay won't re-arm the
channel.
> + mutex_unlock(&priv->ptp_mutex);
> + return ret;
> +}
> +
> +/* Called with ptp_mutex held and PHC access blocked across the MAC reset. */
> +int stmmac_ptp_restore(struct stmmac_priv *priv)
> +{
[ ... ]
> + for (i = 0; !ret && i < STMMAC_PPS_MAX; i++) {
> + struct stmmac_pps_cfg cfg = priv->pps[i];
> +
> + if (!(priv->ptp_perout & BIT(i)))
> + continue;
> + stmmac_get_systime(priv, priv->ptpaddr, &ns);
> + period = timespec64_to_ns(&cfg.period);
> + /* Retain phase, but move an expired target into the future. */
> + cfg.start = stmmac_calc_tas_basetime(timespec64_to_ktime(cfg.start),
> + ns + PTP_SAFE_TIME_OFFSET_NS, period);
[Severity: Low]
Are the cached PEROUT values checked before they are replayed here?
Whenever flex_pps_config accepts a request, __stmmac_enable() stores start
and period in priv->pps[] exactly as userspace supplied them.
A negative period such as sec=-1, nsec=999999999 wraps to a huge u64 in
the dwmac5/xgmac flex_pps_config, which accepts it. A negative start.sec
skips the fixup for start times in the past, because target_ns wraps.
On replay, timespec64_to_ns(&cfg.period) is negative and becomes
cycle_time for stmmac_calc_tas_basetime(). That function calls
div64_s64() with a negative divisor and does signed multiplications that
can overflow, so the resulting start time is meaningless. With
period == -1 and current - start == S64_MIN, the division would trap on
x86.
Would it make sense to reject non-positive or non-normalized periods and
negative starts before caching them?
[ ... ]
@@ -337,6 +337,12 @@ struct stmmac_priv {
int use_riwt;
int irq_wake;
rwlock_t ptp_lock;
+ /* Serialize PHC changes with a hardware reset; gettime uses ptp_lock. */
+ struct mutex ptp_mutex;
+ bool ptp_blocked;
+ long ptp_scaled_ppm;
+ u32 ptp_perout;
+ u32 ptp_extts;
/* Protects auxiliary snapshot registers from concurrent access. */
struct mutex aux_ts_lock;
wait_queue_head_t tstamp_busy_wait;
@@ -404,6 +410,7 @@ void stmmac_set_ethtool_ops(struct net_device *netdev);
void stmmac_ptp_register(struct stmmac_priv *priv);
void stmmac_ptp_unregister(struct stmmac_priv *priv);
+int stmmac_ptp_restore(struct stmmac_priv *priv);
int stmmac_xdp_open(struct net_device *dev);
void stmmac_xdp_release(struct net_device *dev);
int stmmac_get_phy_intf_sel(phy_interface_t interface);
@@ -941,6 +941,11 @@ static void stmmac_setup_ptp(struct stmmac_priv *priv)
{
int ret;
+ priv->ptp_scaled_ppm = 0;
+ priv->ptp_perout = 0;
+ priv->ptp_extts = 0;
+ priv->ptp_blocked = false;
+
ret = clk_prepare_enable(priv->plat->clk_ptp_ref);
if (ret < 0)
netdev_warn(priv->dev,
@@ -6285,7 +6290,8 @@ static void stmmac_common_interrupt(struct stmmac_priv *priv)
for (queue = 0; queue < queues_count; queue++)
stmmac_host_mtl_irq_status(priv, priv->hw, queue);
- stmmac_timestamp_interrupt(priv, priv);
+ if (!READ_ONCE(priv->ptp_blocked))
+ stmmac_timestamp_interrupt(priv, priv);
}
}
@@ -7696,6 +7702,14 @@ static int stmmac_dl_ts_coarse_set(struct devlink *dl, u32 id,
{
struct stmmac_devlink_priv *dl_priv = devlink_priv(dl);
struct stmmac_priv *priv = dl_priv->stmmac_priv;
+ unsigned long flags;
+
+ mutex_lock(&priv->ptp_mutex);
+ if (priv->ptp_blocked) {
+ mutex_unlock(&priv->ptp_mutex);
+ return -EBUSY;
+ }
+ write_lock_irqsave(&priv->ptp_lock, flags);
priv->tsfupdt_coarse = ctx->val.vbool;
@@ -7708,6 +7722,8 @@ static int stmmac_dl_ts_coarse_set(struct devlink *dl, u32 id,
* reconfigure the systime, subsecond increment and addend.
*/
stmmac_update_subsecond_increment(priv);
+ write_unlock_irqrestore(&priv->ptp_lock, flags);
+ mutex_unlock(&priv->ptp_mutex);
return 0;
}
@@ -8048,6 +8064,7 @@ static int __stmmac_dvr_probe(struct device *device,
mutex_init(&priv->lock);
rwlock_init(&priv->ptp_lock);
+ mutex_init(&priv->ptp_mutex);
stmmac_fpe_init(priv);
@@ -12,6 +12,16 @@
#define PTP_SAFE_TIME_OFFSET_NS 500000
+static int stmmac_ptp_begin(struct stmmac_priv *priv)
+{
+ mutex_lock(&priv->ptp_mutex);
+ if (priv->ptp_blocked) {
+ mutex_unlock(&priv->ptp_mutex);
+ return -EBUSY;
+ }
+ return 0;
+}
+
/**
* stmmac_adjust_freq
*
@@ -28,14 +38,21 @@ static int stmmac_adjust_freq(struct ptp_clock_info *ptp, long scaled_ppm)
container_of(ptp, struct stmmac_priv, ptp_clock_ops);
unsigned long flags;
u32 addend;
+ int ret;
- addend = adjust_by_scaled_ppm(priv->default_addend, scaled_ppm);
+ ret = stmmac_ptp_begin(priv);
+ if (ret)
+ return ret;
write_lock_irqsave(&priv->ptp_lock, flags);
- stmmac_config_addend(priv, priv->ptpaddr, addend);
+ addend = adjust_by_scaled_ppm(priv->default_addend, scaled_ppm);
+ ret = stmmac_config_addend(priv, priv->ptpaddr, addend);
+ if (!ret)
+ priv->ptp_scaled_ppm = scaled_ppm;
write_unlock_irqrestore(&priv->ptp_lock, flags);
+ mutex_unlock(&priv->ptp_mutex);
- return 0;
+ return ret;
}
/**
@@ -57,6 +74,10 @@ static int stmmac_adjust_time(struct ptp_clock_info *ptp, s64 delta)
bool xmac, est_rst = false;
int ret;
+ ret = stmmac_ptp_begin(priv);
+ if (ret)
+ return ret;
+
xmac = dwmac_is_xmac(priv->plat->core_type);
if (delta < 0) {
@@ -110,6 +131,7 @@ static int stmmac_adjust_time(struct ptp_clock_info *ptp, s64 delta)
netdev_err(priv->dev, "failed to configure EST\n");
}
+ mutex_unlock(&priv->ptp_mutex);
return 0;
}
@@ -128,14 +150,18 @@ static int stmmac_get_time(struct ptp_clock_info *ptp, struct timespec64 *ts)
container_of(ptp, struct stmmac_priv, ptp_clock_ops);
unsigned long flags;
u64 ns = 0;
+ int ret = 0;
read_lock_irqsave(&priv->ptp_lock, flags);
- stmmac_get_systime(priv, priv->ptpaddr, &ns);
+ if (priv->ptp_blocked)
+ ret = -EBUSY;
+ else
+ stmmac_get_systime(priv, priv->ptpaddr, &ns);
read_unlock_irqrestore(&priv->ptp_lock, flags);
*ts = ns_to_timespec64(ns);
- return 0;
+ return ret;
}
/**
@@ -153,25 +179,34 @@ static int stmmac_set_time(struct ptp_clock_info *ptp,
struct stmmac_priv *priv =
container_of(ptp, struct stmmac_priv, ptp_clock_ops);
unsigned long flags;
+ int ret;
+
+ ret = stmmac_ptp_begin(priv);
+ if (ret)
+ return ret;
write_lock_irqsave(&priv->ptp_lock, flags);
- stmmac_init_systime(priv, priv->ptpaddr, ts->tv_sec, ts->tv_nsec);
+ ret = stmmac_init_systime(priv, priv->ptpaddr, ts->tv_sec, ts->tv_nsec);
write_unlock_irqrestore(&priv->ptp_lock, flags);
+ mutex_unlock(&priv->ptp_mutex);
- return 0;
+ return ret;
}
-static int stmmac_enable(struct ptp_clock_info *ptp,
- struct ptp_clock_request *rq, int on)
+static int __stmmac_enable(struct ptp_clock_info *ptp,
+ struct ptp_clock_request *rq, int on)
{
struct stmmac_priv *priv =
container_of(ptp, struct stmmac_priv, ptp_clock_ops);
void __iomem *ptpaddr = priv->ptpaddr;
- struct stmmac_pps_cfg *cfg;
+ struct stmmac_pps_cfg pps, saved, *cfg = &pps;
int ret = -EOPNOTSUPP;
unsigned long flags;
u32 acr_value;
+ if (priv->plat->core_type == DWMAC_CORE_GMAC)
+ return dwmac1000_ptp_enable(ptp, rq, on);
+
switch (rq->type) {
case PTP_CLK_REQ_PEROUT: {
struct timespec64 curr_time;
@@ -182,8 +217,6 @@ static int stmmac_enable(struct ptp_clock_info *ptp,
if (rq->perout.flags)
return -EOPNOTSUPP;
- cfg = &priv->pps[rq->perout.index];
-
cfg->start.tv_sec = rq->perout.start.sec;
cfg->start.tv_nsec = rq->perout.start.nsec;
@@ -213,6 +246,7 @@ static int stmmac_enable(struct ptp_clock_info *ptp,
cfg->period.tv_sec = rq->perout.period.sec;
cfg->period.tv_nsec = rq->perout.period.nsec;
+ saved = *cfg;
write_lock_irqsave(&priv->ptp_lock, flags);
ret = stmmac_flex_pps_config(priv, priv->ioaddr,
@@ -220,6 +254,9 @@ static int stmmac_enable(struct ptp_clock_info *ptp,
priv->sub_second_inc,
priv->systime_flags);
write_unlock_irqrestore(&priv->ptp_lock, flags);
+ /* Some cores convert cfg->start to binary rollover units. */
+ if (!ret)
+ priv->pps[rq->perout.index] = saved;
break;
}
case PTP_CLK_REQ_EXTTS: {
@@ -265,6 +302,64 @@ static int stmmac_enable(struct ptp_clock_info *ptp,
return ret;
}
+static int stmmac_enable(struct ptp_clock_info *ptp,
+ struct ptp_clock_request *rq, int on)
+{
+ struct stmmac_priv *priv =
+ container_of(ptp, struct stmmac_priv, ptp_clock_ops);
+ int ret;
+
+ ret = stmmac_ptp_begin(priv);
+ if (ret)
+ return ret;
+ ret = __stmmac_enable(ptp, rq, on);
+ if (!ret) {
+ if (rq->type == PTP_CLK_REQ_PEROUT) {
+ if (on)
+ priv->ptp_perout |= BIT(rq->perout.index);
+ else
+ priv->ptp_perout &= ~BIT(rq->perout.index);
+ } else if (rq->type == PTP_CLK_REQ_EXTTS) {
+ priv->ptp_extts = on ? BIT(rq->extts.index) : 0;
+ }
+ }
+ mutex_unlock(&priv->ptp_mutex);
+ return ret;
+}
+
+/* Called with ptp_mutex held and PHC access blocked across the MAC reset. */
+int stmmac_ptp_restore(struct stmmac_priv *priv)
+{
+ struct ptp_clock_request rq = { .type = PTP_CLK_REQ_EXTTS };
+ unsigned long flags;
+ u64 ns = 0, period;
+ u32 addend;
+ int i, ret;
+
+ write_lock_irqsave(&priv->ptp_lock, flags);
+ addend = adjust_by_scaled_ppm(priv->default_addend, priv->ptp_scaled_ppm);
+ ret = stmmac_config_addend(priv, priv->ptpaddr, addend);
+ for (i = 0; !ret && i < STMMAC_PPS_MAX; i++) {
+ struct stmmac_pps_cfg cfg = priv->pps[i];
+
+ if (!(priv->ptp_perout & BIT(i)))
+ continue;
+ stmmac_get_systime(priv, priv->ptpaddr, &ns);
+ period = timespec64_to_ns(&cfg.period);
+ /* Retain phase, but move an expired target into the future. */
+ cfg.start = stmmac_calc_tas_basetime(timespec64_to_ktime(cfg.start),
+ ns + PTP_SAFE_TIME_OFFSET_NS, period);
+ ret = stmmac_flex_pps_config(priv, priv->ioaddr, i, &cfg, true,
+ priv->sub_second_inc, priv->systime_flags);
+ }
+ write_unlock_irqrestore(&priv->ptp_lock, flags);
+ if (ret || !priv->ptp_extts)
+ return ret;
+
+ rq.extts.index = __ffs(priv->ptp_extts);
+ return __stmmac_enable(&priv->ptp_clock_ops, &rq, 1);
+}
+
/**
* stmmac_get_syncdevicetime
* @device: current device time
@@ -287,9 +382,15 @@ static int stmmac_getcrosststamp(struct ptp_clock_info *ptp,
{
struct stmmac_priv *priv =
container_of(ptp, struct stmmac_priv, ptp_clock_ops);
+ int ret;
- return get_device_system_crosststamp(stmmac_get_syncdevicetime,
- priv, NULL, xtstamp);
+ ret = stmmac_ptp_begin(priv);
+ if (ret)
+ return ret;
+ ret = get_device_system_crosststamp(stmmac_get_syncdevicetime,
+ priv, NULL, xtstamp);
+ mutex_unlock(&priv->ptp_mutex);
+ return ret;
}
/* structure describing a PTP hardware clock */
@@ -323,7 +424,7 @@ const struct ptp_clock_info dwmac1000_ptp_clock_ops = {
.adjtime = stmmac_adjust_time,
.gettime64 = stmmac_get_time,
.settime64 = stmmac_set_time,
- .enable = dwmac1000_ptp_enable,
+ .enable = stmmac_enable,
};
/**