From 8d4b989d9c9afe5f185aa5853b666fc4617afe9e Mon Sep 17 00:00:00 2001 From: Alison Schofield Date: Wed, 27 May 2026 19:16:22 -0700 Subject: [PATCH 1/8] nvdimm/btt: Handle preemption in BTT lane acquisition BTT lanes serialize access to per-lane metadata and workspace state during BTT I/O. The btt-check unit test reports data mismatches during BTT writes due to a race in lane acquisition that can lead to silent data corruption. The existing lane model uses a spinlock together with a per-CPU recursion count. That recursion model stopped being valid after BTT lanes became preemptible: another task can run on the same CPU, observe a non-zero recursion count, bypass locking, and use the same lane concurrently. BTT lanes are also held across arena_write_bytes() calls. That path reaches nsio_rw_bytes(), which flushes writes with nvdimm_flush(). Some provider flush callbacks can sleep, making a spinlock the wrong primitive for the lane lifetime. Replace the spinlock-based recursion model with a dynamically allocated per-lane mutex array and take the lane lock unconditionally. Add might_sleep() to catch any future atomic-context caller. Found with the ndctl unit test btt-check.sh. Fixes: 36c75ce3bd29 ("nd_btt: Make BTT lanes preemptible") Assisted-by: Claude-Sonnet:4.5 Tested-by: Aboorva Devarajan Reviewed-by: Aboorva Devarajan Reviewed-by: Vishal Verma Reviewed-by: Dave Jiang Link: https://patch.msgid.link/20260528021625.618462-1-alison.schofield@intel.com Signed-off-by: Alison Schofield --- Documentation/driver-api/nvdimm/btt.rst | 5 +- drivers/nvdimm/nd.h | 11 ++--- drivers/nvdimm/region_devs.c | 66 +++++++++---------------- 3 files changed, 29 insertions(+), 53 deletions(-) diff --git a/Documentation/driver-api/nvdimm/btt.rst b/Documentation/driver-api/nvdimm/btt.rst index 2d8269f834bd..d29fab95f149 100644 --- a/Documentation/driver-api/nvdimm/btt.rst +++ b/Documentation/driver-api/nvdimm/btt.rst @@ -161,9 +161,8 @@ process:: nlanes = min(nfree, num_cpus) A lane number is obtained at the start of any IO, and is used for indexing into -all the on-disk and in-memory data structures for the duration of the IO. If -there are more CPUs than the max number of available lanes, than lanes are -protected by spinlocks. +all the on-disk and in-memory data structures for the duration of the IO. Lanes +are protected by mutexes. d. In-memory data structure: Read Tracking Table (RTT) diff --git a/drivers/nvdimm/nd.h b/drivers/nvdimm/nd.h index b199eea3260e..197e5368c0a4 100644 --- a/drivers/nvdimm/nd.h +++ b/drivers/nvdimm/nd.h @@ -365,11 +365,6 @@ unsigned sizeof_namespace_label(struct nvdimm_drvdata *ndd); for (res = (ndd)->dpa.child, next = res ? res->sibling : NULL; \ res; res = next, next = next ? next->sibling : NULL) -struct nd_percpu_lane { - int count; - spinlock_t lock; -}; - enum nd_label_flags { ND_LABEL_REAP, }; @@ -400,6 +395,10 @@ struct nd_mapping { struct nvdimm_drvdata *ndd; }; +struct nd_lane { + struct mutex lock; /* serialize lane access */ +} ____cacheline_aligned_in_smp; + struct nd_region { struct device dev; struct ida ns_ida; @@ -420,7 +419,7 @@ struct nd_region { struct kernfs_node *bb_state; struct badblocks bb; struct nd_interleave_set *nd_set; - struct nd_percpu_lane __percpu *lane; + struct nd_lane *lane; int (*flush)(struct nd_region *nd_region, struct bio *bio); struct nd_mapping mapping[] __counted_by(ndr_mappings); }; diff --git a/drivers/nvdimm/region_devs.c b/drivers/nvdimm/region_devs.c index e35c2e18518f..5e079d61cbaa 100644 --- a/drivers/nvdimm/region_devs.c +++ b/drivers/nvdimm/region_devs.c @@ -192,7 +192,9 @@ static void nd_region_release(struct device *dev) put_device(&nvdimm->dev); } - free_percpu(nd_region->lane); + for (i = 0; i < nd_region->num_lanes; i++) + mutex_destroy(&nd_region->lane[i].lock); + kfree(nd_region->lane); if (!test_bit(ND_REGION_CXL, &nd_region->flags)) memregion_free(nd_region->id); kfree(nd_region); @@ -904,52 +906,30 @@ void nd_region_advance_seeds(struct nd_region *nd_region, struct device *dev) * nd_region_acquire_lane - allocate and lock a lane * @nd_region: region id and number of lanes possible * - * A lane correlates to a BLK-data-window and/or a log slot in the BTT. - * We optimize for the common case where there are 256 lanes, one - * per-cpu. For larger systems we need to lock to share lanes. For now - * this implementation assumes the cost of maintaining an allocator for - * free lanes is on the order of the lock hold time, so it implements a - * static lane = cpu % num_lanes mapping. + * A lane correlates to a log slot in the BTT. Lanes are shared across + * CPUs using a static lane = cpu % num_lanes mapping, with a per-lane + * mutex to serialize access. * - * In the case of a BTT instance on top of a BLK namespace a lane may be - * acquired recursively. We lock on the first instance. - * - * In the case of a BTT instance on top of PMEM, we only acquire a lane - * for the BTT metadata updates. + * Callers must be in sleepable context. The only in-tree caller is + * BTT's ->submit_bio handler (btt_read_pg / btt_write_pg). */ unsigned int nd_region_acquire_lane(struct nd_region *nd_region) + __acquires(&nd_region->lane[lane].lock) { - unsigned int cpu, lane; + unsigned int lane; - migrate_disable(); - cpu = smp_processor_id(); - if (nd_region->num_lanes < nr_cpu_ids) { - struct nd_percpu_lane *ndl_lock, *ndl_count; - - lane = cpu % nd_region->num_lanes; - ndl_count = per_cpu_ptr(nd_region->lane, cpu); - ndl_lock = per_cpu_ptr(nd_region->lane, lane); - if (ndl_count->count++ == 0) - spin_lock(&ndl_lock->lock); - } else - lane = cpu; + might_sleep(); + lane = raw_smp_processor_id() % nd_region->num_lanes; + mutex_lock(&nd_region->lane[lane].lock); return lane; } EXPORT_SYMBOL(nd_region_acquire_lane); void nd_region_release_lane(struct nd_region *nd_region, unsigned int lane) + __releases(&nd_region->lane[lane].lock) { - if (nd_region->num_lanes < nr_cpu_ids) { - unsigned int cpu = smp_processor_id(); - struct nd_percpu_lane *ndl_lock, *ndl_count; - - ndl_count = per_cpu_ptr(nd_region->lane, cpu); - ndl_lock = per_cpu_ptr(nd_region->lane, lane); - if (--ndl_count->count == 0) - spin_unlock(&ndl_lock->lock); - } - migrate_enable(); + mutex_unlock(&nd_region->lane[lane].lock); } EXPORT_SYMBOL(nd_region_release_lane); @@ -1019,17 +999,16 @@ static struct nd_region *nd_region_create(struct nvdimm_bus *nvdimm_bus, goto err_id; } - nd_region->lane = alloc_percpu(struct nd_percpu_lane); + nd_region->num_lanes = ndr_desc->num_lanes; + if (!nd_region->num_lanes) + goto err_percpu; + nd_region->lane = kcalloc(nd_region->num_lanes, + sizeof(*nd_region->lane), GFP_KERNEL); if (!nd_region->lane) goto err_percpu; - for (i = 0; i < nr_cpu_ids; i++) { - struct nd_percpu_lane *ndl; - - ndl = per_cpu_ptr(nd_region->lane, i); - spin_lock_init(&ndl->lock); - ndl->count = 0; - } + for (i = 0; i < nd_region->num_lanes; i++) + mutex_init(&nd_region->lane[i].lock); for (i = 0; i < ndr_desc->num_mappings; i++) { struct nd_mapping_desc *mapping = &ndr_desc->mapping[i]; @@ -1046,7 +1025,6 @@ static struct nd_region *nd_region_create(struct nvdimm_bus *nvdimm_bus, } nd_region->provider_data = ndr_desc->provider_data; nd_region->nd_set = ndr_desc->nd_set; - nd_region->num_lanes = ndr_desc->num_lanes; nd_region->flags = ndr_desc->flags; nd_region->ro = ro; nd_region->numa_node = ndr_desc->numa_node; From 13fe4cd9ddd0aacb7777812328be525a11ea3fea Mon Sep 17 00:00:00 2001 From: Abdun Nihaal Date: Tue, 19 May 2026 11:20:12 +0530 Subject: [PATCH 2/8] nvdimm/btt: Free arena sub-allocations on discover_arenas() error path Memory allocated by btt_freelist_init(), btt_rtt_init(), and btt_maplocks_init() is not freed on some discover_arenas() error paths. This leaks memory when arena discovery fails. Add the missing kfree() calls to release the allocations before returning an error. [ as: commit message and log edits ] Fixes: 5212e11fde4d ("nd_btt: atomic sector updates") Cc: stable@vger.kernel.org Signed-off-by: Abdun Nihaal Reviewed-by: Alison Schofield Link: https://patch.msgid.link/20260519-nvdimmleaks-v1-1-592300fb7a43@cse.iitm.ac.in Signed-off-by: Alison Schofield --- drivers/nvdimm/btt.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/nvdimm/btt.c b/drivers/nvdimm/btt.c index fdcb080a4314..e0b6a85a8124 100644 --- a/drivers/nvdimm/btt.c +++ b/drivers/nvdimm/btt.c @@ -919,6 +919,9 @@ static int discover_arenas(struct btt *btt) return ret; out: + kfree(arena->freelist); + kfree(arena->rtt); + kfree(arena->map_locks); kfree(arena); free_arenas(btt); return ret; From 1a6b6442a982d0ca5fb6a1a39b6f6dfd760eda57 Mon Sep 17 00:00:00 2001 From: Abdun Nihaal Date: Tue, 19 May 2026 11:20:13 +0530 Subject: [PATCH 3/8] nvdimm/btt: Free arenas on btt_init() error paths The arenas allocated by discover_arenas() or create_arenas() are not freed on some error paths in btt_init(). This leaks memory when BTT initialization fails. Call free_arenas() from the affected error paths to release the allocations. [ as: commit message and log edits ] Fixes: 5212e11fde4d ("nd_btt: atomic sector updates") Cc: stable@vger.kernel.org Signed-off-by: Abdun Nihaal Reviewed-by: Alison Schofield Link: https://patch.msgid.link/20260519-nvdimmleaks-v1-2-592300fb7a43@cse.iitm.ac.in Signed-off-by: Alison Schofield --- drivers/nvdimm/btt.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/nvdimm/btt.c b/drivers/nvdimm/btt.c index e0b6a85a8124..7e1112960d7f 100644 --- a/drivers/nvdimm/btt.c +++ b/drivers/nvdimm/btt.c @@ -1592,7 +1592,7 @@ static struct btt *btt_init(struct nd_btt *nd_btt, unsigned long long rawsize, if (btt->init_state != INIT_READY && nd_region->ro) { dev_warn(dev, "%s is read-only, unable to init btt metadata\n", dev_name(&nd_region->dev)); - return NULL; + goto err; } else if (btt->init_state != INIT_READY) { btt->num_arenas = (rawsize / ARENA_MAX_SIZE) + ((rawsize % ARENA_MAX_SIZE) ? 1 : 0); @@ -1602,25 +1602,28 @@ static struct btt *btt_init(struct nd_btt *nd_btt, unsigned long long rawsize, ret = create_arenas(btt); if (ret) { dev_info(dev, "init: create_arenas: %d\n", ret); - return NULL; + goto err; } ret = btt_meta_init(btt); if (ret) { dev_err(dev, "init: error in meta_init: %d\n", ret); - return NULL; + goto err; } } ret = btt_blk_init(btt); if (ret) { dev_err(dev, "init: error in blk_init: %d\n", ret); - return NULL; + goto err; } btt_debugfs_init(btt); return btt; +err: + free_arenas(btt); + return NULL; } /** From cd1a8d788763bb6c0af3c53fbbd9abb555e18953 Mon Sep 17 00:00:00 2001 From: Tomasz Wolski Date: Thu, 28 May 2026 08:45:46 +0200 Subject: [PATCH 4/8] dax/bus: Upgrade resource conflict message to dev_err() in alloc_dax_region() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dax_region resource conflict in alloc_dax_region() indicates a serious configuration problem — two subsystems (e.g. dax_hmem and dax_cxl) are attempting to register overlapping address ranges. This is not a transient or debug-level condition; it represents a genuine resource conflict that an administrator needs to be aware of. Promote the log level from dev_dbg() to dev_err() so that the conflict is visible by default without requiring dynamic debug to be enabled. Suggested-by: Dan Williams Link: https://lore.kernel.org/linux-cxl/69c1a8d1c0fa9_7ee3100a1@dwillia2-mobl4.notmuch/ Signed-off-by: Tomasz Wolski Reviewed-by: Alison Schofield Reviewed-by: Dave Jiang Reviewed-by: Richard Cheng Link: https://patch.msgid.link/20260528064546.23362-1-tomasz.wolski@fujitsu.com Signed-off-by: Alison Schofield --- drivers/dax/bus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/dax/bus.c b/drivers/dax/bus.c index ccfe65004888..b809e1a264af 100644 --- a/drivers/dax/bus.c +++ b/drivers/dax/bus.c @@ -670,7 +670,7 @@ struct dax_region *alloc_dax_region(struct device *parent, int region_id, rc = request_resource(&dax_regions, &dax_region->res); if (rc) { - dev_dbg(parent, "dax_region resource conflict for %pR\n", + dev_err(parent, "dax_region resource conflict for %pR\n", &dax_region->res); goto err_res; } From 1e7afc906f2ffb0ef1ee58c44510f8e9e263048e Mon Sep 17 00:00:00 2001 From: Yury Norov Date: Thu, 28 May 2026 14:36:18 -0400 Subject: [PATCH 5/8] nvdimm: Use sysfs_emit() for cpumask show callback nvdimm_pmu_cpumask_show() is a sysfs show callback. Use sysfs_emit() and cpumask_pr_args() to emit the mask. This prepares for removing cpumap_print_to_pagebuf(). Signed-off-by: Yury Norov Reviewed-by: Alison Schofield Link: https://patch.msgid.link/20260528183625.870813-12-ynorov@nvidia.com Signed-off-by: Alison Schofield --- drivers/nvdimm/nd_perf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvdimm/nd_perf.c b/drivers/nvdimm/nd_perf.c index e0b51438dc9b..9e497cae65b3 100644 --- a/drivers/nvdimm/nd_perf.c +++ b/drivers/nvdimm/nd_perf.c @@ -123,7 +123,7 @@ static ssize_t nvdimm_pmu_cpumask_show(struct device *dev, nd_pmu = container_of(pmu, struct nvdimm_pmu, pmu); - return cpumap_print_to_pagebuf(true, buf, cpumask_of(nd_pmu->cpu)); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(nd_pmu->cpu))); } static int nvdimm_pmu_cpu_offline(unsigned int cpu, struct hlist_node *node) From e3fc08f4ab66da6226f5e34b87bcbcf35ddebe56 Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Wed, 22 Apr 2026 17:10:03 -0700 Subject: [PATCH 6/8] MAINTAINERS: Add maintainer info for libnvdimm and DAX Add Alison Schofield to libnvdimm and DAX maintainer. Cc: Alison Schofield Cc: Ira Ira Weiny Cc: Dan Williams Signed-off-by: Dave Jiang Acked-by: Vishal Verma Acked-by: Ira Weiny Link: https://patch.msgid.link/20260423001003.2887295-1-dave.jiang@intel.com Signed-off-by: Alison Schofield --- MAINTAINERS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 9ec290e38b44..6e65a68d3eeb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7299,6 +7299,7 @@ DEVICE DIRECT ACCESS (DAX) M: Dan Williams M: Vishal Verma M: Dave Jiang +M: Alison Schofield L: nvdimm@lists.linux.dev L: linux-cxl@vger.kernel.org S: Supported @@ -14673,6 +14674,7 @@ LIBNVDIMM BTT: BLOCK TRANSLATION TABLE M: Vishal Verma M: Dan Williams M: Dave Jiang +M: Alison Schofield L: nvdimm@lists.linux.dev S: Supported Q: https://patchwork.kernel.org/project/linux-nvdimm/list/ @@ -14683,6 +14685,7 @@ LIBNVDIMM PMEM: PERSISTENT MEMORY DRIVER M: Dan Williams M: Vishal Verma M: Dave Jiang +M: Alison Schofield L: nvdimm@lists.linux.dev S: Supported Q: https://patchwork.kernel.org/project/linux-nvdimm/list/ @@ -14702,6 +14705,7 @@ M: Dan Williams M: Vishal Verma M: Dave Jiang M: Ira Weiny +M: Alison Schofield L: nvdimm@lists.linux.dev S: Supported Q: https://patchwork.kernel.org/project/linux-nvdimm/list/ From a6e0072cfa82f84557961dc13d552de69fff85b1 Mon Sep 17 00:00:00 2001 From: Ira Weiny Date: Mon, 4 May 2026 17:38:10 -0500 Subject: [PATCH 7/8] MAINTAINERS: Update address for Ira Weiny Update MAINTAINERS and .mailmap to point to my kernel.org address: iweiny@kernel.org Downgrade from maintainer to reviewer whilst doing so. Signed-off-by: Ira Weiny Acked-by: Jonathan Cameron Acked-by: Dave Jiang Link: https://patch.msgid.link/20260504-change-maintain-file-v1-1-6679b030d3e0@intel.com Signed-off-by: Alison Schofield --- .mailmap | 1 + MAINTAINERS | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.mailmap b/.mailmap index a009f73d7ea5..afdb363752e8 100644 --- a/.mailmap +++ b/.mailmap @@ -448,6 +448,7 @@ Juha Yrjola Juha Yrjola Julien Thierry Justin Iurman +Ira Weiny Iskren Chernev Kalle Valo Kalle Valo diff --git a/MAINTAINERS b/MAINTAINERS index 6e65a68d3eeb..4a49d718123d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4265,7 +4265,7 @@ M: Greg Kroah-Hartman M: "Rafael J. Wysocki" M: Danilo Krummrich R: Dave Ertman -R: Ira Weiny +R: Ira Weiny R: Leon Romanovsky L: driver-core@lists.linux.dev S: Supported @@ -6435,8 +6435,8 @@ M: Jonathan Cameron M: Dave Jiang M: Alison Schofield M: Vishal Verma -M: Ira Weiny M: Dan Williams +R: Ira Weiny L: linux-cxl@vger.kernel.org S: Maintained F: Documentation/driver-api/cxl @@ -14704,8 +14704,8 @@ LIBNVDIMM: NON-VOLATILE MEMORY DEVICE SUBSYSTEM M: Dan Williams M: Vishal Verma M: Dave Jiang -M: Ira Weiny M: Alison Schofield +R: Ira Weiny L: nvdimm@lists.linux.dev S: Supported Q: https://patchwork.kernel.org/project/linux-nvdimm/list/ From 86e411b6ec277dbb8ac1f1d855dc337181a62a29 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 18 May 2026 12:43:07 +0200 Subject: [PATCH 8/8] MAINTAINERS: nvdimm: Include maintainer profile No dedicated NVDIMM maintainers are returned by get_maintainers.pl for the subsystem maintainer profile, thus patches changing that file miss the actual owners of the file. Signed-off-by: Krzysztof Kozlowski Acked-by: Dave Jiang Link: https://patch.msgid.link/20260518104306.39289-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Alison Schofield --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 4a49d718123d..54090d8f7c1d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14711,6 +14711,7 @@ S: Supported Q: https://patchwork.kernel.org/project/linux-nvdimm/list/ P: Documentation/nvdimm/maintainer-entry-profile.rst T: git git://git.kernel.org/pub/scm/linux/kernel/git/nvdimm/nvdimm.git +F: Documentation/nvdimm/maintainer-entry-profile.rst F: drivers/acpi/nfit/* F: drivers/nvdimm/* F: include/linux/libnvdimm.h