[PATCH v1] ptp: idt82p33: Stop PTP work producers before teardown

From: Yibo Tan

Date: Tue Sep 22 2026 - 07:38:27 EST


idt82p33_ptp_clock_unregister_all() cancels each channel's adjtime work
before unregistering its PTP clock. An adjustment callback already in
progress can schedule that work after cancellation returns. Device
removal then frees the channel while its delayed work remains queued,
causing a use-after-free.

Mark the device as stopping under its lock before draining work. Reject
new adjustment and EXTS requests, clear the EXTS polling mask, and
synchronously drain the EXTS and channel adjustment work before the
device storage is released.

With an i2c-stub device and kprobe-controlled callback timing, three
runs of the unmodified driver reported KASAN use-after-free after
unbind. Three runs with this change completed without a kernel
diagnostic. No physical IDT82P33 device was tested.

Signed-off-by: Yibo Tan <lhfff@xxxxxxxxxx>
---
drivers/ptp/ptp_idt82p33.c | 25 +++++++++++++++++++++----
drivers/ptp/ptp_idt82p33.h | 1 +
2 files changed, 22 insertions(+), 4 deletions(-)

diff --git a/drivers/ptp/ptp_idt82p33.c b/drivers/ptp/ptp_idt82p33.c
index f01c50dfa44e8..ffc2ec1fc4bed 100644
--- a/drivers/ptp/ptp_idt82p33.c
+++ b/drivers/ptp/ptp_idt82p33.c
@@ -918,6 +918,13 @@ static void idt82p33_ptp_clock_unregister_all(struct idt82p33 *idt82p33)
struct idt82p33_channel *channel;
u8 i;

+ mutex_lock(idt82p33->lock);
+ idt82p33->stopping = true;
+ idt82p33->extts_mask = 0;
+ mutex_unlock(idt82p33->lock);
+
+ cancel_delayed_work_sync(&idt82p33->extts_work);
+
for (i = 0; i < MAX_PHC_PLL; i++) {
channel = &idt82p33->channel[i];
cancel_delayed_work_sync(&channel->adjtime_work);
@@ -937,6 +944,10 @@ static int idt82p33_enable(struct ptp_clock_info *ptp,
int err = -EOPNOTSUPP;

mutex_lock(idt82p33->lock);
+ if (idt82p33->stopping) {
+ err = -ENODEV;
+ goto out;
+ }

switch (rq->type) {
case PTP_CLK_REQ_PEROUT:
@@ -958,6 +969,7 @@ static int idt82p33_enable(struct ptp_clock_info *ptp,
break;
}

+out:
mutex_unlock(idt82p33->lock);

if (err)
@@ -1044,11 +1056,14 @@ static int idt82p33_adjtime(struct ptp_clock_info *ptp, s64 delta_ns)
return -EBUSY;

mutex_lock(idt82p33->lock);
+ if (idt82p33->stopping) {
+ err = -ENODEV;
+ goto out;
+ }

if (abs(delta_ns) < phase_snap_threshold) {
err = idt82p33_start_ddco(channel, delta_ns);
- mutex_unlock(idt82p33->lock);
- return err;
+ goto out;
}

/* Use more accurate internal 1pps triggered write first */
@@ -1056,6 +1071,7 @@ static int idt82p33_adjtime(struct ptp_clock_info *ptp, s64 delta_ns)
if (err && delta_ns > IMMEDIATE_SNAP_THRESHOLD_NS)
err = _idt82p33_adjtime_immediate(channel, delta_ns);

+out:
mutex_unlock(idt82p33->lock);

if (err)
@@ -1342,6 +1358,8 @@ static void idt82p33_extts_check(struct work_struct *work)
return;

mutex_lock(idt82p33->lock);
+ if (idt82p33->stopping)
+ goto out;

for (i = 0; i < MAX_PHC_PLL; i++) {
mask = 1 << i;
@@ -1367,6 +1385,7 @@ static void idt82p33_extts_check(struct work_struct *work)
schedule_delayed_work(&idt82p33->extts_work,
msecs_to_jiffies(EXTTS_PERIOD_MS));

+out:
mutex_unlock(idt82p33->lock);
}

@@ -1442,8 +1461,6 @@ static void idt82p33_remove(struct platform_device *pdev)
{
struct idt82p33 *idt82p33 = platform_get_drvdata(pdev);

- cancel_delayed_work_sync(&idt82p33->extts_work);
-
idt82p33_ptp_clock_unregister_all(idt82p33);
}

diff --git a/drivers/ptp/ptp_idt82p33.h b/drivers/ptp/ptp_idt82p33.h
index 6a63c14b6966b..b88c921adaa91 100644
--- a/drivers/ptp/ptp_idt82p33.h
+++ b/drivers/ptp/ptp_idt82p33.h
@@ -91,6 +91,7 @@ struct idt82p33 {
u8 extts_mask;
bool extts_single_shot;
struct delayed_work extts_work;
+ bool stopping;
/* Remember the ptp channel to report extts */
struct idt82p33_channel *event_channel[MAX_PHC_PLL];
/* Mutex to protect operations from being interrupted */
--
2.39.5