[PATCH V11 5/9] famfs_fuse: register fs-dax daxdevs via FUSE_DEV_IOC_DAXDEV_OPEN

John Groves <[email protected]> Mon, 20 Jul 2026 03:46:13 +0000
Newsgroups dev.linux.lists.fuse-devel,dev.linux.lists.nvdimm,org.kernel.vger.linux-cxl,org.kernel.vger.linux-doc,org.kernel.vger.linux-fsdevel,org.kernel.vger.linux-kernel
Message-ID <0100019f7da1a534-2fc6658c-4f30-4cc6-b010-5e3f8fd7164b-000000@email.amazonses.com>
From: John Groves <[email protected]>=0D=0A=0D=0AAdd a dedicated ioctl, FUS=
E_DEV_IOC_DAXDEV_OPEN, by which a fuse server=0D=0Aregisters the devdax d=
evices that back an fs-dax (famfs) filesystem: the=0D=0Aserver hands the =
kernel an fd to a /dev/daxN.N device plus its=0D=0Acluster-invariant famf=
s index.=0D=0A=0D=0AA dedicated ioctl, rather than overloading FUSE_DEV_I=
OC_BACKING_OPEN,=0D=0Aavoids a dependency on CONFIG_FUSE_PASSTHROUGH and =
the backing-file=0D=0Amachinery, which famfs does not use.=0D=0A=0D=0A- s=
truct fuse_backing_map is reused as the argument; the index rides on=0D=0A=
  the reserved 'padding' field (daxdev_index).=0D=0A- fuse_dev_ioctl_daxd=
ev_open() copies the map and calls=0D=0A  famfs_daxdev_open(), which reso=
lves the fd to a dax device by its inode=0D=0A  i_rdev. The daxdev table =
store is added in the following patch.=0D=0A=0D=0AAccess control:=0D=0A=0D=
=0A- The ioctl is gated on famfs mode (enabled at FUSE_INIT only when the=
=0D=0A  server held CAP_SYS_RAWIO), and additionally re-checks=0D=0A  cap=
able(CAP_SYS_RAWIO) on each call. The famfs-mode flag attests only=0D=0A =
 to the session founder's privilege; the fuse device fd may be inherited=0D=
=0A  across fork() or SCM_RIGHTS-passed to a less-privileged task, so the=
=0D=0A  capability is re-checked against the task actually performing the=
=0D=0A  registration.=0D=0A- famfs_daxdev_open() resolves the fd with fge=
t(), not fget_raw(), so=0D=0A  O_PATH fds are rejected. A successful look=
up then proves the caller=0D=0A  holds an fd from a real open() of the da=
xdev -- i.e. it already passed=0D=0A  may_open_dev() and the device node'=
s DAC checks -- rather than an=0D=0A  O_PATH reference that bypasses them=
=2E=0D=0A=0D=0ASigned-off-by: John Groves <[email protected]>=0D=0A---=0D=0A=
 fs/fuse/dev.c             |  31 ++++=0D=0A fs/fuse/dev.h             |  =
 1 +=0D=0A fs/fuse/famfs.c           | 330 ++++++++++++++++++++++++++++++=
++++++++=0D=0A fs/fuse/famfs_kfmap.h     |  27 ++++=0D=0A fs/fuse/fuse_i.=
h          |  28 ++++=0D=0A fs/fuse/inode.c           |   7 +-=0D=0A incl=
ude/uapi/linux/fuse.h |   7 +-=0D=0A 7 files changed, 429 insertions(+), =
2 deletions(-)=0D=0A=0D=0Adiff --git a/fs/fuse/dev.c b/fs/fuse/dev.c=0D=0A=
index 5763a7cd3b37..3e6aa15e0346 100644=0D=0A--- a/fs/fuse/dev.c=0D=0A+++=
 b/fs/fuse/dev.c=0D=0A@@ -2330,6 +2330,34 @@ static long fuse_dev_ioctl_b=
acking_close(struct file *file, __u32 __user *argp)=0D=0A =09return fuse_=
backing_close(fud->chan->conn, backing_id);=0D=0A }=0D=0A=20=0D=0A+static=
 long fuse_dev_ioctl_daxdev_open(struct file *file,=0D=0A+=09=09=09=09   =
    struct fuse_backing_map __user *argp)=0D=0A+{=0D=0A+=09struct fuse_de=
v *fud =3D fuse_get_dev(file);=0D=0A+=09struct fuse_backing_map map;=0D=0A=
+=0D=0A+=09if (IS_ERR(fud))=0D=0A+=09=09return PTR_ERR(fud);=0D=0A+=0D=0A=
+=09if (!IS_ENABLED(CONFIG_FUSE_FAMFS_DAX))=0D=0A+=09=09return -EOPNOTSUP=
P;=0D=0A+=0D=0A+=09/*=0D=0A+=09 * The famfs-mode gate (fc->famfs_iomap) l=
ives in famfs_daxdev_open(),=0D=0A+=09 * which has the full fuse_conn def=
inition. Here, re-check CAP_SYS_RAWIO=0D=0A+=09 * against the task perfor=
ming the registration: famfs mode being enabled=0D=0A+=09 * only attests =
that the session founder held it at FUSE_INIT, and the=0D=0A+=09 * fuse d=
evice fd may have been passed to a less-privileged process.=0D=0A+=09 */=0D=
=0A+=09if (!capable(CAP_SYS_RAWIO))=0D=0A+=09=09return -EPERM;=0D=0A+=0D=0A=
+=09if (copy_from_user(&map, argp, sizeof(map)))=0D=0A+=09=09return -EFAU=
LT;=0D=0A+=0D=0A+=09return famfs_daxdev_open(fud->chan->conn, &map);=0D=0A=
+}=0D=0A+=0D=0A static long fuse_dev_ioctl_sync_init(struct file *file)=0D=
=0A {=0D=0A =09struct fuse_dev *fud =3D fuse_file_to_fud(file);=0D=0A@@ -=
2359,6 +2387,9 @@ static long fuse_dev_ioctl(struct file *file, unsigned =
int cmd,=0D=0A =09case FUSE_DEV_IOC_SYNC_INIT:=0D=0A =09=09return fuse_de=
v_ioctl_sync_init(file);=0D=0A=20=0D=0A+=09case FUSE_DEV_IOC_DAXDEV_OPEN:=
=0D=0A+=09=09return fuse_dev_ioctl_daxdev_open(file, argp);=0D=0A+=0D=0A =
=09default:=0D=0A =09=09return -ENOTTY;=0D=0A =09}=0D=0Adiff --git a/fs/f=
use/dev.h b/fs/fuse/dev.h=0D=0Aindex aed69fd14c41..545940b635cc 100644=0D=
=0A--- a/fs/fuse/dev.h=0D=0A+++ b/fs/fuse/dev.h=0D=0A@@ -87,6 +87,7 @@ in=
t fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code,=0D=0A=20=0D=
=0A int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map *=
map);=0D=0A int fuse_backing_close(struct fuse_conn *fc, int backing_id);=
=0D=0A+int famfs_daxdev_open(struct fuse_conn *fc, struct fuse_backing_ma=
p *map);=0D=0A=20=0D=0A int fuse_copy_one(struct fuse_copy_state *cs, voi=
d *val, unsigned size);=0D=0A int fuse_copy_folio(struct fuse_copy_state =
*cs, struct folio **foliop,=0D=0Adiff --git a/fs/fuse/famfs.c b/fs/fuse/f=
amfs.c=0D=0Aindex 8f7ee7d6151b..a2a7dd631dc0 100644=0D=0A--- a/fs/fuse/fa=
mfs.c=0D=0A+++ b/fs/fuse/famfs.c=0D=0A@@ -11,6 +11,7 @@=0D=0A=20=0D=0A #i=
nclude <linux/cleanup.h>=0D=0A #include <linux/fs.h>=0D=0A+#include <linu=
x/file.h>=0D=0A #include <linux/mm.h>=0D=0A #include <linux/dax.h>=0D=0A =
#include <linux/iomap.h>=0D=0A@@ -22,6 +23,331 @@=0D=0A #include "famfs_k=
fmap.h"=0D=0A #include "fuse_i.h"=0D=0A=20=0D=0A+static void famfs_set_da=
xdev_err(=0D=0A+=09struct fuse_conn *fc, struct dax_device *dax_devp);=0D=
=0A+=0D=0A+static int=0D=0A+famfs_dax_notify_failure(struct dax_device *d=
ax_devp, u64 offset,=0D=0A+=09=09=09u64 len, int mf_flags)=0D=0A+{=0D=0A+=
=09struct fuse_conn *fc =3D dax_holder(dax_devp);=0D=0A+=0D=0A+=09famfs_s=
et_daxdev_err(fc, dax_devp);=0D=0A+=0D=0A+=09return 0;=0D=0A+}=0D=0A+=0D=0A=
+static const struct dax_holder_operations famfs_fuse_dax_holder_ops =3D =
{=0D=0A+=09.notify_failure=09=09=3D famfs_dax_notify_failure,=0D=0A+};=0D=
=0A+=0D=0A+/*************************************************************=
****************/=0D=0A+=0D=0A+/*=0D=0A+ * famfs_teardown()=0D=0A+ *=0D=0A=
+ * Deallocate famfs metadata for a fuse_conn=0D=0A+ */=0D=0A+void=0D=0A+=
famfs_teardown(struct fuse_conn *fc)=0D=0A+{=0D=0A+=09struct famfs_dax_de=
vlist *devlist __free(kfree) =3D NULL;=0D=0A+=09int i;=0D=0A+=0D=0A+=09/*=
=0D=0A+=09 * Detach the table under the same lock famfs_set_daxdev_err() =
takes, so=0D=0A+=09 * a notify_failure racing teardown either runs first =
against the live=0D=0A+=09 * table or observes dax_devlist =3D=3D NULL an=
d bails, rather than=0D=0A+=09 * dereferencing it after we clear it. The =
daxdev holders are dropped=0D=0A+=09 * below, after which no further noti=
fy_failure can arrive.=0D=0A+=09 */=0D=0A+=09scoped_guard(rwsem_write, &f=
c->famfs_devlist_sem) {=0D=0A+=09=09devlist =3D fc->dax_devlist;=0D=0A+=09=
=09fc->dax_devlist =3D NULL;=0D=0A+=09}=0D=0A+=0D=0A+=09if (!devlist)=0D=0A=
+=09=09return;=0D=0A+=0D=0A+=09if (!devlist->devlist)=0D=0A+=09=09return;=
=0D=0A+=0D=0A+=09/* Close & release all the daxdevs in our table */=0D=0A=
+=09for (i =3D 0; i < devlist->nslots; i++) {=0D=0A+=09=09struct famfs_da=
xdev *dd =3D &devlist->devlist[i];=0D=0A+=0D=0A+=09=09if (!dd->valid)=0D=0A=
+=09=09=09continue;=0D=0A+=0D=0A+=09=09/* Only call fs_put_dax if fs_dax_=
get succeeded */=0D=0A+=09=09if (dd->devp) {=0D=0A+=09=09=09if (!dd->dax_=
err)=0D=0A+=09=09=09=09fs_put_dax(dd->devp, fc);=0D=0A+=09=09=09put_dax(d=
d->devp);=0D=0A+=09=09}=0D=0A+=0D=0A+=09=09kfree(dd->name);=0D=0A+=09}=0D=
=0A+=09kfree(devlist->devlist);=0D=0A+}=0D=0A+=0D=0A+/* Allocate the daxd=
ev table on first use (idempotent via cmpxchg) */=0D=0A+static int famfs_=
devlist_alloc(struct fuse_conn *fc)=0D=0A+{=0D=0A+=09struct famfs_dax_dev=
list *devlist;=0D=0A+=0D=0A+=09if (fc->dax_devlist)=0D=0A+=09=09return 0;=
=0D=0A+=0D=0A+=09devlist =3D kcalloc(1, sizeof(*devlist), GFP_KERNEL);=0D=
=0A+=09if (!devlist)=0D=0A+=09=09return -ENOMEM;=0D=0A+=0D=0A+=09devlist-=
>nslots =3D MAX_DAXDEVS;=0D=0A+=09devlist->devlist =3D kcalloc(MAX_DAXDEV=
S, sizeof(struct famfs_daxdev),=0D=0A+=09=09=09=09   GFP_KERNEL);=0D=0A+=09=
if (!devlist->devlist) {=0D=0A+=09=09kfree(devlist);=0D=0A+=09=09return -=
ENOMEM;=0D=0A+=09}=0D=0A+=0D=0A+=09/* If another thread allocated it firs=
t, drop ours */=0D=0A+=09if (cmpxchg(&fc->dax_devlist, NULL, devlist) !=3D=
 NULL) {=0D=0A+=09=09kfree(devlist->devlist);=0D=0A+=09=09kfree(devlist);=
=0D=0A+=09}=0D=0A+=0D=0A+=09return 0;=0D=0A+}=0D=0A+=0D=0A+/*=0D=0A+ * fa=
mfs_install_daxdev() - exclusively acquire a resolved daxdev and publish=0D=
=0A+ * it in the table at @index. Shared by the GET_DAXDEV (pull) and=0D=0A=
+ * DAXDEV_OPEN (push) registration paths.=0D=0A+ *=0D=0A+ * Serializes w=
ith concurrent installers under famfs_devlist_sem and rechecks=0D=0A+ * -=
>valid. A daxdev is entered in the table only once it has been exclusivel=
y=0D=0A+ * acquired via fs_dax_get(); on failure the dax_dev_get() refere=
nce is=0D=0A+ * released and the slot is left invalid, so the referencing=
 fmap is rejected=0D=0A+ * rather than mapped without an exclusive holder=
=2E @name may be NULL (the push=0D=0A+ * path passes no pathname).=0D=0A+=
 */=0D=0A+static int famfs_install_daxdev(struct fuse_conn *fc, u64 index=
, dev_t devno,=0D=0A+=09=09=09=09const char *name)=0D=0A+{=0D=0A+=09struc=
t famfs_daxdev *daxdev;=0D=0A+=09int rc =3D 0;=0D=0A+=0D=0A+=09if (index =
>=3D fc->dax_devlist->nslots) {=0D=0A+=09=09pr_err("%s: index(%llu) >=3D =
nslots(%d)\n",=0D=0A+=09=09       __func__, index, fc->dax_devlist->nslot=
s);=0D=0A+=09=09return -EINVAL;=0D=0A+=09}=0D=0A+=0D=0A+=09scoped_guard(r=
wsem_write, &fc->famfs_devlist_sem) {=0D=0A+=09=09daxdev =3D &fc->dax_dev=
list->devlist[index];=0D=0A+=0D=0A+=09=09/* Installed already by a concur=
rent push/pull */=0D=0A+=09=09if (daxdev->valid)=0D=0A+=09=09=09return 0;=
=0D=0A+=0D=0A+=09=09/*=0D=0A+=09=09 * A prior attempt already determined =
this daxdev cannot be=0D=0A+=09=09 * exclusively acquired (see the fs_dax=
_get() failure handling=0D=0A+=09=09 * below). Don't thrash on GET_DAXDEV=
/fs_dax_get(); fail fast.=0D=0A+=09=09 */=0D=0A+=09=09if (daxdev->dax_err=
)=0D=0A+=09=09=09return -EIO;=0D=0A+=0D=0A+=09=09/*=0D=0A+=09=09 * Tempor=
ary: dax_dev_get() is the exported upstream lookup, but=0D=0A+=09=09 * un=
like dax_dev_find() it allocates for any dev_t and does not=0D=0A+=09=09 =
* reject non-dax devices. Restore dax_dev_find() (and that=0D=0A+=09=09 *=
 rejection) once it is upstream.=0D=0A+=09=09 */=0D=0A+=09=09daxdev->devp=
 =3D dax_dev_get(devno);=0D=0A+=09=09if (!daxdev->devp) {=0D=0A+=09=09=09=
pr_warn("%s: device %u:%u not found or not dax\n",=0D=0A+=09=09=09=09__fu=
nc__, MAJOR(devno), MINOR(devno));=0D=0A+=09=09=09return -ENODEV;=0D=0A+=09=
=09}=0D=0A+=0D=0A+=09=09rc =3D fs_dax_get(daxdev->devp, fc, &famfs_fuse_d=
ax_holder_ops);=0D=0A+=09=09if (rc) {=0D=0A+=09=09=09/*=0D=0A+=09=09=09 *=
 Distinguish a lost race from a real failure. -EBUSY=0D=0A+=09=09=09 * wi=
th the daxdev already held by *this* fuse_conn=0D=0A+=09=09=09 * means a =
concurrent acquire won and will publish the=0D=0A+=09=09=09 * slot valid:=
 not an error, and must not be cached as=0D=0A+=09=09=09 * dax_err. Any o=
ther failure (foreign holder, not a dax=0D=0A+=09=09=09 * device, wrong d=
river type) is permanent for this=0D=0A+=09=09=09 * connection, so record=
 dax_err to stop re-fetching and=0D=0A+=09=09=09 * re-acquiring it.=0D=0A=
+=09=09=09 */=0D=0A+=09=09=09if (!(rc =3D=3D -EBUSY && dax_holder(daxdev-=
>devp) =3D=3D fc)) {=0D=0A+=09=09=09=09pr_err("%s: fs_dax_get(%u:%u) fail=
ed rc=3D%d\n",=0D=0A+=09=09=09=09       __func__, MAJOR(devno), MINOR(dev=
no), rc);=0D=0A+=09=09=09=09daxdev->dax_err =3D true;=0D=0A+=09=09=09}=0D=
=0A+=09=09=09put_dax(daxdev->devp);=0D=0A+=09=09=09daxdev->devp =3D NULL;=
=0D=0A+=09=09=09return rc;=0D=0A+=09=09}=0D=0A+=0D=0A+=09=09daxdev->devno=
 =3D devno;=0D=0A+=09=09if (name) {=0D=0A+=09=09=09daxdev->name =3D kstrd=
up(name, GFP_KERNEL);=0D=0A+=09=09=09if (!daxdev->name) {=0D=0A+=09=09=09=
=09fs_put_dax(daxdev->devp, fc);=0D=0A+=09=09=09=09put_dax(daxdev->devp);=
=0D=0A+=09=09=09=09daxdev->devp =3D NULL;=0D=0A+=09=09=09=09return -ENOME=
M;=0D=0A+=09=09=09}=0D=0A+=09=09}=0D=0A+=0D=0A+=09=09wmb(); /* All other =
fields must be visible before valid */=0D=0A+=09=09daxdev->valid =3D 1;=0D=
=0A+=09}=0D=0A+=0D=0A+=09return 0;=0D=0A+}=0D=0A+=0D=0A+/**=0D=0A+ * famf=
s_daxdev_open() - Register a daxdev via FUSE_DEV_IOC_DAXDEV_OPEN=0D=0A+ *=
 @fc:   fuse_conn=0D=0A+ * @map:  fuse_backing_map; @map->fd is an fd to =
the devdax device and=0D=0A+ *        @map->daxdev_index is the (cluster-=
invariant) famfs index.=0D=0A+ *=0D=0A+ * The server pushes a daxdev to t=
he kernel by reference (an fd), rather than=0D=0A+ * the kernel pulling i=
t by name via GET_DAXDEV. The resolved daxdev is=0D=0A+ * exclusively acq=
uired and entered in the table at @map->daxdev_index.=0D=0A+ *=0D=0A+ * R=
eturn: 0=3Dsuccess=0D=0A+ *         -errno=3Dfailure=0D=0A+ */=0D=0A+int =
famfs_daxdev_open(struct fuse_conn *fc, struct fuse_backing_map *map)=0D=0A=
+{=0D=0A+=09struct inode *inode;=0D=0A+=09struct file *file;=0D=0A+=09dev=
_t devno;=0D=0A+=09int rc;=0D=0A+=0D=0A+=09/* Only fs-dax (famfs) mode ac=
cepts daxdev registration */=0D=0A+=09if (!fc->famfs_iomap)=0D=0A+=09=09r=
eturn -EOPNOTSUPP;=0D=0A+=0D=0A+=09file =3D fget(map->fd);=0D=0A+=09if (!=
file)=0D=0A+=09=09return -EBADF;=0D=0A+=0D=0A+=09inode =3D file_inode(fil=
e);=0D=0A+=09if (!S_ISCHR(inode->i_mode)) {=0D=0A+=09=09fput(file);=0D=0A=
+=09=09return -EINVAL;=0D=0A+=09}=0D=0A+=09devno =3D inode->i_rdev;=0D=0A=
+=09fput(file);=0D=0A+=0D=0A+=09rc =3D famfs_devlist_alloc(fc);=0D=0A+=09=
if (rc)=0D=0A+=09=09return rc;=0D=0A+=0D=0A+=09rc =3D famfs_install_daxde=
v(fc, map->daxdev_index, devno, NULL);=0D=0A+=09if (rc)=0D=0A+=09=09pr_er=
r("%s: failed to install daxdev\n", __func__);=0D=0A+=0D=0A+=09return rc;=
=0D=0A+}=0D=0A+=0D=0A+/**=0D=0A+ * famfs_check_daxdev_table() - Verify an=
 fmap's referenced daxdevs are installed=0D=0A+ * @fm:   fuse_mount=0D=0A=
+ * @meta: famfs_file_meta, in-memory format, built from a GET_FMAP respo=
nse=0D=0A+ *=0D=0A+ * Called for each new file fmap. Every daxdev the fma=
p references must already=0D=0A+ * be installed in the table, having been=
 pushed in via FUSE_DEV_IOC_DAXDEV_OPEN=0D=0A+ * before any file that use=
s it is accessed. If any referenced daxdev is not=0D=0A+ * present, the f=
map is rejected so the file is never mapped against a daxdev=0D=0A+ * tha=
t has no exclusive holder.=0D=0A+ *=0D=0A+ * Return: 0=3Dsuccess (all ref=
erenced daxdevs present)=0D=0A+ *         <0=3Da referenced daxdev is mis=
sing from the table=0D=0A+ */=0D=0A+static int=0D=0A+famfs_check_daxdev_t=
able(=0D=0A+=09struct fuse_mount *fm,=0D=0A+=09const struct famfs_file_me=
ta *meta)=0D=0A+{=0D=0A+=09struct fuse_conn *fc =3D fm->fc;=0D=0A+=09int =
nmissing =3D 0;=0D=0A+=09int err;=0D=0A+=0D=0A+=09err =3D famfs_devlist_a=
lloc(fc);=0D=0A+=09if (err)=0D=0A+=09=09return err;=0D=0A+=0D=0A+=09/* Co=
unt missing daxdevs while holding the reader lock */=0D=0A+=09scoped_guar=
d(rwsem_read, &fc->famfs_devlist_sem) {=0D=0A+=09=09unsigned long i;=0D=0A=
+=0D=0A+=09=09for_each_set_bit(i, (unsigned long *)&meta->dev_bitmap,=0D=0A=
+=09=09=09=09 MAX_DAXDEVS) {=0D=0A+=09=09=09struct famfs_daxdev *dd =3D &=
fc->dax_devlist->devlist[i];=0D=0A+=0D=0A+=09=09=09/*=0D=0A+=09=09=09 * S=
kip daxdevs already installed (valid) or already=0D=0A+=09=09=09 * known =
to be unusable (dax_err). Re-fetching either=0D=0A+=09=09=09 * just thras=
hes on GET_DAXDEV and fs_dax_get().=0D=0A+=09=09=09 */=0D=0A+=09=09=09if =
(!dd->valid && !dd->dax_err)=0D=0A+=09=09=09=09nmissing++;=0D=0A+=09=09}=0D=
=0A+=09}=0D=0A+=0D=0A+=09if (nmissing > 0) {=0D=0A+=09=09/* this file ref=
erenced at least one daxdev that is not in=0D=0A+=09=09 * the table. Daxd=
evs must be known before any file that=0D=0A+=09=09 * uses them is access=
ed=0D=0A+=09=09 */=0D=0A+=09=09pr_err("%s: %d missing daxdev(s)\n", __fun=
c__, nmissing);=0D=0A+=09=09return -ENODEV;=0D=0A+=09}=0D=0A+=0D=0A+=09re=
turn 0;=0D=0A+}=0D=0A+=0D=0A+static void=0D=0A+famfs_set_daxdev_err(=0D=0A=
+=09struct fuse_conn *fc,=0D=0A+=09struct dax_device *dax_devp)=0D=0A+{=0D=
=0A+=09int i;=0D=0A+=0D=0A+=09/*=0D=0A+=09 * Search the list by dax_devp =
under the write lock: we set dd->error,=0D=0A+=09 * and it serializes aga=
inst famfs_teardown() clearing the table.=0D=0A+=09 */=0D=0A+=09scoped_gu=
ard(rwsem_write, &fc->famfs_devlist_sem) {=0D=0A+=09=09if (!fc->dax_devli=
st)=0D=0A+=09=09=09return;=0D=0A+=09=09for (i =3D 0; i < fc->dax_devlist-=
>nslots; i++) {=0D=0A+=09=09=09if (fc->dax_devlist->devlist[i].valid) {=0D=
=0A+=09=09=09=09struct famfs_daxdev *dd;=0D=0A+=0D=0A+=09=09=09=09dd =3D =
&fc->dax_devlist->devlist[i];=0D=0A+=09=09=09=09if (dd->devp !=3D dax_dev=
p)=0D=0A+=09=09=09=09=09continue;=0D=0A+=0D=0A+=09=09=09=09dd->error =3D =
true;=0D=0A+=0D=0A+=09=09=09=09pr_err("%s: memory error on daxdev %s (%d)=
\n",=0D=0A+=09=09=09=09       __func__, dd->name, i);=0D=0A+=09=09=09=09r=
eturn;=0D=0A+=09=09=09}=0D=0A+=09=09}=0D=0A+=09}=0D=0A+=09pr_err("%s: mem=
ory err on unrecognized daxdev\n", __func__);=0D=0A+}=0D=0A=20=0D=0A /***=
************************************************************************/=
=0D=0A=20=0D=0A@@ -228,6 +554,10 @@ famfs_file_init_dax(=0D=0A =09if (rc)=
=0D=0A =09=09goto errout;=0D=0A=20=0D=0A+=09/* Make sure this fmap doesn'=
t reference any unknown daxdevs */=0D=0A+=09if (famfs_check_daxdev_table(=
fm, meta))=0D=0A+=09=09meta->error =3D true;=0D=0A+=0D=0A =09/* Publish t=
he famfs metadata on fi->famfs_meta */=0D=0A =09inode_lock(inode);=0D=0A=20=
=0D=0Adiff --git a/fs/fuse/famfs_kfmap.h b/fs/fuse/famfs_kfmap.h=0D=0Aind=
ex d87b065e8ac8..6b78e78325c8 100644=0D=0A--- a/fs/fuse/famfs_kfmap.h=0D=0A=
+++ b/fs/fuse/famfs_kfmap.h=0D=0A@@ -61,4 +61,31 @@ struct famfs_file_met=
a {=0D=0A =09struct famfs_meta_simple_ext  *se;=0D=0A };=0D=0A=20=0D=0A+/=
*=0D=0A+ * famfs_daxdev - tracking struct for a daxdev within a famfs fil=
e system=0D=0A+ *=0D=0A+ * This is the in-memory daxdev metadata that is =
populated by parsing=0D=0A+ * the responses to GET_FMAP messages=0D=0A+ *=
/=0D=0A+struct famfs_daxdev {=0D=0A+=09/* Include dev uuid=3F */=0D=0A+=09=
bool valid;=0D=0A+=09bool error; /* Dax has reported a memory error (prob=
ably poison) */=0D=0A+=09bool dax_err; /* fs_dax_get() failed */=0D=0A+=09=
dev_t devno;=0D=0A+=09struct dax_device *devp;=0D=0A+=09char *name;=0D=0A=
+};=0D=0A+=0D=0A+#define MAX_DAXDEVS 24=0D=0A+=0D=0A+/*=0D=0A+ * famfs_da=
x_devlist - list of famfs_daxdev's=0D=0A+ */=0D=0A+struct famfs_dax_devli=
st {=0D=0A+=09int nslots;=0D=0A+=09int ndevs;=0D=0A+=09struct famfs_daxde=
v *devlist;=0D=0A+};=0D=0A+=0D=0A #endif /* FAMFS_KFMAP_H */=0D=0Adiff --=
git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h=0D=0Aindex 46a7040b38dc..5394aa=
e9dbac 100644=0D=0A--- a/fs/fuse/fuse_i.h=0D=0A+++ b/fs/fuse/fuse_i.h=0D=0A=
@@ -773,6 +773,11 @@ struct fuse_conn {=0D=0A =09/** @backing_files_map: =
IDR for backing files ids */=0D=0A =09struct idr backing_files_map;=0D=0A=
 #endif=0D=0A+=0D=0A+#if IS_ENABLED(CONFIG_FUSE_FAMFS_DAX)=0D=0A+=09struc=
t rw_semaphore famfs_devlist_sem;=0D=0A+=09struct famfs_dax_devlist *dax_=
devlist;=0D=0A+#endif=0D=0A };=0D=0A=20=0D=0A /*=0D=0A@@ -1352,6 +1357,8 =
@@ int famfs_file_init_dax(struct fuse_mount *fm,=0D=0A =09=09=09size_t f=
map_size);=0D=0A void __famfs_meta_free(void *map);=0D=0A=20=0D=0A+void f=
amfs_teardown(struct fuse_conn *fc);=0D=0A+=0D=0A /* Set fi->famfs_meta =3D=
 NULL regardless of prior value */=0D=0A static inline void famfs_meta_in=
it(struct fuse_inode *fi)=0D=0A {=0D=0A@@ -1373,6 +1380,11 @@ static inli=
ne void famfs_meta_free(struct fuse_inode *fi)=0D=0A =09}=0D=0A }=0D=0A=20=
=0D=0A+static inline void famfs_init_devlist_sem(struct fuse_conn *fc)=0D=
=0A+{=0D=0A+=09init_rwsem(&fc->famfs_devlist_sem);=0D=0A+}=0D=0A+=0D=0A s=
tatic inline int fuse_file_famfs(struct fuse_inode *fi)=0D=0A {=0D=0A =09=
return (READ_ONCE(fi->famfs_meta) !=3D NULL);=0D=0A@@ -1380,8 +1392,14 @@=
 static inline int fuse_file_famfs(struct fuse_inode *fi)=0D=0A=20=0D=0A =
int fuse_get_fmap(struct fuse_mount *fm, struct inode *inode);=0D=0A=20=0D=
=0A+int famfs_daxdev_open(struct fuse_conn *fc, struct fuse_backing_map *=
map);=0D=0A+=0D=0A #else /* !CONFIG_FUSE_FAMFS_DAX */=0D=0A=20=0D=0A+stat=
ic inline void famfs_teardown(struct fuse_conn *fc)=0D=0A+{=0D=0A+}=0D=0A=
+=0D=0A static inline struct fuse_backing *famfs_meta_set(struct fuse_ino=
de *fi,=0D=0A =09=09=09=09=09=09  void *meta)=0D=0A {=0D=0A@@ -1392,6 +14=
10,10 @@ static inline void famfs_meta_free(struct fuse_inode *fi)=0D=0A =
{=0D=0A }=0D=0A=20=0D=0A+static inline void famfs_init_devlist_sem(struct=
 fuse_conn *fc)=0D=0A+{=0D=0A+}=0D=0A+=0D=0A static inline int fuse_file_=
famfs(struct fuse_inode *fi)=0D=0A {=0D=0A =09return 0;=0D=0A@@ -1403,6 +=
1425,12 @@ fuse_get_fmap(struct fuse_mount *fm, struct inode *inode)=0D=0A=
 =09return 0;=0D=0A }=0D=0A=20=0D=0A+static inline int=0D=0A+famfs_daxdev=
_open(struct fuse_conn *fc, struct fuse_backing_map *map)=0D=0A+{=0D=0A+=09=
return -EOPNOTSUPP;=0D=0A+}=0D=0A+=0D=0A #endif /* CONFIG_FUSE_FAMFS_DAX =
*/=0D=0A=20=0D=0A #endif /* _FS_FUSE_I_H */=0D=0Adiff --git a/fs/fuse/ino=
de.c b/fs/fuse/inode.c=0D=0Aindex 78ffc5fd50d0..9fc37015fb11 100644=0D=0A=
--- a/fs/fuse/inode.c=0D=0A+++ b/fs/fuse/inode.c=0D=0A@@ -1021,6 +1021,9 =
@@ void fuse_conn_put(struct fuse_conn *fc)=0D=0A =09=09WARN_ON(atomic_re=
ad(&bucket->count) !=3D 1);=0D=0A =09=09kfree(bucket);=0D=0A =09}=0D=0A+=09=
if (IS_ENABLED(CONFIG_FUSE_FAMFS_DAX))=0D=0A+=09=09famfs_teardown(fc);=0D=
=0A+=0D=0A =09if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH))=0D=0A =09=09fuse_b=
acking_files_free(fc);=0D=0A =09call_rcu(&fc->rcu, delayed_release);=0D=0A=
@@ -1427,8 +1430,10 @@ static void process_init_reply(struct fuse_args *a=
rgs, int error)=0D=0A =09=09=09=09u64 in_flags =3D FIELD_PREP(GENMASK_ULL=
(63, 32), ia->in.flags2)=0D=0A =09=09=09=09=09=09| ia->in.flags;=0D=0A=20=
=0D=0A-=09=09=09=09if (in_flags & FUSE_DAX_FMAP)=0D=0A+=09=09=09=09if (in=
_flags & FUSE_DAX_FMAP) {=0D=0A+=09=09=09=09=09famfs_init_devlist_sem(fc)=
;=0D=0A =09=09=09=09=09fc->famfs_iomap =3D 1;=0D=0A+=09=09=09=09}=0D=0A =09=
=09=09}=0D=0A =09=09} else {=0D=0A =09=09=09ra_pages =3D fc->max_read / P=
AGE_SIZE;=0D=0Adiff --git a/include/uapi/linux/fuse.h b/include/uapi/linu=
x/fuse.h=0D=0Aindex 4b84a58a8f1c..a143e6818416 100644=0D=0A--- a/include/=
uapi/linux/fuse.h=0D=0A+++ b/include/uapi/linux/fuse.h=0D=0A@@ -1143,7 +1=
143,10 @@ struct fuse_notify_prune_out {=0D=0A struct fuse_backing_map {=0D=
=0A =09int32_t=09=09fd;=0D=0A =09uint32_t=09flags;=0D=0A-=09uint64_t=09pa=
dding;=0D=0A+=09union {=0D=0A+=09=09uint64_t=09padding;=0D=0A+=09=09uint6=
4_t=09daxdev_index;=09/* FUSE_DEV_IOC_DAXDEV_OPEN */=0D=0A+=09};=0D=0A };=
=0D=0A=20=0D=0A /* Device ioctls: */=0D=0A@@ -1153,6 +1156,8 @@ struct fu=
se_backing_map {=0D=0A =09=09=09=09=09     struct fuse_backing_map)=0D=0A=
 #define FUSE_DEV_IOC_BACKING_CLOSE=09_IOW(FUSE_DEV_IOC_MAGIC, 2, uint32_=
t)=0D=0A #define FUSE_DEV_IOC_SYNC_INIT=09=09_IO(FUSE_DEV_IOC_MAGIC, 3)=0D=
=0A+#define FUSE_DEV_IOC_DAXDEV_OPEN=09_IOW(FUSE_DEV_IOC_MAGIC, 4, \=0D=0A=
+=09=09=09=09=09     struct fuse_backing_map)=0D=0A=20=0D=0A struct fuse_=
lseek_in {=0D=0A =09uint64_t=09fh;=0D=0A--=20=0D=0A2.53.0=0D=0A=0D=0A