Skip to content

Commit eaf9615

Browse files
committed
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 <[email protected]> Cc: <[email protected]> Cc: Greg KH <[email protected]> Cc: Robert Moore <[email protected]> Cc: Rafael J. Wysocki <[email protected]> Acked-by: Christoph Hellwig <[email protected]> Acked-by: Rafael J. Wysocki <[email protected]> Signed-off-by: Dan Williams <[email protected]>
1 parent 9f53f9f commit eaf9615

File tree

8 files changed

+269
-5
lines changed

8 files changed

+269
-5
lines changed

drivers/acpi/nfit.c

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include <linux/ndctl.h>
1717
#include <linux/list.h>
1818
#include <linux/acpi.h>
19+
#include <linux/sort.h>
1920
#include "nfit.h"
2021

2122
static bool force_enable_dimms;
@@ -785,6 +786,91 @@ static const struct attribute_group *acpi_nfit_region_attribute_groups[] = {
785786
NULL,
786787
};
787788

789+
/* enough info to uniquely specify an interleave set */
790+
struct nfit_set_info {
791+
struct nfit_set_info_map {
792+
u64 region_offset;
793+
u32 serial_number;
794+
u32 pad;
795+
} mapping[0];
796+
};
797+
798+
static size_t sizeof_nfit_set_info(int num_mappings)
799+
{
800+
return sizeof(struct nfit_set_info)
801+
+ num_mappings * sizeof(struct nfit_set_info_map);
802+
}
803+
804+
static int cmp_map(const void *m0, const void *m1)
805+
{
806+
const struct nfit_set_info_map *map0 = m0;
807+
const struct nfit_set_info_map *map1 = m1;
808+
809+
return memcmp(&map0->region_offset, &map1->region_offset,
810+
sizeof(u64));
811+
}
812+
813+
/* Retrieve the nth entry referencing this spa */
814+
static struct acpi_nfit_memory_map *memdev_from_spa(
815+
struct acpi_nfit_desc *acpi_desc, u16 range_index, int n)
816+
{
817+
struct nfit_memdev *nfit_memdev;
818+
819+
list_for_each_entry(nfit_memdev, &acpi_desc->memdevs, list)
820+
if (nfit_memdev->memdev->range_index == range_index)
821+
if (n-- == 0)
822+
return nfit_memdev->memdev;
823+
return NULL;
824+
}
825+
826+
static int acpi_nfit_init_interleave_set(struct acpi_nfit_desc *acpi_desc,
827+
struct nd_region_desc *ndr_desc,
828+
struct acpi_nfit_system_address *spa)
829+
{
830+
int i, spa_type = nfit_spa_type(spa);
831+
struct device *dev = acpi_desc->dev;
832+
struct nd_interleave_set *nd_set;
833+
u16 nr = ndr_desc->num_mappings;
834+
struct nfit_set_info *info;
835+
836+
if (spa_type == NFIT_SPA_PM || spa_type == NFIT_SPA_VOLATILE)
837+
/* pass */;
838+
else
839+
return 0;
840+
841+
nd_set = devm_kzalloc(dev, sizeof(*nd_set), GFP_KERNEL);
842+
if (!nd_set)
843+
return -ENOMEM;
844+
845+
info = devm_kzalloc(dev, sizeof_nfit_set_info(nr), GFP_KERNEL);
846+
if (!info)
847+
return -ENOMEM;
848+
for (i = 0; i < nr; i++) {
849+
struct nd_mapping *nd_mapping = &ndr_desc->nd_mapping[i];
850+
struct nfit_set_info_map *map = &info->mapping[i];
851+
struct nvdimm *nvdimm = nd_mapping->nvdimm;
852+
struct nfit_mem *nfit_mem = nvdimm_provider_data(nvdimm);
853+
struct acpi_nfit_memory_map *memdev = memdev_from_spa(acpi_desc,
854+
spa->range_index, i);
855+
856+
if (!memdev || !nfit_mem->dcr) {
857+
dev_err(dev, "%s: failed to find DCR\n", __func__);
858+
return -ENODEV;
859+
}
860+
861+
map->region_offset = memdev->region_offset;
862+
map->serial_number = nfit_mem->dcr->serial_number;
863+
}
864+
865+
sort(&info->mapping[0], nr, sizeof(struct nfit_set_info_map),
866+
cmp_map, NULL);
867+
nd_set->cookie = nd_fletcher64(info, sizeof_nfit_set_info(nr), 0);
868+
ndr_desc->nd_set = nd_set;
869+
devm_kfree(dev, info);
870+
871+
return 0;
872+
}
873+
788874
static int acpi_nfit_init_mapping(struct acpi_nfit_desc *acpi_desc,
789875
struct nd_mapping *nd_mapping, struct nd_region_desc *ndr_desc,
790876
struct acpi_nfit_memory_map *memdev,
@@ -838,7 +924,7 @@ static int acpi_nfit_register_region(struct acpi_nfit_desc *acpi_desc,
838924
struct nd_region_desc ndr_desc;
839925
struct nvdimm_bus *nvdimm_bus;
840926
struct resource res;
841-
int count = 0;
927+
int count = 0, rc;
842928

843929
if (spa->range_index == 0) {
844930
dev_dbg(acpi_desc->dev, "%s: detected invalid spa index\n",
@@ -857,7 +943,6 @@ static int acpi_nfit_register_region(struct acpi_nfit_desc *acpi_desc,
857943
list_for_each_entry(nfit_memdev, &acpi_desc->memdevs, list) {
858944
struct acpi_nfit_memory_map *memdev = nfit_memdev->memdev;
859945
struct nd_mapping *nd_mapping;
860-
int rc;
861946

862947
if (memdev->range_index != spa->range_index)
863948
continue;
@@ -875,6 +960,10 @@ static int acpi_nfit_register_region(struct acpi_nfit_desc *acpi_desc,
875960

876961
ndr_desc.nd_mapping = nd_mappings;
877962
ndr_desc.num_mappings = count;
963+
rc = acpi_nfit_init_interleave_set(acpi_desc, &ndr_desc, spa);
964+
if (rc)
965+
return rc;
966+
878967
nvdimm_bus = acpi_desc->nvdimm_bus;
879968
if (nfit_spa_type(spa) == NFIT_SPA_PM) {
880969
if (!nvdimm_pmem_region_create(nvdimm_bus, &ndr_desc))

drivers/nvdimm/bus.c

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,21 @@ static struct module *to_bus_provider(struct device *dev)
6868
return NULL;
6969
}
7070

71+
static 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+
78+
static 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+
7186
static int nvdimm_bus_probe(struct device *dev)
7287
{
7388
struct nd_device_driver *nd_drv = to_nd_device_driver(dev->driver);
@@ -78,7 +93,12 @@ static int nvdimm_bus_probe(struct device *dev)
7893
if (!try_module_get(provider))
7994
return -ENXIO;
8095

96+
nvdimm_bus_probe_start(nvdimm_bus);
8197
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+
82102
dev_dbg(&nvdimm_bus->dev, "%s.probe(%s) = %d\n", dev->driver->name,
83103
dev_name(dev), rc);
84104
if (rc != 0)
@@ -94,6 +114,8 @@ static int nvdimm_bus_remove(struct device *dev)
94114
int rc;
95115

96116
rc = nd_drv->remove(dev);
117+
nd_region_disable(nvdimm_bus, dev);
118+
97119
dev_dbg(&nvdimm_bus->dev, "%s.remove(%s) = %d\n", dev->driver->name,
98120
dev_name(dev), rc);
99121
module_put(provider);
@@ -359,6 +381,34 @@ u32 nd_cmd_out_size(struct nvdimm *nvdimm, int cmd,
359381
}
360382
EXPORT_SYMBOL_GPL(nd_cmd_out_size);
361383

384+
static 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 */
397+
static 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+
362412
static int __nd_ioctl(struct nvdimm_bus *nvdimm_bus, struct nvdimm *nvdimm,
363413
int read_only, unsigned int ioctl_cmd, unsigned long arg)
364414
{
@@ -469,11 +519,18 @@ static int __nd_ioctl(struct nvdimm_bus *nvdimm_bus, struct nvdimm *nvdimm,
469519
goto out;
470520
}
471521

522+
nvdimm_bus_lock(&nvdimm_bus->dev);
523+
rc = nd_cmd_clear_to_send(nvdimm, cmd);
524+
if (rc)
525+
goto out_unlock;
526+
472527
rc = nd_desc->ndctl(nd_desc, nvdimm, cmd, buf, buf_len);
473528
if (rc < 0)
474-
goto out;
529+
goto out_unlock;
475530
if (copy_to_user(p, buf, buf_len))
476531
rc = -EFAULT;
532+
out_unlock:
533+
nvdimm_bus_unlock(&nvdimm_bus->dev);
477534
out:
478535
vfree(buf);
479536
return rc;

drivers/nvdimm/core.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,22 @@ bool is_nvdimm_bus_locked(struct device *dev)
5454
}
5555
EXPORT_SYMBOL(is_nvdimm_bus_locked);
5656

57+
u64 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+
}
71+
EXPORT_SYMBOL_GPL(nd_fletcher64);
72+
5773
static void nvdimm_bus_release(struct device *dev)
5874
{
5975
struct nvdimm_bus *nvdimm_bus;
@@ -175,6 +191,7 @@ struct nvdimm_bus *__nvdimm_bus_register(struct device *parent,
175191
if (!nvdimm_bus)
176192
return NULL;
177193
INIT_LIST_HEAD(&nvdimm_bus->list);
194+
init_waitqueue_head(&nvdimm_bus->probe_wait);
178195
nvdimm_bus->id = ida_simple_get(&nd_ida, 0, 0, GFP_KERNEL);
179196
mutex_init(&nvdimm_bus->reconfig_mutex);
180197
if (nvdimm_bus->id < 0) {

drivers/nvdimm/dimm_devs.c

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,24 @@ static ssize_t commands_show(struct device *dev,
185185
}
186186
static DEVICE_ATTR_RO(commands);
187187

188+
static 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+
}
202+
static DEVICE_ATTR_RO(state);
203+
188204
static struct attribute *nvdimm_attributes[] = {
205+
&dev_attr_state.attr,
189206
&dev_attr_commands.attr,
190207
NULL,
191208
};
@@ -213,7 +230,7 @@ struct nvdimm *nvdimm_create(struct nvdimm_bus *nvdimm_bus, void *provider_data,
213230
nvdimm->provider_data = provider_data;
214231
nvdimm->flags = flags;
215232
nvdimm->dsm_mask = dsm_mask;
216-
233+
atomic_set(&nvdimm->busy, 0);
217234
dev = &nvdimm->dev;
218235
dev_set_name(dev, "nmem%d", nvdimm->id);
219236
dev->parent = &nvdimm_bus->dev;

drivers/nvdimm/nd-core.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,21 @@
1414
#define __ND_CORE_H__
1515
#include <linux/libnvdimm.h>
1616
#include <linux/device.h>
17+
#include <linux/libnvdimm.h>
18+
#include <linux/sizes.h>
19+
#include <linux/mutex.h>
1720

1821
extern struct list_head nvdimm_bus_list;
1922
extern struct mutex nvdimm_bus_list_mutex;
2023
extern int nvdimm_major;
2124

2225
struct nvdimm_bus {
2326
struct nvdimm_bus_descriptor *nd_desc;
27+
wait_queue_head_t probe_wait;
2428
struct module *module;
2529
struct list_head list;
2630
struct device dev;
27-
int id;
31+
int id, probe_active;
2832
struct mutex reconfig_mutex;
2933
};
3034

@@ -33,6 +37,7 @@ struct nvdimm {
3337
void *provider_data;
3438
unsigned long *dsm_mask;
3539
struct device dev;
40+
atomic_t busy;
3641
int id;
3742
};
3843

@@ -42,10 +47,13 @@ bool is_nd_pmem(struct device *dev);
4247
struct nvdimm_bus *walk_to_nvdimm_bus(struct device *nd_dev);
4348
int __init nvdimm_bus_init(void);
4449
void nvdimm_bus_exit(void);
50+
void nd_region_probe_success(struct nvdimm_bus *nvdimm_bus, struct device *dev);
51+
void nd_region_disable(struct nvdimm_bus *nvdimm_bus, struct device *dev);
4552
int nvdimm_bus_create_ndctl(struct nvdimm_bus *nvdimm_bus);
4653
void nvdimm_bus_destroy_ndctl(struct nvdimm_bus *nvdimm_bus);
4754
void nd_synchronize(void);
4855
int nvdimm_bus_register_dimms(struct nvdimm_bus *nvdimm_bus);
4956
int nvdimm_bus_register_regions(struct nvdimm_bus *nvdimm_bus);
57+
int nvdimm_bus_init_interleave_sets(struct nvdimm_bus *nvdimm_bus);
5058
int nd_match_dimm(struct device *dev, void *data);
5159
#endif /* __ND_CORE_H__ */

drivers/nvdimm/nd.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ struct nd_region {
3535
u64 ndr_start;
3636
int id;
3737
void *provider_data;
38+
struct nd_interleave_set *nd_set;
3839
struct nd_mapping mapping[0];
3940
};
4041

0 commit comments

Comments
 (0)