* [PATCH net] net: ti: icss-iep: fix deadlock when disabling PPS
@ 2026-11-07 6:37 hyunjungg
2026-11-07 6:40 ` netdev-bot+sinfo
` (2 more replies)
0 siblings, 3 replies; 5+ messages in thread
From: hyunjungg @ 2026-11-07 6:37 UTC (permalink / raw)
To: MD Danish Anwar, Roger Quadros, netdev
Cc: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, linux-arm-kernel, linux-kernel, Hyunjung Ko, stable
From: Hyunjung Ko <hj351016@gmail.com>
icss_iep_pps_enable() holds iep->ptp_clk_mutex across the whole function.
When PPS is being disabled it calls cancel_work_sync(&iep->work) while that
mutex is still held.
The work handler, icss_iep_cap_cmp_work(), acquires iep->ptp_clk_mutex at
its start. The capture-compare IRQ (icss_iep_cap_cmp_irq) schedules this
work on every CMP1 event while PPS is enabled, so a handler instance can be
running when userspace disables PPS. In that case cancel_work_sync() blocks
waiting for the handler to finish while holding ptp_clk_mutex, and the
handler blocks trying to acquire the same ptp_clk_mutex -> deadlock.
lockdep reports this as a possible circular locking dependency between
the work and ptp_clk_mutex.
Unlike a teardown path, the capture-compare IRQ is not disabled here, so
the race is reachable at runtime simply by toggling PPS off while the
hardware is generating CMP events.
Drain the work only after dropping ptp_clk_mutex. The hardware compare is
already disabled by icss_iep_perout_enable_hw() before the drain, so no new
work can be scheduled once the mutex is released.
Fixes: f18ad402cd8b ("net: ti: icss-iep: Enable compare events")
Cc: stable@vger.kernel.org
Signed-off-by: Hyunjung Ko <hj351016@gmail.com>
---
---
drivers/net/ethernet/ti/icssg/icss_iep.c | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/ti/icssg/icss_iep.c b/drivers/net/ethernet/ti/icssg/icss_iep.c
index ec08589..6264061 100644
--- a/drivers/net/ethernet/ti/icssg/icss_iep.c
+++ b/drivers/net/ethernet/ti/icssg/icss_iep.c
@@ -579,6 +579,7 @@ static int icss_iep_pps_enable(struct icss_iep *iep, int on)
{
struct ptp_clock_request rq;
struct timespec64 ts;
+ bool drain_work = false;
int ret = 0;
u64 ns;
@@ -607,7 +608,7 @@ static int icss_iep_pps_enable(struct icss_iep *iep, int on)
} else {
ret = icss_iep_perout_enable_hw(iep, &rq.perout, on);
if (iep->cap_cmp_irq)
- cancel_work_sync(&iep->work);
+ drain_work = true;
}
if (!ret)
@@ -616,6 +617,15 @@ static int icss_iep_pps_enable(struct icss_iep *iep, int on)
exit:
mutex_unlock(&iep->ptp_clk_mutex);
+ /*
+ * Drain the capture-compare work only after dropping ptp_clk_mutex.
+ * icss_iep_cap_cmp_work() acquires ptp_clk_mutex itself, so calling
+ * cancel_work_sync() while the mutex is held deadlocks when a handler
+ * instance is already running and waiting for that same mutex.
+ */
+ if (drain_work)
+ cancel_work_sync(&iep->work);
+
return ret;
}
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread* Re: [PATCH net] net: ti: icss-iep: fix deadlock when disabling PPS 2026-11-07 6:37 [PATCH net] net: ti: icss-iep: fix deadlock when disabling PPS hyunjungg @ 2026-11-07 6:40 ` netdev-bot+sinfo 2026-11-08 6:59 ` netdev-bot+sashiko 2026-11-08 7:47 ` [PATCH net v2] " hyunjungg 2 siblings, 0 replies; 5+ messages in thread From: netdev-bot+sinfo @ 2026-11-07 6:40 UTC (permalink / raw) To: hyunjungg Cc: MD Danish Anwar, Roger Quadros, netdev, Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-arm-kernel, linux-kernel, stable Hi! This is an automated message. This series looks like a fix, but its commit messages seem to be missing some information: - How the issue was discovered, e.g. hit in production, hit during development, syzbot report, manual code inspection, LLM or static analysis tool scan. - What hardware the change was tested on. For driver fixes please mention the device (and if relevant firmware version) used for testing, or say that the change was not tested on real hardware. Please do not repost the series just to address the above. Instead, reply to this email with the missing information, so that reviewers can take it into account. If the series needs another revision for other reasons, please include the information in the commit messages then. The evaluation is done by an LLM so it may be wrong, if you think that is the case please reply and explain. ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net] net: ti: icss-iep: fix deadlock when disabling PPS 2026-11-07 6:37 [PATCH net] net: ti: icss-iep: fix deadlock when disabling PPS hyunjungg 2026-11-07 6:40 ` netdev-bot+sinfo @ 2026-11-08 6:59 ` netdev-bot+sashiko 2026-11-08 7:47 ` khj 2026-11-08 7:47 ` [PATCH net v2] " hyunjungg 2 siblings, 1 reply; 5+ messages in thread From: netdev-bot+sashiko @ 2026-11-08 6:59 UTC (permalink / raw) To: hj351016 Cc: danishanwar, rogerq, netdev, andrew+netdev, davem, edumazet, kuba, pabeni, linux-arm-kernel, linux-kernel, stable Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider. Critical: 0 · High: 0 (1) · Medium: 3 · Low: 0 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). - [Medium] The patch moves cancel_work_sync(&iep->work) out of the ptp_clk_mutex critical section in icss_iep_pps_enable()… - [Medium] After the patch, an icss_iep_cap_cmp_work() instance can run after icss_iep_pps_enable(0) has disabled and cleared the compare… - [Medium] The commit message says that because the compare is disabled before the drain, 'no new work can be scheduled once the mutex is… Pre-existing issues: - [High] This problem wasn't introduced by this patch, but the shared iep->work is never drained when periodic output (perout) is disabled,… -- net: ti: icss-iep: fix deadlock when disabling PPS This commit moves cancel_work_sync(&iep->work) in icss_iep_pps_enable() out of the ptp_clk_mutex critical section. The PPS disable path then no longer waits for icss_iep_cap_cmp_work() while it holds the mutex that the work handler needs. > diff --git a/drivers/net/ethernet/ti/icssg/icss_iep.c b/drivers/net/ethernet/ti/icssg/icss_iep.c > index ec085897edf09..6264061a4eab3 100644 > --- a/drivers/net/ethernet/ti/icssg/icss_iep.c > +++ b/drivers/net/ethernet/ti/icssg/icss_iep.c [ ... ] > @@ -607,7 +608,7 @@ static int icss_iep_pps_enable(struct icss_iep *iep, int on) > } else { > ret = icss_iep_perout_enable_hw(iep, &rq.perout, on); > if (iep->cap_cmp_irq) > - cancel_work_sync(&iep->work); > + drain_work = true; > } > > if (!ret) > @@ -616,6 +617,15 @@ static int icss_iep_pps_enable(struct icss_iep *iep, int on) > exit: > mutex_unlock(&iep->ptp_clk_mutex); [Severity: Medium] Once the mutex is dropped here, can icss_iep_cap_cmp_work() run against compare hardware that has just been disabled and cleared? There seem to be two ways for this to happen: - A handler already blocked in mutex_lock(&iep->ptp_clk_mutex) now continues. This is the case the patch fixes. - A work item that was pending but not yet started begins running between mutex_unlock() and cancel_work_sync(). In that case cancel_work_sync() only waits for it to finish. The handler doesn't check pps_enabled or perout_enabled after it takes the mutex: icss_iep_cap_cmp_work() { mutex_lock(&iep->ptp_clk_mutex); ns = readl(iep->base + reg_offs[ICSS_IEP_CMP1_REG0]); ... ns_next = ns + iep->period; writel(lower_32_bits(ns_next), iep->base + reg_offs[ICSS_IEP_CMP1_REG0]); ... pevent.pps_times.ts_real = ns_to_timespec64(ns); pevent.type = PTP_CLOCK_PPSUSR; pevent.index = 0; ptp_clock_event(iep->ptp_clock, &pevent); ... } icss_iep_perout_enable_hw(on=0) has just written 0 to CMP1_REG0/REG1. The late handler would then set CMP1 to 0 + iep->period, which undoes the "clear CMP regs" step. It would also send a PTP_CLOCK_PPSUSR event with a zero (1970) timestamp. pps_event() doesn't check whether PPS is enabled. It records the assert, increments assert_sequence, wakes /dev/ppsN readers and calls pps_kc_event(), all after userspace has turned PPS off. Before this patch, the blocked case deadlocked and the pending case was cancelled under the mutex, so this behaviour is new. Should icss_iep_cap_cmp_work() return early under the mutex when neither pps_enabled nor perout_enabled is set? > > + /* > + * Drain the capture-compare work only after dropping ptp_clk_mutex. > + * icss_iep_cap_cmp_work() acquires ptp_clk_mutex itself, so calling > + * cancel_work_sync() while the mutex is held deadlocks when a handler > + * instance is already running and waiting for that same mutex. > + */ > + if (drain_work) > + cancel_work_sync(&iep->work); [Severity: Medium] The drain now runs outside ptp_clk_mutex and doesn't re-check pps_enabled or perout_enabled. Could it cancel work that belongs to a later enable? pps_enable_store() and period_store() in drivers/ptp/ptp_sysfs.c call ops->enable() without ptp->pincfg_mux. That means they aren't serialized against the PTP_ENABLE_PPS ioctl path or against each other. One possible sequence: CPU0 (disable) CPU1 (sysfs enable) icss_iep_pps_enable(iep, 0) pps_enabled = false mutex_unlock() icss_iep_pps_enable(iep, 1) program CMP1, set CMP_EN pps_enabled = true first CMP1 hit: icss_iep_cap_cmp_irq() schedule_work(&iep->work) cancel_work_sync(&iep->work) removes the new session's work icss_iep_cap_cmp_work() is the only code that moves CMP1 forward. Once its work is cancelled, no further compare events or PPS pulses are produced, while the driver still reports pps_enabled == true. The other order is possible as well. Suppose an old-session work instance is blocked in mutex_lock() and gets the mutex after CPU1's enable. It would read the new boundary B from CMP1, write B + period and send a PPSUSR event stamped B before time B arrives. The first real edge of the new session would be skipped. Before this patch the cancel ran under ptp_clk_mutex, so a concurrent enable had to wait for it. [Severity: Medium] The commit message says: The hardware compare is already disabled by icss_iep_perout_enable_hw() before the drain, so no new work can be scheduled once the mutex is released. Is that guaranteed? icss_iep_cap_cmp_irq() reads pps_enabled with no lock and no ordering against this path. Nothing calls synchronize_irq(iep->cap_cmp_irq) before the drain: icss_iep_cap_cmp_irq() { val = readl(iep->base + reg_offs[ICSS_IEP_CMP_STAT_REG]); if (val & BIT(1)) { writel(BIT(1), iep->base + reg_offs[ICSS_IEP_CMP_STAT_REG]); if (iep->pps_enabled || iep->perout_enabled) schedule_work(&iep->work); ... } Suppose a CMP1 hit was latched just before CMP_EN was cleared, and its handler on CPU1 has already read pps_enabled == true. CPU0 can then set pps_enabled = false, drop the mutex and call cancel_work_sync(). That call returns immediately because nothing is pending. CPU1 then calls schedule_work(), and the work runs after PPS has been disabled. On teardown, ptp_clock_unregister() -> ptp_disable_all_events() -> icss_iep_pps_enable(0) runs first. After that the pps source and ptp_clock are released, and icss_iep_exit() sets iep->ptp_clock to NULL. A work item queued this late could call ptp_clock_event() on a freed or NULL ptp_clock. On PREEMPT_RT the handler is force-threaded and can be preempted between the flag read and schedule_work(), so the window is wider there. The old code had the same race with a larger window. However, the commit message's argument depends on this window being closed. Would a synchronize_irq() after clearing pps_enabled, together with a state check in the work handler, close it? > + > return ret; > } > [Severity: High] This isn't a bug introduced by this patch, but the perout disable path has a related problem. icss_iep_cap_cmp_irq() also queues iep->work when perout_enabled is set, but icss_iep_perout_enable(..., 0) never drains it: icss_iep_perout_enable() { ... ret = icss_iep_perout_enable_hw(iep, req, on); if (!ret) iep->perout_enabled = !!on; exit: mutex_unlock(&iep->ptp_clk_mutex); return ret; } On teardown, icss_iep_exit() unregisters the clock and sets it to NULL before perout is turned off: icss_iep_exit() { if (iep->ptp_clock) { ptp_clock_unregister(iep->ptp_clock); iep->ptp_clock = NULL; } icss_iep_disable(iep); if (iep->pps_enabled) icss_iep_pps_enable(iep, false); else if (iep->perout_enabled) icss_iep_perout_enable(iep, NULL, false); ... } The ptp_disable_all_events() call inside ptp_clock_unregister() only disables EXTTS and PPS. The PPS disable returns -EBUSY while perout is enabled, so the work isn't drained there either. Can a work item queued while perout is active run icss_iep_cap_cmp_work() after this point? It could be queued before teardown, or by an IRQ that arrives before CMP_EN is cleared. If it runs, ptp_clock_event() would use a NULL or freed iep->ptp_clock and pps_device. This can be reached from ndo_stop via prueth_emac_common_stop(), or from prueth_remove(). It needs an IEP with an iep_cap_cmp interrupt and the icssg_prueth clockops, which set n_per_out = 1. Also, iep->ptp_clock = NULL is assigned without holding ptp_clk_mutex. Should icss_iep_perout_enable() also call cancel_work_sync() on disable? And should icss_iep_exit() drain iep->work before it unregisters the clock? -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261107063726.3388730-1-hj351016%40gmail.com ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net] net: ti: icss-iep: fix deadlock when disabling PPS 2026-11-08 6:59 ` netdev-bot+sashiko @ 2026-11-08 7:47 ` khj 0 siblings, 0 replies; 5+ messages in thread From: khj @ 2026-11-08 7:47 UTC (permalink / raw) To: netdev-bot+sashiko Cc: danishanwar, rogerq, netdev, andrew+netdev, davem, edumazet, kuba, pabeni, linux-arm-kernel, linux-kernel, stable Thanks, this is a good catch. All three are the same underlying point: dropping ptp_clk_mutex before the drain lets a work item that was already queued (or latched by the IRQ just before the compare was disabled) run icss_iep_cap_cmp_work() after PPS has been turned off, where it re-arms CMP1 and emits a stale PPS event. v2 fixes that by guarding the handler: it now returns early, under ptp_clk_mutex, when neither pps_enabled nor perout_enabled is set, so a late or stale work does nothing instead of touching the (just-disabled) compare hardware or emitting an event. That covers the "handler already blocked on the mutex" case and the "pending work starts between unlock and cancel" case. On the concurrent-sysfs window (pps_enable_store / period_store not serialized against the ioctl path): with the guard, a work cancelled by a disable that races a fresh enable simply bails if the enable has not yet set the flag; I don't think this patch can fully close the ptp_sysfs serialization gap, which predates it. On the pre-existing perout/teardown issue you flagged (High): you're right that icss_iep_perout_enable(.., 0) never drains iep->work, and that icss_iep_exit() clears iep->ptp_clock before the perout disable runs, so a work queued during perout can still reach ptp_clock_event() on teardown. The handler guard reduces but does not fully close that, because on the teardown path perout_enabled can still be set when the work runs. I'd prefer to fix the perout-disable drain and the exit ordering as a separate change rather than fold a teardown rework into this deadlock fix — happy to send it as a follow-up unless you'd rather have it here. Not tested on real hardware (no TI ICSSG/PRU device); the above is from code inspection. pw-bot: cr 2026년 11월 8일 (일) 오후 3:59, <netdev-bot+sashiko@kernel.org>님이 작성: > > Thank you for your contribution! Sashiko AI review found 3 potential > issue(s) to consider. > > Critical: 0 · High: 0 (1) · Medium: 3 · Low: 0 > > 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). > > - [Medium] The patch moves cancel_work_sync(&iep->work) out of the > ptp_clk_mutex critical section in icss_iep_pps_enable()… > - [Medium] After the patch, an icss_iep_cap_cmp_work() instance can run > after icss_iep_pps_enable(0) has disabled and cleared the compare… > - [Medium] The commit message says that because the compare is disabled > before the drain, 'no new work can be scheduled once the mutex is… > > Pre-existing issues: > - [High] This problem wasn't introduced by this patch, but the shared > iep->work is never drained when periodic output (perout) is disabled,… > > -- > > net: ti: icss-iep: fix deadlock when disabling PPS > > This commit moves cancel_work_sync(&iep->work) in icss_iep_pps_enable() > out of the ptp_clk_mutex critical section. The PPS disable path then no > longer waits for icss_iep_cap_cmp_work() while it holds the mutex that the > work handler needs. > > > diff --git a/drivers/net/ethernet/ti/icssg/icss_iep.c b/drivers/net/ethernet/ti/icssg/icss_iep.c > > index ec085897edf09..6264061a4eab3 100644 > > --- a/drivers/net/ethernet/ti/icssg/icss_iep.c > > +++ b/drivers/net/ethernet/ti/icssg/icss_iep.c > > [ ... ] > > > @@ -607,7 +608,7 @@ static int icss_iep_pps_enable(struct icss_iep *iep, int on) > > } else { > > ret = icss_iep_perout_enable_hw(iep, &rq.perout, on); > > if (iep->cap_cmp_irq) > > - cancel_work_sync(&iep->work); > > + drain_work = true; > > } > > > > if (!ret) > > @@ -616,6 +617,15 @@ static int icss_iep_pps_enable(struct icss_iep *iep, int on) > > exit: > > mutex_unlock(&iep->ptp_clk_mutex); > > [Severity: Medium] > Once the mutex is dropped here, can icss_iep_cap_cmp_work() run against > compare hardware that has just been disabled and cleared? > > There seem to be two ways for this to happen: > > - A handler already blocked in mutex_lock(&iep->ptp_clk_mutex) now > continues. This is the case the patch fixes. > > - A work item that was pending but not yet started begins running > between mutex_unlock() and cancel_work_sync(). In that case > cancel_work_sync() only waits for it to finish. > > The handler doesn't check pps_enabled or perout_enabled after it takes the > mutex: > > icss_iep_cap_cmp_work() { > mutex_lock(&iep->ptp_clk_mutex); > > ns = readl(iep->base + reg_offs[ICSS_IEP_CMP1_REG0]); > ... > ns_next = ns + iep->period; > writel(lower_32_bits(ns_next), > iep->base + reg_offs[ICSS_IEP_CMP1_REG0]); > ... > pevent.pps_times.ts_real = ns_to_timespec64(ns); > pevent.type = PTP_CLOCK_PPSUSR; > pevent.index = 0; > ptp_clock_event(iep->ptp_clock, &pevent); > ... > } > > icss_iep_perout_enable_hw(on=0) has just written 0 to CMP1_REG0/REG1. The > late handler would then set CMP1 to 0 + iep->period, which undoes the > "clear CMP regs" step. It would also send a PTP_CLOCK_PPSUSR event with a > zero (1970) timestamp. > > pps_event() doesn't check whether PPS is enabled. It records the assert, > increments assert_sequence, wakes /dev/ppsN readers and calls > pps_kc_event(), all after userspace has turned PPS off. > > Before this patch, the blocked case deadlocked and the pending case was > cancelled under the mutex, so this behaviour is new. > > Should icss_iep_cap_cmp_work() return early under the mutex when neither > pps_enabled nor perout_enabled is set? > > > > > + /* > > + * Drain the capture-compare work only after dropping ptp_clk_mutex. > > + * icss_iep_cap_cmp_work() acquires ptp_clk_mutex itself, so calling > > + * cancel_work_sync() while the mutex is held deadlocks when a handler > > + * instance is already running and waiting for that same mutex. > > + */ > > + if (drain_work) > > + cancel_work_sync(&iep->work); > > [Severity: Medium] > The drain now runs outside ptp_clk_mutex and doesn't re-check > pps_enabled or perout_enabled. Could it cancel work that belongs to a > later enable? > > pps_enable_store() and period_store() in drivers/ptp/ptp_sysfs.c call > ops->enable() without ptp->pincfg_mux. That means they aren't serialized > against the PTP_ENABLE_PPS ioctl path or against each other. > > One possible sequence: > > CPU0 (disable) CPU1 (sysfs enable) > icss_iep_pps_enable(iep, 0) > pps_enabled = false > mutex_unlock() > icss_iep_pps_enable(iep, 1) > program CMP1, set CMP_EN > pps_enabled = true > first CMP1 hit: > icss_iep_cap_cmp_irq() > schedule_work(&iep->work) > cancel_work_sync(&iep->work) > removes the new session's work > > icss_iep_cap_cmp_work() is the only code that moves CMP1 forward. Once > its work is cancelled, no further compare events or PPS pulses are > produced, while the driver still reports pps_enabled == true. > > The other order is possible as well. Suppose an old-session work instance > is blocked in mutex_lock() and gets the mutex after CPU1's enable. It > would read the new boundary B from CMP1, write B + period and send a > PPSUSR event stamped B before time B arrives. The first real edge of the > new session would be skipped. > > Before this patch the cancel ran under ptp_clk_mutex, so a concurrent > enable had to wait for it. > > [Severity: Medium] > The commit message says: > > The hardware compare is > already disabled by icss_iep_perout_enable_hw() before the drain, so no new > work can be scheduled once the mutex is released. > > Is that guaranteed? icss_iep_cap_cmp_irq() reads pps_enabled with no lock > and no ordering against this path. Nothing calls > synchronize_irq(iep->cap_cmp_irq) before the drain: > > icss_iep_cap_cmp_irq() { > val = readl(iep->base + reg_offs[ICSS_IEP_CMP_STAT_REG]); > if (val & BIT(1)) { > writel(BIT(1), iep->base + reg_offs[ICSS_IEP_CMP_STAT_REG]); > if (iep->pps_enabled || iep->perout_enabled) > schedule_work(&iep->work); > ... > } > > Suppose a CMP1 hit was latched just before CMP_EN was cleared, and its > handler on CPU1 has already read pps_enabled == true. > > CPU0 can then set pps_enabled = false, drop the mutex and call > cancel_work_sync(). That call returns immediately because nothing is > pending. > > CPU1 then calls schedule_work(), and the work runs after PPS has been > disabled. > > On teardown, ptp_clock_unregister() -> ptp_disable_all_events() -> > icss_iep_pps_enable(0) runs first. After that the pps source and > ptp_clock are released, and icss_iep_exit() sets iep->ptp_clock to NULL. > A work item queued this late could call ptp_clock_event() on a freed or > NULL ptp_clock. > > On PREEMPT_RT the handler is force-threaded and can be preempted between > the flag read and schedule_work(), so the window is wider there. > > The old code had the same race with a larger window. However, the commit > message's argument depends on this window being closed. > > Would a synchronize_irq() after clearing pps_enabled, together with a > state check in the work handler, close it? > > > + > > return ret; > > } > > > > [Severity: High] > This isn't a bug introduced by this patch, but the perout disable path has > a related problem. icss_iep_cap_cmp_irq() also queues iep->work when > perout_enabled is set, but icss_iep_perout_enable(..., 0) never drains it: > > icss_iep_perout_enable() { > ... > ret = icss_iep_perout_enable_hw(iep, req, on); > if (!ret) > iep->perout_enabled = !!on; > > exit: > mutex_unlock(&iep->ptp_clk_mutex); > > return ret; > } > > On teardown, icss_iep_exit() unregisters the clock and sets it to NULL > before perout is turned off: > > icss_iep_exit() { > if (iep->ptp_clock) { > ptp_clock_unregister(iep->ptp_clock); > iep->ptp_clock = NULL; > } > icss_iep_disable(iep); > > if (iep->pps_enabled) > icss_iep_pps_enable(iep, false); > else if (iep->perout_enabled) > icss_iep_perout_enable(iep, NULL, false); > ... > } > > The ptp_disable_all_events() call inside ptp_clock_unregister() only > disables EXTTS and PPS. The PPS disable returns -EBUSY while perout is > enabled, so the work isn't drained there either. > > Can a work item queued while perout is active run icss_iep_cap_cmp_work() > after this point? It could be queued before teardown, or by an IRQ that > arrives before CMP_EN is cleared. If it runs, ptp_clock_event() would use > a NULL or freed iep->ptp_clock and pps_device. > > This can be reached from ndo_stop via prueth_emac_common_stop(), or from > prueth_remove(). It needs an IEP with an iep_cap_cmp interrupt and the > icssg_prueth clockops, which set n_per_out = 1. > > Also, iep->ptp_clock = NULL is assigned without holding ptp_clk_mutex. > > Should icss_iep_perout_enable() also call cancel_work_sync() on disable? > And should icss_iep_exit() drain iep->work before it unregisters the > clock? > > -- > Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261107063726.3388730-1-hj351016%40gmail.com ^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH net v2] net: ti: icss-iep: fix deadlock when disabling PPS 2026-11-07 6:37 [PATCH net] net: ti: icss-iep: fix deadlock when disabling PPS hyunjungg 2026-11-07 6:40 ` netdev-bot+sinfo 2026-11-08 6:59 ` netdev-bot+sashiko @ 2026-11-08 7:47 ` hyunjungg 2 siblings, 0 replies; 5+ messages in thread From: hyunjungg @ 2026-11-08 7:47 UTC (permalink / raw) To: Danish Anwar, Roger Quadros Cc: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, netdev, linux-arm-kernel, linux-kernel, stable, Hyunjung Ko From: Hyunjung Ko <hj351016@gmail.com> icss_iep_pps_enable() holds iep->ptp_clk_mutex across the whole function. When PPS is being disabled it calls cancel_work_sync(&iep->work) while that mutex is still held. The work handler, icss_iep_cap_cmp_work(), acquires iep->ptp_clk_mutex at its start. The capture-compare IRQ (icss_iep_cap_cmp_irq) schedules this work on every CMP1 event while PPS is enabled, so a handler instance can be running when userspace disables PPS. In that case cancel_work_sync() blocks waiting for the handler to finish while holding ptp_clk_mutex, and the handler blocks trying to acquire the same ptp_clk_mutex -> deadlock. lockdep would report this as a possible circular locking dependency between the work and ptp_clk_mutex. Unlike a teardown path, the capture-compare IRQ is not disabled here, so the race is reachable at runtime simply by toggling PPS off while the hardware is generating CMP events. Drain the work only after dropping ptp_clk_mutex, so the disable path no longer waits for the handler while holding the lock the handler needs. Dropping the mutex before the drain means a work item that was already queued (or an IRQ latched just before the compare was disabled) can still run icss_iep_cap_cmp_work() after PPS/perout has been turned off. Guard the handler so it returns early, under the mutex, when neither pps_enabled nor perout_enabled is set, instead of re-arming CMP1 and emitting a stale PPS event once the event source is gone. This was found by a static analysis tool -- an in-house shape/pattern checker that flags cancel_work_sync() calls made while holding a lock the cancelled work's handler also acquires -- and confirmed by manual review of the PPS enable/disable path. It was not tested on real hardware: I do not have a TI ICSSG/PRU (AM65x-family) device, so the deadlock and the late-work window are established by code inspection rather than an observed lockdep or KASAN trace. Fixes: f18ad402cd8b ("net: ti: icss-iep: Enable compare events") Cc: stable@vger.kernel.org Signed-off-by: Hyunjung Ko <hj351016@gmail.com> --- v2: - Guard icss_iep_cap_cmp_work() to return early, under ptp_clk_mutex, when PPS/perout is disabled, so a work item that runs after the mutex is dropped no longer re-arms the compare or emits a stale PPS event (addresses the late-work windows raised in review of v1). - Soften "lockdep reports" -> "lockdep would report" (not an observed trace). - Record discovery (static analysis + manual inspection) and that the change was not tested on real hardware in the commit message, as requested by the netdev commit-message bot. v1: https://lore.kernel.org/netdev/20261107063726.3388730-1-hj351016@gmail.com/ drivers/net/ethernet/ti/icssg/icss_iep.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/ti/icssg/icss_iep.c b/drivers/net/ethernet/ti/icssg/icss_iep.c index ec08589..3b2a88c 100644 --- a/drivers/net/ethernet/ti/icssg/icss_iep.c +++ b/drivers/net/ethernet/ti/icssg/icss_iep.c @@ -534,6 +534,17 @@ static void icss_iep_cap_cmp_work(struct work_struct *work) mutex_lock(&iep->ptp_clk_mutex); + /* + * PPS/perout may have been disabled (and the compare hardware + * cleared) while this work was queued or waiting for the mutex. + * Bail out rather than re-arm CMP1 and emit a stale PPS event once + * the event source is gone. + */ + if (!iep->pps_enabled && !iep->perout_enabled) { + mutex_unlock(&iep->ptp_clk_mutex); + return; + } + ns = readl(iep->base + reg_offs[ICSS_IEP_CMP1_REG0]); if (iep->plat_data->flags & ICSS_IEP_64BIT_COUNTER_SUPPORT) { val = readl(iep->base + reg_offs[ICSS_IEP_CMP1_REG1]); @@ -579,6 +590,7 @@ static int icss_iep_pps_enable(struct icss_iep *iep, int on) { struct ptp_clock_request rq; struct timespec64 ts; + bool drain_work = false; int ret = 0; u64 ns; @@ -607,7 +619,7 @@ static int icss_iep_pps_enable(struct icss_iep *iep, int on) } else { ret = icss_iep_perout_enable_hw(iep, &rq.perout, on); if (iep->cap_cmp_irq) - cancel_work_sync(&iep->work); + drain_work = true; } if (!ret) @@ -616,6 +628,15 @@ static int icss_iep_pps_enable(struct icss_iep *iep, int on) exit: mutex_unlock(&iep->ptp_clk_mutex); + /* + * Drain the capture-compare work only after dropping ptp_clk_mutex. + * icss_iep_cap_cmp_work() takes ptp_clk_mutex itself, so cancelling it + * under the mutex deadlocks against a handler instance already running + * and waiting for that same mutex. + */ + if (drain_work) + cancel_work_sync(&iep->work); + return ret; } -- 2.43.0 ^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-11-08 7:48 UTC | newest] Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2026-11-07 6:37 [PATCH net] net: ti: icss-iep: fix deadlock when disabling PPS hyunjungg 2026-11-07 6:40 ` netdev-bot+sinfo 2026-11-08 6:59 ` netdev-bot+sashiko 2026-11-08 7:47 ` khj 2026-11-08 7:47 ` [PATCH net v2] " hyunjungg
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®