xref: /linux/drivers/dax/hmem/hmem.c (revision e4de6b910bf3645c224cd873d4e03ce3dd81fbe0)
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 struct dax_defer_work {
63 	struct platform_device *pdev;
64 	struct work_struct work;
65 };
66 
67 static void process_defer_work(struct work_struct *w);
68 
69 static struct dax_defer_work dax_hmem_work = {
70 	.work = __WORK_INITIALIZER(dax_hmem_work.work, process_defer_work),
71 };
72 
73 void dax_hmem_flush_work(void)
74 {
75 	flush_work(&dax_hmem_work.work);
76 }
77 EXPORT_SYMBOL_GPL(dax_hmem_flush_work);
78 
79 static int __hmem_register_device(struct device *host, int target_nid,
80 				  const struct resource *res)
81 {
82 	struct platform_device *pdev;
83 	struct memregion_info info;
84 	long id;
85 	int rc;
86 
87 	rc = region_intersects_soft_reserve(res->start, resource_size(res));
88 	if (rc != REGION_INTERSECTS)
89 		return 0;
90 
91 	/* TODO: Add Soft-Reserved memory back to iomem */
92 
93 	id = memregion_alloc(GFP_KERNEL);
94 	if (id < 0) {
95 		dev_err(host, "memregion allocation failure for %pr\n", res);
96 		return -ENOMEM;
97 	}
98 	rc = devm_add_action_or_reset(host, release_memregion, (void *) id);
99 	if (rc)
100 		return rc;
101 
102 	pdev = platform_device_alloc("hmem", id);
103 	if (!pdev) {
104 		dev_err(host, "device allocation failure for %pr\n", res);
105 		return -ENOMEM;
106 	}
107 
108 	pdev->dev.numa_node = numa_map_to_online_node(target_nid);
109 	info = (struct memregion_info) {
110 		.target_node = target_nid,
111 		.range = {
112 			.start = res->start,
113 			.end = res->end,
114 		},
115 	};
116 	rc = platform_device_add_data(pdev, &info, sizeof(info));
117 	if (rc < 0) {
118 		dev_err(host, "memregion_info allocation failure for %pr\n",
119 		       res);
120 		goto out_put;
121 	}
122 
123 	rc = platform_device_add(pdev);
124 	if (rc < 0) {
125 		dev_err(host, "%s add failed for %pr\n", dev_name(&pdev->dev),
126 			res);
127 		goto out_put;
128 	}
129 
130 	return devm_add_action_or_reset(host, release_hmem, pdev);
131 
132 out_put:
133 	platform_device_put(pdev);
134 	return rc;
135 }
136 
137 static int hmem_register_device(struct device *host, int target_nid,
138 				const struct resource *res)
139 {
140 	if (IS_ENABLED(CONFIG_DEV_DAX_CXL) &&
141 	    region_intersects(res->start, resource_size(res), IORESOURCE_MEM,
142 			      IORES_DESC_CXL) != REGION_DISJOINT) {
143 		if (!dax_hmem_initial_probe) {
144 			dev_dbg(host, "await CXL initial probe: %pr\n", res);
145 			queue_work(system_long_wq, &dax_hmem_work.work);
146 			return 0;
147 		}
148 		dev_dbg(host, "deferring range to CXL: %pr\n", res);
149 		return 0;
150 	}
151 
152 	return __hmem_register_device(host, target_nid, res);
153 }
154 
155 static int hmem_register_cxl_device(struct device *host, int target_nid,
156 				    const struct resource *res)
157 {
158 	if (region_intersects(res->start, resource_size(res), IORESOURCE_MEM,
159 			      IORES_DESC_CXL) == REGION_DISJOINT)
160 		return 0;
161 
162 	if (cxl_region_contains_resource((struct resource *)res)) {
163 		dev_dbg(host, "CXL claims resource, dropping: %pr\n", res);
164 		return 0;
165 	}
166 
167 	dev_dbg(host, "CXL did not claim resource, registering: %pr\n", res);
168 	return __hmem_register_device(host, target_nid, res);
169 }
170 
171 static void process_defer_work(struct work_struct *w)
172 {
173 	struct dax_defer_work *work = container_of(w, typeof(*work), work);
174 	struct platform_device *pdev;
175 
176 	if (!work->pdev)
177 		return;
178 
179 	pdev = work->pdev;
180 
181 	/* Relies on cxl_acpi and cxl_pci having had a chance to load */
182 	wait_for_device_probe();
183 
184 	guard(device)(&pdev->dev);
185 	if (!pdev->dev.driver)
186 		return;
187 
188 	if (!dax_hmem_initial_probe) {
189 		dax_hmem_initial_probe = true;
190 		walk_hmem_resources(&pdev->dev, hmem_register_cxl_device);
191 	}
192 }
193 
194 static int dax_hmem_platform_probe(struct platform_device *pdev)
195 {
196 	if (work_pending(&dax_hmem_work.work))
197 		return -EBUSY;
198 
199 	if (!dax_hmem_work.pdev)
200 		dax_hmem_work.pdev =
201 			to_platform_device(get_device(&pdev->dev));
202 
203 	return walk_hmem_resources(&pdev->dev, hmem_register_device);
204 }
205 
206 static struct platform_driver dax_hmem_platform_driver = {
207 	.probe = dax_hmem_platform_probe,
208 	.driver = {
209 		.name = "hmem_platform",
210 	},
211 };
212 
213 static __init int dax_hmem_init(void)
214 {
215 	int rc;
216 
217 	/*
218 	 * Ensure that cxl_acpi and cxl_pci have a chance to kick off
219 	 * CXL topology discovery at least once before scanning the
220 	 * iomem resource tree for IORES_DESC_CXL resources.
221 	 */
222 	if (IS_ENABLED(CONFIG_DEV_DAX_CXL)) {
223 		request_module("cxl_acpi");
224 		request_module("cxl_pci");
225 	}
226 
227 	rc = platform_driver_register(&dax_hmem_platform_driver);
228 	if (rc)
229 		return rc;
230 
231 	rc = platform_driver_register(&dax_hmem_driver);
232 	if (rc)
233 		platform_driver_unregister(&dax_hmem_platform_driver);
234 
235 	return rc;
236 }
237 
238 static __exit void dax_hmem_exit(void)
239 {
240 	if (dax_hmem_work.pdev) {
241 		flush_work(&dax_hmem_work.work);
242 		put_device(&dax_hmem_work.pdev->dev);
243 	}
244 
245 	platform_driver_unregister(&dax_hmem_driver);
246 	platform_driver_unregister(&dax_hmem_platform_driver);
247 }
248 
249 module_init(dax_hmem_init);
250 module_exit(dax_hmem_exit);
251 
252 MODULE_ALIAS("platform:hmem*");
253 MODULE_ALIAS("platform:hmem_platform*");
254 MODULE_DESCRIPTION("HMEM DAX: direct access to 'specific purpose' memory");
255 MODULE_LICENSE("GPL v2");
256 MODULE_AUTHOR("Intel Corporation");
257