Re: [PATCH V7 06/19] dax: Add dax_set_ops() for setting dax_operations at bind time

Dave Jiang
2026-02-19T15:45:28Z

On 1/18/26 3:32 PM, John Groves wrote:
> From: John Groves <John@Groves.net>
>
> Add a new dax_set_ops() function that allows drivers to set the
> dax_operations after the dax_device has been allocated. This is needed
> for fsdev_dax where the operations need to be set during probe and
> cleared during unbind.
>
> The fsdev driver uses devm_add_action_or_reset() for cleanup consistency,
> avoiding the complexity of mixing devm-managed resources with manual
> cleanup in a remove() callback. This ensures cleanup happens automatically
> in the correct reverse order when the device is unbound.
>
> Signed-off-by: John Groves <john@groves.net>

Reviewed-by: Dave Jiang <dave.jiang@intel.com>

> ---
> drivers/dax/fsdev.c | 16 ++++++++++++++++
> drivers/dax/super.c | 38 +++++++++++++++++++++++++++++++++++++-
> include/linux/dax.h | 1 +
> 3 files changed, 54 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/dax/fsdev.c b/drivers/dax/fsdev.c
> index 5d17ad39227f..4949aa41dcf4 100644
> --- a/drivers/dax/fsdev.c
> +++ b/drivers/dax/fsdev.c
> @@ -119,6 +119,13 @@ static void fsdev_kill(void *dev_dax)
> kill_dev_dax(dev_dax);
> }
>
> +static void fsdev_clear_ops(void *data)
> +{
> + struct dev_dax *dev_dax = data;
> +
> + dax_set_ops(dev_dax->dax_dev, NULL);
> +}
> +
> /*
> * Page map operations for FS-DAX mode
> * Similar to fsdax_pagemap_ops in drivers/nvdimm/pmem.c
> @@ -301,6 +308,15 @@ static int fsdev_dax_probe(struct dev_dax *dev_dax)
> if (rc)
> return rc;
>
> + /* Set the dax operations for fs-dax access path */
> + rc = dax_set_ops(dax_dev, &dev_dax_ops);
> + if (rc)
> + return rc;
> +
> + rc = devm_add_action_or_reset(dev, fsdev_clear_ops, dev_dax);
> + if (rc)
> + return rc;
> +
> run_dax(dax_dev);
> return devm_add_action_or_reset(dev, fsdev_kill, dev_dax);
> }
> diff --git a/drivers/dax/super.c b/drivers/dax/super.c
> index c00b9dff4a06..ba0b4cd18a77 100644
> --- a/drivers/dax/super.c
> +++ b/drivers/dax/super.c
> @@ -157,6 +157,9 @@ long dax_direct_access(struct dax_device *dax_dev, pgoff_t pgoff, long nr_pages,
> if (!dax_alive(dax_dev))
> return -ENXIO;
>
> + if (!dax_dev->ops)
> + return -EOPNOTSUPP;
> +
> if (nr_pages < 0)
> return -EINVAL;
>
> @@ -207,6 +210,10 @@ int dax_zero_page_range(struct dax_device *dax_dev, pgoff_t pgoff,
>
> if (!dax_alive(dax_dev))
> return -ENXIO;
> +
> + if (!dax_dev->ops)
> + return -EOPNOTSUPP;
> +
> /*
> * There are no callers that want to zero more than one page as of now.
> * Once users are there, this check can be removed after the
> @@ -223,7 +230,7 @@ EXPORT_SYMBOL_GPL(dax_zero_page_range);
> size_t dax_recovery_write(struct dax_device *dax_dev, pgoff_t pgoff,
> void *addr, size_t bytes, struct iov_iter *iter)
> {
> - if (!dax_dev->ops->recovery_write)
> + if (!dax_dev->ops || !dax_dev->ops->recovery_write)
> return 0;
> return dax_dev->ops->recovery_write(dax_dev, pgoff, addr, bytes, iter);
> }
> @@ -307,6 +314,35 @@ void set_dax_nomc(struct dax_device *dax_dev)
> }
> EXPORT_SYMBOL_GPL(set_dax_nomc);
>
> +/**
> + * dax_set_ops - set the dax_operations for a dax_device
> + * @dax_dev: the dax_device to configure
> + * @ops: the operations to set (may be NULL to clear)
> + *
> + * This allows drivers to set the dax_operations after the dax_device
> + * has been allocated. This is needed when the device is created before
> + * the driver that needs specific ops is bound (e.g., fsdev_dax binding
> + * to a dev_dax created by hmem).
> + *
> + * When setting non-NULL ops, fails if ops are already set (returns -EBUSY).
> + * When clearing ops (NULL), always succeeds.
> + *
> + * Return: 0 on success, -EBUSY if ops already set
> + */
> +int dax_set_ops(struct dax_device *dax_dev, const struct dax_operations *ops)
> +{
> + if (ops) {
> + /* Setting ops: fail if already set */
> + if (cmpxchg(&dax_dev->ops, NULL, ops) != NULL)
> + return -EBUSY;
> + } else {
> + /* Clearing ops: always allowed */
> + dax_dev->ops = NULL;
> + }
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(dax_set_ops);
> +
> bool dax_alive(struct dax_device *dax_dev)
> {
> lockdep_assert_held(&dax_srcu);
> diff --git a/include/linux/dax.h b/include/linux/dax.h
> index fe1315135fdd..5aaaca135737 100644
> --- a/include/linux/dax.h
> +++ b/include/linux/dax.h
> @@ -247,6 +247,7 @@ static inline void dax_break_layout_final(struct inode *inode)
>
> bool dax_alive(struct dax_device *dax_dev);
> void *dax_get_private(struct dax_device *dax_dev);
> +int dax_set_ops(struct dax_device *dax_dev, const struct dax_operations *ops);
> long dax_direct_access(struct dax_device *dax_dev, pgoff_t pgoff, long nr_pages,
> enum dax_access_mode mode, void **kaddr, unsigned long *pfn);
> size_t dax_copy_from_iter(struct dax_device *dax_dev, pgoff_t pgoff, void *addr,