Commit | Line | Data |
---|---|---|
1f7df6f8 DW |
1 | /* |
2 | * Copyright(c) 2013-2015 Intel Corporation. All rights reserved. | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify | |
5 | * it under the terms of version 2 of the GNU General Public License as | |
6 | * published by the Free Software Foundation. | |
7 | * | |
8 | * This program is distributed in the hope that it will be useful, but | |
9 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
11 | * General Public License for more details. | |
12 | */ | |
eaf96153 DW |
13 | #include <linux/scatterlist.h> |
14 | #include <linux/sched.h> | |
1f7df6f8 | 15 | #include <linux/slab.h> |
eaf96153 | 16 | #include <linux/sort.h> |
1f7df6f8 DW |
17 | #include <linux/io.h> |
18 | #include "nd-core.h" | |
19 | #include "nd.h" | |
20 | ||
21 | static DEFINE_IDA(region_ida); | |
22 | ||
23 | static void nd_region_release(struct device *dev) | |
24 | { | |
25 | struct nd_region *nd_region = to_nd_region(dev); | |
26 | u16 i; | |
27 | ||
28 | for (i = 0; i < nd_region->ndr_mappings; i++) { | |
29 | struct nd_mapping *nd_mapping = &nd_region->mapping[i]; | |
30 | struct nvdimm *nvdimm = nd_mapping->nvdimm; | |
31 | ||
32 | put_device(&nvdimm->dev); | |
33 | } | |
34 | ida_simple_remove(®ion_ida, nd_region->id); | |
35 | kfree(nd_region); | |
36 | } | |
37 | ||
38 | static struct device_type nd_blk_device_type = { | |
39 | .name = "nd_blk", | |
40 | .release = nd_region_release, | |
41 | }; | |
42 | ||
43 | static struct device_type nd_pmem_device_type = { | |
44 | .name = "nd_pmem", | |
45 | .release = nd_region_release, | |
46 | }; | |
47 | ||
48 | static struct device_type nd_volatile_device_type = { | |
49 | .name = "nd_volatile", | |
50 | .release = nd_region_release, | |
51 | }; | |
52 | ||
3d88002e | 53 | bool is_nd_pmem(struct device *dev) |
1f7df6f8 DW |
54 | { |
55 | return dev ? dev->type == &nd_pmem_device_type : false; | |
56 | } | |
57 | ||
3d88002e DW |
58 | bool is_nd_blk(struct device *dev) |
59 | { | |
60 | return dev ? dev->type == &nd_blk_device_type : false; | |
61 | } | |
62 | ||
1f7df6f8 DW |
63 | struct nd_region *to_nd_region(struct device *dev) |
64 | { | |
65 | struct nd_region *nd_region = container_of(dev, struct nd_region, dev); | |
66 | ||
67 | WARN_ON(dev->type->release != nd_region_release); | |
68 | return nd_region; | |
69 | } | |
70 | EXPORT_SYMBOL_GPL(to_nd_region); | |
71 | ||
3d88002e DW |
72 | /** |
73 | * nd_region_to_nstype() - region to an integer namespace type | |
74 | * @nd_region: region-device to interrogate | |
75 | * | |
76 | * This is the 'nstype' attribute of a region as well, an input to the | |
77 | * MODALIAS for namespace devices, and bit number for a nvdimm_bus to match | |
78 | * namespace devices with namespace drivers. | |
79 | */ | |
80 | int nd_region_to_nstype(struct nd_region *nd_region) | |
81 | { | |
82 | if (is_nd_pmem(&nd_region->dev)) { | |
83 | u16 i, alias; | |
84 | ||
85 | for (i = 0, alias = 0; i < nd_region->ndr_mappings; i++) { | |
86 | struct nd_mapping *nd_mapping = &nd_region->mapping[i]; | |
87 | struct nvdimm *nvdimm = nd_mapping->nvdimm; | |
88 | ||
89 | if (nvdimm->flags & NDD_ALIASING) | |
90 | alias++; | |
91 | } | |
92 | if (alias) | |
93 | return ND_DEVICE_NAMESPACE_PMEM; | |
94 | else | |
95 | return ND_DEVICE_NAMESPACE_IO; | |
96 | } else if (is_nd_blk(&nd_region->dev)) { | |
97 | return ND_DEVICE_NAMESPACE_BLK; | |
98 | } | |
99 | ||
100 | return 0; | |
101 | } | |
102 | ||
1f7df6f8 DW |
103 | static ssize_t size_show(struct device *dev, |
104 | struct device_attribute *attr, char *buf) | |
105 | { | |
106 | struct nd_region *nd_region = to_nd_region(dev); | |
107 | unsigned long long size = 0; | |
108 | ||
109 | if (is_nd_pmem(dev)) { | |
110 | size = nd_region->ndr_size; | |
111 | } else if (nd_region->ndr_mappings == 1) { | |
112 | struct nd_mapping *nd_mapping = &nd_region->mapping[0]; | |
113 | ||
114 | size = nd_mapping->size; | |
115 | } | |
116 | ||
117 | return sprintf(buf, "%llu\n", size); | |
118 | } | |
119 | static DEVICE_ATTR_RO(size); | |
120 | ||
121 | static ssize_t mappings_show(struct device *dev, | |
122 | struct device_attribute *attr, char *buf) | |
123 | { | |
124 | struct nd_region *nd_region = to_nd_region(dev); | |
125 | ||
126 | return sprintf(buf, "%d\n", nd_region->ndr_mappings); | |
127 | } | |
128 | static DEVICE_ATTR_RO(mappings); | |
129 | ||
3d88002e DW |
130 | static ssize_t nstype_show(struct device *dev, |
131 | struct device_attribute *attr, char *buf) | |
132 | { | |
133 | struct nd_region *nd_region = to_nd_region(dev); | |
134 | ||
135 | return sprintf(buf, "%d\n", nd_region_to_nstype(nd_region)); | |
136 | } | |
137 | static DEVICE_ATTR_RO(nstype); | |
138 | ||
eaf96153 DW |
139 | static ssize_t set_cookie_show(struct device *dev, |
140 | struct device_attribute *attr, char *buf) | |
141 | { | |
142 | struct nd_region *nd_region = to_nd_region(dev); | |
143 | struct nd_interleave_set *nd_set = nd_region->nd_set; | |
144 | ||
145 | if (is_nd_pmem(dev) && nd_set) | |
146 | /* pass, should be precluded by region_visible */; | |
147 | else | |
148 | return -ENXIO; | |
149 | ||
150 | return sprintf(buf, "%#llx\n", nd_set->cookie); | |
151 | } | |
152 | static DEVICE_ATTR_RO(set_cookie); | |
153 | ||
3d88002e DW |
154 | static ssize_t init_namespaces_show(struct device *dev, |
155 | struct device_attribute *attr, char *buf) | |
156 | { | |
157 | struct nd_region_namespaces *num_ns = dev_get_drvdata(dev); | |
158 | ssize_t rc; | |
159 | ||
160 | nvdimm_bus_lock(dev); | |
161 | if (num_ns) | |
162 | rc = sprintf(buf, "%d/%d\n", num_ns->active, num_ns->count); | |
163 | else | |
164 | rc = -ENXIO; | |
165 | nvdimm_bus_unlock(dev); | |
166 | ||
167 | return rc; | |
168 | } | |
169 | static DEVICE_ATTR_RO(init_namespaces); | |
170 | ||
1f7df6f8 DW |
171 | static struct attribute *nd_region_attributes[] = { |
172 | &dev_attr_size.attr, | |
3d88002e | 173 | &dev_attr_nstype.attr, |
1f7df6f8 | 174 | &dev_attr_mappings.attr, |
eaf96153 | 175 | &dev_attr_set_cookie.attr, |
3d88002e | 176 | &dev_attr_init_namespaces.attr, |
1f7df6f8 DW |
177 | NULL, |
178 | }; | |
179 | ||
eaf96153 DW |
180 | static umode_t region_visible(struct kobject *kobj, struct attribute *a, int n) |
181 | { | |
182 | struct device *dev = container_of(kobj, typeof(*dev), kobj); | |
183 | struct nd_region *nd_region = to_nd_region(dev); | |
184 | struct nd_interleave_set *nd_set = nd_region->nd_set; | |
185 | ||
186 | if (a != &dev_attr_set_cookie.attr) | |
187 | return a->mode; | |
188 | ||
189 | if (is_nd_pmem(dev) && nd_set) | |
190 | return a->mode; | |
191 | ||
192 | return 0; | |
193 | } | |
194 | ||
1f7df6f8 DW |
195 | struct attribute_group nd_region_attribute_group = { |
196 | .attrs = nd_region_attributes, | |
eaf96153 | 197 | .is_visible = region_visible, |
1f7df6f8 DW |
198 | }; |
199 | EXPORT_SYMBOL_GPL(nd_region_attribute_group); | |
200 | ||
eaf96153 DW |
201 | /* |
202 | * Upon successful probe/remove, take/release a reference on the | |
203 | * associated interleave set (if present) | |
204 | */ | |
205 | static void nd_region_notify_driver_action(struct nvdimm_bus *nvdimm_bus, | |
206 | struct device *dev, bool probe) | |
207 | { | |
208 | if (is_nd_pmem(dev) || is_nd_blk(dev)) { | |
209 | struct nd_region *nd_region = to_nd_region(dev); | |
210 | int i; | |
211 | ||
212 | for (i = 0; i < nd_region->ndr_mappings; i++) { | |
213 | struct nd_mapping *nd_mapping = &nd_region->mapping[i]; | |
214 | struct nvdimm *nvdimm = nd_mapping->nvdimm; | |
215 | ||
216 | if (probe) | |
217 | atomic_inc(&nvdimm->busy); | |
218 | else | |
219 | atomic_dec(&nvdimm->busy); | |
220 | } | |
221 | } | |
222 | } | |
223 | ||
224 | void nd_region_probe_success(struct nvdimm_bus *nvdimm_bus, struct device *dev) | |
225 | { | |
226 | nd_region_notify_driver_action(nvdimm_bus, dev, true); | |
227 | } | |
228 | ||
229 | void nd_region_disable(struct nvdimm_bus *nvdimm_bus, struct device *dev) | |
230 | { | |
231 | nd_region_notify_driver_action(nvdimm_bus, dev, false); | |
232 | } | |
233 | ||
1f7df6f8 DW |
234 | static ssize_t mappingN(struct device *dev, char *buf, int n) |
235 | { | |
236 | struct nd_region *nd_region = to_nd_region(dev); | |
237 | struct nd_mapping *nd_mapping; | |
238 | struct nvdimm *nvdimm; | |
239 | ||
240 | if (n >= nd_region->ndr_mappings) | |
241 | return -ENXIO; | |
242 | nd_mapping = &nd_region->mapping[n]; | |
243 | nvdimm = nd_mapping->nvdimm; | |
244 | ||
245 | return sprintf(buf, "%s,%llu,%llu\n", dev_name(&nvdimm->dev), | |
246 | nd_mapping->start, nd_mapping->size); | |
247 | } | |
248 | ||
249 | #define REGION_MAPPING(idx) \ | |
250 | static ssize_t mapping##idx##_show(struct device *dev, \ | |
251 | struct device_attribute *attr, char *buf) \ | |
252 | { \ | |
253 | return mappingN(dev, buf, idx); \ | |
254 | } \ | |
255 | static DEVICE_ATTR_RO(mapping##idx) | |
256 | ||
257 | /* | |
258 | * 32 should be enough for a while, even in the presence of socket | |
259 | * interleave a 32-way interleave set is a degenerate case. | |
260 | */ | |
261 | REGION_MAPPING(0); | |
262 | REGION_MAPPING(1); | |
263 | REGION_MAPPING(2); | |
264 | REGION_MAPPING(3); | |
265 | REGION_MAPPING(4); | |
266 | REGION_MAPPING(5); | |
267 | REGION_MAPPING(6); | |
268 | REGION_MAPPING(7); | |
269 | REGION_MAPPING(8); | |
270 | REGION_MAPPING(9); | |
271 | REGION_MAPPING(10); | |
272 | REGION_MAPPING(11); | |
273 | REGION_MAPPING(12); | |
274 | REGION_MAPPING(13); | |
275 | REGION_MAPPING(14); | |
276 | REGION_MAPPING(15); | |
277 | REGION_MAPPING(16); | |
278 | REGION_MAPPING(17); | |
279 | REGION_MAPPING(18); | |
280 | REGION_MAPPING(19); | |
281 | REGION_MAPPING(20); | |
282 | REGION_MAPPING(21); | |
283 | REGION_MAPPING(22); | |
284 | REGION_MAPPING(23); | |
285 | REGION_MAPPING(24); | |
286 | REGION_MAPPING(25); | |
287 | REGION_MAPPING(26); | |
288 | REGION_MAPPING(27); | |
289 | REGION_MAPPING(28); | |
290 | REGION_MAPPING(29); | |
291 | REGION_MAPPING(30); | |
292 | REGION_MAPPING(31); | |
293 | ||
294 | static umode_t mapping_visible(struct kobject *kobj, struct attribute *a, int n) | |
295 | { | |
296 | struct device *dev = container_of(kobj, struct device, kobj); | |
297 | struct nd_region *nd_region = to_nd_region(dev); | |
298 | ||
299 | if (n < nd_region->ndr_mappings) | |
300 | return a->mode; | |
301 | return 0; | |
302 | } | |
303 | ||
304 | static struct attribute *mapping_attributes[] = { | |
305 | &dev_attr_mapping0.attr, | |
306 | &dev_attr_mapping1.attr, | |
307 | &dev_attr_mapping2.attr, | |
308 | &dev_attr_mapping3.attr, | |
309 | &dev_attr_mapping4.attr, | |
310 | &dev_attr_mapping5.attr, | |
311 | &dev_attr_mapping6.attr, | |
312 | &dev_attr_mapping7.attr, | |
313 | &dev_attr_mapping8.attr, | |
314 | &dev_attr_mapping9.attr, | |
315 | &dev_attr_mapping10.attr, | |
316 | &dev_attr_mapping11.attr, | |
317 | &dev_attr_mapping12.attr, | |
318 | &dev_attr_mapping13.attr, | |
319 | &dev_attr_mapping14.attr, | |
320 | &dev_attr_mapping15.attr, | |
321 | &dev_attr_mapping16.attr, | |
322 | &dev_attr_mapping17.attr, | |
323 | &dev_attr_mapping18.attr, | |
324 | &dev_attr_mapping19.attr, | |
325 | &dev_attr_mapping20.attr, | |
326 | &dev_attr_mapping21.attr, | |
327 | &dev_attr_mapping22.attr, | |
328 | &dev_attr_mapping23.attr, | |
329 | &dev_attr_mapping24.attr, | |
330 | &dev_attr_mapping25.attr, | |
331 | &dev_attr_mapping26.attr, | |
332 | &dev_attr_mapping27.attr, | |
333 | &dev_attr_mapping28.attr, | |
334 | &dev_attr_mapping29.attr, | |
335 | &dev_attr_mapping30.attr, | |
336 | &dev_attr_mapping31.attr, | |
337 | NULL, | |
338 | }; | |
339 | ||
340 | struct attribute_group nd_mapping_attribute_group = { | |
341 | .is_visible = mapping_visible, | |
342 | .attrs = mapping_attributes, | |
343 | }; | |
344 | EXPORT_SYMBOL_GPL(nd_mapping_attribute_group); | |
345 | ||
346 | void *nd_region_provider_data(struct nd_region *nd_region) | |
347 | { | |
348 | return nd_region->provider_data; | |
349 | } | |
350 | EXPORT_SYMBOL_GPL(nd_region_provider_data); | |
351 | ||
352 | static struct nd_region *nd_region_create(struct nvdimm_bus *nvdimm_bus, | |
353 | struct nd_region_desc *ndr_desc, struct device_type *dev_type, | |
354 | const char *caller) | |
355 | { | |
356 | struct nd_region *nd_region; | |
357 | struct device *dev; | |
358 | u16 i; | |
359 | ||
360 | for (i = 0; i < ndr_desc->num_mappings; i++) { | |
361 | struct nd_mapping *nd_mapping = &ndr_desc->nd_mapping[i]; | |
362 | struct nvdimm *nvdimm = nd_mapping->nvdimm; | |
363 | ||
364 | if ((nd_mapping->start | nd_mapping->size) % SZ_4K) { | |
365 | dev_err(&nvdimm_bus->dev, "%s: %s mapping%d is not 4K aligned\n", | |
366 | caller, dev_name(&nvdimm->dev), i); | |
367 | ||
368 | return NULL; | |
369 | } | |
370 | } | |
371 | ||
372 | nd_region = kzalloc(sizeof(struct nd_region) | |
373 | + sizeof(struct nd_mapping) * ndr_desc->num_mappings, | |
374 | GFP_KERNEL); | |
375 | if (!nd_region) | |
376 | return NULL; | |
377 | nd_region->id = ida_simple_get(®ion_ida, 0, 0, GFP_KERNEL); | |
378 | if (nd_region->id < 0) { | |
379 | kfree(nd_region); | |
380 | return NULL; | |
381 | } | |
382 | ||
383 | memcpy(nd_region->mapping, ndr_desc->nd_mapping, | |
384 | sizeof(struct nd_mapping) * ndr_desc->num_mappings); | |
385 | for (i = 0; i < ndr_desc->num_mappings; i++) { | |
386 | struct nd_mapping *nd_mapping = &ndr_desc->nd_mapping[i]; | |
387 | struct nvdimm *nvdimm = nd_mapping->nvdimm; | |
388 | ||
389 | get_device(&nvdimm->dev); | |
390 | } | |
391 | nd_region->ndr_mappings = ndr_desc->num_mappings; | |
392 | nd_region->provider_data = ndr_desc->provider_data; | |
eaf96153 | 393 | nd_region->nd_set = ndr_desc->nd_set; |
1f7df6f8 DW |
394 | dev = &nd_region->dev; |
395 | dev_set_name(dev, "region%d", nd_region->id); | |
396 | dev->parent = &nvdimm_bus->dev; | |
397 | dev->type = dev_type; | |
398 | dev->groups = ndr_desc->attr_groups; | |
399 | nd_region->ndr_size = resource_size(ndr_desc->res); | |
400 | nd_region->ndr_start = ndr_desc->res->start; | |
401 | nd_device_register(dev); | |
402 | ||
403 | return nd_region; | |
404 | } | |
405 | ||
406 | struct nd_region *nvdimm_pmem_region_create(struct nvdimm_bus *nvdimm_bus, | |
407 | struct nd_region_desc *ndr_desc) | |
408 | { | |
409 | return nd_region_create(nvdimm_bus, ndr_desc, &nd_pmem_device_type, | |
410 | __func__); | |
411 | } | |
412 | EXPORT_SYMBOL_GPL(nvdimm_pmem_region_create); | |
413 | ||
414 | struct nd_region *nvdimm_blk_region_create(struct nvdimm_bus *nvdimm_bus, | |
415 | struct nd_region_desc *ndr_desc) | |
416 | { | |
417 | if (ndr_desc->num_mappings > 1) | |
418 | return NULL; | |
419 | return nd_region_create(nvdimm_bus, ndr_desc, &nd_blk_device_type, | |
420 | __func__); | |
421 | } | |
422 | EXPORT_SYMBOL_GPL(nvdimm_blk_region_create); | |
423 | ||
424 | struct nd_region *nvdimm_volatile_region_create(struct nvdimm_bus *nvdimm_bus, | |
425 | struct nd_region_desc *ndr_desc) | |
426 | { | |
427 | return nd_region_create(nvdimm_bus, ndr_desc, &nd_volatile_device_type, | |
428 | __func__); | |
429 | } | |
430 | EXPORT_SYMBOL_GPL(nvdimm_volatile_region_create); |