From: John Groves <[email protected]>

Start building up from the famfs module operations. This commit
includes the following:

* Register as a file system
* Parse mount parameters
* Allocate or find (and initialize) a superblock via famfs_get_tree()
* Lookup the host dax device, and bail if it's in use (or not dax)
* Add Kconfig and Makefile misc to build famfs
* Add FAMFS_SUPER_MAGIC to include/uapi/linux/magic.h
* Add export of fs/namei.c:may_open_dev(), which famfs needs to call
* Update MAINTAINERS file for the fs/famfs/ path
* module_init uses the MODULE_INCOMPLETE guard to protect bisect-
  ability by preventing registration as a file system until the famfs
  code is complete

Add and export fs/super.c:kill_char_super().

famfs keys its superblock on the backing devdax device's dev_t (via
sget_dev()), so a second mount of the same device shares one super,
much as a block filesystem keys on its block device. As a result
sb->s_dev is a real char-device number: there is no s_bdev, and no
anonymous block device was allocated. None of the existing kill_sb
helpers fit:

  - kill_block_super() releases an s_bdev, which famfs does not have.
  - kill_anon_super()/kill_litter_super() call free_anon_bdev(s_dev),
    but s_dev is the devdax dev_t, not an anon-bdev minor famfs
    allocated; freeing it would corrupt the anonymous-dev IDA.
  - generic_shutdown_super() alone omits kill_super_notify(), which
    unlinks the dying sb from fs_supers and wakes concurrent mounters
    (SB_DEAD); skipping it can leave a dead sb discoverable and hang a
    racing mount.

The correct teardown is generic_shutdown_super() + kill_super_notify()
with no device free. kill_super_notify() is static to fs/super.c, so a
module cannot compose it -- hence this small exported helper.

This commit builds but is otherwise too incomplete to run

Signed-off-by: John Groves <[email protected]>
---
v13:
 - Expanded the commit message to explain why the existing kill_*_super
   helpers do not fit per Darrick.
 - Set famfs_fs_type.owner = THIS_MODULE so the module cannot be unloaded
   while a famfs is still mounted (Sashiko bot).
 - Kconfig: add "depends on 64BIT". famfs targets CXL / fabric-attached
   memory, which only exists on 64-bit systems. (This is also why the
   interleaved offset resolver uses native u64 arithmetic rather than
   do_div(): Sashiko flagged the 64-bit division, but the resolution is that
   famfs is 64-bit-only, not do_div.)
 - Prefixed the non-static symbol lookup_daxdev() -> famfs_lookup_daxdev() so
   it does not pollute the global/kallsyms namespace (Sashiko bot).
 - famfs_parse_param(): reject an unrecognized mount parameter (return
   -ENOPARAM) and an invalid dax= value (return -EINVAL) instead of silently
   accepting them (Sashiko bot).
 - Bisect safety: init_famfs_fs() returns -ENODEV while FAMFS_MODULE_INCOMPLETE
   is defined (added in this patch), so intermediate commits of the series
   build but famfs cannot be mounted until it is complete. The guard is
   removed in the statfs patch (v12 11/12).

 MAINTAINERS                |   7 +
 fs/Kconfig                 |   2 +
 fs/Makefile                |   1 +
 fs/famfs/Kconfig           |  12 ++
 fs/famfs/Makefile          |   5 +
 fs/famfs/famfs_inode.c     | 307 +++++++++++++++++++++++++++++++++++++
 fs/famfs/famfs_internal.h  |  32 ++++
 fs/namei.c                 |   1 +
 fs/super.c                 |   7 +
 include/linux/fs.h         |   1 +
 include/uapi/linux/magic.h |   1 +
 11 files changed, 376 insertions(+)
 create mode 100644 fs/famfs/Kconfig
 create mode 100644 fs/famfs/Makefile
 create mode 100644 fs/famfs/famfs_inode.c
 create mode 100644 fs/famfs/famfs_internal.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 5114e6db7307..156fa62f7086 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9905,6 +9905,13 @@ F:       Documentation/networking/failover.rst
 F:     include/net/failover.h
 F:     net/core/failover.c
 
+FAMFS [Fabric-Attached Memory File System]
+M:     John Groves <[email protected]>
+L:     [email protected]
+L:     [email protected]
+S:     Supported
+F:     fs/famfs/
+
 FANOTIFY
 M:     Jan Kara <[email protected]>
 R:     Amir Goldstein <[email protected]>
diff --git a/fs/Kconfig b/fs/Kconfig
index cf6ae64776e6..2db647accc00 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -131,6 +131,8 @@ source "fs/autofs/Kconfig"
 source "fs/fuse/Kconfig"
 source "fs/overlayfs/Kconfig"
 
+source "fs/famfs/Kconfig"
+
 menu "Caches"
 
 source "fs/netfs/Kconfig"
diff --git a/fs/Makefile b/fs/Makefile
index 89a8a9d207d1..f49f9a000210 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -129,3 +129,4 @@ obj-$(CONFIG_VBOXSF_FS)             += vboxsf/
 obj-$(CONFIG_ZONEFS_FS)                += zonefs/
 obj-$(CONFIG_BPF_LSM)          += bpf_fs_kfuncs.o
 obj-$(CONFIG_RESCTRL_FS)       += resctrl/
+obj-$(CONFIG_FAMFS)            += famfs/
diff --git a/fs/famfs/Kconfig b/fs/famfs/Kconfig
new file mode 100644
index 000000000000..c914b6be0627
--- /dev/null
+++ b/fs/famfs/Kconfig
@@ -0,0 +1,12 @@
+
+
+config FAMFS
+       tristate "famfs: shared memory file system"
+       depends on 64BIT
+       depends on DEV_DAX && FS_DAX && DEV_DAX_FSDEV
+       default m if DEV_DAX && FS_DAX && DEV_DAX_FSDEV
+       help
+         Support for the famfs file system. Famfs is a dax file system that
+         can support scale-out shared access to fabric-attached memory
+         (e.g. CXL shared memory). Famfs is not a general purpose file system;
+         it is an enabler for data sets in shared memory.
diff --git a/fs/famfs/Makefile b/fs/famfs/Makefile
new file mode 100644
index 000000000000..62230bcd6793
--- /dev/null
+++ b/fs/famfs/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_FAMFS) += famfs.o
+
+famfs-y := famfs_inode.o
diff --git a/fs/famfs/famfs_inode.c b/fs/famfs/famfs_inode.c
new file mode 100644
index 000000000000..5a13903da61b
--- /dev/null
+++ b/fs/famfs/famfs_inode.c
@@ -0,0 +1,307 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * famfs - dax file system for shared fabric-attached memory
+ *
+ * Copyright 2023-2024 Micron Technology, inc
+ *
+ * This file system, originally based on ramfs the dax support from xfs,
+ * is intended to allow multiple host systems to mount a common file system
+ * view of dax files that map to shared memory.
+ */
+
+#include <linux/fs.h>
+#include <linux/cleanup.h>
+#include <linux/time.h>
+#include <linux/init.h>
+#include <linux/string.h>
+#include <linux/parser.h>
+#include <linux/magic.h>
+#include <linux/slab.h>
+#include <linux/fs_context.h>
+#include <linux/fs_parser.h>
+#include <linux/dax.h>
+#include <linux/hugetlb.h>
+#include <linux/iomap.h>
+#include <linux/path.h>
+#include <linux/namei.h>
+
+#include "famfs_internal.h"
+
+#define FAMFS_DEFAULT_MODE     0755
+
+static struct inode *
+famfs_get_inode(
+       struct super_block *sb,
+       const struct inode *dir,
+       umode_t mode, dev_t dev)
+{
+       struct inode *inode = new_inode(sb);
+       struct timespec64 tv;
+
+       if (!inode)
+               return NULL;
+
+       inode->i_ino = get_next_ino();
+       inode_init_owner(&nop_mnt_idmap, inode, dir, mode);
+       inode->i_mapping->a_ops = &ram_aops;
+       mapping_set_gfp_mask(inode->i_mapping, GFP_HIGHUSER);
+       mapping_set_unevictable(inode->i_mapping);
+       tv = inode_set_ctime_current(inode);
+       inode_set_mtime_to_ts(inode, tv);
+       inode_set_atime_to_ts(inode, tv);
+
+       switch (mode & S_IFMT) {
+       default:
+               init_special_inode(inode, mode, dev);
+               break;
+       case S_IFREG:
+               inode->i_op = NULL /* famfs_file_inode_operations */;
+               inode->i_fop = NULL /* &famfs_file_operations */;
+               break;
+       case S_IFDIR:
+               inode->i_op = NULL /* famfs_dir_inode_operations */;
+               inode->i_fop = &simple_dir_operations;
+
+               /* Directory inodes start off with i_nlink == 2 (for ".") */
+               inc_nlink(inode);
+               break;
+       case S_IFLNK:
+               inode->i_op = &page_symlink_inode_operations;
+               inode_nohighmem(inode);
+               break;
+       }
+       return inode;
+}
+
+/*
+ * famfs dax_operations (for famfs-mode dax)
+ */
+/*****************************************************************************
+ * fs_context_operations
+ */
+
+static void
+famfs_fill_super(struct super_block *sb, struct fs_context *fc)
+{
+       sb->s_maxbytes          = MAX_LFS_FILESIZE;
+       sb->s_blocksize         = PAGE_SIZE;
+       sb->s_blocksize_bits    = PAGE_SHIFT;
+       sb->s_magic             = FAMFS_SUPER_MAGIC;
+       sb->s_op                = NULL /* famfs_super_ops */;
+       sb->s_time_gran         = 1;
+}
+
+int
+famfs_lookup_daxdev(const char *pathname, dev_t *devno)
+{
+       struct inode *inode;
+       struct path path;
+       int err;
+
+       if (!pathname || !*pathname)
+               return -EINVAL;
+
+       err = kern_path(pathname, LOOKUP_FOLLOW, &path);
+       if (err)
+               return err;
+
+       inode = d_backing_inode(path.dentry);
+       if (!S_ISCHR(inode->i_mode)) {
+               err = -EINVAL;
+               goto out_path_put;
+       }
+
+       if (!may_open_dev(&path)) {
+               err = -EACCES;
+               goto out_path_put;
+       }
+
+       /* i_rdev is the char dev_t; fs_dax_get() confirms it is dax later */
+       *devno = inode->i_rdev;
+
+out_path_put:
+       path_put(&path);
+       return err;
+}
+
+static int
+famfs_get_tree(struct fs_context *fc)
+{
+       struct famfs_fs_info *fsi = fc->s_fs_info;
+       struct super_block *sb;
+       struct inode *inode;
+       dev_t daxdevno;
+       int err;
+
+       err = famfs_lookup_daxdev(fc->source, &daxdevno);
+       if (err)
+               return err;
+
+       /* This will set sb->s_dev=daxdevno */
+       sb = sget_dev(fc, daxdevno);
+       if (IS_ERR(sb)) {
+               pr_debug("%s: sget_dev error\n", __func__);
+               return PTR_ERR(sb);
+       }
+
+       if (sb->s_root) {
+               pr_debug("%s: found a matching superblock for %s\n",
+                       __func__, fc->source);
+
+               /* We don't expect to find a match by dev_t; if we do, it must
+                * already be mounted, so we bail
+                */
+               err = -EBUSY;
+               goto deactivate_out;
+       } else {
+               pr_debug("%s: initializing new superblock for %s\n",
+                       __func__, fc->source);
+               famfs_fill_super(sb, fc);
+       }
+
+       inode = famfs_get_inode(sb, NULL, S_IFDIR | fsi->mount_opts.mode, 0);
+       sb->s_root = d_make_root(inode);
+       if (!sb->s_root) {
+               pr_debug("%s: d_make_root() failed\n", __func__);
+               err = -ENOMEM;
+               goto deactivate_out;
+       }
+
+       sb->s_flags |= SB_ACTIVE;
+
+       WARN_ON(fc->root);
+       fc->root = dget(sb->s_root);
+       return 0;
+
+deactivate_out:
+       pr_debug("%s: deactivating sb=%llx\n", __func__, (u64)sb);
+       deactivate_locked_super(sb);
+       return err;
+}
+
+/*****************************************************************************/
+
+enum famfs_param {
+       Opt_mode,
+       Opt_dax,
+};
+
+const struct fs_parameter_spec famfs_fs_parameters[] = {
+       fsparam_u32oct("mode",    Opt_mode),
+       fsparam_string("dax",     Opt_dax),
+       {}
+};
+
+static int
+famfs_parse_param(struct fs_context *fc, struct fs_parameter *param)
+{
+       struct famfs_fs_info *fsi = fc->s_fs_info;
+       struct fs_parse_result result;
+       int opt;
+
+       opt = fs_parse(fc, famfs_fs_parameters, param, &result);
+       if (opt == -ENOPARAM) {
+               opt = vfs_parse_fs_param_source(fc, param);
+               if (opt != -ENOPARAM)
+                       return opt;
+
+               return -ENOPARAM;
+       }
+       if (opt < 0)
+               return opt;
+
+       switch (opt) {
+       case Opt_mode:
+               fsi->mount_opts.mode = result.uint_32 & S_IALLUGO;
+               break;
+       case Opt_dax:
+               if (strcmp(param->string, "always")) {
+                       pr_debug("%s: invalid dax mode %s\n",
+                                 __func__, param->string);
+                       return -EINVAL;
+               }
+               break;
+       }
+
+       return 0;
+}
+
+static void
+famfs_free_fc(struct fs_context *fc)
+{
+       kfree(fc->s_fs_info);
+}
+
+static const struct fs_context_operations famfs_context_ops = {
+       .free           = famfs_free_fc,
+       .parse_param    = famfs_parse_param,
+       .get_tree       = famfs_get_tree,
+};
+
+static int
+famfs_init_fs_context(struct fs_context *fc)
+{
+       struct famfs_fs_info *fsi;
+
+       fsi = kzalloc_obj(*fsi, GFP_KERNEL);
+       if (!fsi)
+               return -ENOMEM;
+
+       fsi->mount_opts.mode = FAMFS_DEFAULT_MODE;
+       fc->s_fs_info        = fsi;
+       fc->ops              = &famfs_context_ops;
+       return 0;
+}
+
+static void
+famfs_kill_sb(struct super_block *sb)
+{
+       struct famfs_fs_info *fsi = sb->s_fs_info;
+
+       kill_char_super(sb);
+
+       kfree(fsi);
+       sb->s_fs_info = NULL;
+}
+
+#define MODULE_NAME "famfs"
+static struct file_system_type famfs_fs_type = {
+       .owner            = THIS_MODULE,
+       .name             = MODULE_NAME,
+       .init_fs_context  = famfs_init_fs_context,
+       .parameters       = famfs_fs_parameters,
+       .kill_sb          = famfs_kill_sb,
+       .fs_flags         = FS_REQUIRES_DEV,
+};
+
+/******************************************************************************
+ * Module stuff
+ */
+#define FAMFS_MODULE_INCOMPLETE 1
+
+static int __init
+init_famfs_fs(void)
+{
+       int rc;
+
+       if (FAMFS_MODULE_INCOMPLETE)
+               return -ENODEV;
+
+       rc = register_filesystem(&famfs_fs_type);
+
+       return rc;
+}
+
+static void __exit
+famfs_exit(void)
+{
+       unregister_filesystem(&famfs_fs_type);
+       pr_info("%s: unregistered\n", __func__);
+}
+
+fs_initcall(init_famfs_fs);
+module_exit(famfs_exit);
+
+MODULE_AUTHOR("John Groves");
+MODULE_DESCRIPTION("Fabric-Attached Memory File System: see famfs.org");
+MODULE_LICENSE("GPL");
diff --git a/fs/famfs/famfs_internal.h b/fs/famfs/famfs_internal.h
new file mode 100644
index 000000000000..378544b1aabe
--- /dev/null
+++ b/fs/famfs/famfs_internal.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * famfs - dax file system for shared fabric-attached memory
+ *
+ * Copyright 2023-2024 Micron Technology, Inc.
+ *
+ * This file system, originally based on ramfs the dax support from xfs,
+ * is intended to allow multiple host systems to mount a common file system
+ * view of dax files that map to shared memory.
+ */
+#ifndef FAMFS_INTERNAL_H
+#define FAMFS_INTERNAL_H
+
+struct famfs_mount_opts {
+       umode_t mode;
+};
+
+/**
+ * @famfs_fs_info
+ *
+ * @mount_opts:  The mount options
+ * @deverror:    True if the dax device has called our notify_failure entry
+ *               point, or if other "shutdown" conditions exist
+ */
+struct famfs_fs_info {
+       struct famfs_mount_opts   mount_opts;
+       bool                      deverror;
+};
+
+int famfs_lookup_daxdev(const char *pathname, dev_t *devno);
+
+#endif /* FAMFS_INTERNAL_H */
diff --git a/fs/namei.c b/fs/namei.c
index 19ce43c9a6e6..d67194e89963 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -4232,6 +4232,7 @@ bool may_open_dev(const struct path *path)
        return !(path->mnt->mnt_flags & MNT_NODEV) &&
                !(path->mnt->mnt_sb->s_iflags & SB_I_NODEV);
 }
+EXPORT_SYMBOL(may_open_dev);
 
 static int may_open(struct mnt_idmap *idmap, const struct path *path,
                    int acc_mode, int flag)
diff --git a/fs/super.c b/fs/super.c
index ffdcc6a2e0de..bcdfd3563aaa 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -1241,6 +1241,13 @@ void kill_anon_super(struct super_block *sb)
 }
 EXPORT_SYMBOL(kill_anon_super);
 
+void kill_char_super(struct super_block *sb)
+{
+       generic_shutdown_super(sb);
+       kill_super_notify(sb);
+}
+EXPORT_SYMBOL(kill_char_super);
+
 int set_anon_super_fc(struct super_block *sb, struct fs_context *fc)
 {
        return set_anon_super(sb, NULL);
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 50ce731a2b78..a8b030d6f218 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -2328,6 +2328,7 @@ void retire_super(struct super_block *sb);
 void generic_shutdown_super(struct super_block *sb);
 void kill_block_super(struct super_block *sb);
 void kill_anon_super(struct super_block *sb);
+void kill_char_super(struct super_block *sb);
 void deactivate_super(struct super_block *sb);
 void deactivate_locked_super(struct super_block *sb);
 int set_anon_super(struct super_block *s, void *data);
diff --git a/include/uapi/linux/magic.h b/include/uapi/linux/magic.h
index 4f2da935a76c..e644e1fd49bd 100644
--- a/include/uapi/linux/magic.h
+++ b/include/uapi/linux/magic.h
@@ -38,6 +38,7 @@
 #define OVERLAYFS_SUPER_MAGIC  0x794c7630
 #define FUSE_SUPER_MAGIC       0x65735546
 #define BCACHEFS_SUPER_MAGIC   0xca451a4e
+#define FAMFS_SUPER_MAGIC      0x87b282ff
 
 #define MINIX_SUPER_MAGIC      0x137F          /* minix v1 fs, 14 char names */
 #define MINIX_SUPER_MAGIC2     0x138F          /* minix v1 fs, 30 char names */
-- 
2.53.0



Reply via email to