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 | ||
f0dc089c DW |
217 | void __nd_iostat_start(struct bio *bio, unsigned long *start) |
218 | { | |
219 | struct gendisk *disk = bio->bi_bdev->bd_disk; | |
220 | const int rw = bio_data_dir(bio); | |
221 | int cpu = part_stat_lock(); | |
222 | ||
223 | *start = jiffies; | |
224 | part_round_stats(cpu, &disk->part0); | |
225 | part_stat_inc(cpu, &disk->part0, ios[rw]); | |
226 | part_stat_add(cpu, &disk->part0, sectors[rw], bio_sectors(bio)); | |
227 | part_inc_in_flight(&disk->part0, rw); | |
228 | part_stat_unlock(); | |
229 | } | |
230 | EXPORT_SYMBOL(__nd_iostat_start); | |
231 | ||
232 | void nd_iostat_end(struct bio *bio, unsigned long start) | |
233 | { | |
234 | struct gendisk *disk = bio->bi_bdev->bd_disk; | |
235 | unsigned long duration = jiffies - start; | |
236 | const int rw = bio_data_dir(bio); | |
237 | int cpu = part_stat_lock(); | |
238 | ||
239 | part_stat_add(cpu, &disk->part0, ticks[rw], duration); | |
240 | part_round_stats(cpu, &disk->part0); | |
241 | part_dec_in_flight(&disk->part0, rw); | |
242 | part_stat_unlock(); | |
243 | } | |
244 | EXPORT_SYMBOL(nd_iostat_end); | |
245 | ||
62232e45 DW |
246 | static ssize_t commands_show(struct device *dev, |
247 | struct device_attribute *attr, char *buf) | |
248 | { | |
249 | int cmd, len = 0; | |
250 | struct nvdimm_bus *nvdimm_bus = to_nvdimm_bus(dev); | |
251 | struct nvdimm_bus_descriptor *nd_desc = nvdimm_bus->nd_desc; | |
252 | ||
253 | for_each_set_bit(cmd, &nd_desc->dsm_mask, BITS_PER_LONG) | |
254 | len += sprintf(buf + len, "%s ", nvdimm_bus_cmd_name(cmd)); | |
255 | len += sprintf(buf + len, "\n"); | |
256 | return len; | |
257 | } | |
258 | static DEVICE_ATTR_RO(commands); | |
259 | ||
45def22c DW |
260 | static const char *nvdimm_bus_provider(struct nvdimm_bus *nvdimm_bus) |
261 | { | |
262 | struct nvdimm_bus_descriptor *nd_desc = nvdimm_bus->nd_desc; | |
263 | struct device *parent = nvdimm_bus->dev.parent; | |
264 | ||
265 | if (nd_desc->provider_name) | |
266 | return nd_desc->provider_name; | |
267 | else if (parent) | |
268 | return dev_name(parent); | |
269 | else | |
270 | return "unknown"; | |
271 | } | |
272 | ||
273 | static ssize_t provider_show(struct device *dev, | |
274 | struct device_attribute *attr, char *buf) | |
275 | { | |
276 | struct nvdimm_bus *nvdimm_bus = to_nvdimm_bus(dev); | |
277 | ||
278 | return sprintf(buf, "%s\n", nvdimm_bus_provider(nvdimm_bus)); | |
279 | } | |
280 | static DEVICE_ATTR_RO(provider); | |
281 | ||
4d88a97a DW |
282 | static int flush_namespaces(struct device *dev, void *data) |
283 | { | |
284 | device_lock(dev); | |
285 | device_unlock(dev); | |
286 | return 0; | |
287 | } | |
288 | ||
289 | static int flush_regions_dimms(struct device *dev, void *data) | |
290 | { | |
291 | device_lock(dev); | |
292 | device_unlock(dev); | |
293 | device_for_each_child(dev, NULL, flush_namespaces); | |
294 | return 0; | |
295 | } | |
296 | ||
297 | static ssize_t wait_probe_show(struct device *dev, | |
298 | struct device_attribute *attr, char *buf) | |
299 | { | |
300 | nd_synchronize(); | |
301 | device_for_each_child(dev, NULL, flush_regions_dimms); | |
302 | return sprintf(buf, "1\n"); | |
303 | } | |
304 | static DEVICE_ATTR_RO(wait_probe); | |
305 | ||
45def22c | 306 | static struct attribute *nvdimm_bus_attributes[] = { |
62232e45 | 307 | &dev_attr_commands.attr, |
4d88a97a | 308 | &dev_attr_wait_probe.attr, |
45def22c DW |
309 | &dev_attr_provider.attr, |
310 | NULL, | |
311 | }; | |
312 | ||
313 | struct attribute_group nvdimm_bus_attribute_group = { | |
314 | .attrs = nvdimm_bus_attributes, | |
315 | }; | |
316 | EXPORT_SYMBOL_GPL(nvdimm_bus_attribute_group); | |
317 | ||
3d88002e DW |
318 | struct nvdimm_bus *__nvdimm_bus_register(struct device *parent, |
319 | struct nvdimm_bus_descriptor *nd_desc, struct module *module) | |
b94d5230 DW |
320 | { |
321 | struct nvdimm_bus *nvdimm_bus; | |
322 | int rc; | |
323 | ||
324 | nvdimm_bus = kzalloc(sizeof(*nvdimm_bus), GFP_KERNEL); | |
325 | if (!nvdimm_bus) | |
326 | return NULL; | |
45def22c | 327 | INIT_LIST_HEAD(&nvdimm_bus->list); |
eaf96153 | 328 | init_waitqueue_head(&nvdimm_bus->probe_wait); |
b94d5230 | 329 | nvdimm_bus->id = ida_simple_get(&nd_ida, 0, 0, GFP_KERNEL); |
3d88002e | 330 | mutex_init(&nvdimm_bus->reconfig_mutex); |
b94d5230 DW |
331 | if (nvdimm_bus->id < 0) { |
332 | kfree(nvdimm_bus); | |
333 | return NULL; | |
334 | } | |
335 | nvdimm_bus->nd_desc = nd_desc; | |
3d88002e | 336 | nvdimm_bus->module = module; |
b94d5230 DW |
337 | nvdimm_bus->dev.parent = parent; |
338 | nvdimm_bus->dev.release = nvdimm_bus_release; | |
45def22c | 339 | nvdimm_bus->dev.groups = nd_desc->attr_groups; |
b94d5230 DW |
340 | dev_set_name(&nvdimm_bus->dev, "ndbus%d", nvdimm_bus->id); |
341 | rc = device_register(&nvdimm_bus->dev); | |
342 | if (rc) { | |
343 | dev_dbg(&nvdimm_bus->dev, "registration failed: %d\n", rc); | |
45def22c | 344 | goto err; |
b94d5230 DW |
345 | } |
346 | ||
45def22c DW |
347 | rc = nvdimm_bus_create_ndctl(nvdimm_bus); |
348 | if (rc) | |
349 | goto err; | |
350 | ||
351 | mutex_lock(&nvdimm_bus_list_mutex); | |
352 | list_add_tail(&nvdimm_bus->list, &nvdimm_bus_list); | |
353 | mutex_unlock(&nvdimm_bus_list_mutex); | |
354 | ||
b94d5230 | 355 | return nvdimm_bus; |
45def22c DW |
356 | err: |
357 | put_device(&nvdimm_bus->dev); | |
358 | return NULL; | |
b94d5230 | 359 | } |
3d88002e | 360 | EXPORT_SYMBOL_GPL(__nvdimm_bus_register); |
b94d5230 | 361 | |
e6dfb2de DW |
362 | static int child_unregister(struct device *dev, void *data) |
363 | { | |
364 | /* | |
365 | * the singular ndctl class device per bus needs to be | |
366 | * "device_destroy"ed, so skip it here | |
367 | * | |
368 | * i.e. remove classless children | |
369 | */ | |
370 | if (dev->class) | |
371 | /* pass */; | |
372 | else | |
4d88a97a | 373 | nd_device_unregister(dev, ND_SYNC); |
e6dfb2de DW |
374 | return 0; |
375 | } | |
376 | ||
b94d5230 DW |
377 | void nvdimm_bus_unregister(struct nvdimm_bus *nvdimm_bus) |
378 | { | |
379 | if (!nvdimm_bus) | |
380 | return; | |
45def22c DW |
381 | |
382 | mutex_lock(&nvdimm_bus_list_mutex); | |
383 | list_del_init(&nvdimm_bus->list); | |
384 | mutex_unlock(&nvdimm_bus_list_mutex); | |
385 | ||
4d88a97a | 386 | nd_synchronize(); |
e6dfb2de | 387 | device_for_each_child(&nvdimm_bus->dev, NULL, child_unregister); |
45def22c DW |
388 | nvdimm_bus_destroy_ndctl(nvdimm_bus); |
389 | ||
b94d5230 DW |
390 | device_unregister(&nvdimm_bus->dev); |
391 | } | |
392 | EXPORT_SYMBOL_GPL(nvdimm_bus_unregister); | |
393 | ||
41cd8b70 VV |
394 | #ifdef CONFIG_BLK_DEV_INTEGRITY |
395 | static int nd_pi_nop_generate_verify(struct blk_integrity_iter *iter) | |
396 | { | |
397 | return 0; | |
398 | } | |
399 | ||
400 | int nd_integrity_init(struct gendisk *disk, unsigned long meta_size) | |
401 | { | |
402 | struct blk_integrity integrity = { | |
403 | .name = "ND-PI-NOP", | |
404 | .generate_fn = nd_pi_nop_generate_verify, | |
405 | .verify_fn = nd_pi_nop_generate_verify, | |
406 | .tuple_size = meta_size, | |
407 | .tag_size = meta_size, | |
408 | }; | |
409 | int ret; | |
410 | ||
fcae6957 VV |
411 | if (meta_size == 0) |
412 | return 0; | |
413 | ||
41cd8b70 VV |
414 | ret = blk_integrity_register(disk, &integrity); |
415 | if (ret) | |
416 | return ret; | |
417 | ||
418 | blk_queue_max_integrity_segments(disk->queue, 1); | |
419 | ||
420 | return 0; | |
421 | } | |
422 | EXPORT_SYMBOL(nd_integrity_init); | |
423 | ||
424 | #else /* CONFIG_BLK_DEV_INTEGRITY */ | |
425 | int nd_integrity_init(struct gendisk *disk, unsigned long meta_size) | |
426 | { | |
427 | return 0; | |
428 | } | |
429 | EXPORT_SYMBOL(nd_integrity_init); | |
430 | ||
431 | #endif | |
432 | ||
45def22c DW |
433 | static __init int libnvdimm_init(void) |
434 | { | |
4d88a97a DW |
435 | int rc; |
436 | ||
437 | rc = nvdimm_bus_init(); | |
438 | if (rc) | |
439 | return rc; | |
440 | rc = nvdimm_init(); | |
441 | if (rc) | |
442 | goto err_dimm; | |
3d88002e DW |
443 | rc = nd_region_init(); |
444 | if (rc) | |
445 | goto err_region; | |
4d88a97a | 446 | return 0; |
3d88002e DW |
447 | err_region: |
448 | nvdimm_exit(); | |
4d88a97a DW |
449 | err_dimm: |
450 | nvdimm_bus_exit(); | |
451 | return rc; | |
45def22c DW |
452 | } |
453 | ||
454 | static __exit void libnvdimm_exit(void) | |
455 | { | |
456 | WARN_ON(!list_empty(&nvdimm_bus_list)); | |
3d88002e | 457 | nd_region_exit(); |
4d88a97a | 458 | nvdimm_exit(); |
45def22c DW |
459 | nvdimm_bus_exit(); |
460 | } | |
461 | ||
b94d5230 DW |
462 | MODULE_LICENSE("GPL v2"); |
463 | MODULE_AUTHOR("Intel Corporation"); | |
45def22c DW |
464 | subsys_initcall(libnvdimm_init); |
465 | module_exit(libnvdimm_exit); |