xref: /linux/drivers/dax/hmem/hmem.c (revision 53597deca0e38c30e6cd4ba2114fa42d2bcd85bb)
1 // SPDX-License-Identifier: GPL-2.0
2 #include <linux/platform_device.h>
3 #include <linux/memregion.h>
4 #include <linux/module.h>
5 #include <linux/dax.h>
6 #include "../../cxl/cxl.h"
7 #include "../bus.h"
8 
9 static bool region_idle;
10 module_param_named(region_idle, region_idle, bool, 0644);
11 
12 static int dax_hmem_probe(struct platform_device *pdev)
13 {
14 	unsigned long flags = IORESOURCE_DAX_KMEM;
15 	struct device *dev = &pdev->dev;
16 	struct dax_region *dax_region;
17 	struct memregion_info *mri;
18 	struct dev_dax_data data;
19 
20 	/*
21 	 * @region_idle == true indicates that an administrative agent
22 	 * wants to manipulate the range partitioning before the devices
23 	 * are created, so do not send them to the dax_kmem driver by
24 	 * default.
25 	 */
26 	if (region_idle)
27 		flags = 0;
28 
29 	mri = dev->platform_data;
30 	dax_region = alloc_dax_region(dev, pdev->id, &mri->range,
31 				      mri->target_node, PMD_SIZE, flags);
32 	if (!dax_region)
33 		return -ENOMEM;
34 
35 	data = (struct dev_dax_data) {
36 		.dax_region = dax_region,
37 		.id = -1,
38 		.size = region_idle ? 0 : range_len(&mri->range),
39 		.memmap_on_memory = false,
40 	};
41 
42 	return PTR_ERR_OR_ZERO(devm_create_dev_dax(&data));
43 }
44 
45 static struct platform_driver dax_hmem_driver = {
46 	.probe = dax_hmem_probe,
47 	.driver = {
48 		.name = "hmem",
49 	},
50 };
51 
52 static void release_memregion(void *data)
53 {
54 	memregion_free((long) data);
55 }
56 
57 static void release_hmem(void *pdev)
58 {
59 	platform_device_unregister(pdev);
60 }
61 
62 static struct workqueue_struct *dax_hmem_wq;
63 
64 void dax_hmem_flush_work(void)
65 {
66 	flush_workqueue(dax_hmem_wq);
67 }
68 EXPORT_SYMBOL_FOR_MODULES(dax_hmem_flush_work, "dax_cxl");
69 
70 static int __hmem_register_device(struct device *host, int target_nid,
71 				  const struct resource *res)
72 {
73 	struct platform_device *pdev;
74 	struct memregion_info info;
75 	long id;
76 	int rc;
77 
78 	rc = region_intersects_soft_reserve(res->start, resource_size(res));
79 	if (rc != REGION_INTERSECTS)
80 		return 0;
81 
82 	/* TODO: Add Soft-Reserved memory back to iomem */
83 
84 	id = memregion_alloc(GFP_KERNEL);
85 	if (id < 0) {
86 		dev_err(host, "memregion allocation failure for %pr\n", res);
87 		return -ENOMEM;
88 	}
89 	rc = devm_add_action_or_reset(host, release_memregion, (void *) id);
90 	if (rc)
91 		return rc;
92 
93 	pdev = platform_device_alloc("hmem", id);
94 	if (!pdev) {
95 		dev_err(host, "device allocation failure for %pr\n", res);
96 		return -ENOMEM;
97 	}
98 
99 	pdev->dev.parent = host;
100 	pdev->dev.numa_node = numa_map_to_online_node(target_nid);
101 	info = (struct memregion_info) {
102 		.target_node = target_nid,
103 		.range = {
104 			.start = res->start,
105 			.end = res->end,
106 		},
107 	};
108 	rc = platform_device_add_data(pdev, &info, sizeof(info));
109 	if (rc < 0) {
110 		dev_err(host, "memregion_info allocation failure for %pr\n",
111 		       res);
112 		goto out_put;
113 	}
114 
115 	rc = platform_device_add(pdev);
116 	if (rc < 0) {
117 		dev_err(host, "%s add failed for %pr\n", dev_name(&pdev->dev),
118 			res);
119 		goto out_put;
120 	}
121 
122 	return devm_add_action_or_reset(host, release_hmem, pdev);
123 
124 out_put:
125 	platform_device_put(pdev);
126 	return rc;
127 }
128 
129 static int hmem_register_cxl_device(struct device *host, int target_nid,
130 				    const struct resource *res)
131 {
132 	if (region_intersects(res->start, resource_size(res), IORESOURCE_MEM,
133 			      IORES_DESC_CXL) == REGION_DISJOINT)
134 		return 0;
135 
136 	if (cxl_region_contains_resource(res)) {
137 		dev_dbg(host, "CXL claims resource, dropping: %pr\n", res);
138 		return 0;
139 	}
140 
141 	dev_dbg(host, "CXL did not claim resource, registering: %pr\n", res);
142 	return __hmem_register_device(host, target_nid, res);
143 }
144 
145 static void process_defer_work(struct work_struct *w)
146 {
147 	struct hmem_platform_device *hpdev = container_of(w, typeof(*hpdev), work);
148 	struct device *dev = &hpdev->pdev.dev;
149 
150 	/* Relies on cxl_acpi and cxl_pci having had a chance to load */
151 	wait_for_device_probe();
152 
153 	guard(device)(dev);
154 	if (!dev->driver)
155 		goto out;
156 
157 	if (!hpdev->did_probe) {
158 		hpdev->did_probe = true;
159 		walk_hmem_resources(dev, hmem_register_cxl_device);
160 	}
161 out:
162 	put_device(dev);
163 }
164 
165 static int hmem_register_device(struct device *host, int target_nid,
166 				const struct resource *res)
167 {
168 	struct platform_device *pdev = to_platform_device(host);
169 	struct hmem_platform_device *hpdev = to_hmem_platform_device(pdev);
170 
171 	if (IS_ENABLED(CONFIG_DEV_DAX_CXL) &&
172 	    region_intersects(res->start, resource_size(res), IORESOURCE_MEM,
173 			      IORES_DESC_CXL) != REGION_DISJOINT) {
174 		if (!hpdev->did_probe) {
175 			dev_dbg(host, "await CXL initial probe: %pr\n", res);
176 			hpdev->work.func = process_defer_work;
177 			get_device(host);
178 			if (!queue_work(dax_hmem_wq, &hpdev->work))
179 				put_device(host);
180 			return 0;
181 		}
182 		dev_dbg(host, "deferring range to CXL: %pr\n", res);
183 		return 0;
184 	}
185 
186 	return __hmem_register_device(host, target_nid, res);
187 }
188 
189 static int dax_hmem_platform_probe(struct platform_device *pdev)
190 {
191 	struct hmem_platform_device *hpdev = to_hmem_platform_device(pdev);
192 
193 	/* queue is only flushed on module unload, fail rebind with pending work */
194 	if (work_pending(&hpdev->work))
195 		return -EBUSY;
196 
197 	return walk_hmem_resources(&pdev->dev, hmem_register_device);
198 }
199 
200 static struct platform_driver dax_hmem_platform_driver = {
201 	.probe = dax_hmem_platform_probe,
202 	.driver = {
203 		.name = "hmem_platform",
204 	},
205 };
206 
207 static __init int dax_hmem_init(void)
208 {
209 	int rc;
210 
211 	/*
212 	 * Ensure that cxl_acpi and cxl_pci have a chance to kick off
213 	 * CXL topology discovery at least once before scanning the
214 	 * iomem resource tree for IORES_DESC_CXL resources.
215 	 */
216 	if (IS_ENABLED(CONFIG_DEV_DAX_CXL)) {
217 		request_module("cxl_acpi");
218 		request_module("cxl_pci");
219 	}
220 
221 	dax_hmem_wq = alloc_ordered_workqueue("dax_hmem_wq", 0);
222 	if (!dax_hmem_wq)
223 		return -ENOMEM;
224 
225 	rc = platform_driver_register(&dax_hmem_platform_driver);
226 	if (rc)
227 		goto err_platform_driver;
228 
229 	rc = platform_driver_register(&dax_hmem_driver);
230 	if (rc)
231 		goto err_driver;
232 
233 	return 0;
234 
235 err_driver:
236 	platform_driver_unregister(&dax_hmem_platform_driver);
237 err_platform_driver:
238 	destroy_workqueue(dax_hmem_wq);
239 
240 	return rc;
241 }
242 
243 static __exit void dax_hmem_exit(void)
244 {
245 	platform_driver_unregister(&dax_hmem_driver);
246 	platform_driver_unregister(&dax_hmem_platform_driver);
247 	destroy_workqueue(dax_hmem_wq);
248 }
249 
250 module_init(dax_hmem_init);
251 module_exit(dax_hmem_exit);
252 
253 MODULE_ALIAS("platform:hmem*");
254 MODULE_ALIAS("platform:hmem_platform*");
255 MODULE_DESCRIPTION("HMEM DAX: direct access to 'specific purpose' memory");
256 MODULE_LICENSE("GPL v2");
257 MODULE_AUTHOR("Intel Corporation");
258