Merge tag 'libnvdimm-for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/nvdimm/nvdimm

Pull NVDIMM and DAX updates from Alison Schofield:
 "Most are DAX preparatory patches for FAMFS support, along with a few
  NVDIMM fixes and documentation cleanups.

   - Documentation cleanup, removing kernel-doc warnings

   - preparing DAX for FAMFS

   - misc NVDIMM fixups with cleanups for issues reported by Coccinelle"

* tag 'libnvdimm-for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/nvdimm/nvdimm:
  nvdimm-btt: clean up kernel-doc warnings
  libnvdimm: nd.h: clean up kernel-doc warnings
  dax: fsdev.c minor formatting cleanup
  dax: fix holder_ops race in fs_put_dax()
  dax: read holder_ops once in dax_holder_notify_failure()
  dax/fsdev: fail probe on invalid pgmap offset
  dax/fsdev: use __va(phys) for kaddr in direct_access
  dax/fsdev: clear pgmap ops and owner on unbind
  dax/fsdev: don't leave a dangling dev_dax->pgmap on probe failure
  dax/fsdev: clear vmemmap_shift when binding static pgmap
  dax/fsdev: fix multi-range offset in memory_failure handler
  dax: fix misleading comment about share/index union in dax_folio_reset_order()
  nvdimm/btt: reject an arena whose nfree is below the lane count
  libnvdimm/labels: Bound the on-media label size before the shift
  libnvdimm/labels: Prevent integer overflow in __nd_label_validate()
  nvdimm: ndtest: remove redundant NULL check before vfree()
  nvdimm: nfit: remove redundant NULL check before vfree()
This commit is contained in:
Linus Torvalds
2026-08-20 12:17:36 -07:00
10 changed files with 194 additions and 69 deletions

View File

@@ -69,7 +69,6 @@ struct dev_dax_range {
* data while the device is activated in the driver.
* @region: parent region
* @dax_dev: core dax functionality
* @virt_addr: kva from memremap; used by fsdev_dax
* @cached_size: size of daxdev cached by fsdev_dax
* @align: alignment of this instance
* @target_node: effective numa node if dev_dax memory range is onlined
@@ -85,7 +84,6 @@ struct dev_dax_range {
struct dev_dax {
struct dax_region *region;
struct dax_device *dax_dev;
void *virt_addr;
u64 cached_size;
unsigned int align;
int target_node;

View File

@@ -45,15 +45,13 @@ static void fsdev_write_dax(void *addr, struct page *page,
}
static long __fsdev_dax_direct_access(struct dax_device *dax_dev, pgoff_t pgoff,
long nr_pages, enum dax_access_mode mode, void **kaddr,
unsigned long *pfn)
long nr_pages, enum dax_access_mode mode, void **kaddr,
unsigned long *pfn)
{
struct dev_dax *dev_dax = dax_get_private(dax_dev);
size_t size = nr_pages << PAGE_SHIFT;
size_t offset = pgoff << PAGE_SHIFT;
void *virt_addr = dev_dax->virt_addr + offset;
phys_addr_t phys;
unsigned long local_pfn;
phys = dax_pgoff_to_phys(dev_dax, pgoff, size);
if (phys == -1) {
@@ -63,11 +61,10 @@ static long __fsdev_dax_direct_access(struct dax_device *dax_dev, pgoff_t pgoff,
}
if (kaddr)
*kaddr = virt_addr;
*kaddr = __va(phys);
local_pfn = PHYS_PFN(phys);
if (pfn)
*pfn = local_pfn;
*pfn = PHYS_PFN(phys);
/*
* Use cached_size which was computed at probe time. The size cannot
@@ -83,7 +80,8 @@ static int fsdev_dax_zero_page_range(struct dax_device *dax_dev,
long rc;
WARN_ONCE(nr_pages > 1, "%s: nr_pages > 1\n", __func__);
rc = __fsdev_dax_direct_access(dax_dev, pgoff, 1, DAX_ACCESS, &kaddr, NULL);
rc = __fsdev_dax_direct_access(dax_dev, pgoff, 1, DAX_ACCESS,
&kaddr, NULL);
if (rc < 0)
return rc;
fsdev_write_dax(kaddr, ZERO_PAGE(0), 0, PAGE_SIZE);
@@ -91,15 +89,15 @@ static int fsdev_dax_zero_page_range(struct dax_device *dax_dev,
}
static long fsdev_dax_direct_access(struct dax_device *dax_dev,
pgoff_t pgoff, long nr_pages, enum dax_access_mode mode,
void **kaddr, unsigned long *pfn)
pgoff_t pgoff, long nr_pages, enum dax_access_mode mode,
void **kaddr, unsigned long *pfn)
{
return __fsdev_dax_direct_access(dax_dev, pgoff, nr_pages, mode,
kaddr, pfn);
}
static size_t fsdev_dax_recovery_write(struct dax_device *dax_dev, pgoff_t pgoff,
void *addr, size_t bytes, struct iov_iter *i)
static size_t fsdev_dax_recovery_write(struct dax_device *dax_dev,
pgoff_t pgoff, void *addr, size_t bytes, struct iov_iter *i)
{
return _copy_from_iter_flushcache(addr, bytes, i);
}
@@ -127,6 +125,23 @@ static void fsdev_clear_ops(void *data)
dax_set_ops(dev_dax->dax_dev, NULL);
}
static void fsdev_clear_pgmap_ops(void *data)
{
struct dev_pagemap *pgmap = data;
/*
* fsdev installs pgmap->ops and ->owner at probe. For a static device
* the pgmap is shared and long-lived (owned by the dax bus), so
* leaving fsdev's ops behind on unbind would let a later
* memory_failure -- after rebind to another driver, or after this
* module is unloaded -- dispatch through a stale or freed
* ->memory_failure handler. Clear them so the pgmap carries no fsdev
* state once we are unbound.
*/
pgmap->ops = NULL;
pgmap->owner = NULL;
}
/*
* Page map operations for FS-DAX mode
* Similar to fsdax_pagemap_ops in drivers/nvdimm/pmem.c
@@ -135,11 +150,26 @@ static void fsdev_clear_ops(void *data)
* The core mm code in free_zone_device_folio() handles the wake_up_var()
* directly for this memory type.
*/
static u64 fsdev_pfn_to_offset(struct dev_dax *dev_dax, unsigned long pfn)
{
phys_addr_t phys = PFN_PHYS(pfn);
u64 offset = 0;
for (int i = 0; i < dev_dax->nr_range; i++) {
struct range *range = &dev_dax->ranges[i].range;
if (phys >= range->start && phys <= range->end)
return offset + (phys - range->start);
offset += range_len(range);
}
return -1ULL;
}
static int fsdev_pagemap_memory_failure(struct dev_pagemap *pgmap,
unsigned long pfn, unsigned long nr_pages, int mf_flags)
{
struct dev_dax *dev_dax = pgmap->owner;
u64 offset = PFN_PHYS(pfn) - dev_dax->ranges[0].range.start;
u64 offset = fsdev_pfn_to_offset(dev_dax, pfn);
u64 len = nr_pages << PAGE_SHIFT;
return dax_holder_notify_failure(dev_dax->dax_dev, offset,
@@ -204,6 +234,48 @@ static const struct file_operations fsdev_fops = {
.release = fsdev_release,
};
/*
* Acquire the dev_pagemap for probe: the static (pre-populated) one if
* present, or a devm-allocated one for the dynamic case. Note that
* dev_dax->pgmap is not set here; fsdev_dax_probe() sets it only once
* probe succeeds, so a failed probe never leaves a dangling pointer
* to a devres-freed pgmap.
*/
static struct dev_pagemap *fsdev_acquire_pgmap(struct dev_dax *dev_dax)
{
struct device *dev = &dev_dax->dev;
struct dev_pagemap *pgmap;
size_t pgmap_size;
if (static_dev_dax(dev_dax)) {
if (dev_dax->nr_range > 1) {
dev_warn(dev,
"static pgmap / multi-range device conflict\n");
return ERR_PTR(-EINVAL);
}
pgmap = dev_dax->pgmap;
pgmap->vmemmap_shift = 0;
return pgmap;
}
if (dev_dax->pgmap) {
dev_warn(dev, "dynamic-dax with pre-populated page map\n");
return ERR_PTR(-EINVAL);
}
pgmap_size = struct_size(pgmap, ranges, dev_dax->nr_range - 1);
pgmap = devm_kzalloc(dev, pgmap_size, GFP_KERNEL);
if (!pgmap)
return ERR_PTR(-ENOMEM);
pgmap->nr_range = dev_dax->nr_range;
for (int i = 0; i < dev_dax->nr_range; i++)
pgmap->ranges[i] = dev_dax->ranges[i].range;
return pgmap;
}
static int fsdev_dax_probe(struct dev_dax *dev_dax)
{
struct dax_device *dax_dev = dev_dax->dax_dev;
@@ -215,35 +287,9 @@ static int fsdev_dax_probe(struct dev_dax *dev_dax)
void *addr;
int rc, i;
if (static_dev_dax(dev_dax)) {
if (dev_dax->nr_range > 1) {
dev_warn(dev, "static pgmap / multi-range device conflict\n");
return -EINVAL;
}
pgmap = dev_dax->pgmap;
} else {
size_t pgmap_size;
if (dev_dax->pgmap) {
dev_warn(dev, "dynamic-dax with pre-populated page map\n");
return -EINVAL;
}
pgmap_size = struct_size(pgmap, ranges, dev_dax->nr_range - 1);
pgmap = devm_kzalloc(dev, pgmap_size, GFP_KERNEL);
if (!pgmap)
return -ENOMEM;
pgmap->nr_range = dev_dax->nr_range;
dev_dax->pgmap = pgmap;
for (i = 0; i < dev_dax->nr_range; i++) {
struct range *range = &dev_dax->ranges[i].range;
pgmap->ranges[i] = *range;
}
}
pgmap = fsdev_acquire_pgmap(dev_dax);
if (IS_ERR(pgmap))
return PTR_ERR(pgmap);
for (i = 0; i < dev_dax->nr_range; i++) {
struct range *range = &dev_dax->ranges[i].range;
@@ -275,6 +321,11 @@ static int fsdev_dax_probe(struct dev_dax *dev_dax)
if (IS_ERR(addr))
return PTR_ERR(addr);
/* Drop fsdev's pgmap->ops/owner on unbind so no stale ops survive. */
rc = devm_add_action_or_reset(dev, fsdev_clear_pgmap_ops, pgmap);
if (rc)
return rc;
/*
* Clear any stale compound folio state left over from a previous
* driver (e.g., device_dax with vmemmap_shift). Also register this
@@ -290,15 +341,18 @@ static int fsdev_dax_probe(struct dev_dax *dev_dax)
/* Detect whether the data is at a non-zero offset into the memory */
if (pgmap->range.start != dev_dax->ranges[0].range.start) {
u64 phys = dev_dax->ranges[0].range.start;
u64 pgmap_phys = dev_dax->pgmap[0].range.start;
u64 pgmap_phys = pgmap[0].range.start;
if (!WARN_ON(pgmap_phys > phys))
data_offset = phys - pgmap_phys;
if (pgmap_phys > phys) {
dev_err(dev, "pgmap start %#llx exceeds data start %#llx\n",
pgmap_phys, phys);
return -EINVAL;
}
data_offset = phys - pgmap_phys;
pr_debug("%s: offset detected phys=%llx pgmap_phys=%llx offset=%llx\n",
__func__, phys, pgmap_phys, data_offset);
}
dev_dax->virt_addr = addr + data_offset;
inode = dax_inode(dax_dev);
cdev = inode->i_cdev;
@@ -323,7 +377,13 @@ static int fsdev_dax_probe(struct dev_dax *dev_dax)
return rc;
run_dax(dax_dev);
return devm_add_action_or_reset(dev, fsdev_kill, dev_dax);
rc = devm_add_action_or_reset(dev, fsdev_kill, dev_dax);
if (rc)
return rc;
/* Probe can no longer fail; expose the pgmap via dev_dax */
dev_dax->pgmap = pgmap;
return 0;
}
static struct dax_device_driver fsdev_dax_driver = {

View File

@@ -116,11 +116,47 @@ EXPORT_SYMBOL_GPL(fs_dax_get_by_bdev);
#if IS_ENABLED(CONFIG_FS_DAX)
/**
* fs_put_dax() - release holder ownership of a dax_device
* @dax_dev: dax device to release (may be NULL)
* @holder: the holder pointer previously passed to fs_dax_get() or
* fs_dax_get_by_bdev(); must match exactly, as it is used
* in a cmpxchg to atomically release ownership
*
* Must only be called by the current holder. Clears holder_ops before
* holder_data to avoid a race where a concurrent fs_dax_get() could have
* its newly installed holder_ops overwritten.
*/
void fs_put_dax(struct dax_device *dax_dev, void *holder)
{
if (dax_dev && holder &&
cmpxchg(&dax_dev->holder_data, holder, NULL) == holder)
dax_dev->holder_ops = NULL;
if (dax_dev && holder) {
void *prev;
/*
* Clear holder_ops before releasing holder_data. A concurrent
* dax_holder_notify_failure() that sees NULL ops returns
* -EOPNOTSUPP cleanly. A concurrent fs_dax_get() that acquires
* holder_data after the cmpxchg below is guaranteed to observe
* holder_ops=NULL first (cmpxchg provides release ordering), so
* its subsequent store of new ops will not be overwritten.
*/
WRITE_ONCE(dax_dev->holder_ops, NULL);
prev = cmpxchg(&dax_dev->holder_data, holder, NULL);
/*
* prev == holder: normal release.
* prev == NULL: already released by kill_dax() when the
* device was removed under a live holder;
* not a bug.
* prev != holder (non-NULL): fs_put_dax() called by something
* that is not the current holder; an API
* contract violation. A lock would be needed
* to guard against this, but we WARN_ON()
* instead since violating the contract is
* a bug.
*/
WARN_ON(prev && prev != holder);
}
put_dax(dax_dev);
}
EXPORT_SYMBOL_GPL(fs_put_dax);
@@ -303,6 +339,7 @@ EXPORT_SYMBOL_GPL(dax_recovery_write);
int dax_holder_notify_failure(struct dax_device *dax_dev, u64 off,
u64 len, int mf_flags)
{
const struct dax_holder_operations *ops;
int rc, id;
id = dax_read_lock();
@@ -311,12 +348,19 @@ int dax_holder_notify_failure(struct dax_device *dax_dev, u64 off,
goto out;
}
if (!dax_dev->holder_ops) {
/*
* Read holder_ops once: a concurrent fs_put_dax() can clear it without
* synchronizing against readers. Without the single fetch the compiler
* could reload between the NULL check and the call and dereference a
* NULL ops.
*/
ops = READ_ONCE(dax_dev->holder_ops);
if (!ops) {
rc = -EOPNOTSUPP;
goto out;
}
rc = dax_dev->holder_ops->notify_failure(dax_dev, off, len, mf_flags);
rc = ops->notify_failure(dax_dev, off, len, mf_flags);
out:
dax_read_unlock(id);
return rc;

View File

@@ -883,6 +883,14 @@ static int discover_arenas(struct btt *btt)
arena->external_lba_start = cur_nlba;
parse_arena_meta(arena, super, cur_off);
if (arena->nfree < btt->nd_region->num_lanes) {
dev_err(to_dev(arena),
"nfree %u smaller than lane count %d\n",
arena->nfree, btt->nd_region->num_lanes);
ret = -ENODEV;
goto out;
}
ret = log_set_indices(arena);
if (ret) {
dev_err(to_dev(arena),

View File

@@ -210,7 +210,7 @@ struct badblocks;
* @lbasize: LBA size as requested and presented to upper layers.
* This is sector_size + size of any metadata.
* @sector_size: The Linux sector size - 512 or 4096
* @lanes: Per-lane spinlocks
* @nd_region: &struct nd_region pointer
* @init_lock: Mutex used for the BTT initialization
* @init_state: Flag describing the initialization state for the BTT
* @num_arenas: Number of arenas in the BTT instance

View File

@@ -145,10 +145,21 @@ static int __nd_label_validate(struct nvdimm_drvdata *ndd)
/* label sizes larger than 128 arrived with v1.2 */
version = __le16_to_cpu(nsindex[i]->major) * 100
+ __le16_to_cpu(nsindex[i]->minor);
if (version >= 102)
if (version >= 102) {
/*
* labelsize feeds the shift below; only 0 (128-byte)
* and 1 (256-byte) are valid -- a larger value would
* overflow or exceed the width of int.
*/
if (nsindex[i]->labelsize > 1) {
dev_dbg(dev, "nsindex%d labelsize: %d invalid\n",
i, nsindex[i]->labelsize);
continue;
}
labelsize = 1 << (7 + nsindex[i]->labelsize);
else
} else {
labelsize = 128;
}
if (labelsize != sizeof_namespace_label(ndd)) {
dev_dbg(dev, "nsindex%d labelsize %d invalid\n",
@@ -202,7 +213,7 @@ static int __nd_label_validate(struct nvdimm_drvdata *ndd)
}
nslot = __le32_to_cpu(nsindex[i]->nslot);
if (nslot * sizeof_namespace_label(ndd)
if ((u64)nslot * sizeof_namespace_label(ndd)
+ 2 * sizeof_namespace_index(ndd)
> ndd->nsarea.config_size) {
dev_dbg(dev, "nsindex%d nslot: %u invalid, config_size: %#x\n",

View File

@@ -392,12 +392,12 @@ int dax_folio_reset_order(struct folio *folio)
int order = folio_order(folio);
/*
* DAX maintains the invariant that folio->share != 0 only when
* folio->mapping == NULL (enforced by dax_folio_make_shared()).
* Equivalently: folio->mapping != NULL implies folio->share == 0.
* Callers ensure share has been decremented to zero before
* calling here, so unconditionally clearing both fields is
* correct.
* Clear the mapping and the index/share union word. folio->share
* and folio->index occupy the same union in struct folio. For
* non-shared folios (mapping != NULL), the union holds folio->index
* (file page offset); for shared folios (mapping == NULL), it holds
* folio->share (reference count). Either way, we are releasing the
* folio and both fields should be zeroed.
*/
folio->mapping = NULL;
folio->share = 0;

View File

@@ -110,7 +110,7 @@ static inline struct nd_namespace_common *to_ndns(struct device *dev)
/**
* struct nd_namespace_io - device representation of a persistent memory range
* @dev: namespace device created by the nd region driver
* @common: namespace device core infrastructure created by the nd region driver
* @res: struct resource conversion of a NFIT SPA table
* @size: cached resource_size(@res) for fast path size checks
* @addr: virtual address to access the namespace range
@@ -158,8 +158,11 @@ static inline struct nd_namespace_pmem *to_nd_namespace_pmem(const struct device
* @offset: namespace-relative starting offset
* @buf: buffer to fill
* @size: transfer length
* @flags: process (0) or atomic (1) context
*
* @buf is up-to-date upon return from this routine.
*
* Returns: %0 on success or a negative error code on failure
*/
static inline int nvdimm_read_bytes(struct nd_namespace_common *ndns,
resource_size_t offset, void *buf, size_t size,
@@ -174,11 +177,14 @@ static inline int nvdimm_read_bytes(struct nd_namespace_common *ndns,
* @offset: namespace-relative starting offset
* @buf: buffer to drain
* @size: transfer length
* @flags: process (0) or atomic (1) context
*
* NVDIMM Namepaces disks do not implement sectors internally. Depending on
* the @ndns, the contents of @buf may be in cpu cache, platform buffers,
* or on backing memory media upon return from this routine. Flushing
* to media is handled internal to the @ndns driver, if at all.
*
* Returns: %0 on success or a negative error code on failure
*/
static inline int nvdimm_write_bytes(struct nd_namespace_common *ndns,
resource_size_t offset, void *buf, size_t size,

View File

@@ -376,8 +376,7 @@ static void *ndtest_alloc_resource(struct ndtest_priv *p, size_t size,
buf_err:
if (__dma && size >= DIMM_SIZE)
gen_pool_free(ndtest_pool, __dma, size);
if (buf)
vfree(buf);
vfree(buf);
kfree(res);
return NULL;

View File

@@ -1644,8 +1644,7 @@ static void *__test_alloc(struct nfit_test *t, size_t size, dma_addr_t *dma,
err:
if (*dma && size >= DIMM_SIZE)
gen_pool_free(nfit_pool, *dma, size);
if (buf)
vfree(buf);
vfree(buf);
kfree(nfit_res);
return NULL;
}