@@ -20,6 +20,7 @@
#include <ext4fs.h>
#include <ext4l.h>
#include <fat.h>
+#include <isofs.h>
#include <fs_legacy.h>
#include <sandboxfs.h>
#include <semihostingfs.h>
@@ -295,6 +296,29 @@ static struct fstype_info fstypes[] = {
.statfs = ext4l_statfs_legacy,
},
#endif
+#if CONFIG_IS_ENABLED(FS_ISOFS)
+ {
+ .fstype = FS_TYPE_ISO,
+ .name = "iso9660",
+ .null_dev_desc_ok = false,
+ .probe = isofs_probe,
+ .close = isofs_close,
+ .ls = isofs_ls,
+ .exists = isofs_exists,
+ .size = isofs_size,
+ .read = isofs_read,
+ .write = fs_write_unsupported,
+ .uuid = fs_uuid_unsupported,
+ .opendir = isofs_opendir,
+ .readdir = isofs_readdir,
+ .closedir = isofs_closedir,
+ .unlink = fs_unlink_unsupported,
+ .mkdir = fs_mkdir_unsupported,
+ .ln = fs_ln_unsupported,
+ .rename = fs_rename_unsupported,
+ .statfs = fs_statfs_unsupported,
+ },
+#endif
#if IS_ENABLED(CONFIG_SANDBOX) && !IS_ENABLED(CONFIG_XPL_BUILD)
{
.fstype = FS_TYPE_SANDBOX,
new file mode 100644
@@ -0,0 +1,19 @@
+config FS_ISOFS
+ bool "ISO 9660 filesystem support (Linux port)"
+ depends on FS
+ select FS_LINUX
+ help
+ Support for reading ISO 9660 CD-ROM/DVD filesystems. This is a port
+ of the Linux kernel isofs driver, providing full read-only access
+ including Rock Ridge extensions for POSIX filenames and permissions.
+
+ This driver uses the same porting approach as ext4l, keeping the
+ Linux source files nearly identical.
+
+config JOLIET
+ bool "Microsoft Joliet CD-ROM extensions"
+ depends on FS_ISOFS
+ help
+ Support for Microsoft Joliet CD-ROM extensions which provide
+ Unicode filenames on ISO 9660 filesystems. This requires NLS
+ (National Language Support) for character set conversion.
new file mode 100644
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0
+
+# U-Boot interface and support
+obj-y := interface.o support.o
+
+# VFS layer integration
+obj-$(CONFIG_$(PHASE_)VFS) += fs.o
+
+# Core Linux isofs files (kept close to upstream)
+obj-y += namei.o inode.o dir.o util.o rock.o export.o
+
+# Optional extensions
+obj-$(CONFIG_JOLIET) += joliet.o
new file mode 100644
@@ -0,0 +1,228 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * isofs filesystem driver for the VFS layer
+ *
+ * Wraps the Linux-ported isofs implementation to provide UCLASS_FS
+ * and UCLASS_DIR devices, following the same pattern as ext4l/fs.c.
+ *
+ * Copyright 2026 Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY UCLASS_FS
+
+#include <dir.h>
+#include <dm.h>
+#include <file.h>
+#include <fs.h>
+#include <fs_legacy.h>
+#include <isofs.h>
+#include <iovec.h>
+#include <malloc.h>
+#include <vfs.h>
+#include <dm/device-internal.h>
+
+/**
+ * struct isofs_dir_priv - Private info for isofs directory devices
+ *
+ * @strm: Directory stream from isofs_opendir(), or NULL
+ */
+struct isofs_dir_priv {
+ struct fs_dir_stream *strm;
+};
+
+static int isofs_vfs_mount(struct udevice *dev)
+{
+ struct fs_priv *uc_priv = dev_get_uclass_priv(dev);
+ struct fs_plat *plat = dev_get_uclass_plat(dev);
+ int ret;
+
+ if (uc_priv->mounted)
+ return log_msg_ret("emm", -EISCONN);
+
+ if (!plat->desc)
+ return log_msg_ret("emd", -ENODEV);
+
+ ret = isofs_probe(plat->desc, &plat->part);
+ if (ret)
+ return log_msg_ret("emp", ret);
+
+ uc_priv->mounted = true;
+
+ return 0;
+}
+
+static int isofs_vfs_unmount(struct udevice *dev)
+{
+ struct fs_priv *uc_priv = dev_get_uclass_priv(dev);
+
+ if (!uc_priv->mounted)
+ return log_msg_ret("euu", -ENOTCONN);
+
+ isofs_close();
+ uc_priv->mounted = false;
+
+ return 0;
+}
+
+static int isofs_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(isofs_vfs_dir), path, &dir);
+ if (ret)
+ return log_msg_ret("eld", ret);
+
+ *dirp = dir;
+
+ return 0;
+}
+
+static const struct fs_ops isofs_vfs_ops = {
+ .mount = isofs_vfs_mount,
+ .unmount = isofs_vfs_unmount,
+ .lookup_dir = isofs_vfs_lookup_dir,
+};
+
+U_BOOT_DRIVER(isofs_fs) = {
+ .name = "isofs_fs",
+ .id = UCLASS_FS,
+ .ops = &isofs_vfs_ops,
+};
+
+/* isofs directory driver */
+
+static int isofs_dir_open(struct udevice *dev, struct fs_dir_stream *strm)
+{
+ struct dir_uc_priv *uc_priv = dev_get_uclass_priv(dev);
+ struct isofs_dir_priv *priv = dev_get_priv(dev);
+ struct fs_dir_stream *iso_strm;
+ const char *path;
+ int ret;
+
+ path = *uc_priv->path ? uc_priv->path : "/";
+ ret = isofs_opendir(path, &iso_strm);
+ if (ret)
+ return log_msg_ret("edo", ret);
+
+ priv->strm = iso_strm;
+
+ return 0;
+}
+
+static int isofs_dir_read(struct udevice *dev, struct fs_dir_stream *strm,
+ struct fs_dirent *dent)
+{
+ struct isofs_dir_priv *priv = dev_get_priv(dev);
+ struct fs_dirent *iso_dent;
+ int ret;
+
+ ret = isofs_readdir(priv->strm, &iso_dent);
+ if (ret)
+ return ret;
+
+ *dent = *iso_dent;
+
+ return 0;
+}
+
+static int isofs_dir_close(struct udevice *dev, struct fs_dir_stream *strm)
+{
+ struct isofs_dir_priv *priv = dev_get_priv(dev);
+
+ isofs_closedir(priv->strm);
+ priv->strm = NULL;
+
+ return 0;
+}
+
+/* isofs file driver */
+
+/**
+ * struct isofs_file_priv - Private info for isofs file devices
+ *
+ * @path: Full path within the isofs filesystem
+ */
+struct isofs_file_priv {
+ char path[FILE_MAX_PATH_LEN];
+};
+
+static ssize_t isofs_vfs_read_iter(struct udevice *dev, struct iov_iter *iter,
+ loff_t pos)
+{
+ struct isofs_file_priv *priv = dev_get_priv(dev);
+ loff_t actual;
+ int ret;
+
+ ret = isofs_read(priv->path, iter_iov_ptr(iter), pos,
+ iter_iov_avail(iter), &actual);
+ if (ret)
+ return log_msg_ret("efr", ret);
+ iter_advance(iter, actual);
+
+ return actual;
+}
+
+static struct file_ops isofs_file_ops = {
+ .read_iter = isofs_vfs_read_iter,
+};
+
+U_BOOT_DRIVER(isofs_vfs_file) = {
+ .name = "isofs_vfs_file",
+ .id = UCLASS_FILE,
+ .ops = &isofs_file_ops,
+ .priv_auto = sizeof(struct isofs_file_priv),
+};
+
+static int isofs_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);
+ char path[FILE_MAX_PATH_LEN];
+ struct isofs_file_priv *priv;
+ struct udevice *dev;
+ loff_t size = 0;
+ int ret;
+
+ /* ISO 9660 is read-only */
+ if (oflags != DIR_O_RDONLY)
+ return log_msg_ret("eow", -EROFS);
+
+ if (*uc_priv->path)
+ snprintf(path, sizeof(path), "%s/%s", uc_priv->path, leaf);
+ else
+ snprintf(path, sizeof(path), "/%s", leaf);
+
+ if (!isofs_exists(path))
+ return log_msg_ret("eoe", -ENOENT);
+ ret = isofs_size(path, &size);
+ if (ret)
+ return log_msg_ret("eos", ret);
+
+ ret = file_add_probe(dir, DM_DRIVER_REF(isofs_vfs_file), leaf,
+ size, oflags, &dev);
+ if (ret)
+ return log_msg_ret("eop", ret);
+
+ priv = dev_get_priv(dev);
+ strlcpy(priv->path, path, sizeof(priv->path));
+ *filp = dev;
+
+ return 0;
+}
+
+static struct dir_ops isofs_dir_vfs_ops = {
+ .open = isofs_dir_open,
+ .read = isofs_dir_read,
+ .close = isofs_dir_close,
+ .open_file = isofs_dir_open_file,
+};
+
+U_BOOT_DRIVER(isofs_vfs_dir) = {
+ .name = "isofs_vfs_dir",
+ .id = UCLASS_DIR,
+ .ops = &isofs_dir_vfs_ops,
+ .priv_auto = sizeof(struct isofs_dir_priv),
+};
@@ -492,7 +492,7 @@ root_found:
#ifdef CONFIG_JOLIET
if (joliet_level) {
- char *p = opt->iocharset ? opt->iocharset : CONFIG_NLS_DEFAULT;
+ char *p = opt->iocharset ? opt->iocharset : CFG_NLS_DEFAULT;
if (strcmp(p, "utf8") != 0) {
sbi->s_nls_iocharset = opt->iocharset ?
load_nls(opt->iocharset) : load_nls_default();
new file mode 100644
@@ -0,0 +1,623 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * U-Boot interface for isofs filesystem (Linux port)
+ *
+ * Copyright 2026 Simon Glass <sjg@chromium.org>
+ *
+ * This provides the interface between U-Boot's filesystem layer and
+ * the isofs driver (ISO 9660 CD-ROM filesystem).
+ */
+
+#include <blk.h>
+#include <fs.h>
+#include <fs_legacy.h>
+#include <part.h>
+#include <malloc.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+
+#include "isofs.h"
+
+/**
+ * struct isofs_state - global mount state for the isofs driver
+ *
+ * @blk_dev: Block device descriptor
+ * @partition: Partition info
+ * @sb: Superblock pointer
+ * @mounted: Whether a filesystem is currently mounted
+ */
+static struct isofs_state {
+ struct blk_desc *blk_dev;
+ struct disk_partition partition;
+ struct super_block *sb;
+ bool mounted;
+} ifs;
+
+/**
+ * struct isofs_dir_actor_ctx - Context for directory listing callback
+ * @ctx: Base dir_context (must be first)
+ * @print: Whether to print entries
+ */
+struct isofs_dir_actor_ctx {
+ struct dir_context ctx;
+ bool print;
+};
+
+/**
+ * struct isofs_dir - isofs directory stream state
+ * @parent: base fs_dir_stream structure
+ * @dirent: directory entry to return to caller
+ * @dir_inode: pointer to directory inode
+ * @file: file structure for readdir
+ * @entry_found: flag set by actor when entry is captured
+ */
+struct isofs_dir {
+ struct fs_dir_stream parent;
+ struct fs_dirent dirent;
+ struct inode *dir_inode;
+ struct file file;
+ bool entry_found;
+};
+
+struct isofs_readdir_ctx {
+ struct dir_context ctx;
+ struct isofs_dir *dir;
+};
+
+/**
+ * isofs_probe() - Mount an ISO 9660 filesystem
+ * @fs_dev_desc: Block device descriptor
+ * @fs_partition: Partition info
+ * Return: 0 on success, negative on error
+ */
+int isofs_probe(struct blk_desc *fs_dev_desc,
+ struct disk_partition *fs_partition)
+{
+ struct isofs_options *opt;
+ struct super_block *sb;
+ struct fs_context fc;
+ loff_t part_offset;
+ u8 *buf;
+ int ret;
+
+ if (!fs_dev_desc)
+ return -EINVAL;
+
+ /* Close any previous mount */
+ if (ifs.sb)
+ isofs_close();
+
+ /* Initialise inode cache */
+ ret = isofs_init_inodecache();
+ if (ret)
+ return ret;
+
+ /* Allocate super_block */
+ sb = kzalloc(sizeof(*sb), GFP_KERNEL);
+ if (!sb) {
+ ret = -ENOMEM;
+ goto err_destroy_cache;
+ }
+ INIT_LIST_HEAD(&sb->s_inodes);
+
+ /* Allocate block_device */
+ sb->s_bdev = kzalloc(sizeof(*sb->s_bdev), GFP_KERNEL);
+ if (!sb->s_bdev) {
+ ret = -ENOMEM;
+ goto err_free_sb;
+ }
+
+ /* Initialise super_block fields */
+ sb->s_bdev->bd_super = sb;
+ sb->s_bdev->bd_blk = fs_dev_desc->bdev;
+ sb->s_bdev->bd_part_start = fs_partition ? fs_partition->start : 0;
+ sb->s_blocksize = ISOFS_BLOCK_SIZE;
+ sb->s_blocksize_bits = ISOFS_BLOCK_BITS;
+ sb->s_flags = SB_RDONLY; /* ISO 9660 is always read-only */
+
+ /* Save device info */
+ ifs.blk_dev = fs_dev_desc;
+ if (fs_partition)
+ ifs.partition = *fs_partition;
+ else
+ memset(&ifs.partition, '\0', sizeof(ifs.partition));
+ ifs.mounted = true;
+
+ /* Read first sector to verify it's an ISO filesystem */
+ part_offset = fs_partition ?
+ (loff_t)fs_partition->start * fs_dev_desc->blksz : 0;
+
+ buf = malloc(ISOFS_BLOCK_SIZE);
+ if (!buf) {
+ ret = -ENOMEM;
+ goto err_free_bdev;
+ }
+
+ /*
+ * Read system area block 16 where the primary volume descriptor
+ * should be located.
+ */
+ if (blk_read(fs_dev_desc->bdev,
+ (part_offset + 16 * ISOFS_BLOCK_SIZE) / fs_dev_desc->blksz,
+ ISOFS_BLOCK_SIZE / fs_dev_desc->blksz, buf) !=
+ ISOFS_BLOCK_SIZE / fs_dev_desc->blksz) {
+ ret = -EIO;
+ goto err_free_buf;
+ }
+
+ /* Quick check for ISO standard ID "CD001" at offset 1 */
+ if (strncmp((char *)buf + 1, ISO_STANDARD_ID, 5)) {
+ ret = -EINVAL;
+ goto err_free_buf;
+ }
+ free(buf);
+
+ /* Set up mount options */
+ opt = kzalloc(sizeof(*opt), GFP_KERNEL);
+ if (!opt) {
+ ret = -ENOMEM;
+ goto err_free_bdev;
+ }
+
+ opt->map = 'n';
+ opt->rock = 1;
+ opt->joliet = 1;
+ opt->cruft = 0;
+ opt->hide = 0;
+ opt->showassoc = 0;
+ opt->check = 'u';
+ opt->nocompress = 0;
+ opt->blocksize = ISOFS_BLOCK_SIZE;
+ opt->fmode = ISOFS_INVALID_MODE;
+ opt->dmode = ISOFS_INVALID_MODE;
+ opt->uid_set = 0;
+ opt->gid_set = 0;
+ opt->gid = GLOBAL_ROOT_GID;
+ opt->uid = GLOBAL_ROOT_UID;
+ opt->iocharset = NULL;
+ opt->overriderockperm = 0;
+ opt->session = -1;
+ opt->sbsector = -1;
+
+ /* Set up fs_context */
+ memset(&fc, '\0', sizeof(fc));
+ fc.fs_private = opt;
+ fc.sb_flags = SB_RDONLY;
+
+ /* Mount the filesystem */
+ ret = isofs_fill_super(sb, &fc);
+ kfree(opt);
+ if (ret) {
+ printf("isofs: mount failed: %d\n", ret);
+ goto err_free_bdev;
+ }
+
+ ifs.sb = sb;
+ return 0;
+
+err_free_buf:
+ free(buf);
+err_free_bdev:
+ kfree(sb->s_bdev);
+err_free_sb:
+ kfree(sb);
+err_destroy_cache:
+ isofs_destroy_inodecache();
+ ifs.mounted = false;
+
+ return ret;
+}
+
+/**
+ * isofs_close() - Unmount the ISO 9660 filesystem
+ */
+void isofs_close(void)
+{
+ struct super_block *sb = ifs.sb;
+
+ if (!sb)
+ return;
+
+ /* Free all inodes */
+ isofs_free_inodes(sb);
+
+ /* Free root dentry */
+ kfree(sb->s_root);
+ sb->s_root = NULL;
+
+ /* Free sb_info */
+ kfree(sb->s_fs_info);
+ sb->s_fs_info = NULL;
+
+ /* Clear cached buffers for this device before freeing it */
+ bh_cache_clear(sb->s_bdev);
+
+ /* Free block device */
+ kfree(sb->s_bdev);
+ kfree(sb);
+
+ ifs.sb = NULL;
+
+ /* Destroy inode cache */
+ isofs_destroy_inodecache();
+
+ ifs.blk_dev = NULL;
+ ifs.mounted = false;
+}
+
+/**
+ * isofs_resolve_path() - Resolve a path to an inode
+ *
+ * This duplicates ext4l_resolve_path_internal(). Consider refactoring
+ * into a shared vfs_resolve_path() in linux_fs.c with a lookup callback.
+ *
+ * @path: Path to resolve
+ * @inodep: Output inode pointer
+ * Return: 0 on success, negative on error
+ */
+static int isofs_resolve_path(const char *path, struct inode **inodep)
+{
+ char *path_copy, *component, *next_component;
+ struct dentry *dentry, *result;
+ struct inode *dir;
+ int ret;
+
+ if (!ifs.mounted)
+ return -ENODEV;
+
+ dir = ifs.sb->s_root->d_inode;
+
+ if (!path || !*path || !strcmp(path, "/")) {
+ *inodep = dir;
+ return 0;
+ }
+
+ path_copy = strdup(path);
+ if (!path_copy)
+ return -ENOMEM;
+
+ component = path_copy;
+ if (*component == '/')
+ component++;
+
+ while (component && *component) {
+ next_component = strchr(component, '/');
+ if (next_component) {
+ *next_component = '\0';
+ next_component++;
+ }
+
+ if (!*component || !strcmp(component, ".")) {
+ component = next_component;
+ continue;
+ }
+
+ dentry = kzalloc(sizeof(*dentry), GFP_KERNEL);
+ if (!dentry) {
+ ret = -ENOMEM;
+ goto out_free;
+ }
+
+ dentry->d_name.name = component;
+ dentry->d_name.len = strlen(component);
+ dentry->d_sb = ifs.sb;
+ dentry->d_parent = NULL;
+
+ result = isofs_lookup(dir, dentry, 0);
+
+ if (IS_ERR(result)) {
+ ret = PTR_ERR(result);
+ kfree(dentry);
+ goto out_free;
+ }
+
+ if (result) {
+ if (!result->d_inode) {
+ if (result != dentry)
+ kfree(dentry);
+ kfree(result);
+ ret = -ENOENT;
+ goto out_free;
+ }
+ dir = result->d_inode;
+ if (result != dentry)
+ kfree(dentry);
+ kfree(result);
+ } else {
+ if (!dentry->d_inode) {
+ kfree(dentry);
+ ret = -ENOENT;
+ goto out_free;
+ }
+ dir = dentry->d_inode;
+ kfree(dentry);
+ }
+
+ if (!dir) {
+ ret = -ENOENT;
+ goto out_free;
+ }
+
+ /*
+ * Follow symlinks (Rock Ridge). Read the link target and
+ * resolve recursively. Limit depth to prevent loops.
+ */
+ if (S_ISLNK(dir->i_mode)) {
+ /* Symlinks not yet implemented for isofs */
+ ret = -ENOENT;
+ goto out_free;
+ }
+
+ component = next_component;
+ }
+
+ *inodep = dir;
+ ret = 0;
+
+out_free:
+ free(path_copy);
+
+ return ret;
+}
+
+static int isofs_dir_actor(struct dir_context *ctx, const char *name,
+ int namelen, loff_t offset, u64 ino,
+ unsigned int d_type)
+{
+ char namebuf[256];
+
+ if (namelen >= sizeof(namebuf))
+ namelen = sizeof(namebuf) - 1;
+ memcpy(namebuf, name, namelen);
+ namebuf[namelen] = '\0';
+
+ if (d_type == DT_DIR)
+ printf(" %s/\n", namebuf);
+ else if (d_type == DT_LNK)
+ printf(" <SYM> %s\n", namebuf);
+ else
+ printf(" %s\n", namebuf);
+
+ return 0;
+}
+
+int isofs_ls(const char *dirname)
+{
+ struct dir_context ctx;
+ struct inode *dir;
+ struct file file;
+ int ret;
+
+ ret = isofs_resolve_path(dirname, &dir);
+ if (ret)
+ return ret;
+
+ if (!S_ISDIR(dir->i_mode))
+ return -ENOTDIR;
+
+ memset(&file, '\0', sizeof(file));
+ file.f_inode = dir;
+ file.f_mapping = dir->i_mapping;
+
+ memset(&ctx, '\0', sizeof(ctx));
+ ctx.actor = isofs_dir_actor;
+
+ /* Call isofs_readdir via the file operations */
+ if (dir->i_fop && dir->i_fop->iterate_shared)
+ ret = dir->i_fop->iterate_shared(&file, &ctx);
+ else
+ ret = -ENOTDIR;
+
+ return ret;
+}
+
+int isofs_exists(const char *filename)
+{
+ struct inode *inode;
+
+ if (!filename)
+ return 0;
+
+ if (isofs_resolve_path(filename, &inode))
+ return 0;
+
+ return 1;
+}
+
+int isofs_size(const char *filename, loff_t *sizep)
+{
+ struct inode *inode;
+ int ret;
+
+ ret = isofs_resolve_path(filename, &inode);
+ if (ret)
+ return ret;
+
+ *sizep = inode->i_size;
+
+ return 0;
+}
+
+int isofs_read(const char *filename, void *buf, loff_t offset, loff_t len,
+ loff_t *actread)
+{
+ uint copy_len, blk_off, blksize;
+ loff_t bytes_left, file_size;
+ struct buffer_head *bh;
+ struct inode *inode;
+ sector_t block;
+ char *dst;
+ int ret;
+
+ *actread = 0;
+
+ ret = isofs_resolve_path(filename, &inode);
+ if (ret) {
+ printf("** File not found %s **\n", filename);
+ return ret;
+ }
+
+ file_size = inode->i_size;
+ if (offset >= file_size)
+ return 0;
+
+ /* If len is 0, read the whole file from offset */
+ if (!len)
+ len = file_size - offset;
+
+ /* Clamp to file size */
+ if (offset + len > file_size)
+ len = file_size - offset;
+
+ blksize = inode->i_sb->s_blocksize;
+ bytes_left = len;
+ dst = buf;
+
+ while (bytes_left > 0) {
+ /* Calculate logical block number and offset within block */
+ block = offset / blksize;
+ blk_off = offset % blksize;
+
+ /* Read the block using isofs_bread (handles block mapping) */
+ bh = isofs_bread(inode, block);
+ if (!bh)
+ return -EIO;
+
+ /* Calculate how much to copy from this block */
+ copy_len = blksize - blk_off;
+ if (copy_len > bytes_left)
+ copy_len = bytes_left;
+
+ memcpy(dst, bh->b_data + blk_off, copy_len);
+ brelse(bh);
+
+ dst += copy_len;
+ offset += copy_len;
+ bytes_left -= copy_len;
+ *actread += copy_len;
+ }
+
+ return 0;
+}
+
+static int isofs_opendir_actor(struct dir_context *ctx, const char *name,
+ int namelen, loff_t offset, u64 ino,
+ unsigned int d_type)
+{
+ struct isofs_readdir_ctx *rctx;
+ struct dentry de, *result;
+ struct fs_dirent *dent;
+ struct isofs_dir *dir;
+
+ rctx = container_of(ctx, struct isofs_readdir_ctx, ctx);
+ dir = rctx->dir;
+ dent = &dir->dirent;
+
+ namelen = min(namelen, (int)FS_DIRENT_NAME_LEN - 1);
+ memcpy(dent->name, name, namelen);
+ dent->name[namelen] = '\0';
+
+ dent->size = 0;
+ dent->type = FS_DT_REG;
+
+ /* "." and ".." are always directories */
+ if (namelen <= 2 && name[0] == '.') {
+ dent->type = FS_DT_DIR;
+ } else {
+ /* Look up the entry to get its inode for size and type */
+ memset(&de, '\0', sizeof(de));
+ de.d_name.name = (const unsigned char *)dent->name;
+ de.d_name.len = namelen;
+ de.d_sb = dir->dir_inode->i_sb;
+
+ result = isofs_lookup(dir->dir_inode, &de, 0);
+ if (!IS_ERR_OR_NULL(result) && result->d_inode) {
+ dent->size = result->d_inode->i_size;
+ if (S_ISDIR(result->d_inode->i_mode))
+ dent->type = FS_DT_DIR;
+ else if (S_ISLNK(result->d_inode->i_mode))
+ dent->type = FS_DT_LNK;
+ } else if (de.d_inode) {
+ dent->size = de.d_inode->i_size;
+ if (S_ISDIR(de.d_inode->i_mode))
+ dent->type = FS_DT_DIR;
+ else if (S_ISLNK(de.d_inode->i_mode))
+ dent->type = FS_DT_LNK;
+ }
+ }
+
+ dir->entry_found = true;
+
+ /* Return non-zero to stop after one entry */
+ return 1;
+}
+
+int isofs_opendir(const char *filename, struct fs_dir_stream **dirsp)
+{
+ struct isofs_dir *dir;
+ struct inode *inode;
+ int ret;
+
+ if (!ifs.mounted)
+ return -ENODEV;
+
+ ret = isofs_resolve_path(filename, &inode);
+ if (ret)
+ return ret;
+
+ if (!S_ISDIR(inode->i_mode))
+ return -ENOTDIR;
+
+ dir = calloc(1, sizeof(*dir));
+ if (!dir)
+ return -ENOMEM;
+
+ dir->dir_inode = inode;
+ dir->file.f_inode = inode;
+ dir->file.f_mapping = inode->i_mapping;
+
+ *dirsp = (struct fs_dir_stream *)dir;
+
+ return 0;
+}
+
+int isofs_readdir(struct fs_dir_stream *dirs, struct fs_dirent **dentp)
+{
+ struct isofs_dir *dir = container_of(dirs, struct isofs_dir, parent);
+ struct isofs_readdir_ctx rctx;
+ int ret;
+
+ if (!ifs.mounted)
+ return -ENODEV;
+
+ memset(&dir->dirent, '\0', sizeof(dir->dirent));
+ dir->entry_found = false;
+
+ memset(&rctx, '\0', sizeof(rctx));
+ rctx.ctx.actor = isofs_opendir_actor;
+ rctx.ctx.pos = dir->file.f_pos;
+ rctx.dir = dir;
+
+ if (dir->dir_inode->i_fop && dir->dir_inode->i_fop->iterate_shared)
+ ret = dir->dir_inode->i_fop->iterate_shared(&dir->file,
+ &rctx.ctx);
+ else
+ ret = -ENOTDIR;
+
+ dir->file.f_pos = rctx.ctx.pos;
+
+ if (ret < 0)
+ return ret;
+
+ if (!dir->entry_found)
+ return -ENOENT;
+
+ *dentp = &dir->dirent;
+
+ return 0;
+}
+
+void isofs_closedir(struct fs_dir_stream *dirs)
+{
+ free(dirs);
+}
new file mode 100644
@@ -0,0 +1,308 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * U-Boot compatibility header for isofs filesystem
+ *
+ * Copyright 2026 Simon Glass <sjg@chromium.org>
+ *
+ * This provides minimal definitions to allow Linux isofs code to compile
+ * in U-Boot. The isofs driver is read-only and much simpler than ext4,
+ * so the compatibility layer is lighter.
+ */
+
+#ifndef __ISOFS_UBOOT_H__
+#define __ISOFS_UBOOT_H__
+
+/*
+ * Suppress warnings for unused static functions and variables in Linux isofs
+ * source files.
+ */
+#pragma GCC diagnostic ignored "-Wunused-function"
+#pragma GCC diagnostic ignored "-Wunused-variable"
+
+/* U-Boot headers */
+#include <memalign.h>
+#include <vsprintf.h>
+
+/* Linux types - must come first */
+#include <linux/types.h>
+
+/* Linux headers */
+#include <asm/byteorder.h>
+#include <asm-generic/unaligned.h>
+#include <asm-generic/atomic.h>
+#include <linux/bitops.h>
+#include <linux/bug.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/list.h>
+#include <linux/log2.h>
+#include <linux/nls.h>
+#include <linux/rcupdate.h>
+#include <linux/slab.h>
+#include <linux/stat.h>
+#include <linux/string.h>
+#include <linux/time.h>
+#include <linux/buffer_head.h>
+#include <linux/cred.h>
+#include <linux/ctype.h>
+#include <linux/dcache.h>
+#include <linux/exportfs.h>
+#include <linux/fs_context.h>
+#include <linux/fs_parser.h>
+#include <linux/module.h>
+#include <linux/pagemap.h>
+#include <linux/seq_file.h>
+#include <linux/statfs.h>
+
+/*
+ * Suppress printk messages from Linux isofs code. In Linux these are
+ * filtered by log level; in U-Boot printk maps to printf for all levels.
+ * The isofs code uses printk only for debug and error messages that are
+ * not useful in U-Boot's context.
+ */
+#undef printk
+#define printk(fmt, ...) ({ if (0) printf(fmt, ##__VA_ARGS__); 0; })
+
+/* Page allocation wrappers */
+#ifndef __get_free_page
+#define __get_free_page(gfp) \
+ ((unsigned long)memalign(PAGE_SIZE, PAGE_SIZE))
+#endif
+void free_page(unsigned long addr);
+
+#define alloc_page(gfp) \
+ ((struct page *)memalign(PAGE_SIZE, PAGE_SIZE))
+#define __free_page(page) free(page)
+
+/* Joliet support */
+#ifndef CFG_NLS_DEFAULT
+#define CFG_NLS_DEFAULT "utf8"
+#endif
+
+/* CDROM support stubs - not available in U-Boot */
+struct cdrom_device_info;
+
+static inline struct cdrom_device_info *disk_to_cdi(void *disk)
+{
+ return NULL;
+}
+
+#define CDROM_LBA 0
+#define CDROM_DATA_TRACK 4
+
+struct cdrom_tocentry {
+ int cdte_track;
+ int cdte_format;
+ struct { int lba; } cdte_addr;
+ int cdte_ctrl;
+};
+
+struct cdrom_multisession {
+ int addr_format;
+ int xa_flag;
+ struct { int lba; } addr;
+};
+
+static inline int cdrom_read_tocentry(struct cdrom_device_info *cdi,
+ struct cdrom_tocentry *te)
+{
+ return -ENODEV;
+}
+
+static inline int cdrom_multisession(struct cdrom_device_info *cdi,
+ struct cdrom_multisession *ms)
+{
+ return -ENODEV;
+}
+
+/* set_default_d_op - no dentry ops needed in U-Boot */
+static inline void set_default_d_op(struct super_block *sb,
+ const struct dentry_operations *ops) { }
+
+/* inode_nohighmem stub */
+static inline void inode_nohighmem(struct inode *inode) { }
+
+/* generic_ro_fops, page_symlink_inode_operations - defined in linux/fs.h */
+
+/* generic_setlease - lease management stub */
+struct file_lease;
+static inline int generic_setlease(struct file *fp, int arg,
+ struct file_lease **flp, void **priv)
+{
+ return -EINVAL;
+}
+
+/* init_special_inode - override the macro from fs.h */
+#undef init_special_inode
+static inline void init_special_inode(struct inode *inode, umode_t mode,
+ dev_t rdev)
+{
+ inode->i_mode = mode;
+ inode->i_rdev = rdev;
+}
+
+/* User namespace helpers for mount options */
+static inline uid_t from_kuid_munged(void *ns, kuid_t uid)
+{
+ return uid.val;
+}
+
+static inline gid_t from_kgid_munged(void *ns, kgid_t gid)
+{
+ return gid.val;
+}
+
+/* gendisk is forward-declared in linux/blkdev.h */
+
+/*
+ * mpage stubs - isofs uses mpage_read_folio/mpage_readahead for
+ * regular file I/O. We stub these since file reading goes through
+ * the interface layer directly.
+ */
+static inline int mpage_read_folio(struct folio *folio,
+ get_block_t *get_block)
+{
+ return -EIO;
+}
+
+static inline void mpage_readahead(struct readahead_control *rac,
+ get_block_t *get_block) { }
+
+/* generic_block_bmap stub */
+static inline sector_t generic_block_bmap(struct address_space *mapping,
+ sector_t block,
+ get_block_t *get_block)
+{
+ return 0;
+}
+
+/* huge_encode_dev - encode device for statfs */
+static inline u64 huge_encode_dev(dev_t dev)
+{
+ return dev;
+}
+
+/* u64_to_fsid - convert u64 to kernel_fsid_t */
+static inline __kernel_fsid_t u64_to_fsid(u64 v)
+{
+ __kernel_fsid_t fsid;
+
+ fsid.val[0] = v;
+ fsid.val[1] = v >> 32;
+ return fsid;
+}
+
+/* NAME_MAX */
+#ifndef NAME_MAX
+#define NAME_MAX 255
+#endif
+
+/* S_IXUGO */
+#ifndef S_IXUGO
+#define S_IXUGO (S_IXUSR | S_IXGRP | S_IXOTH)
+#endif
+
+/* FILEID_INVALID for export.c */
+#ifndef FILEID_INVALID
+#define FILEID_INVALID 0xff
+#endif
+
+/* page_address - for page used as buffer */
+static inline void *page_address(void *page)
+{
+ return page;
+}
+
+/* folio_address is defined in linux/pagemap.h */
+/* folio_end_read is defined as a macro in linux/pagemap.h */
+
+/* Dentry operations - needed by isofs for case-insensitive lookup */
+struct dentry_operations {
+ int (*d_hash)(const struct dentry *dentry, struct qstr *qstr);
+ int (*d_compare)(const struct dentry *dentry, unsigned int len,
+ const char *str, const struct qstr *name);
+};
+
+/* Name hash functions for dentry operations */
+static inline unsigned long init_name_hash(const struct dentry *dentry)
+{
+ return 0;
+}
+
+static inline unsigned long partial_name_hash(unsigned long c,
+ unsigned long prevhash)
+{
+ return (prevhash + (c << 4) + (c >> 4)) * 11;
+}
+
+static inline unsigned long end_name_hash(unsigned long hash)
+{
+ return (unsigned int)hash;
+}
+
+static inline unsigned int full_name_hash(const struct dentry *dentry,
+ const char *name, unsigned int len)
+{
+ unsigned long hash = init_name_hash(dentry);
+
+ while (len--)
+ hash = partial_name_hash((unsigned char)*name++, hash);
+ return end_name_hash(hash);
+}
+
+/* generic_file_llseek - stub for directory operations */
+static inline loff_t generic_file_llseek(struct file *file, loff_t offset,
+ int whence)
+{
+ return offset;
+}
+
+/* isofs_close - forward declaration for interface.c */
+void isofs_close(void);
+
+/*
+ * iget5_locked - isofs uses custom test/set callbacks for inode lookup.
+ * Implemented in support.c.
+ */
+struct inode *iget5_locked(struct super_block *sb, unsigned long hashval,
+ int (*test)(struct inode *, void *),
+ int (*set)(struct inode *, void *), void *data);
+
+/* iget_failed - override the macro from fs.h */
+#undef iget_failed
+void iget_failed(struct inode *inode);
+
+/*
+ * d_obtain_alias - override the stub macro from dcache.h.
+ * Needed by export.c for NFS file handle lookup.
+ */
+#undef d_obtain_alias
+static inline struct dentry *d_obtain_alias(struct inode *inode)
+{
+ struct dentry *d;
+
+ if (IS_ERR(inode))
+ return ERR_CAST(inode);
+ d = kzalloc(sizeof(*d), GFP_KERNEL);
+ if (!d) {
+ iput(inode);
+ return ERR_PTR(-ENOMEM);
+ }
+ d->d_inode = inode;
+ return d;
+}
+
+/* dir_emit, dir_emit_dot, dir_emit_dotdot - defined in linux/fs.h */
+
+/* sb_bread, brelse, etc. are now in fs/linux_fs.c */
+struct buffer_head *sb_bread(struct super_block *sb, sector_t block);
+void bh_cache_clear(struct block_device *bdev);
+void isofs_free_inodes(struct super_block *sb);
+
+/* isofs inode.c exports */
+int isofs_fill_super(struct super_block *s, struct fs_context *fc);
+int isofs_init_inodecache(void);
+void isofs_destroy_inodecache(void);
+
+#endif /* __ISOFS_UBOOT_H__ */
new file mode 100644
@@ -0,0 +1,85 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Internal support functions for isofs filesystem
+ *
+ * Copyright 2026 Simon Glass <sjg@chromium.org>
+ *
+ * This provides isofs-specific support functions: iget5_locked() with
+ * custom test/set callbacks, and inode management.
+ *
+ * Common VFS functions (buffer cache, block I/O, brelse, dir_emit, etc.)
+ * are provided by fs/linux_fs.c.
+ */
+
+#include <malloc.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+
+#include "isofs.h"
+
+/**
+ * iget5_locked() - Get an inode with custom test/set callbacks
+ * @sb: Superblock
+ * @hashval: Hash value (unused in U-Boot)
+ * @test: Test function to check if inode matches
+ * @set: Set function to initialise inode data
+ * @data: Opaque data passed to test/set
+ *
+ * In U-Boot we always allocate a new inode since we don't cache them.
+ */
+struct inode *iget5_locked(struct super_block *sb, unsigned long hashval,
+ int (*test)(struct inode *, void *),
+ int (*set)(struct inode *, void *), void *data)
+{
+ struct iso_inode_info *ei;
+ struct inode *inode;
+
+ ei = kzalloc(sizeof(*ei), GFP_KERNEL);
+ if (!ei)
+ return NULL;
+
+ inode = &ei->vfs_inode;
+ memset(inode, '\0', sizeof(*inode));
+ inode->i_sb = sb;
+ inode->i_blkbits = sb->s_blocksize_bits;
+ inode->i_state = I_NEW;
+ inode->i_count.counter = 1;
+ inode->i_mapping = &inode->i_data;
+ inode->i_data.host = inode;
+ INIT_LIST_HEAD(&inode->i_sb_list);
+
+ if (set)
+ set(inode, data);
+
+ list_add(&inode->i_sb_list, &sb->s_inodes);
+
+ return inode;
+}
+
+/**
+ * iget_failed() - Mark inode as failed and release
+ * @inode: Inode that failed to initialise
+ */
+void iget_failed(struct inode *inode)
+{
+ if (!inode)
+ return;
+ list_del_init(&inode->i_sb_list);
+ kfree(ISOFS_I(inode));
+}
+
+/**
+ * isofs_free_inodes() - Free all inodes on the superblock list
+ * @sb: Superblock whose inodes to free
+ */
+void isofs_free_inodes(struct super_block *sb)
+{
+ while (!list_empty(&sb->s_inodes)) {
+ struct inode *inode;
+
+ inode = list_first_entry(&sb->s_inodes,
+ struct inode, i_sb_list);
+ list_del_init(&inode->i_sb_list);
+ kfree(ISOFS_I(inode));
+ }
+}
@@ -22,6 +22,7 @@ enum fs_type_t {
FS_TYPE_SEMIHOSTING,
FS_TYPE_EXFAT,
FS_TYPE_VIRTIO,
+ FS_TYPE_ISO,
};
/*
new file mode 100644
@@ -0,0 +1,93 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Public interface for the isofs (ISO 9660) filesystem driver
+ *
+ * Copyright 2026 Simon Glass <sjg@chromium.org>
+ */
+
+#ifndef __ISOFS_H__
+#define __ISOFS_H__
+
+#include <blk.h>
+#include <part.h>
+#include <fs.h>
+
+/**
+ * isofs_probe() - Mount an ISO 9660 filesystem
+ *
+ * @fs_dev_desc: Block device descriptor
+ * @fs_partition: Partition info (can be NULL for whole disk)
+ * Return: 0 on success, negative on error
+ */
+int isofs_probe(struct blk_desc *fs_dev_desc,
+ struct disk_partition *fs_partition);
+
+/**
+ * isofs_close() - Unmount the ISO 9660 filesystem
+ */
+void isofs_close(void);
+
+/**
+ * isofs_ls() - List files in a directory
+ *
+ * @dirname: Directory path
+ * Return: 0 on success, negative on error
+ */
+int isofs_ls(const char *dirname);
+
+/**
+ * isofs_exists() - Check if a file exists
+ *
+ * @filename: File path
+ * Return: 1 if file exists, 0 if not
+ */
+int isofs_exists(const char *filename);
+
+/**
+ * isofs_size() - Get the size of a file
+ *
+ * @filename: File path
+ * @sizep: Output file size
+ * Return: 0 on success, negative on error
+ */
+int isofs_size(const char *filename, loff_t *sizep);
+
+/**
+ * isofs_read() - Read data from a file
+ *
+ * @filename: File path
+ * @buf: Output buffer
+ * @offset: Byte offset to read from
+ * @len: Number of bytes to read (0 = entire file)
+ * @actread: Output actual bytes read
+ * Return: 0 on success, negative on error
+ */
+int isofs_read(const char *filename, void *buf, loff_t offset, loff_t len,
+ loff_t *actread);
+
+/**
+ * isofs_opendir() - Open a directory for iteration
+ *
+ * @filename: Directory path
+ * @dirsp: Output directory stream pointer
+ * Return: 0 on success, negative on error
+ */
+int isofs_opendir(const char *filename, struct fs_dir_stream **dirsp);
+
+/**
+ * isofs_readdir() - Read next directory entry
+ *
+ * @dirs: Directory stream from isofs_opendir()
+ * @dentp: Output directory entry pointer
+ * Return: 0 on success, -ENOENT at end, negative on error
+ */
+int isofs_readdir(struct fs_dir_stream *dirs, struct fs_dirent **dentp);
+
+/**
+ * isofs_closedir() - Close a directory stream
+ *
+ * @dirs: Directory stream to close
+ */
+void isofs_closedir(struct fs_dir_stream *dirs);
+
+#endif /* __ISOFS_H__ */
@@ -72,6 +72,8 @@ static inline void *kzalloc(size_t size, gfp_t flags)
return kmalloc(size, flags | __GFP_ZERO);
}
+#define kzalloc_obj(obj, ...) kzalloc(sizeof(obj), GFP_KERNEL)
+
static inline void *kmalloc_array(size_t n, size_t size, gfp_t flags)
{
if (size != 0 && n > SIZE_MAX / size)