summaryrefslogtreecommitdiffstats
path: root/drivers/nvdimm
diff options
context:
space:
mode:
authorDan Williams <dan.j.williams@intel.com>2015-05-01 13:11:27 -0400
committerDan Williams <dan.j.williams@intel.com>2015-06-24 21:24:10 -0400
commiteaf961536e1622ad21247ac8d44acd48ba65566e (patch)
tree479b1d2f81f9f8cc9abf99fa8f9b6496cbc88a25 /drivers/nvdimm
parent9f53f9fa4ad1d8bddd4d14359cdabc531aedffe8 (diff)
libnvdimm, nfit: add interleave-set state-tracking infrastructure
On platforms that have firmware support for reading/writing per-dimm label space, a portion of the dimm may be accessible via an interleave set PMEM mapping in addition to the dimm's BLK (block-data-window aperture(s)) interface. A label, stored in a "configuration data region" on the dimm, disambiguates which dimm addresses are accessed through which exclusive interface. Add infrastructure that allows the kernel to block modifications to a label in the set while any member dimm is active. Note that this is meant only for enforcing "no modifications of active labels" via the coarse ioctl command. Adding/deleting namespaces from an active interleave set is always possible via sysfs. Another aspect of tracking interleave sets is tracking their integrity when DIMMs in a set are physically re-ordered. For this purpose we generate an "interleave-set cookie" that can be recorded in a label and validated against the current configuration. It is the bus provider implementation's responsibility to calculate the interleave set cookie and attach it to a given region. Cc: Neil Brown <neilb@suse.de> Cc: <linux-acpi@vger.kernel.org> Cc: Greg KH <gregkh@linuxfoundation.org> Cc: Robert Moore <robert.moore@intel.com> Cc: Rafael J. Wysocki <rafael.j.wysocki@intel.com> Acked-by: Christoph Hellwig <hch@lst.de> Acked-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com> Signed-off-by: Dan Williams <dan.j.williams@intel.com>
Diffstat (limited to 'drivers/nvdimm')
-rw-r--r--drivers/nvdimm/bus.c59
-rw-r--r--drivers/nvdimm/core.c17
-rw-r--r--drivers/nvdimm/dimm_devs.c19
-rw-r--r--drivers/nvdimm/nd-core.h10
-rw-r--r--drivers/nvdimm/nd.h1
-rw-r--r--drivers/nvdimm/region_devs.c69
6 files changed, 172 insertions, 3 deletions
diff --git a/drivers/nvdimm/bus.c b/drivers/nvdimm/bus.c
index 4b77665a6cc8..ffb43cada625 100644
--- a/drivers/nvdimm/bus.c
+++ b/drivers/nvdimm/bus.c
@@ -68,6 +68,21 @@ static struct module *to_bus_provider(struct device *dev)
68 return NULL; 68 return NULL;
69} 69}
70 70
71static void nvdimm_bus_probe_start(struct nvdimm_bus *nvdimm_bus)
72{
73 nvdimm_bus_lock(&nvdimm_bus->dev);
74 nvdimm_bus->probe_active++;
75 nvdimm_bus_unlock(&nvdimm_bus->dev);
76}
77
78static void nvdimm_bus_probe_end(struct nvdimm_bus *nvdimm_bus)
79{
80 nvdimm_bus_lock(&nvdimm_bus->dev);
81 if (--nvdimm_bus->probe_active == 0)
82 wake_up(&nvdimm_bus->probe_wait);
83 nvdimm_bus_unlock(&nvdimm_bus->dev);
84}
85
71static int nvdimm_bus_probe(struct device *dev) 86static int nvdimm_bus_probe(struct device *dev)
72{ 87{
73 struct nd_device_driver *nd_drv = to_nd_device_driver(dev->driver); 88 struct nd_device_driver *nd_drv = to_nd_device_driver(dev->driver);
@@ -78,7 +93,12 @@ static int nvdimm_bus_probe(struct device *dev)
78 if (!try_module_get(provider)) 93 if (!try_module_get(provider))
79 return -ENXIO; 94 return -ENXIO;
80 95
96 nvdimm_bus_probe_start(nvdimm_bus);
81 rc = nd_drv->probe(dev); 97 rc = nd_drv->probe(dev);
98 if (rc == 0)
99 nd_region_probe_success(nvdimm_bus, dev);
100 nvdimm_bus_probe_end(nvdimm_bus);
101
82 dev_dbg(&nvdimm_bus->dev, "%s.probe(%s) = %d\n", dev->driver->name, 102 dev_dbg(&nvdimm_bus->dev, "%s.probe(%s) = %d\n", dev->driver->name,
83 dev_name(dev), rc); 103 dev_name(dev), rc);
84 if (rc != 0) 104 if (rc != 0)
@@ -94,6 +114,8 @@ static int nvdimm_bus_remove(struct device *dev)
94 int rc; 114 int rc;
95 115
96 rc = nd_drv->remove(dev); 116 rc = nd_drv->remove(dev);
117 nd_region_disable(nvdimm_bus, dev);
118
97 dev_dbg(&nvdimm_bus->dev, "%s.remove(%s) = %d\n", dev->driver->name, 119 dev_dbg(&nvdimm_bus->dev, "%s.remove(%s) = %d\n", dev->driver->name,
98 dev_name(dev), rc); 120 dev_name(dev), rc);
99 module_put(provider); 121 module_put(provider);
@@ -359,6 +381,34 @@ u32 nd_cmd_out_size(struct nvdimm *nvdimm, int cmd,
359} 381}
360EXPORT_SYMBOL_GPL(nd_cmd_out_size); 382EXPORT_SYMBOL_GPL(nd_cmd_out_size);
361 383
384static void wait_nvdimm_bus_probe_idle(struct nvdimm_bus *nvdimm_bus)
385{
386 do {
387 if (nvdimm_bus->probe_active == 0)
388 break;
389 nvdimm_bus_unlock(&nvdimm_bus->dev);
390 wait_event(nvdimm_bus->probe_wait,
391 nvdimm_bus->probe_active == 0);
392 nvdimm_bus_lock(&nvdimm_bus->dev);
393 } while (true);
394}
395
396/* set_config requires an idle interleave set */
397static int nd_cmd_clear_to_send(struct nvdimm *nvdimm, unsigned int cmd)
398{
399 struct nvdimm_bus *nvdimm_bus;
400
401 if (!nvdimm || cmd != ND_CMD_SET_CONFIG_DATA)
402 return 0;
403
404 nvdimm_bus = walk_to_nvdimm_bus(&nvdimm->dev);
405 wait_nvdimm_bus_probe_idle(nvdimm_bus);
406
407 if (atomic_read(&nvdimm->busy))
408 return -EBUSY;
409 return 0;
410}
411
362static int __nd_ioctl(struct nvdimm_bus *nvdimm_bus, struct nvdimm *nvdimm, 412static int __nd_ioctl(struct nvdimm_bus *nvdimm_bus, struct nvdimm *nvdimm,
363 int read_only, unsigned int ioctl_cmd, unsigned long arg) 413 int read_only, unsigned int ioctl_cmd, unsigned long arg)
364{ 414{
@@ -469,11 +519,18 @@ static int __nd_ioctl(struct nvdimm_bus *nvdimm_bus, struct nvdimm *nvdimm,
469 goto out; 519 goto out;
470 } 520 }
471 521
522 nvdimm_bus_lock(&nvdimm_bus->dev);
523 rc = nd_cmd_clear_to_send(nvdimm, cmd);
524 if (rc)
525 goto out_unlock;
526
472 rc = nd_desc->ndctl(nd_desc, nvdimm, cmd, buf, buf_len); 527 rc = nd_desc->ndctl(nd_desc, nvdimm, cmd, buf, buf_len);
473 if (rc < 0) 528 if (rc < 0)
474 goto out; 529 goto out_unlock;
475 if (copy_to_user(p, buf, buf_len)) 530 if (copy_to_user(p, buf, buf_len))
476 rc = -EFAULT; 531 rc = -EFAULT;
532 out_unlock:
533 nvdimm_bus_unlock(&nvdimm_bus->dev);
477 out: 534 out:
478 vfree(buf); 535 vfree(buf);
479 return rc; 536 return rc;
diff --git a/drivers/nvdimm/core.c b/drivers/nvdimm/core.c
index 1b6b15d11f54..7806eaaf4707 100644
--- a/drivers/nvdimm/core.c
+++ b/drivers/nvdimm/core.c
@@ -54,6 +54,22 @@ bool is_nvdimm_bus_locked(struct device *dev)
54} 54}
55EXPORT_SYMBOL(is_nvdimm_bus_locked); 55EXPORT_SYMBOL(is_nvdimm_bus_locked);
56 56
57u64 nd_fletcher64(void *addr, size_t len, bool le)
58{
59 u32 *buf = addr;
60 u32 lo32 = 0;
61 u64 hi32 = 0;
62 int i;
63
64 for (i = 0; i < len / sizeof(u32); i++) {
65 lo32 += le ? le32_to_cpu((__le32) buf[i]) : buf[i];
66 hi32 += lo32;
67 }
68
69 return hi32 << 32 | lo32;
70}
71EXPORT_SYMBOL_GPL(nd_fletcher64);
72
57static void nvdimm_bus_release(struct device *dev) 73static void nvdimm_bus_release(struct device *dev)
58{ 74{
59 struct nvdimm_bus *nvdimm_bus; 75 struct nvdimm_bus *nvdimm_bus;
@@ -175,6 +191,7 @@ struct nvdimm_bus *__nvdimm_bus_register(struct device *parent,
175 if (!nvdimm_bus) 191 if (!nvdimm_bus)
176 return NULL; 192 return NULL;
177 INIT_LIST_HEAD(&nvdimm_bus->list); 193 INIT_LIST_HEAD(&nvdimm_bus->list);
194 init_waitqueue_head(&nvdimm_bus->probe_wait);
178 nvdimm_bus->id = ida_simple_get(&nd_ida, 0, 0, GFP_KERNEL); 195 nvdimm_bus->id = ida_simple_get(&nd_ida, 0, 0, GFP_KERNEL);
179 mutex_init(&nvdimm_bus->reconfig_mutex); 196 mutex_init(&nvdimm_bus->reconfig_mutex);
180 if (nvdimm_bus->id < 0) { 197 if (nvdimm_bus->id < 0) {
diff --git a/drivers/nvdimm/dimm_devs.c b/drivers/nvdimm/dimm_devs.c
index b3ae86f2e1da..bdf8241b6525 100644
--- a/drivers/nvdimm/dimm_devs.c
+++ b/drivers/nvdimm/dimm_devs.c
@@ -185,7 +185,24 @@ static ssize_t commands_show(struct device *dev,
185} 185}
186static DEVICE_ATTR_RO(commands); 186static DEVICE_ATTR_RO(commands);
187 187
188static ssize_t state_show(struct device *dev, struct device_attribute *attr,
189 char *buf)
190{
191 struct nvdimm *nvdimm = to_nvdimm(dev);
192
193 /*
194 * The state may be in the process of changing, userspace should
195 * quiesce probing if it wants a static answer
196 */
197 nvdimm_bus_lock(dev);
198 nvdimm_bus_unlock(dev);
199 return sprintf(buf, "%s\n", atomic_read(&nvdimm->busy)
200 ? "active" : "idle");
201}
202static DEVICE_ATTR_RO(state);
203
188static struct attribute *nvdimm_attributes[] = { 204static struct attribute *nvdimm_attributes[] = {
205 &dev_attr_state.attr,
189 &dev_attr_commands.attr, 206 &dev_attr_commands.attr,
190 NULL, 207 NULL,
191}; 208};
@@ -213,7 +230,7 @@ struct nvdimm *nvdimm_create(struct nvdimm_bus *nvdimm_bus, void *provider_data,
213 nvdimm->provider_data = provider_data; 230 nvdimm->provider_data = provider_data;
214 nvdimm->flags = flags; 231 nvdimm->flags = flags;
215 nvdimm->dsm_mask = dsm_mask; 232 nvdimm->dsm_mask = dsm_mask;
216 233 atomic_set(&nvdimm->busy, 0);
217 dev = &nvdimm->dev; 234 dev = &nvdimm->dev;
218 dev_set_name(dev, "nmem%d", nvdimm->id); 235 dev_set_name(dev, "nmem%d", nvdimm->id);
219 dev->parent = &nvdimm_bus->dev; 236 dev->parent = &nvdimm_bus->dev;
diff --git a/drivers/nvdimm/nd-core.h b/drivers/nvdimm/nd-core.h
index 0e9b41fd2546..6a4b2c066ee7 100644
--- a/drivers/nvdimm/nd-core.h
+++ b/drivers/nvdimm/nd-core.h
@@ -14,6 +14,9 @@
14#define __ND_CORE_H__ 14#define __ND_CORE_H__
15#include <linux/libnvdimm.h> 15#include <linux/libnvdimm.h>
16#include <linux/device.h> 16#include <linux/device.h>
17#include <linux/libnvdimm.h>
18#include <linux/sizes.h>
19#include <linux/mutex.h>
17 20
18extern struct list_head nvdimm_bus_list; 21extern struct list_head nvdimm_bus_list;
19extern struct mutex nvdimm_bus_list_mutex; 22extern struct mutex nvdimm_bus_list_mutex;
@@ -21,10 +24,11 @@ extern int nvdimm_major;
21 24
22struct nvdimm_bus { 25struct nvdimm_bus {
23 struct nvdimm_bus_descriptor *nd_desc; 26 struct nvdimm_bus_descriptor *nd_desc;
27 wait_queue_head_t probe_wait;
24 struct module *module; 28 struct module *module;
25 struct list_head list; 29 struct list_head list;
26 struct device dev; 30 struct device dev;
27 int id; 31 int id, probe_active;
28 struct mutex reconfig_mutex; 32 struct mutex reconfig_mutex;
29}; 33};
30 34
@@ -33,6 +37,7 @@ struct nvdimm {
33 void *provider_data; 37 void *provider_data;
34 unsigned long *dsm_mask; 38 unsigned long *dsm_mask;
35 struct device dev; 39 struct device dev;
40 atomic_t busy;
36 int id; 41 int id;
37}; 42};
38 43
@@ -42,10 +47,13 @@ bool is_nd_pmem(struct device *dev);
42struct nvdimm_bus *walk_to_nvdimm_bus(struct device *nd_dev); 47struct nvdimm_bus *walk_to_nvdimm_bus(struct device *nd_dev);
43int __init nvdimm_bus_init(void); 48int __init nvdimm_bus_init(void);
44void nvdimm_bus_exit(void); 49void nvdimm_bus_exit(void);
50void nd_region_probe_success(struct nvdimm_bus *nvdimm_bus, struct device *dev);
51void nd_region_disable(struct nvdimm_bus *nvdimm_bus, struct device *dev);
45int nvdimm_bus_create_ndctl(struct nvdimm_bus *nvdimm_bus); 52int nvdimm_bus_create_ndctl(struct nvdimm_bus *nvdimm_bus);
46void nvdimm_bus_destroy_ndctl(struct nvdimm_bus *nvdimm_bus); 53void nvdimm_bus_destroy_ndctl(struct nvdimm_bus *nvdimm_bus);
47void nd_synchronize(void); 54void nd_synchronize(void);
48int nvdimm_bus_register_dimms(struct nvdimm_bus *nvdimm_bus); 55int nvdimm_bus_register_dimms(struct nvdimm_bus *nvdimm_bus);
49int nvdimm_bus_register_regions(struct nvdimm_bus *nvdimm_bus); 56int nvdimm_bus_register_regions(struct nvdimm_bus *nvdimm_bus);
57int nvdimm_bus_init_interleave_sets(struct nvdimm_bus *nvdimm_bus);
50int nd_match_dimm(struct device *dev, void *data); 58int nd_match_dimm(struct device *dev, void *data);
51#endif /* __ND_CORE_H__ */ 59#endif /* __ND_CORE_H__ */
diff --git a/drivers/nvdimm/nd.h b/drivers/nvdimm/nd.h
index bc5a08e36a25..0285e4588b03 100644
--- a/drivers/nvdimm/nd.h
+++ b/drivers/nvdimm/nd.h
@@ -35,6 +35,7 @@ struct nd_region {
35 u64 ndr_start; 35 u64 ndr_start;
36 int id; 36 int id;
37 void *provider_data; 37 void *provider_data;
38 struct nd_interleave_set *nd_set;
38 struct nd_mapping mapping[0]; 39 struct nd_mapping mapping[0];
39}; 40};
40 41
diff --git a/drivers/nvdimm/region_devs.c b/drivers/nvdimm/region_devs.c
index b5c5b9095b28..1571424578f0 100644
--- a/drivers/nvdimm/region_devs.c
+++ b/drivers/nvdimm/region_devs.c
@@ -10,7 +10,10 @@
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details. 11 * General Public License for more details.
12 */ 12 */
13#include <linux/scatterlist.h>
14#include <linux/sched.h>
13#include <linux/slab.h> 15#include <linux/slab.h>
16#include <linux/sort.h>
14#include <linux/io.h> 17#include <linux/io.h>
15#include "nd-core.h" 18#include "nd-core.h"
16#include "nd.h" 19#include "nd.h"
@@ -133,6 +136,21 @@ static ssize_t nstype_show(struct device *dev,
133} 136}
134static DEVICE_ATTR_RO(nstype); 137static DEVICE_ATTR_RO(nstype);
135 138
139static 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}
152static DEVICE_ATTR_RO(set_cookie);
153
136static ssize_t init_namespaces_show(struct device *dev, 154static ssize_t init_namespaces_show(struct device *dev,
137 struct device_attribute *attr, char *buf) 155 struct device_attribute *attr, char *buf)
138{ 156{
@@ -154,15 +172,65 @@ static struct attribute *nd_region_attributes[] = {
154 &dev_attr_size.attr, 172 &dev_attr_size.attr,
155 &dev_attr_nstype.attr, 173 &dev_attr_nstype.attr,
156 &dev_attr_mappings.attr, 174 &dev_attr_mappings.attr,
175 &dev_attr_set_cookie.attr,
157 &dev_attr_init_namespaces.attr, 176 &dev_attr_init_namespaces.attr,
158 NULL, 177 NULL,
159}; 178};
160 179
180static 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
161struct attribute_group nd_region_attribute_group = { 195struct attribute_group nd_region_attribute_group = {
162 .attrs = nd_region_attributes, 196 .attrs = nd_region_attributes,
197 .is_visible = region_visible,
163}; 198};
164EXPORT_SYMBOL_GPL(nd_region_attribute_group); 199EXPORT_SYMBOL_GPL(nd_region_attribute_group);
165 200
201/*
202 * Upon successful probe/remove, take/release a reference on the
203 * associated interleave set (if present)
204 */
205static 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
224void 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
229void nd_region_disable(struct nvdimm_bus *nvdimm_bus, struct device *dev)
230{
231 nd_region_notify_driver_action(nvdimm_bus, dev, false);
232}
233
166static ssize_t mappingN(struct device *dev, char *buf, int n) 234static ssize_t mappingN(struct device *dev, char *buf, int n)
167{ 235{
168 struct nd_region *nd_region = to_nd_region(dev); 236 struct nd_region *nd_region = to_nd_region(dev);
@@ -322,6 +390,7 @@ static struct nd_region *nd_region_create(struct nvdimm_bus *nvdimm_bus,
322 } 390 }
323 nd_region->ndr_mappings = ndr_desc->num_mappings; 391 nd_region->ndr_mappings = ndr_desc->num_mappings;
324 nd_region->provider_data = ndr_desc->provider_data; 392 nd_region->provider_data = ndr_desc->provider_data;
393 nd_region->nd_set = ndr_desc->nd_set;
325 dev = &nd_region->dev; 394 dev = &nd_region->dev;
326 dev_set_name(dev, "region%d", nd_region->id); 395 dev_set_name(dev, "region%d", nd_region->id);
327 dev->parent = &nvdimm_bus->dev; 396 dev->parent = &nvdimm_bus->dev;