diff --git a/drivers/base/core.c b/drivers/base/core.c index 6fab0005e88026571af10c5374e1c1f1809634db..82b50a89cedc7e2baf488a76dd87a4fa6aed2caf 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -53,6 +53,7 @@ static unsigned int defer_sync_state_count = 1; static unsigned int defer_fw_devlink_count; static LIST_HEAD(deferred_fw_devlink); static DEFINE_MUTEX(defer_fw_devlink_lock); +static struct workqueue_struct *device_link_wq; static bool fw_devlink_is_permissive(void); #ifdef CONFIG_SRCU @@ -364,12 +365,26 @@ static void devlink_dev_release(struct device *dev) /* * It may take a while to complete this work because of the SRCU * synchronization in device_link_release_fn() and if the consumer or - * supplier devices get deleted when it runs, so put it into the "long" - * workqueue. + * supplier devices get deleted when it runs, so put it into the + * dedicated workqueue. */ - queue_work(system_long_wq, &link->rm_work); + queue_work(device_link_wq, &link->rm_work); } +/** + * device_link_wait_removal - Wait for ongoing devlink removal jobs to terminate + */ +void device_link_wait_removal(void) +{ + /* + * devlink removal jobs are queued in the dedicated work queue. + * To be sure that all removal jobs are terminated, ensure that any + * scheduled work has run to completion. + */ + flush_workqueue(device_link_wq); +} +EXPORT_SYMBOL_GPL(device_link_wait_removal); + static struct class devlink_class = { .name = "devlink", .owner = THIS_MODULE, @@ -3420,9 +3435,14 @@ int __init devices_init(void) sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj); if (!sysfs_dev_char_kobj) goto char_kobj_err; + device_link_wq = alloc_workqueue("device_link_wq", 0, 0); + if (!device_link_wq) + goto wq_err; return 0; + wq_err: + kobject_put(sysfs_dev_char_kobj); char_kobj_err: kobject_put(sysfs_dev_block_kobj); block_kobj_err: diff --git a/drivers/of/dynamic.c b/drivers/of/dynamic.c index b6a3ee65437b9c3cb508a402b263ec39a032824a..e663445ce0851f3f2d807ebfe1ccc8f7697127ab 100644 --- a/drivers/of/dynamic.c +++ b/drivers/of/dynamic.c @@ -675,6 +675,17 @@ void of_changeset_destroy(struct of_changeset *ocs) { struct of_changeset_entry *ce, *cen; + /* + * When a device is deleted, the device links to/from it are also queued + * for deletion. Until these device links are freed, the devices + * themselves aren't freed. If the device being deleted is due to an + * overlay change, this device might be holding a reference to a device + * node that will be freed. So, wait until all already pending device + * links are deleted before freeing a device node. This ensures we don't + * free any device node that has a non-zero reference count. + */ + device_link_wait_removal(); + list_for_each_entry_safe_reverse(ce, cen, &ocs->entries, node) __of_changeset_entry_destroy(ce); } diff --git a/include/linux/device.h b/include/linux/device.h index 62b127bffddac4dd7c0990fac82282b18e7f5a9d..ceb02c0ac69cd9053a028228ca8251f2c01fa27a 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -962,6 +962,7 @@ void device_link_del(struct device_link *link); void device_link_remove(void *consumer, struct device *supplier); void device_links_supplier_sync_state_pause(void); void device_links_supplier_sync_state_resume(void); +void device_link_wait_removal(void); extern __printf(3, 4) int dev_err_probe(const struct device *dev, int err, const char *fmt, ...); diff --git a/include/linux/of.h b/include/linux/of.h index e6b8e39f524c61f612e2508045ad80889bf71f9f..1ebea14ad39c60f23ae85342a44d3f23ea3c66f9 100644 --- a/include/linux/of.h +++ b/include/linux/of.h @@ -1451,6 +1451,8 @@ static inline int of_reconfig_get_state_change(unsigned long action, } #endif /* CONFIG_OF_DYNAMIC */ +void device_link_wait_removal(void); + /** * of_device_is_system_power_controller - Tells if system-power-controller is found for device_node * @np: Pointer to the given device_node