Implement the VFS interface for the 9P filesystem. Map VFS operations to the underlying 9P protocol client and register the 9P filesystem type, allowing access without traditional block device partition tables.
Signed-off-by: Kuan-Wei Chiu <[email protected]> --- Changes in v2: - Add pseudo block device "9p" handling in disk/part.c. - Look up 9P transport device via DM UCLASS_9P by mount tag or index. - Add p9_client_clunk() calls on open failure. - Dynamically allocate readdir buffer and implement pagination loop. - Propagate return error code in p9_fs_read(). --- disk/part.c | 16 +++++ fs/9p/9p.c | 185 +++++++++++++++++++++++++++++++++++++++++++++++++ fs/9p/Kconfig | 8 +++ fs/9p/Makefile | 5 ++ fs/Kconfig | 2 + fs/Makefile | 1 + fs/fs.c | 25 +++++++ include/9pfs.h | 60 ++++++++++++++++ include/fs.h | 1 + 9 files changed, 303 insertions(+) create mode 100644 fs/9p/9p.c create mode 100644 fs/9p/Kconfig create mode 100644 fs/9p/Makefile create mode 100644 include/9pfs.h diff --git a/disk/part.c b/disk/part.c index 4923dc44593..8fa1dfd4c19 100644 --- a/disk/part.c +++ b/disk/part.c @@ -482,6 +482,22 @@ int blk_get_device_part_str(const char *ifname, const char *dev_part_str, } #endif +#if IS_ENABLED(CONFIG_FS_9P) + /* + * Special-case a pseudo block device "9p", to allow access to the + * 9P filesystem. + */ + if (!strcmp(ifname, "9p")) { + strcpy((char *)info->type, BOOT_PART_TYPE); + if (dev_part_str) + strlcpy((char *)info->name, dev_part_str, sizeof(info->name)); + else + strcpy((char *)info->name, "9P filesystem"); + + return 0; + } +#endif + #if IS_ENABLED(CONFIG_CMD_UBIFS) && !IS_ENABLED(CONFIG_XPL_BUILD) /* * Special-case ubi, ubi goes through a mtd, rather than through diff --git a/fs/9p/9p.c b/fs/9p/9p.c new file mode 100644 index 00000000000..41d46b7328e --- /dev/null +++ b/fs/9p/9p.c @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026, Kuan-Wei Chiu <[email protected]> + */ + +#include <9p.h> +#include <9pfs.h> +#include <dm.h> +#include <log.h> +#include <malloc.h> +#include <part.h> +#include <asm/unaligned.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/types.h> + +static u32 p9_root_fid = 1; +static struct p9_client *current_p9_client; + +int p9_fs_set_blk_dev(struct blk_desc *fs_dev_desc, struct disk_partition *fs_partition) +{ + const char *tag = NULL; + struct udevice *dev; + struct p9_client *c; + int ret; + + if (fs_partition && fs_partition->name[0]) + tag = (const char *)fs_partition->name; + + ret = p9_get_device(tag, &dev); + if (ret) { + log_err("9P Error: No matching 9P transport device found for '%s'\n", + tag ? tag : "-"); + return ret; + } + + c = p9_get_client(dev); + if (!c) + return -ENODEV; + + if (p9_client_version(c)) + return -EIO; + if (p9_client_attach(c, p9_root_fid, "root", "")) + return -EIO; + + current_p9_client = c; + return 0; +} + +int p9_fs_ls(const char *dirname) +{ + struct p9_client *c = current_p9_client; + u32 fid; + u8 *buf, *ptr, *end; + u32 actread; + u64 offset = 0; + int count = 0; + int ret; + + if (!c) + return -ENODEV; + + fid = p9_client_alloc_fid(c); + if (p9_client_walk(c, p9_root_fid, fid, dirname)) + return -ENOENT; + if (p9_client_lopen(c, fid, P9_O_RDONLY)) { + p9_client_clunk(c, fid); + return -EIO; + } + + buf = malloc(4096); + if (!buf) { + p9_client_clunk(c, fid); + return -ENOMEM; + } + + while (1) { + ret = p9_client_readdir(c, fid, offset, 4096, buf, &actread); + if (ret || actread == 0) + break; + + ptr = buf; + end = buf + actread; + while (ptr + sizeof(struct p9_dirent_hdr) <= end) { + struct p9_dirent_hdr *entry = (struct p9_dirent_hdr *)ptr; + u64 next_offset = get_unaligned_le64(&entry->offset); + u8 d_type = entry->type; + u16 name_len = get_unaligned_le16(&entry->name_len); + const char *name = (const char *)(entry + 1); + + if (ptr + sizeof(struct p9_dirent_hdr) + name_len > end) + break; + printf(" %s %.*s\n", (d_type == P9_DT_DIR) ? "<DIR> " : " ", + name_len, name); + offset = next_offset; + ptr += sizeof(struct p9_dirent_hdr) + name_len; + count++; + } + } + + free(buf); + p9_client_clunk(c, fid); + + if (ret) + return ret; + + if (count == 0) + printf(" (empty directory)\n"); + + return 0; +} + +int p9_fs_size(const char *filename, loff_t *size) +{ + struct p9_client *c = current_p9_client; + u32 fid; + int ret; + + if (!c) + return -ENODEV; + + fid = p9_client_alloc_fid(c); + if (p9_client_walk(c, p9_root_fid, fid, filename)) + return -ENOENT; + ret = p9_client_stat(c, fid, size); + p9_client_clunk(c, fid); + + return ret; +} + +int p9_fs_exists(const char *filename) +{ + loff_t size; + + return (p9_fs_size(filename, &size) == 0) ? 1 : 0; +} + +int p9_fs_read(const char *filename, void *buf, loff_t offset, loff_t len, loff_t *actread) +{ + struct p9_client *c = current_p9_client; + u32 fid; + u32 read_bytes = 0; + loff_t total_read = 0; + loff_t remaining; + u32 req_len; + u8 *dst = (u8 *)buf; + int ret = 0; + + if (!c) + return -ENODEV; + + fid = p9_client_alloc_fid(c); + if (p9_client_walk(c, p9_root_fid, fid, filename)) + return -ENOENT; + if (p9_client_lopen(c, fid, P9_O_RDONLY)) { + p9_client_clunk(c, fid); + return -EIO; + } + + while (1) { + if (len == 0) { + req_len = 0; + } else { + remaining = len - total_read; + if (remaining == 0) + break; + + req_len = (remaining > 0xFFFFFFFF) ? 0xFFFFFFFF : (u32)remaining; + } + + ret = p9_client_read(c, fid, offset, req_len, dst, &read_bytes); + + if (ret != 0 || read_bytes == 0) + break; + + total_read += read_bytes; + offset += read_bytes; + dst += read_bytes; + } + + *actread = total_read; + p9_client_clunk(c, fid); + + return ret ? ret : 0; +} diff --git a/fs/9p/Kconfig b/fs/9p/Kconfig new file mode 100644 index 00000000000..bc6d4962288 --- /dev/null +++ b/fs/9p/Kconfig @@ -0,0 +1,8 @@ +config FS_9P + bool "9P filesystem support" + depends on NET_9P + help + This provides support for the 9P2000.L filesystem. + + To use this filesystem, you also need to enable a 9P + transport driver. diff --git a/fs/9p/Makefile b/fs/9p/Makefile new file mode 100644 index 00000000000..c7f7553e8f4 --- /dev/null +++ b/fs/9p/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2026, Kuan-Wei Chiu <[email protected]> + +obj-y += 9p.o diff --git a/fs/Kconfig b/fs/Kconfig index e0b0b901e1d..e4aa726ea35 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -26,4 +26,6 @@ source "fs/squashfs/Kconfig" source "fs/erofs/Kconfig" +source "fs/9p/Kconfig" + endmenu diff --git a/fs/Makefile b/fs/Makefile index ce5e74257a0..5aa34b39172 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -26,5 +26,6 @@ obj-$(CONFIG_CMD_UBIFS) += ubifs/ obj-$(CONFIG_CMD_ZFS) += zfs/ obj-$(CONFIG_FS_SQUASHFS) += squashfs/ obj-$(CONFIG_FS_EROFS) += erofs/ +obj-$(CONFIG_FS_9P) += 9p/ endif obj-y += fs_internal.o diff --git a/fs/fs.c b/fs/fs.c index 2824c7defa2..b18ccec8cb4 100644 --- a/fs/fs.c +++ b/fs/fs.c @@ -32,6 +32,7 @@ #include <squashfs.h> #include <erofs.h> #include <exfat.h> +#include <9pfs.h> static struct blk_desc *fs_dev_desc; static int fs_dev_part; @@ -400,6 +401,26 @@ static struct fstype_info fstypes[] = { .mkdir = exfat_fs_mkdir, .rename = exfat_fs_rename, }, +#endif +#if CONFIG_IS_ENABLED(FS_9P) + { + .fstype = FS_TYPE_9P, + .name = "9p", + .null_dev_desc_ok = true, + .probe = p9_fs_set_blk_dev, + .close = fs_close_unsupported, + .ls = p9_fs_ls, + .exists = p9_fs_exists, + .size = p9_fs_size, + .read = p9_fs_read, + .write = fs_write_unsupported, + .uuid = fs_uuid_unsupported, + .opendir = fs_opendir_unsupported, + .unlink = fs_unlink_unsupported, + .mkdir = fs_mkdir_unsupported, + .ln = fs_ln_unsupported, + .rename = fs_rename_unsupported, + }, #endif { .fstype = FS_TYPE_ANY, @@ -501,11 +522,15 @@ int fs_set_blk_dev(const char *ifname, const char *dev_part_str, int fstype) if (info) { fs_dev_desc = NULL; memset(&fs_partition, 0, sizeof(fs_partition)); + if (dev_part_str) + strlcpy((char *)fs_partition.name, dev_part_str, + sizeof(fs_partition.name)); if (!info->probe(NULL, &fs_partition)) { fs_type = info->fstype; fs_dev_part = 0; return 0; } + return -1; } part = part_get_info_by_dev_and_name_or_num(ifname, dev_part_str, &fs_dev_desc, diff --git a/include/9pfs.h b/include/9pfs.h new file mode 100644 index 00000000000..130cb06ac3c --- /dev/null +++ b/include/9pfs.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2026, Kuan-Wei Chiu <[email protected]> + */ + +#ifndef __9PFS_H__ +#define __9PFS_H__ + +#include <linux/types.h> + +struct blk_desc; +struct disk_partition; + +/** + * p9_fs_set_blk_dev() - Set/probe block device for 9P filesystem + * @fs_dev_desc: Block device descriptor (expected NULL for pseudo dev) + * @fs_partition: Disk partition information + * + * Return: 0 on success, negative error code on failure + */ +int p9_fs_set_blk_dev(struct blk_desc *fs_dev_desc, struct disk_partition *fs_partition); + +/** + * p9_fs_ls() - List files in a directory + * @dirname: Path to directory + * + * Return: 0 on success, negative error code on failure + */ +int p9_fs_ls(const char *dirname); + +/** + * p9_fs_exists() - Check if a file exists + * @filename: Path to file + * + * Return: 1 if file exists, 0 otherwise + */ +int p9_fs_exists(const char *filename); + +/** + * p9_fs_size() - Get size of a file + * @filename: Path to file + * @size: Output pointer for size + * + * Return: 0 on success, negative error code on failure + */ +int p9_fs_size(const char *filename, loff_t *size); + +/** + * p9_fs_read() - Read file content into memory + * @filename: Path to file + * @buf: Destination memory buffer + * @offset: Byte offset within file + * @len: Maximum bytes to read (0 for entire file) + * @actread: Output pointer for actual bytes read + * + * Return: 0 on success, negative error code on failure + */ +int p9_fs_read(const char *filename, void *buf, loff_t offset, loff_t len, loff_t *actread); + +#endif /* __9PFS_H__ */ diff --git a/include/fs.h b/include/fs.h index bec02117737..8afc81c0017 100644 --- a/include/fs.h +++ b/include/fs.h @@ -19,6 +19,7 @@ struct cmd_tbl; #define FS_TYPE_EROFS 7 #define FS_TYPE_SEMIHOSTING 8 #define FS_TYPE_EXFAT 9 +#define FS_TYPE_9P 10 struct blk_desc; -- 2.55.0.897.gb25b4bd76c-goog
