]>
Commit | Line | Data |
---|---|---|
b94d5230 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/libnvdimm.h> | |
14 | #include <linux/export.h> | |
15 | #include <linux/module.h> | |
41cd8b70 | 16 | #include <linux/blkdev.h> |
b94d5230 | 17 | #include <linux/device.h> |
bf9bccc1 | 18 | #include <linux/ctype.h> |
62232e45 | 19 | #include <linux/ndctl.h> |
45def22c | 20 | #include <linux/mutex.h> |
b94d5230 DW |
21 | #include <linux/slab.h> |
22 | #include "nd-core.h" | |
4d88a97a | 23 | #include "nd.h" |
b94d5230 | 24 | |
e6dfb2de DW |
25 | LIST_HEAD(nvdimm_bus_list); |
26 | DEFINE_MUTEX(nvdimm_bus_list_mutex); | |
b94d5230 DW |
27 | static DEFINE_IDA(nd_ida); |
28 | ||
3d88002e DW |
29 | void nvdimm_bus_lock(struct device *dev) |
30 | { | |
31 | struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(dev); | |
32 | ||
33 | if (!nvdimm_bus) | |
34 | return; | |
35 | mutex_lock(&nvdimm_bus->reconfig_mutex); | |
36 | } | |
37 | EXPORT_SYMBOL(nvdimm_bus_lock); | |
38 | ||
39 | void nvdimm_bus_unlock(struct device *dev) | |
40 | { | |
41 | struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(dev); | |
42 | ||
43 | if (!nvdimm_bus) | |
44 | return; | |
45 | mutex_unlock(&nvdimm_bus->reconfig_mutex); | |
46 | } | |
47 | EXPORT_SYMBOL(nvdimm_bus_unlock); | |
48 | ||
49 | bool is_nvdimm_bus_locked(struct device *dev) | |
50 | { | |
51 | struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(dev); | |
52 | ||
53 | if (!nvdimm_bus) | |
54 | return false; | |
55 | return mutex_is_locked(&nvdimm_bus->reconfig_mutex); | |
56 | } | |
57 | EXPORT_SYMBOL(is_nvdimm_bus_locked); | |
58 | ||
eaf96153 DW |
59 | u64 nd_fletcher64(void *addr, size_t len, bool le) |
60 | { | |
61 | u32 *buf = addr; | |
62 | u32 lo32 = 0; | |
63 | u64 hi32 = 0; | |
64 | int i; | |
65 | ||
66 | for (i = 0; i < len / sizeof(u32); i++) { | |
67 | lo32 += le ? le32_to_cpu((__le32) buf[i]) : buf[i]; | |
68 | hi32 += lo32; | |
69 | } | |
70 | ||
71 | return hi32 << 32 | lo32; | |
72 | } | |
73 | EXPORT_SYMBOL_GPL(nd_fletcher64); | |
74 | ||
b94d5230 DW |
75 | static void nvdimm_bus_release(struct device *dev) |
76 | { | |
77 | struct nvdimm_bus *nvdimm_bus; | |
78 | ||
79 | nvdimm_bus = container_of(dev, struct nvdimm_bus, dev); | |
80 | ida_simple_remove(&nd_ida, nvdimm_bus->id); | |
81 | kfree(nvdimm_bus); | |
82 | } | |
83 | ||
45def22c DW |
84 | struct nvdimm_bus *to_nvdimm_bus(struct device *dev) |
85 | { | |
86 | struct nvdimm_bus *nvdimm_bus; | |
87 | ||
88 | nvdimm_bus = container_of(dev, struct nvdimm_bus, dev); | |
89 | WARN_ON(nvdimm_bus->dev.release != nvdimm_bus_release); | |
90 | return nvdimm_bus; | |
91 | } | |
92 | EXPORT_SYMBOL_GPL(to_nvdimm_bus); | |
93 | ||
94 | struct nvdimm_bus_descriptor *to_nd_desc(struct nvdimm_bus *nvdimm_bus) | |
95 | { | |
96 | /* struct nvdimm_bus definition is private to libnvdimm */ | |
97 | return nvdimm_bus->nd_desc; | |
98 | } | |
99 | EXPORT_SYMBOL_GPL(to_nd_desc); | |
100 | ||
e6dfb2de DW |
101 | struct nvdimm_bus *walk_to_nvdimm_bus(struct device *nd_dev) |
102 | { | |
103 | struct device *dev; | |
104 | ||
105 | for (dev = nd_dev; dev; dev = dev->parent) | |
106 | if (dev->release == nvdimm_bus_release) | |
107 | break; | |
108 | dev_WARN_ONCE(nd_dev, !dev, "invalid dev, not on nd bus\n"); | |
109 | if (dev) | |
110 | return to_nvdimm_bus(dev); | |
111 | return NULL; | |
112 | } | |
113 | ||
bf9bccc1 DW |
114 | static bool is_uuid_sep(char sep) |
115 | { | |
116 | if (sep == '\n' || sep == '-' || sep == ':' || sep == '\0') | |
117 | return true; | |
118 | return false; | |
119 | } | |
120 | ||
121 | static int nd_uuid_parse(struct device *dev, u8 *uuid_out, const char *buf, | |
122 | size_t len) | |
123 | { | |
124 | const char *str = buf; | |
125 | u8 uuid[16]; | |
126 | int i; | |
127 | ||
128 | for (i = 0; i < 16; i++) { | |
129 | if (!isxdigit(str[0]) || !isxdigit(str[1])) { | |
130 | dev_dbg(dev, "%s: pos: %d buf[%zd]: %c buf[%zd]: %c\n", | |
131 | __func__, i, str - buf, str[0], | |
132 | str + 1 - buf, str[1]); | |
133 | return -EINVAL; | |
134 | } | |
135 | ||
136 | uuid[i] = (hex_to_bin(str[0]) << 4) | hex_to_bin(str[1]); | |
137 | str += 2; | |
138 | if (is_uuid_sep(*str)) | |
139 | str++; | |
140 | } | |
141 | ||
142 | memcpy(uuid_out, uuid, sizeof(uuid)); | |
143 | return 0; | |
144 | } | |
145 | ||
146 | /** | |
147 | * nd_uuid_store: common implementation for writing 'uuid' sysfs attributes | |
148 | * @dev: container device for the uuid property | |
149 | * @uuid_out: uuid buffer to replace | |
150 | * @buf: raw sysfs buffer to parse | |
151 | * | |
152 | * Enforce that uuids can only be changed while the device is disabled | |
153 | * (driver detached) | |
154 | * LOCKING: expects device_lock() is held on entry | |
155 | */ | |
156 | int nd_uuid_store(struct device *dev, u8 **uuid_out, const char *buf, | |
157 | size_t len) | |
158 | { | |
159 | u8 uuid[16]; | |
160 | int rc; | |
161 | ||
162 | if (dev->driver) | |
163 | return -EBUSY; | |
164 | ||
165 | rc = nd_uuid_parse(dev, uuid, buf, len); | |
166 | if (rc) | |
167 | return rc; | |
168 | ||
169 | kfree(*uuid_out); | |
170 | *uuid_out = kmemdup(uuid, sizeof(uuid), GFP_KERNEL); | |
171 | if (!(*uuid_out)) | |
172 | return -ENOMEM; | |
173 | ||
174 | return 0; | |
175 | } | |
176 | ||
1b40e09a DW |
177 | ssize_t nd_sector_size_show(unsigned long current_lbasize, |
178 | const unsigned long *supported, char *buf) | |
179 | { | |
180 | ssize_t len = 0; | |
181 | int i; | |
182 | ||
183 | for (i = 0; supported[i]; i++) | |
184 | if (current_lbasize == supported[i]) | |
185 | len += sprintf(buf + len, "[%ld] ", supported[i]); | |
186 | else | |
187 | len += sprintf(buf + len, "%ld ", supported[i]); | |
188 | len += sprintf(buf + len, "\n"); | |
189 | return len; | |
190 | } | |
191 | ||
192 | ssize_t nd_sector_size_store(struct device *dev, const char *buf, | |
193 | unsigned long *current_lbasize, const unsigned long *supported) | |
194 | { | |
195 | unsigned long lbasize; | |
196 | int rc, i; | |
197 | ||
198 | if (dev->driver) | |
199 | return -EBUSY; | |
200 | ||
201 | rc = kstrtoul(buf, 0, &lbasize); | |
202 | if (rc) | |
203 | return rc; | |
204 | ||
205 | for (i = 0; supported[i]; i++) | |
206 | if (lbasize == supported[i]) | |
207 | break; | |
208 | ||
209 | if (supported[i]) { | |
210 | *current_lbasize = lbasize; | |
211 | return 0; | |
212 | } else { | |
213 | return -EINVAL; | |
214 | } | |
215 | } | |
216 | ||
62232e45 DW |
217 | static ssize_t commands_show(struct device *dev, |
218 | struct device_attribute *attr, char *buf) | |
219 | { | |
220 | int cmd, len = 0; | |
221 | struct nvdimm_bus *nvdimm_bus = to_nvdimm_bus(dev); | |
222 | struct nvdimm_bus_descriptor *nd_desc = nvdimm_bus->nd_desc; | |
223 | ||
224 | for_each_set_bit(cmd, &nd_desc->dsm_mask, BITS_PER_LONG) | |
225 | len += sprintf(buf + len, "%s ", nvdimm_bus_cmd_name(cmd)); | |
226 | len += sprintf(buf + len, "\n"); | |
227 | return len; | |
228 | } | |
229 | static DEVICE_ATTR_RO(commands); | |
230 | ||
45def22c DW |
231 | static const char *nvdimm_bus_provider(struct nvdimm_bus *nvdimm_bus) |
232 | { | |
233 | struct nvdimm_bus_descriptor *nd_desc = nvdimm_bus->nd_desc; | |
234 | struct device *parent = nvdimm_bus->dev.parent; | |
235 | ||
236 | if (nd_desc->provider_name) | |
237 | return nd_desc->provider_name; | |
238 | else if (parent) | |
239 | return dev_name(parent); | |
240 | else | |
241 | return "unknown"; | |
242 | } | |
243 | ||
244 | static ssize_t provider_show(struct device *dev, | |
245 | struct device_attribute *attr, char *buf) | |
246 | { | |
247 | struct nvdimm_bus *nvdimm_bus = to_nvdimm_bus(dev); | |
248 | ||
249 | return sprintf(buf, "%s\n", nvdimm_bus_provider(nvdimm_bus)); | |
250 | } | |
251 | static DEVICE_ATTR_RO(provider); | |
252 | ||
4d88a97a DW |
253 | static int flush_namespaces(struct device *dev, void *data) |
254 | { | |
255 | device_lock(dev); | |
256 | device_unlock(dev); | |
257 | return 0; | |
258 | } | |
259 | ||
260 | static int flush_regions_dimms(struct device *dev, void *data) | |
261 | { | |
262 | device_lock(dev); | |
263 | device_unlock(dev); | |
264 | device_for_each_child(dev, NULL, flush_namespaces); | |
265 | return 0; | |
266 | } | |
267 | ||
268 | static ssize_t wait_probe_show(struct device *dev, | |
269 | struct device_attribute *attr, char *buf) | |
270 | { | |
271 | nd_synchronize(); | |
272 | device_for_each_child(dev, NULL, flush_regions_dimms); | |
273 | return sprintf(buf, "1\n"); | |
274 | } | |
275 | static DEVICE_ATTR_RO(wait_probe); | |
276 | ||
45def22c | 277 | static struct attribute *nvdimm_bus_attributes[] = { |
62232e45 | 278 | &dev_attr_commands.attr, |
4d88a97a | 279 | &dev_attr_wait_probe.attr, |
45def22c DW |
280 | &dev_attr_provider.attr, |
281 | NULL, | |
282 | }; | |
283 | ||
284 | struct attribute_group nvdimm_bus_attribute_group = { | |
285 | .attrs = nvdimm_bus_attributes, | |
286 | }; | |
287 | EXPORT_SYMBOL_GPL(nvdimm_bus_attribute_group); | |
288 | ||
3d88002e DW |
289 | struct nvdimm_bus *__nvdimm_bus_register(struct device *parent, |
290 | struct nvdimm_bus_descriptor *nd_desc, struct module *module) | |
b94d5230 DW |
291 | { |
292 | struct nvdimm_bus *nvdimm_bus; | |
293 | int rc; | |
294 | ||
295 | nvdimm_bus = kzalloc(sizeof(*nvdimm_bus), GFP_KERNEL); | |
296 | if (!nvdimm_bus) | |
297 | return NULL; | |
45def22c | 298 | INIT_LIST_HEAD(&nvdimm_bus->list); |
eaf96153 | 299 | init_waitqueue_head(&nvdimm_bus->probe_wait); |
b94d5230 | 300 | nvdimm_bus->id = ida_simple_get(&nd_ida, 0, 0, GFP_KERNEL); |
3d88002e | 301 | mutex_init(&nvdimm_bus->reconfig_mutex); |
b94d5230 DW |
302 | if (nvdimm_bus->id < 0) { |
303 | kfree(nvdimm_bus); | |
304 | return NULL; | |
305 | } | |
306 | nvdimm_bus->nd_desc = nd_desc; | |
3d88002e | 307 | nvdimm_bus->module = module; |
b94d5230 DW |
308 | nvdimm_bus->dev.parent = parent; |
309 | nvdimm_bus->dev.release = nvdimm_bus_release; | |
45def22c | 310 | nvdimm_bus->dev.groups = nd_desc->attr_groups; |
b94d5230 DW |
311 | dev_set_name(&nvdimm_bus->dev, "ndbus%d", nvdimm_bus->id); |
312 | rc = device_register(&nvdimm_bus->dev); | |
313 | if (rc) { | |
314 | dev_dbg(&nvdimm_bus->dev, "registration failed: %d\n", rc); | |
45def22c | 315 | goto err; |
b94d5230 DW |
316 | } |
317 | ||
45def22c DW |
318 | rc = nvdimm_bus_create_ndctl(nvdimm_bus); |
319 | if (rc) | |
320 | goto err; | |
321 | ||
322 | mutex_lock(&nvdimm_bus_list_mutex); | |
323 | list_add_tail(&nvdimm_bus->list, &nvdimm_bus_list); | |
324 | mutex_unlock(&nvdimm_bus_list_mutex); | |
325 | ||
b94d5230 | 326 | return nvdimm_bus; |
45def22c DW |
327 | err: |
328 | put_device(&nvdimm_bus->dev); | |
329 | return NULL; | |
b94d5230 | 330 | } |
3d88002e | 331 | EXPORT_SYMBOL_GPL(__nvdimm_bus_register); |
b94d5230 | 332 | |
e6dfb2de DW |
333 | static int child_unregister(struct device *dev, void *data) |
334 | { | |
335 | /* | |
336 | * the singular ndctl class device per bus needs to be | |
337 | * "device_destroy"ed, so skip it here | |
338 | * | |
339 | * i.e. remove classless children | |
340 | */ | |
341 | if (dev->class) | |
342 | /* pass */; | |
343 | else | |
4d88a97a | 344 | nd_device_unregister(dev, ND_SYNC); |
e6dfb2de DW |
345 | return 0; |
346 | } | |
347 | ||
b94d5230 DW |
348 | void nvdimm_bus_unregister(struct nvdimm_bus *nvdimm_bus) |
349 | { | |
350 | if (!nvdimm_bus) | |
351 | return; | |
45def22c DW |
352 | |
353 | mutex_lock(&nvdimm_bus_list_mutex); | |
354 | list_del_init(&nvdimm_bus->list); | |
355 | mutex_unlock(&nvdimm_bus_list_mutex); | |
356 | ||
4d88a97a | 357 | nd_synchronize(); |
e6dfb2de | 358 | device_for_each_child(&nvdimm_bus->dev, NULL, child_unregister); |
45def22c DW |
359 | nvdimm_bus_destroy_ndctl(nvdimm_bus); |
360 | ||
b94d5230 DW |
361 | device_unregister(&nvdimm_bus->dev); |
362 | } | |
363 | EXPORT_SYMBOL_GPL(nvdimm_bus_unregister); | |
364 | ||
41cd8b70 VV |
365 | #ifdef CONFIG_BLK_DEV_INTEGRITY |
366 | static int nd_pi_nop_generate_verify(struct blk_integrity_iter *iter) | |
367 | { | |
368 | return 0; | |
369 | } | |
370 | ||
371 | int nd_integrity_init(struct gendisk *disk, unsigned long meta_size) | |
372 | { | |
373 | struct blk_integrity integrity = { | |
374 | .name = "ND-PI-NOP", | |
375 | .generate_fn = nd_pi_nop_generate_verify, | |
376 | .verify_fn = nd_pi_nop_generate_verify, | |
377 | .tuple_size = meta_size, | |
378 | .tag_size = meta_size, | |
379 | }; | |
380 | int ret; | |
381 | ||
fcae6957 VV |
382 | if (meta_size == 0) |
383 | return 0; | |
384 | ||
41cd8b70 VV |
385 | ret = blk_integrity_register(disk, &integrity); |
386 | if (ret) | |
387 | return ret; | |
388 | ||
389 | blk_queue_max_integrity_segments(disk->queue, 1); | |
390 | ||
391 | return 0; | |
392 | } | |
393 | EXPORT_SYMBOL(nd_integrity_init); | |
394 | ||
395 | #else /* CONFIG_BLK_DEV_INTEGRITY */ | |
396 | int nd_integrity_init(struct gendisk *disk, unsigned long meta_size) | |
397 | { | |
398 | return 0; | |
399 | } | |
400 | EXPORT_SYMBOL(nd_integrity_init); | |
401 | ||
402 | #endif | |
403 | ||
45def22c DW |
404 | static __init int libnvdimm_init(void) |
405 | { | |
4d88a97a DW |
406 | int rc; |
407 | ||
408 | rc = nvdimm_bus_init(); | |
409 | if (rc) | |
410 | return rc; | |
411 | rc = nvdimm_init(); | |
412 | if (rc) | |
413 | goto err_dimm; | |
3d88002e DW |
414 | rc = nd_region_init(); |
415 | if (rc) | |
416 | goto err_region; | |
4d88a97a | 417 | return 0; |
3d88002e DW |
418 | err_region: |
419 | nvdimm_exit(); | |
4d88a97a DW |
420 | err_dimm: |
421 | nvdimm_bus_exit(); | |
422 | return rc; | |
45def22c DW |
423 | } |
424 | ||
425 | static __exit void libnvdimm_exit(void) | |
426 | { | |
427 | WARN_ON(!list_empty(&nvdimm_bus_list)); | |
3d88002e | 428 | nd_region_exit(); |
4d88a97a | 429 | nvdimm_exit(); |
45def22c DW |
430 | nvdimm_bus_exit(); |
431 | } | |
432 | ||
b94d5230 DW |
433 | MODULE_LICENSE("GPL v2"); |
434 | MODULE_AUTHOR("Intel Corporation"); | |
45def22c DW |
435 | subsys_initcall(libnvdimm_init); |
436 | module_exit(libnvdimm_exit); |