[PATCH V12 03/12] famfs: Add daxdev table and dax notify_failure support

John Groves <[email protected]> Mon, 3 Aug 2026 02:28:47 +0000
Newsgroups dev.linux.lists.nvdimm,dev.linux.lists.fuse-devel,org.kernel.vger.linux-cxl,org.kernel.vger.linux-doc,org.kernel.vger.linux-fsdevel,org.kernel.vger.linux-kernel
Message-ID <0100019fc573c7cd-d37cbc1c-7687-4b05-99e7-7d3624087b98-000000@email.amazonses.com>
From: John Groves <[email protected]>=0D=0A=0D=0AFamfs file systems can spa=
n multiple dax devices, and daxdevs are stored=0D=0Ain the daxdev_table. =
This adds the basic table structure, primtives and=0D=0Aserialization cod=
e. Famfs file extents reference daxdevs by index, which=0D=0Ais a cluster=
 invariant maintained by user space.=0D=0A=0D=0AWe also add dax_holder_op=
erations and a notify_failure handler, which=0D=0Ais necessary to properl=
y "open" a famfs-mode daxdev.=0D=0A=0D=0ASigned-off-by: John Groves <john=
@groves.net>=0D=0A---=0D=0A fs/famfs/famfs_inode.c    | 234 +++++++++++++=
+++++++++++++++++++++++++=0D=0A fs/famfs/famfs_internal.h |  46 ++++++++=0D=
=0A 2 files changed, 280 insertions(+)=0D=0A=0D=0Adiff --git a/fs/famfs/f=
amfs_inode.c b/fs/famfs/famfs_inode.c=0D=0Aindex c299a90912a5..ad71e5e7a8=
e3 100644=0D=0A--- a/fs/famfs/famfs_inode.c=0D=0A+++ b/fs/famfs/famfs_ino=
de.c=0D=0A@@ -75,6 +75,225 @@ static struct inode *famfs_get_inode(=0D=0A=
 /*=0D=0A  * famfs dax_operations (for famfs-mode dax)=0D=0A  */=0D=0A+st=
atic void famfs_set_daxdev_err(struct famfs_fs_info *fsi,=0D=0A+=09=09=09=
=09 struct dax_device *dax_devp);=0D=0A+=0D=0A+static int=0D=0A+famfs_dax=
_notify_failure(=0D=0A+=09=09struct dax_device *dax_dev, u64 offset,=0D=0A=
+=09=09u64 len, int mf_flags)=0D=0A+{=0D=0A+=09struct super_block *sb =3D=
 dax_holder(dax_dev);=0D=0A+=09struct famfs_fs_info *fsi =3D sb->s_fs_inf=
o;=0D=0A+=0D=0A+=09pr_err("%s: offset=3D%lld len=3D%llu flags=3D%x\n", __=
func__,=0D=0A+=09       offset, len, mf_flags);=0D=0A+=0D=0A+=09/*=0D=0A+=
=09 * Record the error on the specific daxdev and, near-term, shut the=0D=
=0A+=09 * mount down: famfs_set_daxdev_err() also sets fsi->deverror so=0D=
=0A+=09 * subsequent famfs operations fail. The resolver's per-daxdev=0D=0A=
+=09 * famfs_dax_err() check remains and can make this finer later.=0D=0A=
+=09 */=0D=0A+=09famfs_set_daxdev_err(fsi, dax_dev);=0D=0A+=0D=0A+=09retu=
rn 0;=0D=0A+}=0D=0A+=0D=0A+static const struct dax_holder_operations famf=
s_dax_holder_ops =3D {=0D=0A+=09.notify_failure=09=09=3D famfs_dax_notify=
_failure,=0D=0A+};=0D=0A+=0D=0A+/*=0D=0A+ * Allocate the daxdev table on =
first use (idempotent via cmpxchg).=0D=0A+ */=0D=0A+int famfs_devlist_all=
oc(struct famfs_fs_info *fsi)=0D=0A+{=0D=0A+=09struct famfs_dax_devlist *=
devlist;=0D=0A+=0D=0A+=09if (fsi->dax_devlist)=0D=0A+=09=09return 0;=0D=0A=
+=0D=0A+=09devlist =3D kcalloc(1, sizeof(*devlist), GFP_KERNEL);=0D=0A+=09=
if (!devlist)=0D=0A+=09=09return -ENOMEM;=0D=0A+=0D=0A+=09devlist->nslots=
 =3D FAMFS_MAX_DAXDEVS;=0D=0A+=09devlist->devlist =3D kcalloc(FAMFS_MAX_D=
AXDEVS, sizeof(struct famfs_daxdev),=0D=0A+=09=09=09=09   GFP_KERNEL);=0D=
=0A+=09if (!devlist->devlist) {=0D=0A+=09=09kfree(devlist);=0D=0A+=09=09r=
eturn -ENOMEM;=0D=0A+=09}=0D=0A+=0D=0A+=09/* If another thread allocated =
it first, drop ours */=0D=0A+=09if (cmpxchg(&fsi->dax_devlist, NULL, devl=
ist) !=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+ * famfs_install_daxdev() - exclusively acquire a resolved daxdev and=
 publish=0D=0A+ * it in the table at @index. Slot 0 is the mount primary;=
 slots 1..n come from=0D=0A+ * the daxdev-open ioctl.=0D=0A+ *=0D=0A+ * S=
erializes with concurrent installers under devlist_sem and rechecks=0D=0A=
+ * ->valid, so re-registering an already-installed slot is idempotent. A=
 daxdev=0D=0A+ * is entered in the table only once it has been exclusivel=
y acquired via=0D=0A+ * fs_dax_get() (with the super_block as the holder)=
; on failure the=0D=0A+ * dax_dev_find() reference is released and the sl=
ot is left invalid. @name may=0D=0A+ * be NULL (the ioctl path passes no =
pathname).=0D=0A+ */=0D=0A+int famfs_install_daxdev(=0D=0A+=09=09struct f=
amfs_fs_info *fsi,=0D=0A+=09=09struct super_block *sb,=0D=0A+=09=09u64 in=
dex,=0D=0A+=09=09dev_t devno,=0D=0A+=09=09const char *name)=0D=0A+{=0D=0A=
+=09struct famfs_daxdev *daxdev;=0D=0A+=09int rc =3D 0;=0D=0A+=0D=0A+=09i=
f (index >=3D fsi->dax_devlist->nslots) {=0D=0A+=09=09pr_debug("%s: index=
(%llu) >=3D nslots(%d)\n",=0D=0A+=09=09       __func__, index, fsi->dax_d=
evlist->nslots);=0D=0A+=09=09return -EINVAL;=0D=0A+=09}=0D=0A+=0D=0A+=09s=
coped_guard(rwsem_write, &fsi->devlist_sem) {=0D=0A+=09=09daxdev =3D &fsi=
->dax_devlist->devlist[index];=0D=0A+=0D=0A+=09=09/* Installed already by=
 a concurrent (or repeated) open */=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 a=
lready determined this daxdev cannot be=0D=0A+=09=09 * exclusively acquir=
ed (see the fs_dax_get() failure handling=0D=0A+=09=09 * below). Don't th=
rash on 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=09daxdev->devp =3D =
dax_dev_find(devno);=0D=0A+=09=09if (!daxdev->devp) {=0D=0A+=09=09=09pr_d=
ebug("%s: device %u:%u not found or not dax\n",=0D=0A+=09=09=09=09__func_=
_, 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, sb, &famfs_dax_holde=
r_ops);=0D=0A+=09=09if (rc) {=0D=0A+=09=09=09/*=0D=0A+=09=09=09 * Disting=
uish a lost race from a real failure. -EBUSY=0D=0A+=09=09=09 * with the d=
axdev already held by *this* super_block=0D=0A+=09=09=09 * means a concur=
rent acquire won and will publish the=0D=0A+=09=09=09 * slot valid: not a=
n error, and must not be cached as=0D=0A+=09=09=09 * dax_err. Any other f=
ailure is permanent for this=0D=0A+=09=09=09 * mount, so record dax_err t=
o stop re-acquiring it.=0D=0A+=09=09=09 */=0D=0A+=09=09=09if (!(rc =3D=3D=
 -EBUSY && dax_holder(daxdev->devp) =3D=3D sb)) {=0D=0A+=09=09=09=09pr_de=
bug("%s: fs_dax_get(%u:%u) failed rc=3D%d\n",=0D=0A+=09=09=09=09       __=
func__, MAJOR(devno), MINOR(devno), rc);=0D=0A+=09=09=09=09daxdev->dax_er=
r =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 kstrdup(name, GFP_KERNEL);=0D=0A+=09=09=09if (!dax=
dev->name) {=0D=0A+=09=09=09=09fs_put_dax(daxdev->devp, sb);=0D=0A+=09=09=
=09=09put_dax(daxdev->devp);=0D=0A+=09=09=09=09daxdev->devp =3D NULL;=0D=0A=
+=09=09=09=09return -ENOMEM;=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+ * Release every daxdev in the table and free it. Detach=
 the table under=0D=0A+ * devlist_sem so a notify_failure racing teardown=
 either runs first against=0D=0A+ * the live table or observes dax_devlis=
t =3D=3D NULL and bails.=0D=0A+ */=0D=0A+static void famfs_devlist_free(=0D=
=0A+=09=09=09struct famfs_fs_info *fsi,=0D=0A+=09=09=09struct super_block=
 *sb)=0D=0A+{=0D=0A+=09struct famfs_dax_devlist *devlist __free(kfree) =3D=
 NULL;=0D=0A+=09int i;=0D=0A+=0D=0A+=09scoped_guard(rwsem_write, &fsi->de=
vlist_sem) {=0D=0A+=09=09devlist =3D fsi->dax_devlist;=0D=0A+=09=09fsi->d=
ax_devlist =3D NULL;=0D=0A+=09}=0D=0A+=0D=0A+=09if (!devlist || !devlist-=
>devlist)=0D=0A+=09=09return;=0D=0A+=0D=0A+=09for (i =3D 0; i < devlist->=
nslots; i++) {=0D=0A+=09=09struct famfs_daxdev *dd =3D &devlist->devlist[=
i];=0D=0A+=0D=0A+=09=09if (!dd->valid)=0D=0A+=09=09=09continue;=0D=0A+=0D=
=0A+=09=09if (dd->devp) {=0D=0A+=09=09=09if (!dd->dax_err)=0D=0A+=09=09=09=
=09fs_put_dax(dd->devp, sb);=0D=0A+=09=09=09put_dax(dd->devp);=0D=0A+=09=09=
}=0D=0A+=09=09kfree(dd->name);=0D=0A+=09}=0D=0A+=09kfree(devlist->devlist=
);=0D=0A+}=0D=0A+=0D=0A+/*=0D=0A+ * Record a memory error on the daxdev m=
atching @dax_devp. Searches the table=0D=0A+ * under the write lock (whic=
h serializes against famfs_devlist_free()).=0D=0A+ */=0D=0A+static void f=
amfs_set_daxdev_err(=0D=0A+=09=09=09struct famfs_fs_info *fsi,=0D=0A+=09=09=
=09struct dax_device *dax_devp)=0D=0A+{=0D=0A+=09int i;=0D=0A+=0D=0A+=09s=
coped_guard(rwsem_write, &fsi->devlist_sem) {=0D=0A+=09=09if (!fsi->dax_d=
evlist)=0D=0A+=09=09=09return;=0D=0A+=09=09for (i =3D 0; i < fsi->dax_dev=
list->nslots; i++) {=0D=0A+=09=09=09struct famfs_daxdev *dd =3D &fsi->dax=
_devlist->devlist[i];=0D=0A+=0D=0A+=09=09=09if (!dd->valid || dd->devp !=3D=
 dax_devp)=0D=0A+=09=09=09=09continue;=0D=0A+=0D=0A+=09=09=09dd->error =3D=
 true;=0D=0A+=09=09=09/*=0D=0A+=09=09=09 * Near-term policy: any daxdev m=
emory error shuts down=0D=0A+=09=09=09 * the whole mount. Finer per-daxde=
v handling (via=0D=0A+=09=09=09 * famfs_dax_err() in the resolver) alread=
y exists and=0D=0A+=09=09=09 * can supersede this later.=0D=0A+=09=09=09 =
*/=0D=0A+=09=09=09fsi->deverror =3D true;=0D=0A+=09=09=09pr_err("%s: memo=
ry error on daxdev %s (%d)\n",=0D=0A+=09=09=09       __func__, dd->name, =
i);=0D=0A+=09=09=09return;=0D=0A+=09=09}=0D=0A+=09}=0D=0A+=09pr_debug("%s=
: memory error on unrecognized daxdev\n", __func__);=0D=0A+}=0D=0A+=0D=0A=
 /***********************************************************************=
******=0D=0A  * fs_context_operations=0D=0A  */=0D=0A@@ -158,6 +377,18 @@=
 famfs_get_tree(struct fs_context *fc)=0D=0A =09=09famfs_fill_super(sb, f=
c);=0D=0A =09}=0D=0A=20=0D=0A+=09/* Install the primary daxdev (from the =
mount device) at slot 0 */=0D=0A+=09err =3D famfs_devlist_alloc(fsi);=0D=0A=
+=09if (err)=0D=0A+=09=09goto deactivate_out;=0D=0A+=0D=0A+=09err =3D fam=
fs_install_daxdev(fsi, sb, 0, daxdevno, fc->source);=0D=0A+=09if (err) {=0D=
=0A+=09=09pr_err("%s: failed to install primary daxdev %s\n",=0D=0A+=09=09=
       __func__, fc->source);=0D=0A+=09=09goto deactivate_out;=0D=0A+=09}=
=0D=0A+=0D=0A =09inode =3D famfs_get_inode(sb, NULL, S_IFDIR | fsi->mount=
_opts.mode, 0);=0D=0A =09sb->s_root =3D d_make_root(inode);=0D=0A =09if (=
!sb->s_root) {=0D=0A@@ -241,6 +472,7 @@ static int famfs_init_fs_context(=
struct fs_context *fc)=0D=0A =09if (!fsi)=0D=0A =09=09return -ENOMEM;=0D=0A=
=20=0D=0A+=09init_rwsem(&fsi->devlist_sem);=0D=0A =09fsi->mount_opts.mode=
 =3D FAMFS_DEFAULT_MODE;=0D=0A =09fc->s_fs_info        =3D fsi;=0D=0A =09=
fc->ops              =3D &famfs_context_ops;=0D=0A@@ -251,6 +483,8 @@ sta=
tic void famfs_kill_sb(struct super_block *sb)=0D=0A {=0D=0A =09struct fa=
mfs_fs_info *fsi =3D sb->s_fs_info;=0D=0A=20=0D=0A+=09famfs_devlist_free(=
fsi, sb);=0D=0A+=0D=0A =09kill_char_super(sb);=0D=0A=20=0D=0A =09kfree(fs=
i);=0D=0Adiff --git a/fs/famfs/famfs_internal.h b/fs/famfs/famfs_internal=
=2Eh=0D=0Aindex 6f481d79ed88..ebb9c499cf69 100644=0D=0A--- a/fs/famfs/fam=
fs_internal.h=0D=0A+++ b/fs/famfs/famfs_internal.h=0D=0A@@ -11,22 +11,68 =
@@=0D=0A #ifndef FAMFS_INTERNAL_H=0D=0A #define FAMFS_INTERNAL_H=0D=0A=20=
=0D=0A+#include <linux/rwsem.h>=0D=0A+#include <linux/bits.h>=0D=0A+#incl=
ude <linux/build_bug.h>=0D=0A+=0D=0A struct famfs_mount_opts {=0D=0A =09u=
mode_t mode;=0D=0A };=0D=0A=20=0D=0A+/*=0D=0A+ * famfs_daxdev - one entry=
 in the per-superblock daxdev table=0D=0A+ *=0D=0A+ * @valid:   slot is p=
opulated and the daxdev has been exclusively acquired=0D=0A+ * @error:   =
dax reported a memory error (probably poison) via notify_failure=0D=0A+ *=
 @dax_err: fs_dax_get() failed for this daxdev=0D=0A+ * @devno:   dax dev=
ice dev_t=0D=0A+ * @devp:    the acquired dax_device=0D=0A+ * @name:    d=
ax device path (may be NULL for ioctl-registered daxdevs)=0D=0A+ */=0D=0A=
+struct famfs_daxdev {=0D=0A+=09bool valid;=0D=0A+=09bool error;=0D=0A+=09=
bool dax_err;=0D=0A+=09dev_t devno;=0D=0A+=09struct dax_device *devp;=0D=0A=
+=09char *name;=0D=0A+};=0D=0A+=0D=0A+/*=0D=0A+ * The daxdev index space =
(and thus this table) is capped at 64 so the set of=0D=0A+ * daxdev indic=
es referenced by a file's fmap fits in a u64 bitmap.=0D=0A+ */=0D=0A+#def=
ine FAMFS_MAX_DAXDEVS 64=0D=0A+static_assert(BITS_PER_TYPE(u64) >=3D FAMF=
S_MAX_DAXDEVS);=0D=0A+=0D=0A+/*=0D=0A+ * famfs_dax_devlist - the per-supe=
rblock table of famfs_daxdev's. Slot 0 is=0D=0A+ * the primary daxdev sup=
plied at mount; slots 1..n are registered via ioctl.=0D=0A+ */=0D=0A+stru=
ct famfs_dax_devlist {=0D=0A+=09int nslots;=0D=0A+=09struct famfs_daxdev =
*devlist;=0D=0A+};=0D=0A+=0D=0A /**=0D=0A  * @famfs_fs_info=0D=0A  *=0D=0A=
  * @mount_opts:  The mount options=0D=0A  * @deverror:    True if the da=
x device has called our notify_failure entry=0D=0A  *               point=
, or if other "shutdown" conditions exist=0D=0A+ * @dax_devlist: Table of=
 backing daxdevs (slot 0 is the mount primary)=0D=0A+ * @devlist_sem: Ser=
ializes installs into, and teardown of, @dax_devlist=0D=0A  */=0D=0A stru=
ct famfs_fs_info {=0D=0A =09struct famfs_mount_opts   mount_opts;=0D=0A =09=
bool                      deverror;=0D=0A+=09struct famfs_dax_devlist *da=
x_devlist;=0D=0A+=09struct rw_semaphore       devlist_sem;=0D=0A };=0D=0A=
=20=0D=0A int lookup_daxdev(const char *pathname, dev_t *devno);=0D=0A+in=
t famfs_devlist_alloc(struct famfs_fs_info *fsi);=0D=0A+int famfs_install=
_daxdev(struct famfs_fs_info *fsi, struct super_block *sb,=0D=0A+=09=09=09=
 u64 index, dev_t devno, const char *name);=0D=0A=20=0D=0A #endif /* FAMF=
S_INTERNAL_H */=0D=0A--=20=0D=0A2.53.0=0D=0A=0D=0A