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
22 struct ramdax_dimm {
23 struct nvdimm *nvdimm;
24 void *label_area;
25 };
26
ramdax_remove(struct platform_device * pdev)27 static 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
ramdax_register_region(struct resource * res,struct nvdimm * nvdimm,struct nvdimm_bus * nvdimm_bus)34 static 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(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))
65 goto err_free_nd_set;
66
67 return 0;
68
69 err_free_nd_set:
70 kfree(nd_set);
71 return -ENXIO;
72 }
73
ramdax_register_dimm(struct resource * res,void * data)74 static 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(start + size - LABEL_AREA_SIZE,
88 LABEL_AREA_SIZE, MEMREMAP_WB);
89 if (!dimm->label_area) {
90 err = -ENOMEM;
91 goto err_free_dimm;
92 }
93
94 set_bit(NDD_LABELING, &flags);
95 set_bit(NDD_REGISTER_SYNC, &flags);
96 set_bit(ND_CMD_GET_CONFIG_SIZE, &cmd_mask);
97 set_bit(ND_CMD_GET_CONFIG_DATA, &cmd_mask);
98 set_bit(ND_CMD_SET_CONFIG_DATA, &cmd_mask);
99 dimm->nvdimm = nvdimm_create(nvdimm_bus, dimm,
100 /* dimm_attribute_groups */ NULL,
101 flags, cmd_mask, 0, NULL);
102 if (!dimm->nvdimm) {
103 err = -ENOMEM;
104 goto err_unmap_label;
105 }
106
107 err = ramdax_register_region(res, dimm->nvdimm, nvdimm_bus);
108 if (err)
109 goto err_remove_nvdimm;
110
111 return 0;
112
113 err_remove_nvdimm:
114 nvdimm_delete(dimm->nvdimm);
115 err_unmap_label:
116 memunmap(dimm->label_area);
117 err_free_dimm:
118 kfree(dimm);
119 return err;
120 }
121
ramdax_get_config_size(struct nvdimm * nvdimm,int buf_len,struct nd_cmd_get_config_size * cmd)122 static 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
ramdax_get_config_data(struct nvdimm * nvdimm,int buf_len,struct nd_cmd_get_config_data_hdr * cmd)137 static 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(cmd->in_offset, 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
ramdax_set_config_data(struct nvdimm * nvdimm,int buf_len,struct nd_cmd_set_config_hdr * cmd)154 static 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(cmd->in_offset, 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
ramdax_nvdimm_ctl(struct nvdimm * nvdimm,unsigned int cmd,void * buf,unsigned int buf_len)171 static 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, buf);
182 case ND_CMD_GET_CONFIG_DATA:
183 return ramdax_get_config_data(nvdimm, buf_len, buf);
184 case ND_CMD_SET_CONFIG_DATA:
185 return ramdax_set_config_data(nvdimm, buf_len, buf);
186 default:
187 return -ENOTTY;
188 }
189 }
190
ramdax_ctl(struct nvdimm_bus_descriptor * nd_desc,struct nvdimm * nvdimm,unsigned int cmd,void * buf,unsigned int buf_len,int * cmd_rc)191 static 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
207 static const struct of_device_id ramdax_of_matches[] = {
208 { .compatible = "pmem-region", },
209 { },
210 };
211 #endif
212
ramdax_probe_of(struct platform_device * pdev,struct nvdimm_bus * bus,struct device_node * np)213 static 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(ramdax_of_matches, np))
219 return -ENODEV;
220
221 for (int i = 0; i < pdev->num_resources; i++) {
222 err = ramdax_register_dimm(&pdev->resource[i], bus);
223 if (err)
224 goto err_unregister;
225 }
226
227 return 0;
228
229 err_unregister:
230 /*
231 * FIXME: should we unregister the dimms that were registered
232 * successfully
233 */
234 return err;
235 }
236
ramdax_probe(struct platform_device * pdev)237 static 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(dev, &nd_desc);
249 if (!nvdimm_bus)
250 goto err;
251
252 np = dev_of_node(&pdev->dev);
253 if (np)
254 rc = ramdax_probe_of(pdev, nvdimm_bus, np);
255 else
256 rc = walk_iomem_res_desc(IORES_DESC_PERSISTENT_MEMORY_LEGACY,
257 IORESOURCE_MEM, 0, -1, nvdimm_bus,
258 ramdax_register_dimm);
259 if (rc)
260 goto err;
261
262 platform_set_drvdata(pdev, nvdimm_bus);
263
264 return 0;
265 err:
266 nvdimm_bus_unregister(nvdimm_bus);
267 return rc;
268 }
269
270 static struct platform_driver ramdax_driver = {
271 .probe = ramdax_probe,
272 .remove = ramdax_remove,
273 .driver = {
274 .name = "ramdax",
275 },
276 };
277
278 module_platform_driver(ramdax_driver);
279
280 MODULE_DESCRIPTION("NVDIMM support for e820 type-12 memory and OF pmem-region");
281 MODULE_LICENSE("GPL");
282 MODULE_AUTHOR("Microsoft Corporation");
283