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 <linux/mm.h>
7
8static bool nohmem;
9module_param_named(disable, nohmem, bool, 0444);
10
11void hmem_register_device(int target_nid, struct resource *r)
12{
13	/* define a clean / non-busy resource for the platform device */
14	struct resource res = {
15		.start = r->start,
16		.end = r->end,
17		.flags = IORESOURCE_MEM,
18		.desc = IORES_DESC_SOFT_RESERVED,
19	};
20	struct platform_device *pdev;
21	struct memregion_info info;
22	int rc, id;
23
24	if (nohmem)
25		return;
26
27	rc = region_intersects(res.start, resource_size(&res), IORESOURCE_MEM,
28			IORES_DESC_SOFT_RESERVED);
29	if (rc != REGION_INTERSECTS)
30		return;
31
32	id = memregion_alloc(GFP_KERNEL);
33	if (id < 0) {
34		pr_err("memregion allocation failure for %pr\n", &res);
35		return;
36	}
37
38	pdev = platform_device_alloc("hmem", id);
39	if (!pdev) {
40		pr_err("hmem device allocation failure for %pr\n", &res);
41		goto out_pdev;
42	}
43
44	pdev->dev.numa_node = numa_map_to_online_node(target_nid);
45	info = (struct memregion_info) {
46		.target_node = target_nid,
47	};
48	rc = platform_device_add_data(pdev, &info, sizeof(info));
49	if (rc < 0) {
50		pr_err("hmem memregion_info allocation failure for %pr\n", &res);
51		goto out_pdev;
52	}
53
54	rc = platform_device_add_resources(pdev, &res, 1);
55	if (rc < 0) {
56		pr_err("hmem resource allocation failure for %pr\n", &res);
57		goto out_resource;
58	}
59
60	rc = platform_device_add(pdev);
61	if (rc < 0) {
62		dev_err(&pdev->dev, "device add failed for %pr\n", &res);
63		goto out_resource;
64	}
65
66	return;
67
68out_resource:
69	put_device(&pdev->dev);
70out_pdev:
71	memregion_free(id);
72}
73
74static __init int hmem_register_one(struct resource *res, void *data)
75{
76	/*
77	 * If the resource is not a top-level resource it was already
78	 * assigned to a device by the HMAT parsing.
79	 */
80	if (res->parent != &iomem_resource) {
81		pr_info("HMEM: skip %pr, already claimed\n", res);
82		return 0;
83	}
84
85	hmem_register_device(phys_to_target_node(res->start), res);
86
87	return 0;
88}
89
90static __init int hmem_init(void)
91{
92	walk_iomem_res_desc(IORES_DESC_SOFT_RESERVED,
93			IORESOURCE_MEM, 0, -1, NULL, hmem_register_one);
94	return 0;
95}
96
97/*
98 * As this is a fallback for address ranges unclaimed by the ACPI HMAT
99 * parsing it must be at an initcall level greater than hmat_init().
100 */
101late_initcall(hmem_init);
102