summaryrefslogtreecommitdiff
path: root/fs/devfs.cc
diff options
context:
space:
mode:
Diffstat (limited to 'fs/devfs.cc')
-rw-r--r--fs/devfs.cc137
1 files changed, 137 insertions, 0 deletions
diff --git a/fs/devfs.cc b/fs/devfs.cc
new file mode 100644
index 0000000..b4b2cf3
--- /dev/null
+++ b/fs/devfs.cc
@@ -0,0 +1,137 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+/*
+ * devfs.cc — /dev population and chardev-aware open
+ *
+ * Creates /dev as a subdirectory under the ramfs root, then
+ * populates device nodes. On open(), if the inode is S_IFCHR,
+ * we look up the cdev by i_rdev and use its fops.
+ */
+
+#include <lume/fs.h>
+#include <lume/dcache.h>
+#include <lume/file.h>
+#include <lume/chardev.h>
+#include <lume/mount.h>
+#include <lume/klog.h>
+#include <lume/errno.h>
+#include <lume/fs_types.h>
+
+/* ================================================================== */
+/* devfs_create_node — create a char device inode under parent */
+/* ================================================================== */
+
+static int devfs_create_node(dentry* parent, const char* name,
+ uint32 major, uint32 minor)
+{
+ inode* dir_ip = parent->d_inode;
+ if (!dir_ip || !dir_ip->i_op || !dir_ip->i_op->mknod) {
+ return -EPERM;
+ }
+
+ qstr q{};
+ q.name = name;
+ q.len = 0;
+ for (const char* s = name; *s; s++) {
+ q.len++;
+ }
+
+ dentry* de = d_alloc(parent, &q);
+ if (!de) {
+ return -ENOMEM;
+ }
+
+ int err = dir_ip->i_op->mknod(dir_ip, de, S_IFCHR | 0666,
+ MKDEV(major, minor));
+ if (err) {
+ return err;
+ }
+
+ return 0;
+}
+
+/* ================================================================== */
+/* devfs_open — chardev-aware open hook */
+/* ================================================================== */
+
+/*
+ * Called from sys_openat when the inode is S_IFCHR.
+ * Replaces f_op with the chardev's file_operations.
+ */
+int devfs_chrdev_open(inode* ip, file* fp)
+{
+ if (!S_ISCHR(ip->i_mode)) {
+ return 0;
+ }
+
+ cdev* cd = cdev_find(ip->i_rdev);
+ if (!cd || !cd->fops) {
+ return -ENXIO; /* No such device */
+ }
+
+ fp->f_op = cd->fops;
+
+ /* Call the device's own open if it has one */
+ if (fp->f_op->open) {
+ return fp->f_op->open(ip, fp);
+ }
+
+ return 0;
+}
+
+/* ================================================================== */
+/* devfs_init — create /dev and populate device nodes */
+/* ================================================================== */
+
+void devfs_init()
+{
+ if (!g_root_dentry || !g_root_dentry->d_inode) {
+ printk(KERN_ERR "[devfs] no root filesystem!\n");
+ return;
+ }
+
+ /* Create /dev directory — or reuse if initramfs already created it.
+ * Linux: devtmpfs similarly handles pre-existing directories. */
+ inode* root_ip = g_root_dentry->d_inode;
+
+ qstr dev_name{};
+ dev_name.name = "dev";
+ dev_name.len = 3;
+ dev_name.hash = full_name_hash("dev", 3);
+
+ dentry* dev_de = d_lookup(g_root_dentry, &dev_name);
+ if (dev_de) {
+ /* /dev already exists (from initramfs) — reuse it */
+ printk("[devfs] reusing existing /dev directory\n");
+ } else {
+ /* Create /dev from scratch */
+ if (!root_ip->i_op || !root_ip->i_op->mkdir) {
+ printk(KERN_ERR "[devfs] root has no mkdir!\n");
+ return;
+ }
+
+ dev_de = d_alloc(g_root_dentry, &dev_name);
+ if (!dev_de) {
+ printk(KERN_ERR "[devfs] failed to alloc /dev dentry\n");
+ return;
+ }
+
+ int err = root_ip->i_op->mkdir(root_ip, dev_de, S_IFDIR | 0755);
+ if (err) {
+ printk(KERN_ERR "[devfs] mkdir /dev failed: %d\n", err);
+ return;
+ }
+ }
+
+ /* Populate: /dev/console (5,1) */
+ devfs_create_node(dev_de, "console", 5, 1);
+
+ /* Populate: /dev/null (1,3) */
+ devfs_create_node(dev_de, "null", 1, 3);
+
+ /* Populate: /dev/zero (1,5) */
+ devfs_create_node(dev_de, "zero", 1, 5);
+
+ printk("[devfs] /dev populated (console, null, zero)\n");
+}