[Concept,31/34] fs: Add FAT VFS driver

Message ID 20260403140523.1998228-32-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 a UCLASS_FS driver for the FAT filesystem, allowing it to be
mounted through the VFS layer. The driver wraps the existing FAT
functions for mount, unmount, directory listing, file read/write,
mkdir, unlink and rename.

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

 fs/fat/Makefile |   1 +
 fs/fat/fs.c     | 266 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 267 insertions(+)
 create mode 100644 fs/fat/fs.c
  

Patch

diff --git a/fs/fat/Makefile b/fs/fat/Makefile
index f3982fca4c6..36d75b884d2 100644
--- a/fs/fat/Makefile
+++ b/fs/fat/Makefile
@@ -2,4 +2,5 @@ 
 #
 
 obj-$(CONFIG_$(PHASE_)FS_FAT) = fat.o
+obj-$(CONFIG_$(PHASE_)VFS) += fs.o
 obj-$(CONFIG_$(PHASE_)FAT_WRITE) += fat_write.o
diff --git a/fs/fat/fs.c b/fs/fat/fs.c
new file mode 100644
index 00000000000..43e03337142
--- /dev/null
+++ b/fs/fat/fs.c
@@ -0,0 +1,266 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * FAT filesystem driver for the VFS layer
+ *
+ * Wraps the existing FAT implementation to provide UCLASS_FS and
+ * UCLASS_DIR devices, following the same pattern as sandboxfs.c.
+ *
+ * Copyright 2026 Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY	UCLASS_FS
+
+#include <dir.h>
+#include <dm.h>
+#include <fat.h>
+#include <file.h>
+#include <fs.h>
+#include <fs_legacy.h>
+#include <iovec.h>
+#include <malloc.h>
+#include <vfs.h>
+#include <dm/device-internal.h>
+
+/**
+ * struct fat_dir_priv - Private info for FAT directory devices
+ *
+ * @strm: Directory stream from fat_opendir(), or NULL
+ */
+struct fat_dir_priv {
+	struct fs_dir_stream *strm;
+};
+
+static int fat_vfs_mount(struct udevice *dev)
+{
+	struct fs_priv *uc_priv = dev_get_uclass_priv(dev);
+	struct fs_plat *plat = dev_get_uclass_plat(dev);
+
+	if (uc_priv->mounted)
+		return log_msg_ret("fmm", -EISCONN);
+
+	if (!plat->desc)
+		return log_msg_ret("fmd", -ENODEV);
+
+	if (fat_set_blk_dev(plat->desc, &plat->part))
+		return log_msg_ret("fmf", -EINVAL);
+
+	uc_priv->mounted = true;
+
+	return 0;
+}
+
+static int fat_vfs_unmount(struct udevice *dev)
+{
+	struct fs_priv *uc_priv = dev_get_uclass_priv(dev);
+
+	if (!uc_priv->mounted)
+		return log_msg_ret("fuu", -ENOTCONN);
+
+	fat_close();
+	uc_priv->mounted = false;
+
+	return 0;
+}
+
+static int fat_vfs_lookup_dir(struct udevice *dev, const char *path,
+			      struct udevice **dirp)
+{
+	struct udevice *dir;
+	int ret;
+
+	ret = dir_add_probe(dev, DM_DRIVER_GET(fat_vfs_dir), path, &dir);
+	if (ret)
+		return log_msg_ret("fld", ret);
+
+	*dirp = dir;
+
+	return 0;
+}
+
+static int fat_vfs_mkdir(struct udevice *dev, const char *path)
+{
+	return fat_mkdir(path);
+}
+
+static int fat_vfs_unlink(struct udevice *dev, const char *path)
+{
+	return fat_unlink(path);
+}
+
+static int fat_vfs_rename(struct udevice *dev, const char *old_path,
+			  const char *new_path)
+{
+	return fat_rename(old_path, new_path);
+}
+
+static int fat_vfs_statfs(struct udevice *dev, struct fs_statfs *stats)
+{
+	return fat_statfs(stats);
+}
+
+static const struct fs_ops fat_vfs_ops = {
+	.mount		= fat_vfs_mount,
+	.unmount	= fat_vfs_unmount,
+	.lookup_dir	= fat_vfs_lookup_dir,
+	.mkdir		= fat_vfs_mkdir,
+	.unlink		= fat_vfs_unlink,
+	.rename		= fat_vfs_rename,
+	.statfs		= fat_vfs_statfs,
+};
+
+U_BOOT_DRIVER(fat_fs) = {
+	.name	= "fat_fs",
+	.id	= UCLASS_FS,
+	.ops	= &fat_vfs_ops,
+};
+
+/* FAT directory driver */
+
+static int fat_dir_open(struct udevice *dev, struct fs_dir_stream *strm)
+{
+	struct fat_dir_priv *priv = dev_get_priv(dev);
+	struct dir_uc_priv *uc_priv = dev_get_uclass_priv(dev);
+	struct fs_dir_stream *fat_strm;
+	const char *path;
+	int ret;
+
+	path = *uc_priv->path ? uc_priv->path : "/";
+	ret = fat_opendir(path, &fat_strm);
+	if (ret)
+		return log_msg_ret("fdo", ret);
+
+	priv->strm = fat_strm;
+
+	return 0;
+}
+
+static int fat_dir_read(struct udevice *dev, struct fs_dir_stream *strm,
+			struct fs_dirent *dent)
+{
+	struct fat_dir_priv *priv = dev_get_priv(dev);
+	struct fs_dirent *fat_dent;
+	int ret;
+
+	ret = fat_readdir(priv->strm, &fat_dent);
+	if (ret)
+		return ret;
+
+	memcpy(dent, fat_dent, sizeof(*dent));
+
+	return 0;
+}
+
+static int fat_dir_close(struct udevice *dev, struct fs_dir_stream *strm)
+{
+	struct fat_dir_priv *priv = dev_get_priv(dev);
+
+	fat_closedir(priv->strm);
+	priv->strm = NULL;
+
+	return 0;
+}
+
+/* FAT file driver - stores the full path for path-based I/O */
+
+/**
+ * struct fat_file_priv - Private info for FAT file devices
+ *
+ * @path: Full path within the FAT filesystem
+ */
+struct fat_file_priv {
+	char path[FILE_MAX_PATH_LEN];
+};
+
+static ssize_t fat_read_iter(struct udevice *dev, struct iov_iter *iter,
+			     loff_t pos)
+{
+	struct fat_file_priv *priv = dev_get_priv(dev);
+	loff_t actual;
+	int ret;
+
+	ret = fat_read_file(priv->path, iter_iov_ptr(iter), pos,
+			    iter_iov_avail(iter), &actual);
+	if (ret)
+		return log_msg_ret("ffr", ret);
+	iter_advance(iter, actual);
+
+	return actual;
+}
+
+static ssize_t fat_write_iter(struct udevice *dev, struct iov_iter *iter,
+			      loff_t pos)
+{
+	struct fat_file_priv *priv = dev_get_priv(dev);
+	loff_t actual;
+	int ret;
+
+	ret = file_fat_write(priv->path, (void *)iter_iov_ptr(iter), pos,
+			     iter_iov_avail(iter), &actual);
+	if (ret)
+		return log_msg_ret("ffw", ret);
+	iter_advance(iter, actual);
+
+	return actual;
+}
+
+static struct file_ops fat_file_ops = {
+	.read_iter	= fat_read_iter,
+	.write_iter	= fat_write_iter,
+};
+
+U_BOOT_DRIVER(fat_vfs_file) = {
+	.name		= "fat_vfs_file",
+	.id		= UCLASS_FILE,
+	.ops		= &fat_file_ops,
+	.priv_auto	= sizeof(struct fat_file_priv),
+};
+
+static int fat_dir_open_file(struct udevice *dir, const char *leaf,
+			     enum dir_open_flags_t oflags,
+			     struct udevice **filp)
+{
+	struct dir_uc_priv *uc_priv = dev_get_uclass_priv(dir);
+	struct fat_file_priv *priv;
+	struct udevice *dev;
+	char path[FILE_MAX_PATH_LEN];
+	loff_t size = 0;
+	int ret;
+
+	if (*uc_priv->path)
+		snprintf(path, sizeof(path), "%s/%s", uc_priv->path, leaf);
+	else
+		snprintf(path, sizeof(path), "/%s", leaf);
+
+	if (oflags == DIR_O_RDONLY) {
+		if (!fat_exists(path))
+			return log_msg_ret("foe", -ENOENT);
+		ret = fat_size(path, &size);
+		if (ret)
+			return log_msg_ret("fos", ret);
+	}
+
+	ret = file_add_probe(dir, DM_DRIVER_REF(fat_vfs_file), leaf,
+			     size, oflags, &dev);
+	if (ret)
+		return log_msg_ret("fop", ret);
+
+	priv = dev_get_priv(dev);
+	strlcpy(priv->path, path, sizeof(priv->path));
+	*filp = dev;
+
+	return 0;
+}
+
+static struct dir_ops fat_dir_ops = {
+	.open		= fat_dir_open,
+	.read		= fat_dir_read,
+	.close		= fat_dir_close,
+	.open_file	= fat_dir_open_file,
+};
+
+U_BOOT_DRIVER(fat_vfs_dir) = {
+	.name		= "fat_vfs_dir",
+	.id		= UCLASS_DIR,
+	.ops		= &fat_dir_ops,
+	.priv_auto	= sizeof(struct fat_dir_priv),
+};