ptp: drain virtual clock sysfs operations before unregistering children

Parent clock removal walks its virtual clocks before removing the
n_vclocks sysfs attribute. The mutex taken by ptp_vclock_in_use() is
released before that walk, so a concurrent sysfs deletion can pick the
same child and unregister and free its ptp_vclock a second time. A
reference held by the device iterator does not protect that separately
allocated virtual clock.

Remove n_vclocks before walking the children. Removing the attribute
prevents new stores and drains stores already running, without holding
n_vclocks_mux across a callback that needs that mutex. Skip virtual
clocks, which do not have this attribute.

This race was found by code inspection of virtual-clock registration
failure cleanup and parent removal.

Fixes: bfcbb76b0f ("ptp: unregister virtual clocks when unregistering physical clock.")
Reviewed-by: Vadim Fedorenko <vadim.fedorenko@linux.dev>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
Link: https://patch.msgid.link/20261026-ptp-vclock-sampling-v6-1-e705612541f1@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
James Hilliard 2026-10-26 12:11:27 -06:00 committed by Jakub Kicinski
parent 603a0c40d4
commit 0e4a135aeb
3 changed files with 12 additions and 0 deletions

View File

@ -508,6 +508,11 @@ static int unregister_vclock(struct device *dev, void *data)
int ptp_clock_unregister(struct ptp_clock *ptp)
{
/*
* Stop and drain virtual-clock creation and deletion before walking the
* children. Do not hold n_vclocks_mux while waiting for sysfs callbacks.
*/
ptp_vclock_remove_sysfs(ptp);
if (ptp_vclock_in_use(ptp)) {
device_for_each_child(&ptp->dev, NULL, unregister_vclock);
}

View File

@ -169,6 +169,7 @@ extern const struct attribute_group *ptp_groups[];
int ptp_populate_pin_groups(struct ptp_clock *ptp);
void ptp_cleanup_pin_groups(struct ptp_clock *ptp);
void ptp_vclock_remove_sysfs(struct ptp_clock *ptp);
struct ptp_vclock *ptp_vclock_register(struct ptp_clock *pclock);
void ptp_vclock_unregister(struct ptp_vclock *vclock);

View File

@ -267,6 +267,12 @@ static ssize_t n_vclocks_store(struct device *dev,
}
static DEVICE_ATTR_RW(n_vclocks);
void ptp_vclock_remove_sysfs(struct ptp_clock *ptp)
{
if (!ptp->is_virtual_clock)
device_remove_file(&ptp->dev, &dev_attr_n_vclocks);
}
static ssize_t max_vclocks_show(struct device *dev,
struct device_attribute *attr, char *page)
{