| // SPDX-License-Identifier: GPL-2.0-or-later | |
| /* | |
| * Common code for drivers creating fake platform devices. | |
| * | |
| * Provides synchronous device creation: waits for probe completion and | |
| * returns the probe success or error status to the device creator. | |
| * | |
| * Copyright (C) 2021 Bartosz Golaszewski <brgl@bgdev.pl> | |
| * Copyright (C) 2025 Koichiro Den <koichiro.den@canonical.com> | |
| */ | |
| #include <linux/device.h> | |
| #include <linux/slab.h> | |
| #include "dev-sync-probe.h" | |
| static int dev_sync_probe_notifier_call(struct notifier_block *nb, | |
| unsigned long action, void *data) | |
| { | |
| struct dev_sync_probe_data *pdata; | |
| struct device *dev = data; | |
| pdata = container_of(nb, struct dev_sync_probe_data, bus_notifier); | |
| if (!device_match_name(dev, pdata->name)) | |
| return NOTIFY_DONE; | |
| switch (action) { | |
| case BUS_NOTIFY_BOUND_DRIVER: | |
| pdata->driver_bound = true; | |
| break; | |
| case BUS_NOTIFY_DRIVER_NOT_BOUND: | |
| pdata->driver_bound = false; | |
| break; | |
| default: | |
| return NOTIFY_DONE; | |
| } | |
| complete(&pdata->probe_completion); | |
| return NOTIFY_OK; | |
| } | |
| void dev_sync_probe_init(struct dev_sync_probe_data *data) | |
| { | |
| memset(data, 0, sizeof(*data)); | |
| init_completion(&data->probe_completion); | |
| data->bus_notifier.notifier_call = dev_sync_probe_notifier_call; | |
| } | |
| EXPORT_SYMBOL_GPL(dev_sync_probe_init); | |
| int dev_sync_probe_register(struct dev_sync_probe_data *data, | |
| struct platform_device_info *pdevinfo) | |
| { | |
| struct platform_device *pdev; | |
| char *name; | |
| name = kasprintf(GFP_KERNEL, "%s.%d", pdevinfo->name, pdevinfo->id); | |
| if (!name) | |
| return -ENOMEM; | |
| data->driver_bound = false; | |
| data->name = name; | |
| reinit_completion(&data->probe_completion); | |
| bus_register_notifier(&platform_bus_type, &data->bus_notifier); | |
| pdev = platform_device_register_full(pdevinfo); | |
| if (IS_ERR(pdev)) { | |
| bus_unregister_notifier(&platform_bus_type, &data->bus_notifier); | |
| kfree(data->name); | |
| return PTR_ERR(pdev); | |
| } | |
| wait_for_completion(&data->probe_completion); | |
| bus_unregister_notifier(&platform_bus_type, &data->bus_notifier); | |
| if (!data->driver_bound) { | |
| platform_device_unregister(pdev); | |
| kfree(data->name); | |
| return -ENXIO; | |
| } | |
| data->pdev = pdev; | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_GPL(dev_sync_probe_register); | |
| void dev_sync_probe_unregister(struct dev_sync_probe_data *data) | |
| { | |
| platform_device_unregister(data->pdev); | |
| kfree(data->name); | |
| data->pdev = NULL; | |
| } | |
| EXPORT_SYMBOL_GPL(dev_sync_probe_unregister); | |
| MODULE_AUTHOR("Bartosz Golaszewski <brgl@bgdev.pl>"); | |
| MODULE_AUTHOR("Koichiro Den <koichiro.den@canonical.com>"); | |
| MODULE_DESCRIPTION("Utilities for synchronous fake device creation"); | |
| MODULE_LICENSE("GPL"); |