From c0767b4631aa9622c0c37acbc1dbae198be3f295 Mon Sep 17 00:00:00 2001 From: Marvin Friedrich Date: Sun, 25 Oct 2026 18:45:38 +0100 Subject: [PATCH] vfs: Add devtmpfs + memfiles --- dev/char/chardev.c | 82 +++++++++++++++++++++++++++++++ dev/char/memfiles.c | 102 +++++++++++++++++++++++++++++++++++++++ dev/char/meson.build | 4 ++ dev/meson.build | 1 + fs/devtmpfs.c | 64 ++++++++++++++++++++++++ fs/meson.build | 1 + include/zinnia/chardev.h | 46 ++++++++++++++++++ 7 files changed, 300 insertions(+) create mode 100644 dev/char/chardev.c create mode 100644 dev/char/memfiles.c create mode 100644 dev/char/meson.build create mode 100644 fs/devtmpfs.c create mode 100644 include/zinnia/chardev.h diff --git a/dev/char/chardev.c b/dev/char/chardev.c new file mode 100644 index 0000000..07352f9 --- /dev/null +++ b/dev/char/chardev.c @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: ISC + +#include + +void chardev_init( + struct chardev* dev, + const struct chardev_ops* ops, + const struct vfs_file_ops* file_ops, + uint32_t major, + uint32_t minor +) { + refcount_init(&dev->rc); + dev->ops = ops; + dev->file_ops = file_ops; + dev->dev = dev_make(major, minor); +} + +void chardev_ref(struct chardev* dev) { + refcount_inc(&dev->rc); +} + +void chardev_unref(struct chardev* dev) { + if (refcount_dec(&dev->rc) && dev->ops && dev->ops->destroy) + dev->ops->destroy(dev); +} + +static errno_t chardev_node_open(struct vfs_node* node, struct vfs_file* file) { + struct chardev* dev = node->priv; + file->ops = dev->file_ops; + if (dev->ops && dev->ops->open) + return dev->ops->open(dev, file); + return 0; +} + +static void chardev_node_destroy(struct vfs_node* node) { + chardev_unref(node->priv); +} + +static const struct vfs_node_ops chardev_node_ops = { + .destroy = chardev_node_destroy, + .device = { + .open = chardev_node_open, + }, +}; + +errno_t devtmpfs_add_chardev(const char* name, struct chardev* dev, mode_t mode) { + struct vfs_mknod_target target = { + .type = VNODE_CHARDEV, + .ops = &chardev_node_ops, + .priv = dev, + .rdev = dev->dev, + }; + + chardev_ref(dev); + struct vfs_path root = devtmpfs_root(); + errno_t status = vfs_mknod(root, root, name, mode, &target, ident_kernel(), nullptr); + vfs_path_drop(root); + if (status) + chardev_unref(dev); + return status; +} + +errno_t devtmpfs_mkdir(const char* name, mode_t mode) { + struct vfs_path root = devtmpfs_root(); + errno_t status = vfs_mkdir(root, root, name, mode, ident_kernel()); + vfs_path_drop(root); + return status; +} + +errno_t devtmpfs_remove(const char* name) { + struct vfs_path root = devtmpfs_root(); + errno_t status = vfs_unlink(root, root, name, ident_kernel()); + vfs_path_drop(root); + return status; +} + +errno_t devtmpfs_symlink(const char* name, const char* target) { + struct vfs_path root = devtmpfs_root(); + errno_t status = vfs_symlink(root, root, name, target, ident_kernel()); + vfs_path_drop(root); + return status; +} diff --git a/dev/char/memfiles.c b/dev/char/memfiles.c new file mode 100644 index 0000000..18ef940 --- /dev/null +++ b/dev/char/memfiles.c @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: ISC + +#include +#include +#include +#include + +constexpr size_t MEMFILE_CHUNK = 64; + +static const uint8_t zero_chunk[MEMFILE_CHUNK]; + +static errno_t memfile_discard(struct vfs_file* file, iovec_iter_t* buffer, uint64_t offset, ssize_t* out_written) { + *out_written = iovec_iter_len(buffer); + return 0; +} + +static errno_t memfile_read_zero(struct vfs_file* file, iovec_iter_t* buffer, uint64_t offset, ssize_t* out_read) { + size_t total = 0; + errno_t status = 0; + while (iovec_iter_len(buffer)) { + size_t copied; + status = iovec_iter_copy_to(buffer, zero_chunk, MIN(iovec_iter_len(buffer), MEMFILE_CHUNK), &copied); + total += copied; + if (status) + break; + } + *out_read = total; + return status; +} + +static errno_t null_read(struct vfs_file* file, iovec_iter_t* buffer, uint64_t offset, ssize_t* out_read) { + *out_read = 0; + return 0; +} + +static const struct vfs_file_ops null_file_ops = { + .read = null_read, + .write = memfile_discard, +}; + +static const struct vfs_file_ops zero_file_ops = { + .read = memfile_read_zero, + .write = memfile_discard, +}; + +static errno_t full_write(struct vfs_file* file, iovec_iter_t* buffer, uint64_t offset, ssize_t* out_written) { + *out_written = 0; + return ENOSPC; +} + +static const struct vfs_file_ops full_file_ops = { + .read = memfile_read_zero, + .write = full_write, +}; + +static errno_t random_read(struct vfs_file* file, iovec_iter_t* buffer, uint64_t offset, ssize_t* out_read) { + uint8_t chunk[MEMFILE_CHUNK]; + size_t total = 0; + errno_t status = 0; + while (iovec_iter_len(buffer)) { + const size_t len = MIN(iovec_iter_len(buffer), sizeof(chunk)); + + // TODO: This function sucks and is probably not useful for cryptography. + random_fill(chunk, len); + + size_t copied; + status = iovec_iter_copy_to(buffer, chunk, len, &copied); + total += copied; + if (status) + break; + } + *out_read = total; + return status; +} + +static const struct vfs_file_ops random_file_ops = { + .read = random_read, + .write = memfile_discard, +}; + +static struct chardev null_dev; +static struct chardev zero_dev; +static struct chardev full_dev; +static struct chardev random_dev; +static struct chardev urandom_dev; + +static void memfiles_add(const char* name, struct chardev* dev, const struct vfs_file_ops* ops, uint32_t minor) { + chardev_init(dev, nullptr, ops, 1, minor); + errno_t status = devtmpfs_add_chardev(name, dev, 0666); + assert(status == 0, "Unable to create /dev/%s (%i)", name, status); +} + +static void memfiles_stage_init(struct boot_info* info) { + memfiles_add("null", &null_dev, &null_file_ops, 3); + memfiles_add("zero", &zero_dev, &zero_file_ops, 5); + memfiles_add("full", &full_dev, &full_file_ops, 7); + memfiles_add("random", &random_dev, &random_file_ops, 8); + memfiles_add("urandom", &urandom_dev, &random_file_ops, 9); +} + +INIT_TASK(memfiles_stage, "dev.memfiles", memfiles_stage_init); +INIT_DEPENDS(memfiles_stage, devtmpfs_stage, random_stage); diff --git a/dev/char/meson.build b/dev/char/meson.build new file mode 100644 index 0000000..d9d09e3 --- /dev/null +++ b/dev/char/meson.build @@ -0,0 +1,4 @@ +kernel_sources += files( + 'chardev.c', + 'memfiles.c', +) diff --git a/dev/meson.build b/dev/meson.build index f300e7e..22fc50b 100644 --- a/dev/meson.build +++ b/dev/meson.build @@ -1,4 +1,5 @@ subdir('block') +subdir('char') subdir('dt') subdir('video') diff --git a/fs/devtmpfs.c b/fs/devtmpfs.c new file mode 100644 index 0000000..7da359b --- /dev/null +++ b/fs/devtmpfs.c @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: ISC + +#include +#include +#include +#include + +static struct vfs_mount* dev_mount; + +struct vfs_path devtmpfs_root() { + return vfs_path_clone((struct vfs_path){.mount = dev_mount, .entry = dev_mount->root}); +} + +static errno_t devtmpfs_mount( + const struct vfs_filesystem* fs, + enum mount_flags flags, + void* arg, + struct vfs_mount** out +) { + return vfs_mount_new(flags, dev_mount->root, out); +} + +static const struct vfs_filesystem devtmpfs = { + .name = "devtmpfs", + .mount = devtmpfs_mount, +}; + +static void devtmpfs_stage_init(struct boot_info* info) { + errno_t status = vfs_mount("tmpfs", 0, nullptr, &dev_mount); + assert(status == 0, "Unable to create the devtmpfs (%i)", status); + + status = vfs_register_filesystem(&devtmpfs); + assert(status == 0, "Unable to register the devtmpfs (%i)", status); +} + +INIT_TASK(devtmpfs_stage, "fs.devtmpfs", devtmpfs_stage_init); +INIT_DEPENDS(devtmpfs_stage, vfs_stage); + +static void devtmpfs_mount_stage_init(struct boot_info* info) { + // Create /dev. + struct vfs_path root = vfs_get_root(); + errno_t status = vfs_mkdir(root, root, "/dev", 0755, ident_kernel()); + assert(status == 0 || status == EEXIST, "Unable to create /dev (%i)", status); + + // Get /dev. + struct vfs_path target; + status = vfs_path_lookup(root, root, "/dev", ident_kernel(), VFS_LOOKUP_MUST_EXIST, &target); + vfs_path_drop(root); + assert(status == 0, "Unable to look up /dev (%i)", status); + + // Prepare a devtmpfs for mounting. + struct vfs_mount* mount; + status = vfs_mount("devtmpfs", 0, nullptr, &mount); + assert(status == 0, "Unable to mount the devtmpfs (%i)", status); + + // Mount it on /dev. + status = vfs_path_mount(target, mount); + assert(status == 0, "Unable to mount the devtmpfs on /dev (%i)", status); + vfs_mount_unref(mount); + vfs_path_drop(target); +} + +INIT_TASK(devtmpfs_mount_stage, "fs.devtmpfs.mount", devtmpfs_mount_stage_init); +INIT_DEPENDS(devtmpfs_mount_stage, devtmpfs_stage); diff --git a/fs/meson.build b/fs/meson.build index dc81521..7f544c1 100644 --- a/fs/meson.build +++ b/fs/meson.build @@ -1,6 +1,7 @@ kernel_sources += files( 'anon.c', 'cache.c', + 'devtmpfs.c', 'file.c', 'ident.c', 'init.c', diff --git a/include/zinnia/chardev.h b/include/zinnia/chardev.h new file mode 100644 index 0000000..3529694 --- /dev/null +++ b/include/zinnia/chardev.h @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: ISC + +#pragma once + +#include +#include +#include +#include +#include +#include + +static inline dev_t dev_make(uint32_t major, uint32_t minor) { + return (minor & 0xFF) | ((major & 0xFFF) << 8) | ((dev_t)(minor & ~0xFFU) << 12) | ((dev_t)(major & ~0xFFFU) << 32); +} + +struct chardev; + +struct chardev_ops { + errno_t (*open)(struct chardev* dev, struct vfs_file* file); + void (*destroy)(struct chardev* dev); +}; + +struct chardev { + struct refcount rc; + const struct chardev_ops* ops; + const struct vfs_file_ops* file_ops; + dev_t dev; +}; + +void chardev_init( + struct chardev* dev, + const struct chardev_ops* ops, + const struct vfs_file_ops* file_ops, + uint32_t major, + uint32_t minor +); +void chardev_ref(struct chardev* dev); +void chardev_unref(struct chardev* dev); + +struct vfs_path devtmpfs_root(); +errno_t devtmpfs_add_chardev(const char* name, struct chardev* dev, mode_t mode); +errno_t devtmpfs_mkdir(const char* name, mode_t mode); +errno_t devtmpfs_remove(const char* name); +errno_t devtmpfs_symlink(const char* name, const char* target); + +INIT_EXTERN(devtmpfs_stage);