1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Common code for drivers creating fake platform devices.
4 *
5 * Provides synchronous device creation: waits for probe completion and
6 * returns the probe success or error status to the device creator.
7 *
8 * Copyright (C) 2021 Bartosz Golaszewski <brgl@bgdev.pl>
9 * Copyright (C) 2025 Koichiro Den <koichiro.den@canonical.com>
10 */
11
12 #include <linux/device.h>
13 #include <linux/slab.h>
14
15 #include "dev-sync-probe.h"
16
dev_sync_probe_notifier_call(struct notifier_block * nb,unsigned long action,void * data)17 static int dev_sync_probe_notifier_call(struct notifier_block *nb,
18 unsigned long action, void *data)
19 {
20 struct dev_sync_probe_data *pdata;
21 struct device *dev = data;
22
23 pdata = container_of(nb, struct dev_sync_probe_data, bus_notifier);
24 if (!device_match_name(dev, pdata->name))
25 return NOTIFY_DONE;
26
27 switch (action) {
28 case BUS_NOTIFY_BOUND_DRIVER:
29 pdata->driver_bound = true;
30 break;
31 case BUS_NOTIFY_DRIVER_NOT_BOUND:
32 pdata->driver_bound = false;
33 break;
34 default:
35 return NOTIFY_DONE;
36 }
37
38 complete(&pdata->probe_completion);
39 return NOTIFY_OK;
40 }
41
dev_sync_probe_init(struct dev_sync_probe_data * data)42 void dev_sync_probe_init(struct dev_sync_probe_data *data)
43 {
44 memset(data, 0, sizeof(*data));
45 init_completion(&data->probe_completion);
46 data->bus_notifier.notifier_call = dev_sync_probe_notifier_call;
47 }
48 EXPORT_SYMBOL_GPL(dev_sync_probe_init);
49
dev_sync_probe_register(struct dev_sync_probe_data * data,struct platform_device_info * pdevinfo)50 int dev_sync_probe_register(struct dev_sync_probe_data *data,
51 struct platform_device_info *pdevinfo)
52 {
53 struct platform_device *pdev;
54 char *name;
55
56 name = kasprintf(GFP_KERNEL, "%s.%d", pdevinfo->name, pdevinfo->id);
57 if (!name)
58 return -ENOMEM;
59
60 data->driver_bound = false;
61 data->name = name;
62 reinit_completion(&data->probe_completion);
63 bus_register_notifier(&platform_bus_type, &data->bus_notifier);
64
65 pdev = platform_device_register_full(pdevinfo);
66 if (IS_ERR(pdev)) {
67 bus_unregister_notifier(&platform_bus_type, &data->bus_notifier);
68 kfree(data->name);
69 return PTR_ERR(pdev);
70 }
71
72 wait_for_completion(&data->probe_completion);
73 bus_unregister_notifier(&platform_bus_type, &data->bus_notifier);
74
75 if (!data->driver_bound) {
76 platform_device_unregister(pdev);
77 kfree(data->name);
78 return -ENXIO;
79 }
80
81 data->pdev = pdev;
82 return 0;
83 }
84 EXPORT_SYMBOL_GPL(dev_sync_probe_register);
85
dev_sync_probe_unregister(struct dev_sync_probe_data * data)86 void dev_sync_probe_unregister(struct dev_sync_probe_data *data)
87 {
88 platform_device_unregister(data->pdev);
89 kfree(data->name);
90 data->pdev = NULL;
91 }
92 EXPORT_SYMBOL_GPL(dev_sync_probe_unregister);
93
94 MODULE_AUTHOR("Bartosz Golaszewski <brgl@bgdev.pl>");
95 MODULE_AUTHOR("Koichiro Den <koichiro.den@canonical.com>");
96 MODULE_DESCRIPTION("Utilities for synchronous fake device creation");
97 MODULE_LICENSE("GPL");
98