[Concept,07/34] vfs: Add VFS root and init

Message ID 20260403140523.1998228-8-sjg@u-boot.org
State New
Headers
Series Add a virtual filesystem (VFS) layer to U-Boot |

Commit Message

Simon Glass April 3, 2026, 2:04 p.m. UTC
  From: Simon Glass <sjg@chromium.org>

Add the VFS root filesystem driver and initialisation. The rootfs is a
minimal UCLASS_FS device that provides an empty root directory and
creates mount-point directories on demand.

The VFS root directory driver (in vfs_dir.c) lists mount-point
directories as entries. It is initialised automatically via
EVT_LAST_STAGE_INIT.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

 fs/Kconfig                       |   1 +
 fs/Makefile                      |   2 +-
 fs/vfs.c                         | 124 +++++++++++++++++++++++++++++++
 fs/vfs_dir.c                     |  88 ++++++++++++++++++++++
 fs/vfs_internal.h                |  22 ++++++
 include/vfs.h                    |  18 +++++
 test/dm/fs.c                     |  31 ++++++++
 test/py/tests/test_event_dump.py |   1 +
 8 files changed, 286 insertions(+), 1 deletion(-)
 create mode 100644 fs/vfs.c
 create mode 100644 fs/vfs_dir.c
 create mode 100644 fs/vfs_internal.h
  

Patch

diff --git a/fs/Kconfig b/fs/Kconfig
index 6a55bd71adc..b173b5814ed 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -32,6 +32,7 @@  config FILE
 config VFS
 	bool "Virtual filesystem support"
 	depends on FS && DIR
+	select EVENT
 	help
 	  Provides a virtual filesystem layer with a mount table and
 	  unified path namespace. Includes a root filesystem at "/".
diff --git a/fs/Makefile b/fs/Makefile
index ffb6bbce737..704ac6e4866 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -8,7 +8,7 @@  obj-$(CONFIG_$(PHASE_)FS_LEGACY) += fs_legacy.o fs_internal.o
 obj-$(CONFIG_$(PHASE_)FS) += fs-uclass.o
 obj-$(CONFIG_$(PHASE_)DIR) += dir-uclass.o
 obj-$(CONFIG_$(PHASE_)FILE) += file-uclass.o
-obj-$(CONFIG_$(PHASE_)VFS) += mount-uclass.o
+obj-$(CONFIG_$(PHASE_)VFS) += mount-uclass.o vfs.o vfs_dir.o
 
 ifdef CONFIG_XPL_BUILD
 obj-$(CONFIG_SPL_FS_FAT) += fat/
diff --git a/fs/vfs.c b/fs/vfs.c
new file mode 100644
index 00000000000..439c9760829
--- /dev/null
+++ b/fs/vfs.c
@@ -0,0 +1,124 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Virtual Filesystem layer
+ *
+ * Manages a mount tree using UCLASS_MOUNT devices as children of
+ * UCLASS_DIR devices, providing unified path resolution across mounted
+ * filesystems. Inspired by the Linux VFS.
+ *
+ * Copyright 2026 Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY	UCLASS_MOUNT
+
+#include <dir.h>
+#include <dm.h>
+#include <event.h>
+#include <fs.h>
+#include <vfs.h>
+#include "vfs_internal.h"
+#include <dm/device-internal.h>
+#include <dm/lists.h>
+#include <dm/root.h>
+#include <dm/uclass-internal.h>
+
+/* VFS root filesystem - provides an empty root directory */
+
+static int vfs_rootfs_mount(struct udevice *dev)
+{
+	struct fs_priv *uc_priv = dev_get_uclass_priv(dev);
+
+	if (uc_priv->mounted)
+		return log_msg_ret("rfm", -EISCONN);
+
+	uc_priv->mounted = true;
+
+	return 0;
+}
+
+static int vfs_rootfs_unmount(struct udevice *dev)
+{
+	return log_msg_ret("rfu", -EBUSY);
+}
+
+static int vfs_rootfs_lookup_dir(struct udevice *dev, const char *path,
+				 struct udevice **dirp)
+{
+	struct dir_uc_priv *uc_priv;
+	struct udevice *child, *dir;
+	int ret;
+
+	/* Check for an existing dir with this path */
+	device_foreach_child(child, dev) {
+		if (device_get_uclass_id(child) == UCLASS_DIR) {
+			uc_priv = dev_get_uclass_priv(child);
+			if (uc_priv->path && !strcmp(uc_priv->path, path)) {
+				*dirp = child;
+				return 0;
+			}
+		}
+	}
+
+	/* Create a new dir */
+	ret = dir_add_probe(dev, DM_DRIVER_GET(vfs_rootfs_dir), path, &dir);
+	if (ret)
+		return log_msg_ret("rfD", ret);
+
+	*dirp = dir;
+
+	return 0;
+}
+
+/* Exported functions */
+
+struct udevice *vfs_root(void)
+{
+	struct udevice *dev;
+
+	if (uclass_find_device_by_name(UCLASS_FS, "vfs_rootfs", &dev))
+		return NULL;
+	if (!device_active(dev))
+		return NULL;
+
+	return dev;
+}
+
+int vfs_init(void)
+{
+	struct udevice *dev;
+	int ret;
+
+	/* Already initialised? */
+	dev = vfs_root();
+	if (dev)
+		return 0;
+
+	ret = device_bind_driver(dm_root(), "vfs_rootfs", "vfs_rootfs", &dev);
+	if (ret)
+		return log_msg_ret("vib", ret);
+
+	ret = device_probe(dev);
+	if (ret)
+		return log_msg_ret("vip", ret);
+
+	ret = fs_mount(dev);
+	if (ret)
+		return log_msg_ret("vim", ret);
+
+	return 0;
+}
+
+static const struct fs_ops vfs_rootfs_ops = {
+	.mount		= vfs_rootfs_mount,
+	.unmount	= vfs_rootfs_unmount,
+	.lookup_dir	= vfs_rootfs_lookup_dir,
+};
+
+U_BOOT_DRIVER(vfs_rootfs) = {
+	.name		= "vfs_rootfs",
+	.id		= UCLASS_FS,
+	.ops		= &vfs_rootfs_ops,
+	.priv_auto	= sizeof(struct vfs_priv),
+};
+
+EVENT_SPY_SIMPLE(EVT_LAST_STAGE_INIT, vfs_init);
diff --git a/fs/vfs_dir.c b/fs/vfs_dir.c
new file mode 100644
index 00000000000..87a4193516e
--- /dev/null
+++ b/fs/vfs_dir.c
@@ -0,0 +1,88 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * VFS root directory driver
+ *
+ * Provides the directory driver for the VFS rootfs. The root directory
+ * (path "") lists all mount-point directories as entries. Mount-point
+ * directories themselves have no entries since access crosses into the
+ * mounted filesystem.
+ *
+ * Copyright 2026 Simon Glass <sjg@chromium.org>
+ */
+
+#include <dir.h>
+#include <dm.h>
+#include <fs.h>
+#include <fs_common.h>
+
+static int vfs_rootfs_dir_open(struct udevice *dev,
+			       struct fs_dir_stream *strm)
+{
+	strm->offset = 0;
+
+	return 0;
+}
+
+/**
+ * vfs_rootfs_dir_read() - Read the next directory entry
+ *
+ * For the root directory (path ""), iterates through sibling DIR devices
+ * that have non-empty paths, returning each as a directory entry. For
+ * mount-point directories, returns -ENOENT immediately since their
+ * contents come from the mounted filesystem.
+ */
+static int vfs_rootfs_dir_read(struct udevice *dev,
+			       struct fs_dir_stream *strm,
+			       struct fs_dirent *dent)
+{
+	struct dir_uc_priv *dir_priv = dev_get_uclass_priv(dev);
+	struct udevice *parent, *child;
+	int idx = 0;
+
+	/* Only the root dir has entries to list */
+	if (*dir_priv->path)
+		return -ENOENT;
+
+	/* Walk children of the FS device, skipping to the current offset */
+	parent = dev_get_parent(dev);
+	device_foreach_child(child, parent) {
+		struct dir_uc_priv *uc_priv;
+
+		if (device_get_uclass_id(child) != UCLASS_DIR)
+			continue;
+
+		uc_priv = dev_get_uclass_priv(child);
+		if (!uc_priv->path || !*uc_priv->path)
+			continue;
+
+		if (idx++ < strm->offset)
+			continue;
+
+		strlcpy(dent->name, uc_priv->path, sizeof(dent->name));
+		dent->type = FS_DT_DIR;
+		dent->size = 0;
+		strm->offset++;
+
+		return 0;
+	}
+
+	return -ENOENT;
+}
+
+static int vfs_rootfs_dir_close(struct udevice *dev,
+				struct fs_dir_stream *strm)
+{
+	return 0;
+}
+
+static struct dir_ops vfs_rootfs_dir_ops = {
+	.open	= vfs_rootfs_dir_open,
+	.read	= vfs_rootfs_dir_read,
+	.close	= vfs_rootfs_dir_close,
+};
+
+U_BOOT_DRIVER(vfs_rootfs_dir) = {
+	.name	= "vfs_rootfs_dir",
+	.id	= UCLASS_DIR,
+	.ops	= &vfs_rootfs_dir_ops,
+};
diff --git a/fs/vfs_internal.h b/fs/vfs_internal.h
new file mode 100644
index 00000000000..b7e6b37d55d
--- /dev/null
+++ b/fs/vfs_internal.h
@@ -0,0 +1,22 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Internal VFS helpers - not part of the public API
+ *
+ * Copyright 2026 Simon Glass <sjg@chromium.org>
+ */
+
+#ifndef __VFS_INTERNAL_H
+#define __VFS_INTERNAL_H
+
+struct udevice;
+
+/**
+ * struct vfs_priv - VFS root FS device driver-private data
+ *
+ * @mount_count: Counter for unique mount device names
+ */
+struct vfs_priv {
+	int mount_count;
+};
+
+#endif
diff --git a/include/vfs.h b/include/vfs.h
index 7c31caace25..1bf1762ab52 100644
--- a/include/vfs.h
+++ b/include/vfs.h
@@ -28,4 +28,22 @@  struct vfsmount {
 	struct udevice *target;
 };
 
+/**
+ * vfs_init() - Initialise the VFS
+ *
+ * Creates the VFS root directory device. Normally called automatically
+ * via EVT_LAST_STAGE_INIT during boot. May also be called directly in
+ * tests after a DM tree reset.
+ *
+ * Return: 0 if OK, -ve on error
+ */
+int vfs_init(void);
+
+/**
+ * vfs_root() - Get the VFS root FS device
+ *
+ * Return: VFS root FS device, or NULL if not initialised
+ */
+struct udevice *vfs_root(void);
+
 #endif
diff --git a/test/dm/fs.c b/test/dm/fs.c
index 6cb5f74db35..1d395031dee 100644
--- a/test/dm/fs.c
+++ b/test/dm/fs.c
@@ -9,6 +9,7 @@ 
 #include <dm.h>
 #include <file.h>
 #include <fs.h>
+#include <vfs.h>
 #include <dm/test.h>
 #include <test/ut.h>
 
@@ -104,3 +105,33 @@  static int dm_test_fs_file(struct unit_test_state *uts)
 	return 0;
 }
 DM_TEST(dm_test_fs_file, UTF_SCAN_FDT);
+
+#if IS_ENABLED(CONFIG_VFS)
+/* Test VFS init and root directory operations */
+static int dm_test_vfs_init(struct unit_test_state *uts)
+{
+	struct udevice *vfs, *dir;
+	struct fs_dir_stream *strm;
+	struct fs_dirent dent;
+
+	ut_assertok(vfs_init());
+
+	vfs = vfs_root();
+	ut_assertnonnull(vfs);
+
+	/* Look up the root directory */
+	ut_assertok(fs_lookup_dir(vfs, "", &dir));
+	ut_assertnonnull(dir);
+
+	/* open should succeed, read should return -ENOENT (root is empty) */
+	ut_assertok(dir_open(dir, &strm));
+	ut_asserteq(-ENOENT, dir_read(dir, strm, &dent));
+	ut_assertok(dir_close(dir, strm));
+
+	/* rootfs cannot be unmounted */
+	ut_asserteq(-EBUSY, fs_unmount(vfs));
+
+	return 0;
+}
+DM_TEST(dm_test_vfs_init, UTF_SCAN_FDT);
+#endif
diff --git a/test/py/tests/test_event_dump.py b/test/py/tests/test_event_dump.py
index ff0da82196b..dad0132eeed 100644
--- a/test/py/tests/test_event_dump.py
+++ b/test/py/tests/test_event_dump.py
@@ -21,6 +21,7 @@  EVT_LAST_STAGE_INIT   alloc_write_acpi_tables         .*lib/acpi/acpi_table.c:.*
 EVT_LAST_STAGE_INIT   efi_block_device_create         .*lib/efi_driver/efi_block_device.c:.*
 EVT_LAST_STAGE_INIT   install_smbios_table            .*lib/efi_loader/efi_smbios.c:.*
 EVT_LAST_STAGE_INIT   last_stage_init                 .*arch/sandbox/cpu/start.c:.*
+EVT_LAST_STAGE_INIT   vfs_init                        .*fs/vfs.c:.*
 EVT_MISC_INIT_F       sandbox_early_getopt_check      .*arch/sandbox/cpu/start.c:.*
 EVT_SETTINGS_R        sandbox_settings                .*board/sandbox/sandbox.c:.*
 EVT_TEST              h_adder_simple                  .*test/common/event.c:'''