1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Copyright (c) 2025, Mike Rapoport, Microsoft
4 *
5 * Based on e820 pmem driver:
6 * Copyright (c) 2015, Christoph Hellwig.
7 * Copyright (c) 2015, Intel Corporation.
8 */
9#include <linux/platform_device.h>
10#include <linux/memory_hotplug.h>
11#include <linux/libnvdimm.h>
12#include <linux/module.h>
13#include <linux/numa.h>
14#include <linux/slab.h>
15#include <linux/io.h>
16#include <linux/of.h>
17
18#include <uapi/linux/ndctl.h>
19
20#define LABEL_AREA_SIZE SZ_128K
21
22struct ramdax_dimm {
23 struct nvdimm *nvdimm;
24 void *label_area;
25};
26
27static void ramdax_remove(struct platform_device *pdev)
28{
29 struct nvdimm_bus *nvdimm_bus = platform_get_drvdata(pdev);
30
31 nvdimm_bus_unregister(nvdimm_bus);
32}
33
34static int ramdax_register_region(struct resource *res,
35 struct nvdimm *nvdimm,
36 struct nvdimm_bus *nvdimm_bus)
37{
38 struct nd_mapping_desc mapping;
39 struct nd_region_desc ndr_desc;
40 struct nd_interleave_set *nd_set;
41 int nid = phys_to_target_node(start: res->start);
42
43 nd_set = kzalloc(sizeof(*nd_set), GFP_KERNEL);
44 if (!nd_set)
45 return -ENOMEM;
46
47 nd_set->cookie1 = 0xcafebeefcafebeef;
48 nd_set->cookie2 = nd_set->cookie1;
49 nd_set->altcookie = nd_set->cookie1;
50
51 memset(&mapping, 0, sizeof(mapping));
52 mapping.nvdimm = nvdimm;
53 mapping.start = 0;
54 mapping.size = resource_size(res) - LABEL_AREA_SIZE;
55
56 memset(&ndr_desc, 0, sizeof(ndr_desc));
57 ndr_desc.res = res;
58 ndr_desc.numa_node = numa_map_to_online_node(nid);
59 ndr_desc.target_node = nid;
60 ndr_desc.num_mappings = 1;
61 ndr_desc.mapping = &mapping;
62 ndr_desc.nd_set = nd_set;
63
64 if (!nvdimm_pmem_region_create(nvdimm_bus, ndr_desc: &ndr_desc))
65 goto err_free_nd_set;
66
67 return 0;
68
69err_free_nd_set:
70 kfree(objp: nd_set);
71 return -ENXIO;
72}
73
74static int ramdax_register_dimm(struct resource *res, void *data)
75{
76 resource_size_t start = res->start;
77 resource_size_t size = resource_size(res);
78 unsigned long flags = 0, cmd_mask = 0;
79 struct nvdimm_bus *nvdimm_bus = data;
80 struct ramdax_dimm *dimm;
81 int err;
82
83 dimm = kzalloc(sizeof(*dimm), GFP_KERNEL);
84 if (!dimm)
85 return -ENOMEM;
86
87 dimm->label_area = memremap(offset: start + size - LABEL_AREA_SIZE,
88 LABEL_AREA_SIZE, flags: MEMREMAP_WB);
89 if (!dimm->label_area) {
90 err = -ENOMEM;
91 goto err_free_dimm;
92 }
93
94 set_bit(nr: NDD_LABELING, addr: &flags);
95 set_bit(nr: NDD_REGISTER_SYNC, addr: &flags);
96 set_bit(nr: ND_CMD_GET_CONFIG_SIZE, addr: &cmd_mask);
97 set_bit(nr: ND_CMD_GET_CONFIG_DATA, addr: &cmd_mask);
98 set_bit(nr: ND_CMD_SET_CONFIG_DATA, addr: &cmd_mask);
99 dimm->nvdimm = nvdimm_create(nvdimm_bus, provider_data: dimm,
100 /* dimm_attribute_groups */ NULL,
101 flags, cmd_mask, num_flush: 0, NULL);
102 if (!dimm->nvdimm) {
103 err = -ENOMEM;
104 goto err_unmap_label;
105 }
106
107 err = ramdax_register_region(res, nvdimm: dimm->nvdimm, nvdimm_bus);
108 if (err)
109 goto err_remove_nvdimm;
110
111 return 0;
112
113err_remove_nvdimm:
114 nvdimm_delete(nvdimm: dimm->nvdimm);
115err_unmap_label:
116 memunmap(addr: dimm->label_area);
117err_free_dimm:
118 kfree(objp: dimm);
119 return err;
120}
121
122static int ramdax_get_config_size(struct nvdimm *nvdimm, int buf_len,
123 struct nd_cmd_get_config_size *cmd)
124{
125 if (sizeof(*cmd) > buf_len)
126 return -EINVAL;
127
128 *cmd = (struct nd_cmd_get_config_size){
129 .status = 0,
130 .config_size = LABEL_AREA_SIZE,
131 .max_xfer = 8,
132 };
133
134 return 0;
135}
136
137static int ramdax_get_config_data(struct nvdimm *nvdimm, int buf_len,
138 struct nd_cmd_get_config_data_hdr *cmd)
139{
140 struct ramdax_dimm *dimm = nvdimm_provider_data(nvdimm);
141
142 if (sizeof(*cmd) > buf_len)
143 return -EINVAL;
144 if (struct_size(cmd, out_buf, cmd->in_length) > buf_len)
145 return -EINVAL;
146 if (size_add(addend1: cmd->in_offset, addend2: cmd->in_length) > LABEL_AREA_SIZE)
147 return -EINVAL;
148
149 memcpy(cmd->out_buf, dimm->label_area + cmd->in_offset, cmd->in_length);
150
151 return 0;
152}
153
154static int ramdax_set_config_data(struct nvdimm *nvdimm, int buf_len,
155 struct nd_cmd_set_config_hdr *cmd)
156{
157 struct ramdax_dimm *dimm = nvdimm_provider_data(nvdimm);
158
159 if (sizeof(*cmd) > buf_len)
160 return -EINVAL;
161 if (struct_size(cmd, in_buf, cmd->in_length) > buf_len)
162 return -EINVAL;
163 if (size_add(addend1: cmd->in_offset, addend2: cmd->in_length) > LABEL_AREA_SIZE)
164 return -EINVAL;
165
166 memcpy(dimm->label_area + cmd->in_offset, cmd->in_buf, cmd->in_length);
167
168 return 0;
169}
170
171static int ramdax_nvdimm_ctl(struct nvdimm *nvdimm, unsigned int cmd,
172 void *buf, unsigned int buf_len)
173{
174 unsigned long cmd_mask = nvdimm_cmd_mask(nvdimm);
175
176 if (!test_bit(cmd, &cmd_mask))
177 return -ENOTTY;
178
179 switch (cmd) {
180 case ND_CMD_GET_CONFIG_SIZE:
181 return ramdax_get_config_size(nvdimm, buf_len, cmd: buf);
182 case ND_CMD_GET_CONFIG_DATA:
183 return ramdax_get_config_data(nvdimm, buf_len, cmd: buf);
184 case ND_CMD_SET_CONFIG_DATA:
185 return ramdax_set_config_data(nvdimm, buf_len, cmd: buf);
186 default:
187 return -ENOTTY;
188 }
189}
190
191static int ramdax_ctl(struct nvdimm_bus_descriptor *nd_desc,
192 struct nvdimm *nvdimm, unsigned int cmd, void *buf,
193 unsigned int buf_len, int *cmd_rc)
194{
195 /*
196 * No firmware response to translate, let the transport error
197 * code take precedence.
198 */
199 *cmd_rc = 0;
200
201 if (!nvdimm)
202 return -ENOTTY;
203 return ramdax_nvdimm_ctl(nvdimm, cmd, buf, buf_len);
204}
205
206#ifdef CONFIG_OF
207static const struct of_device_id ramdax_of_matches[] = {
208 { .compatible = "pmem-region", },
209 { },
210};
211#endif
212
213static int ramdax_probe_of(struct platform_device *pdev,
214 struct nvdimm_bus *bus, struct device_node *np)
215{
216 int err;
217
218 if (!of_match_node(matches: ramdax_of_matches, node: np))
219 return -ENODEV;
220
221 for (int i = 0; i < pdev->num_resources; i++) {
222 err = ramdax_register_dimm(res: &pdev->resource[i], data: bus);
223 if (err)
224 goto err_unregister;
225 }
226
227 return 0;
228
229err_unregister:
230 /*
231 * FIXME: should we unregister the dimms that were registered
232 * successfully
233 */
234 return err;
235}
236
237static int ramdax_probe(struct platform_device *pdev)
238{
239 static struct nvdimm_bus_descriptor nd_desc;
240 struct device *dev = &pdev->dev;
241 struct nvdimm_bus *nvdimm_bus;
242 struct device_node *np;
243 int rc = -ENXIO;
244
245 nd_desc.provider_name = "ramdax";
246 nd_desc.module = THIS_MODULE;
247 nd_desc.ndctl = ramdax_ctl;
248 nvdimm_bus = nvdimm_bus_register(parent: dev, nfit_desc: &nd_desc);
249 if (!nvdimm_bus)
250 goto err;
251
252 np = dev_of_node(dev: &pdev->dev);
253 if (np)
254 rc = ramdax_probe_of(pdev, bus: nvdimm_bus, np);
255 else
256 rc = walk_iomem_res_desc(desc: IORES_DESC_PERSISTENT_MEMORY_LEGACY,
257 IORESOURCE_MEM, start: 0, end: -1, arg: nvdimm_bus,
258 func: ramdax_register_dimm);
259 if (rc)
260 goto err;
261
262 platform_set_drvdata(pdev, data: nvdimm_bus);
263
264 return 0;
265err:
266 nvdimm_bus_unregister(nvdimm_bus);
267 return rc;
268}
269
270static struct platform_driver ramdax_driver = {
271 .probe = ramdax_probe,
272 .remove = ramdax_remove,
273 .driver = {
274 .name = "ramdax",
275 },
276};
277
278module_platform_driver(ramdax_driver);
279
280MODULE_DESCRIPTION("NVDIMM support for e820 type-12 memory and OF pmem-region");
281MODULE_LICENSE("GPL");
282MODULE_AUTHOR("Microsoft Corporation");
283

source code of linux/drivers/nvdimm/ramdax.c