Commit 550f5ba1 authored by Dan Williams's avatar Dan Williams Committed by Greg Kroah-Hartman

libnvdimm/namespace: Fix label tracking error

commit c4703ce1 upstream.

Users have reported intermittent occurrences of DIMM initialization
failures due to duplicate allocations of address capacity detected in
the labels, or errors of the form below, both have the same root cause.

    nd namespace1.4: failed to track label: 0
    WARNING: CPU: 17 PID: 1381 at drivers/nvdimm/label.c:863

    RIP: 0010:__pmem_label_update+0x56c/0x590 [libnvdimm]
    Call Trace:
     ? nd_pmem_namespace_label_update+0xd6/0x160 [libnvdimm]
     nd_pmem_namespace_label_update+0xd6/0x160 [libnvdimm]
     uuid_store+0x17e/0x190 [libnvdimm]
     kernfs_fop_write+0xf0/0x1a0
     vfs_write+0xb7/0x1b0
     ksys_write+0x57/0xd0
     do_syscall_64+0x60/0x210

Unfortunately those reports were typically with a busy parallel
namespace creation / destruction loop making it difficult to see the
components of the bug. However, Jane provided a simple reproducer using
the work-in-progress sub-section implementation.

When ndctl is reconfiguring a namespace it may take an existing defunct
/ disabled namespace and reconfigure it with a new uuid and other
parameters. Critically namespace_update_uuid() takes existing address
resources and renames them for the new namespace to use / reconfigure as
it sees fit. The bug is that this rename only happens in the resource
tracking tree. Existing labels with the old uuid are not reaped leading
to a scenario where multiple active labels reference the same span of
address range.

Teach namespace_update_uuid() to flag any references to the old uuid for
reaping at the next label update attempt.

Cc: <stable@vger.kernel.org>
Fixes: bf9bccc1 ("libnvdimm: pmem label sets and namespace instantiation")
Link: https://github.com/pmem/ndctl/issues/91Reported-by: default avatarJane Chu <jane.chu@oracle.com>
Reported-by: default avatarJeff Moyer <jmoyer@redhat.com>
Reported-by: default avatarErwin Tsaur <erwin.tsaur@oracle.com>
Cc: Johannes Thumshirn <jthumshirn@suse.de>
Signed-off-by: default avatarDan Williams <dan.j.williams@intel.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent fda49aec
...@@ -756,6 +756,17 @@ static const guid_t *to_abstraction_guid(enum nvdimm_claim_class claim_class, ...@@ -756,6 +756,17 @@ static const guid_t *to_abstraction_guid(enum nvdimm_claim_class claim_class,
return &guid_null; return &guid_null;
} }
static void reap_victim(struct nd_mapping *nd_mapping,
struct nd_label_ent *victim)
{
struct nvdimm_drvdata *ndd = to_ndd(nd_mapping);
u32 slot = to_slot(ndd, victim->label);
dev_dbg(ndd->dev, "free: %d\n", slot);
nd_label_free_slot(ndd, slot);
victim->label = NULL;
}
static int __pmem_label_update(struct nd_region *nd_region, static int __pmem_label_update(struct nd_region *nd_region,
struct nd_mapping *nd_mapping, struct nd_namespace_pmem *nspm, struct nd_mapping *nd_mapping, struct nd_namespace_pmem *nspm,
int pos, unsigned long flags) int pos, unsigned long flags)
...@@ -763,9 +774,9 @@ static int __pmem_label_update(struct nd_region *nd_region, ...@@ -763,9 +774,9 @@ static int __pmem_label_update(struct nd_region *nd_region,
struct nd_namespace_common *ndns = &nspm->nsio.common; struct nd_namespace_common *ndns = &nspm->nsio.common;
struct nd_interleave_set *nd_set = nd_region->nd_set; struct nd_interleave_set *nd_set = nd_region->nd_set;
struct nvdimm_drvdata *ndd = to_ndd(nd_mapping); struct nvdimm_drvdata *ndd = to_ndd(nd_mapping);
struct nd_label_ent *label_ent, *victim = NULL;
struct nd_namespace_label *nd_label; struct nd_namespace_label *nd_label;
struct nd_namespace_index *nsindex; struct nd_namespace_index *nsindex;
struct nd_label_ent *label_ent;
struct nd_label_id label_id; struct nd_label_id label_id;
struct resource *res; struct resource *res;
unsigned long *free; unsigned long *free;
...@@ -834,18 +845,10 @@ static int __pmem_label_update(struct nd_region *nd_region, ...@@ -834,18 +845,10 @@ static int __pmem_label_update(struct nd_region *nd_region,
list_for_each_entry(label_ent, &nd_mapping->labels, list) { list_for_each_entry(label_ent, &nd_mapping->labels, list) {
if (!label_ent->label) if (!label_ent->label)
continue; continue;
if (memcmp(nspm->uuid, label_ent->label->uuid, if (test_and_clear_bit(ND_LABEL_REAP, &label_ent->flags)
NSLABEL_UUID_LEN) != 0) || memcmp(nspm->uuid, label_ent->label->uuid,
continue; NSLABEL_UUID_LEN) == 0)
victim = label_ent; reap_victim(nd_mapping, label_ent);
list_move_tail(&victim->list, &nd_mapping->labels);
break;
}
if (victim) {
dev_dbg(ndd->dev, "free: %d\n", slot);
slot = to_slot(ndd, victim->label);
nd_label_free_slot(ndd, slot);
victim->label = NULL;
} }
/* update index */ /* update index */
......
...@@ -1247,12 +1247,27 @@ static int namespace_update_uuid(struct nd_region *nd_region, ...@@ -1247,12 +1247,27 @@ static int namespace_update_uuid(struct nd_region *nd_region,
for (i = 0; i < nd_region->ndr_mappings; i++) { for (i = 0; i < nd_region->ndr_mappings; i++) {
struct nd_mapping *nd_mapping = &nd_region->mapping[i]; struct nd_mapping *nd_mapping = &nd_region->mapping[i];
struct nvdimm_drvdata *ndd = to_ndd(nd_mapping); struct nvdimm_drvdata *ndd = to_ndd(nd_mapping);
struct nd_label_ent *label_ent;
struct resource *res; struct resource *res;
for_each_dpa_resource(ndd, res) for_each_dpa_resource(ndd, res)
if (strcmp(res->name, old_label_id.id) == 0) if (strcmp(res->name, old_label_id.id) == 0)
sprintf((void *) res->name, "%s", sprintf((void *) res->name, "%s",
new_label_id.id); new_label_id.id);
mutex_lock(&nd_mapping->lock);
list_for_each_entry(label_ent, &nd_mapping->labels, list) {
struct nd_namespace_label *nd_label = label_ent->label;
struct nd_label_id label_id;
if (!nd_label)
continue;
nd_label_gen_id(&label_id, nd_label->uuid,
__le32_to_cpu(nd_label->flags));
if (strcmp(old_label_id.id, label_id.id) == 0)
set_bit(ND_LABEL_REAP, &label_ent->flags);
}
mutex_unlock(&nd_mapping->lock);
} }
kfree(*old_uuid); kfree(*old_uuid);
out: out:
......
...@@ -113,8 +113,12 @@ struct nd_percpu_lane { ...@@ -113,8 +113,12 @@ struct nd_percpu_lane {
spinlock_t lock; spinlock_t lock;
}; };
enum nd_label_flags {
ND_LABEL_REAP,
};
struct nd_label_ent { struct nd_label_ent {
struct list_head list; struct list_head list;
unsigned long flags;
struct nd_namespace_label *label; struct nd_namespace_label *label;
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment