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 | */ | |
13 | #include <linux/slab.h> | |
14 | #include <linux/io.h> | |
15 | #include "nd-core.h" | |
16 | #include "nd.h" | |
17 | ||
18 | static DEFINE_IDA(region_ida); | |
19 | ||
20 | static void nd_region_release(struct device *dev) | |
21 | { | |
22 | struct nd_region *nd_region = to_nd_region(dev); | |
23 | u16 i; | |
24 | ||
25 | for (i = 0; i < nd_region->ndr_mappings; i++) { | |
26 | struct nd_mapping *nd_mapping = &nd_region->mapping[i]; | |
27 | struct nvdimm *nvdimm = nd_mapping->nvdimm; | |
28 | ||
29 | put_device(&nvdimm->dev); | |
30 | } | |
31 | ida_simple_remove(®ion_ida, nd_region->id); | |
32 | kfree(nd_region); | |
33 | } | |
34 | ||
35 | static struct device_type nd_blk_device_type = { | |
36 | .name = "nd_blk", | |
37 | .release = nd_region_release, | |
38 | }; | |
39 | ||
40 | static struct device_type nd_pmem_device_type = { | |
41 | .name = "nd_pmem", | |
42 | .release = nd_region_release, | |
43 | }; | |
44 | ||
45 | static struct device_type nd_volatile_device_type = { | |
46 | .name = "nd_volatile", | |
47 | .release = nd_region_release, | |
48 | }; | |
49 | ||
50 | static bool is_nd_pmem(struct device *dev) | |
51 | { | |
52 | return dev ? dev->type == &nd_pmem_device_type : false; | |
53 | } | |
54 | ||
55 | struct nd_region *to_nd_region(struct device *dev) | |
56 | { | |
57 | struct nd_region *nd_region = container_of(dev, struct nd_region, dev); | |
58 | ||
59 | WARN_ON(dev->type->release != nd_region_release); | |
60 | return nd_region; | |
61 | } | |
62 | EXPORT_SYMBOL_GPL(to_nd_region); | |
63 | ||
64 | static ssize_t size_show(struct device *dev, | |
65 | struct device_attribute *attr, char *buf) | |
66 | { | |
67 | struct nd_region *nd_region = to_nd_region(dev); | |
68 | unsigned long long size = 0; | |
69 | ||
70 | if (is_nd_pmem(dev)) { | |
71 | size = nd_region->ndr_size; | |
72 | } else if (nd_region->ndr_mappings == 1) { | |
73 | struct nd_mapping *nd_mapping = &nd_region->mapping[0]; | |
74 | ||
75 | size = nd_mapping->size; | |
76 | } | |
77 | ||
78 | return sprintf(buf, "%llu\n", size); | |
79 | } | |
80 | static DEVICE_ATTR_RO(size); | |
81 | ||
82 | static ssize_t mappings_show(struct device *dev, | |
83 | struct device_attribute *attr, char *buf) | |
84 | { | |
85 | struct nd_region *nd_region = to_nd_region(dev); | |
86 | ||
87 | return sprintf(buf, "%d\n", nd_region->ndr_mappings); | |
88 | } | |
89 | static DEVICE_ATTR_RO(mappings); | |
90 | ||
91 | static struct attribute *nd_region_attributes[] = { | |
92 | &dev_attr_size.attr, | |
93 | &dev_attr_mappings.attr, | |
94 | NULL, | |
95 | }; | |
96 | ||
97 | struct attribute_group nd_region_attribute_group = { | |
98 | .attrs = nd_region_attributes, | |
99 | }; | |
100 | EXPORT_SYMBOL_GPL(nd_region_attribute_group); | |
101 | ||
102 | static ssize_t mappingN(struct device *dev, char *buf, int n) | |
103 | { | |
104 | struct nd_region *nd_region = to_nd_region(dev); | |
105 | struct nd_mapping *nd_mapping; | |
106 | struct nvdimm *nvdimm; | |
107 | ||
108 | if (n >= nd_region->ndr_mappings) | |
109 | return -ENXIO; | |
110 | nd_mapping = &nd_region->mapping[n]; | |
111 | nvdimm = nd_mapping->nvdimm; | |
112 | ||
113 | return sprintf(buf, "%s,%llu,%llu\n", dev_name(&nvdimm->dev), | |
114 | nd_mapping->start, nd_mapping->size); | |
115 | } | |
116 | ||
117 | #define REGION_MAPPING(idx) \ | |
118 | static ssize_t mapping##idx##_show(struct device *dev, \ | |
119 | struct device_attribute *attr, char *buf) \ | |
120 | { \ | |
121 | return mappingN(dev, buf, idx); \ | |
122 | } \ | |
123 | static DEVICE_ATTR_RO(mapping##idx) | |
124 | ||
125 | /* | |
126 | * 32 should be enough for a while, even in the presence of socket | |
127 | * interleave a 32-way interleave set is a degenerate case. | |
128 | */ | |
129 | REGION_MAPPING(0); | |
130 | REGION_MAPPING(1); | |
131 | REGION_MAPPING(2); | |
132 | REGION_MAPPING(3); | |
133 | REGION_MAPPING(4); | |
134 | REGION_MAPPING(5); | |
135 | REGION_MAPPING(6); | |
136 | REGION_MAPPING(7); | |
137 | REGION_MAPPING(8); | |
138 | REGION_MAPPING(9); | |
139 | REGION_MAPPING(10); | |
140 | REGION_MAPPING(11); | |
141 | REGION_MAPPING(12); | |
142 | REGION_MAPPING(13); | |
143 | REGION_MAPPING(14); | |
144 | REGION_MAPPING(15); | |
145 | REGION_MAPPING(16); | |
146 | REGION_MAPPING(17); | |
147 | REGION_MAPPING(18); | |
148 | REGION_MAPPING(19); | |
149 | REGION_MAPPING(20); | |
150 | REGION_MAPPING(21); | |
151 | REGION_MAPPING(22); | |
152 | REGION_MAPPING(23); | |
153 | REGION_MAPPING(24); | |
154 | REGION_MAPPING(25); | |
155 | REGION_MAPPING(26); | |
156 | REGION_MAPPING(27); | |
157 | REGION_MAPPING(28); | |
158 | REGION_MAPPING(29); | |
159 | REGION_MAPPING(30); | |
160 | REGION_MAPPING(31); | |
161 | ||
162 | static umode_t mapping_visible(struct kobject *kobj, struct attribute *a, int n) | |
163 | { | |
164 | struct device *dev = container_of(kobj, struct device, kobj); | |
165 | struct nd_region *nd_region = to_nd_region(dev); | |
166 | ||
167 | if (n < nd_region->ndr_mappings) | |
168 | return a->mode; | |
169 | return 0; | |
170 | } | |
171 | ||
172 | static struct attribute *mapping_attributes[] = { | |
173 | &dev_attr_mapping0.attr, | |
174 | &dev_attr_mapping1.attr, | |
175 | &dev_attr_mapping2.attr, | |
176 | &dev_attr_mapping3.attr, | |
177 | &dev_attr_mapping4.attr, | |
178 | &dev_attr_mapping5.attr, | |
179 | &dev_attr_mapping6.attr, | |
180 | &dev_attr_mapping7.attr, | |
181 | &dev_attr_mapping8.attr, | |
182 | &dev_attr_mapping9.attr, | |
183 | &dev_attr_mapping10.attr, | |
184 | &dev_attr_mapping11.attr, | |
185 | &dev_attr_mapping12.attr, | |
186 | &dev_attr_mapping13.attr, | |
187 | &dev_attr_mapping14.attr, | |
188 | &dev_attr_mapping15.attr, | |
189 | &dev_attr_mapping16.attr, | |
190 | &dev_attr_mapping17.attr, | |
191 | &dev_attr_mapping18.attr, | |
192 | &dev_attr_mapping19.attr, | |
193 | &dev_attr_mapping20.attr, | |
194 | &dev_attr_mapping21.attr, | |
195 | &dev_attr_mapping22.attr, | |
196 | &dev_attr_mapping23.attr, | |
197 | &dev_attr_mapping24.attr, | |
198 | &dev_attr_mapping25.attr, | |
199 | &dev_attr_mapping26.attr, | |
200 | &dev_attr_mapping27.attr, | |
201 | &dev_attr_mapping28.attr, | |
202 | &dev_attr_mapping29.attr, | |
203 | &dev_attr_mapping30.attr, | |
204 | &dev_attr_mapping31.attr, | |
205 | NULL, | |
206 | }; | |
207 | ||
208 | struct attribute_group nd_mapping_attribute_group = { | |
209 | .is_visible = mapping_visible, | |
210 | .attrs = mapping_attributes, | |
211 | }; | |
212 | EXPORT_SYMBOL_GPL(nd_mapping_attribute_group); | |
213 | ||
214 | void *nd_region_provider_data(struct nd_region *nd_region) | |
215 | { | |
216 | return nd_region->provider_data; | |
217 | } | |
218 | EXPORT_SYMBOL_GPL(nd_region_provider_data); | |
219 | ||
220 | static struct nd_region *nd_region_create(struct nvdimm_bus *nvdimm_bus, | |
221 | struct nd_region_desc *ndr_desc, struct device_type *dev_type, | |
222 | const char *caller) | |
223 | { | |
224 | struct nd_region *nd_region; | |
225 | struct device *dev; | |
226 | u16 i; | |
227 | ||
228 | for (i = 0; i < ndr_desc->num_mappings; i++) { | |
229 | struct nd_mapping *nd_mapping = &ndr_desc->nd_mapping[i]; | |
230 | struct nvdimm *nvdimm = nd_mapping->nvdimm; | |
231 | ||
232 | if ((nd_mapping->start | nd_mapping->size) % SZ_4K) { | |
233 | dev_err(&nvdimm_bus->dev, "%s: %s mapping%d is not 4K aligned\n", | |
234 | caller, dev_name(&nvdimm->dev), i); | |
235 | ||
236 | return NULL; | |
237 | } | |
238 | } | |
239 | ||
240 | nd_region = kzalloc(sizeof(struct nd_region) | |
241 | + sizeof(struct nd_mapping) * ndr_desc->num_mappings, | |
242 | GFP_KERNEL); | |
243 | if (!nd_region) | |
244 | return NULL; | |
245 | nd_region->id = ida_simple_get(®ion_ida, 0, 0, GFP_KERNEL); | |
246 | if (nd_region->id < 0) { | |
247 | kfree(nd_region); | |
248 | return NULL; | |
249 | } | |
250 | ||
251 | memcpy(nd_region->mapping, ndr_desc->nd_mapping, | |
252 | sizeof(struct nd_mapping) * ndr_desc->num_mappings); | |
253 | for (i = 0; i < ndr_desc->num_mappings; i++) { | |
254 | struct nd_mapping *nd_mapping = &ndr_desc->nd_mapping[i]; | |
255 | struct nvdimm *nvdimm = nd_mapping->nvdimm; | |
256 | ||
257 | get_device(&nvdimm->dev); | |
258 | } | |
259 | nd_region->ndr_mappings = ndr_desc->num_mappings; | |
260 | nd_region->provider_data = ndr_desc->provider_data; | |
261 | dev = &nd_region->dev; | |
262 | dev_set_name(dev, "region%d", nd_region->id); | |
263 | dev->parent = &nvdimm_bus->dev; | |
264 | dev->type = dev_type; | |
265 | dev->groups = ndr_desc->attr_groups; | |
266 | nd_region->ndr_size = resource_size(ndr_desc->res); | |
267 | nd_region->ndr_start = ndr_desc->res->start; | |
268 | nd_device_register(dev); | |
269 | ||
270 | return nd_region; | |
271 | } | |
272 | ||
273 | struct nd_region *nvdimm_pmem_region_create(struct nvdimm_bus *nvdimm_bus, | |
274 | struct nd_region_desc *ndr_desc) | |
275 | { | |
276 | return nd_region_create(nvdimm_bus, ndr_desc, &nd_pmem_device_type, | |
277 | __func__); | |
278 | } | |
279 | EXPORT_SYMBOL_GPL(nvdimm_pmem_region_create); | |
280 | ||
281 | struct nd_region *nvdimm_blk_region_create(struct nvdimm_bus *nvdimm_bus, | |
282 | struct nd_region_desc *ndr_desc) | |
283 | { | |
284 | if (ndr_desc->num_mappings > 1) | |
285 | return NULL; | |
286 | return nd_region_create(nvdimm_bus, ndr_desc, &nd_blk_device_type, | |
287 | __func__); | |
288 | } | |
289 | EXPORT_SYMBOL_GPL(nvdimm_blk_region_create); | |
290 | ||
291 | struct nd_region *nvdimm_volatile_region_create(struct nvdimm_bus *nvdimm_bus, | |
292 | struct nd_region_desc *ndr_desc) | |
293 | { | |
294 | return nd_region_create(nvdimm_bus, ndr_desc, &nd_volatile_device_type, | |
295 | __func__); | |
296 | } | |
297 | EXPORT_SYMBOL_GPL(nvdimm_volatile_region_create); |