]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
feat: /dev/fb0 framebuffer device + fd-backed mmap support
authorTulio A M Mendes <[email protected]>
Thu, 12 Feb 2026 07:26:30 +0000 (04:26 -0300)
committerTulio A M Mendes <[email protected]>
Fri, 13 Feb 2026 02:44:55 +0000 (23:44 -0300)
Added /dev/fb0 device node registered via devfs by vbe.c:
- ioctl: FBIOGET_VSCREENINFO (resolution, bpp), FBIOGET_FSCREENINFO
  (phys addr, pitch, size)
- mmap: maps physical framebuffer into userspace with NOCACHE flags
- read/write: direct pixel buffer access via offset

Extended syscall_mmap_impl to support fd-backed mmap: when
MAP_ANONYMOUS is not set, the file descriptor's node->mmap callback
is invoked. This enables userspace to mmap /dev/fb0 for direct
framebuffer access (required for DOOM).

Marked syscall_mmap_impl as noinline to prevent GCC from merging it
into syscall_handler (4KB kernel stack limit).

include/fb.h [new file with mode: 0644]
include/vbe.h
src/drivers/vbe.c
src/kernel/init.c
src/kernel/syscall.c

diff --git a/include/fb.h b/include/fb.h
new file mode 100644 (file)
index 0000000..17102b7
--- /dev/null
@@ -0,0 +1,24 @@
+#ifndef FB_H
+#define FB_H
+
+#include <stdint.h>
+
+/*
+ * Framebuffer ioctl commands (Linux-compatible subset).
+ */
+#define FBIOGET_VSCREENINFO  0x4600
+#define FBIOGET_FSCREENINFO  0x4602
+
+struct fb_var_screeninfo {
+    uint32_t xres;
+    uint32_t yres;
+    uint32_t bits_per_pixel;
+};
+
+struct fb_fix_screeninfo {
+    uint32_t smem_start;    /* physical address */
+    uint32_t smem_len;      /* length of framebuffer mem */
+    uint32_t line_length;   /* pitch (bytes per scanline) */
+};
+
+#endif
index 15c184803a678bbfdd66b286dfd816c71de6d1b6..ae87c6a4d233c87a7b9b61c4b62c69b399772eba 100644 (file)
@@ -22,4 +22,6 @@ void vbe_put_pixel(uint32_t x, uint32_t y, uint32_t color);
 void vbe_fill_rect(uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint32_t color);
 void vbe_clear(uint32_t color);
 
+void vbe_register_devfs(void);
+
 #endif
index c638ea3ce6f441dfc64843cc9ddabeb90eda80e6..3d18076e67683d7bcb6aa24278d7447152ab20c3 100644 (file)
@@ -1,5 +1,9 @@
 #include "vbe.h"
 #include "vmm.h"
+#include "pmm.h"
+#include "devfs.h"
+#include "fb.h"
+#include "uaccess.h"
 #include "uart_console.h"
 #include "utils.h"
 
@@ -7,6 +11,7 @@
 
 static struct vbe_info g_vbe;
 static int g_vbe_ready = 0;
+static fs_node_t g_dev_fb0_node;
 
 int vbe_init(const struct boot_info* bi) {
     if (!bi || bi->fb_addr == 0 || bi->fb_width == 0 || bi->fb_height == 0 || bi->fb_bpp == 0) {
@@ -112,3 +117,87 @@ void vbe_clear(uint32_t color) {
     if (!g_vbe_ready) return;
     vbe_fill_rect(0, 0, g_vbe.width, g_vbe.height, color);
 }
+
+/* --- /dev/fb0 device callbacks --- */
+
+static uint32_t fb0_read(fs_node_t* node, uint32_t offset, uint32_t size, uint8_t* buffer) {
+    (void)node;
+    if (!g_vbe_ready || !buffer) return 0;
+    if (offset >= g_vbe.size) return 0;
+    uint32_t avail = g_vbe.size - offset;
+    if (size > avail) size = avail;
+    memcpy(buffer, (const uint8_t*)g_vbe.virt_addr + offset, size);
+    return size;
+}
+
+static uint32_t fb0_write(fs_node_t* node, uint32_t offset, uint32_t size, const uint8_t* buffer) {
+    (void)node;
+    if (!g_vbe_ready || !buffer) return 0;
+    if (offset >= g_vbe.size) return 0;
+    uint32_t avail = g_vbe.size - offset;
+    if (size > avail) size = avail;
+    memcpy((uint8_t*)g_vbe.virt_addr + offset, buffer, size);
+    return size;
+}
+
+static int fb0_ioctl(fs_node_t* node, uint32_t cmd, void* arg) {
+    (void)node;
+    if (!g_vbe_ready) return -1;
+    if (!arg) return -1;
+
+    if (cmd == FBIOGET_VSCREENINFO) {
+        if (user_range_ok(arg, sizeof(struct fb_var_screeninfo)) == 0) return -1;
+        struct fb_var_screeninfo v;
+        v.xres = g_vbe.width;
+        v.yres = g_vbe.height;
+        v.bits_per_pixel = g_vbe.bpp;
+        if (copy_to_user(arg, &v, sizeof(v)) < 0) return -1;
+        return 0;
+    }
+
+    if (cmd == FBIOGET_FSCREENINFO) {
+        if (user_range_ok(arg, sizeof(struct fb_fix_screeninfo)) == 0) return -1;
+        struct fb_fix_screeninfo f;
+        f.smem_start = (uint32_t)g_vbe.phys_addr;
+        f.smem_len = g_vbe.size;
+        f.line_length = g_vbe.pitch;
+        if (copy_to_user(arg, &f, sizeof(f)) < 0) return -1;
+        return 0;
+    }
+
+    return -1;
+}
+
+static uintptr_t fb0_mmap(fs_node_t* node, uintptr_t addr, uint32_t length, uint32_t prot, uint32_t offset) {
+    (void)node; (void)prot; (void)offset;
+    if (!g_vbe_ready) return 0;
+
+    uint32_t aligned_len = (length + 0xFFFU) & ~(uint32_t)0xFFFU;
+    if (aligned_len > ((g_vbe.size + 0xFFFU) & ~(uint32_t)0xFFFU))
+        aligned_len = (g_vbe.size + 0xFFFU) & ~(uint32_t)0xFFFU;
+
+    for (uint32_t i = 0; i < aligned_len; i += 0x1000U) {
+        vmm_map_page((uint64_t)(g_vbe.phys_addr + i),
+                     (uint64_t)(addr + i),
+                     VMM_FLAG_PRESENT | VMM_FLAG_RW | VMM_FLAG_USER | VMM_FLAG_NOCACHE);
+    }
+
+    return addr;
+}
+
+void vbe_register_devfs(void) {
+    if (!g_vbe_ready) return;
+
+    memset(&g_dev_fb0_node, 0, sizeof(g_dev_fb0_node));
+    strcpy(g_dev_fb0_node.name, "fb0");
+    g_dev_fb0_node.flags = FS_CHARDEVICE;
+    g_dev_fb0_node.inode = 20;
+    g_dev_fb0_node.length = g_vbe.size;
+    g_dev_fb0_node.read = &fb0_read;
+    g_dev_fb0_node.write = &fb0_write;
+    g_dev_fb0_node.ioctl = &fb0_ioctl;
+    g_dev_fb0_node.mmap = &fb0_mmap;
+    devfs_register_device(&g_dev_fb0_node);
+
+    uart_print("[VBE] Registered /dev/fb0\n");
+}
index 97a6c7083a646f99ac7b249d84670b9ebe0f6c7a..2ef9cfaa31a3253da6b4f0232d7ce4cb8f8b74dc 100644 (file)
@@ -85,6 +85,8 @@ int init_start(const struct boot_info* bi) {
         (void)vfs_mount("/dev", dev);
     }
 
+    vbe_register_devfs();
+
     fs_node_t* persist = persistfs_create_root();
     if (persist) {
         (void)vfs_mount("/persist", persist);
index d73841c3b3f3a4a7e02fdea217b7e31a96e8b674..c32c9c9c5acddac7bb7d00ff7d8ffa22f1e59d0e 100644 (file)
@@ -1538,14 +1538,23 @@ static uintptr_t mmap_find_free(uint32_t length) {
     return 0;
 }
 
+__attribute__((noinline))
 static uintptr_t syscall_mmap_impl(uintptr_t addr, uint32_t length, uint32_t prot,
                                     uint32_t flags, int fd, uint32_t offset) {
-    (void)offset;
     if (!current_process) return (uintptr_t)-EINVAL;
     if (length == 0) return (uintptr_t)-EINVAL;
 
-    if (!(flags & MAP_ANONYMOUS)) return (uintptr_t)-ENOSYS;
-    if (fd != -1) return (uintptr_t)-EINVAL;
+    int is_anon = (flags & MAP_ANONYMOUS) != 0;
+
+    /* fd-backed mmap: the file's node must provide a mmap callback */
+    fs_node_t* mmap_node = NULL;
+    if (!is_anon) {
+        if (fd < 0) return (uintptr_t)-EBADF;
+        struct file* f = fd_get(fd);
+        if (!f || !f->node) return (uintptr_t)-EBADF;
+        if (!f->node->mmap) return (uintptr_t)-ENOSYS;
+        mmap_node = f->node;
+    }
 
     uint32_t aligned_len = (length + 0xFFFU) & ~(uint32_t)0xFFFU;
 
@@ -1565,14 +1574,22 @@ static uintptr_t syscall_mmap_impl(uintptr_t addr, uint32_t length, uint32_t pro
     }
     if (slot < 0) return (uintptr_t)-ENOMEM;
 
-    uint32_t vmm_flags = VMM_FLAG_PRESENT | VMM_FLAG_USER;
-    if (prot & PROT_WRITE) vmm_flags |= VMM_FLAG_RW;
+    if (mmap_node) {
+        /* Device-backed mmap: delegate to the node's mmap callback */
+        uintptr_t result = mmap_node->mmap(mmap_node, base, aligned_len, prot, offset);
+        if (!result) return (uintptr_t)-ENOMEM;
+        base = result;
+    } else {
+        /* Anonymous mmap: allocate fresh zeroed pages */
+        uint32_t vmm_flags = VMM_FLAG_PRESENT | VMM_FLAG_USER;
+        if (prot & PROT_WRITE) vmm_flags |= VMM_FLAG_RW;
 
-    for (uintptr_t va = base; va < base + aligned_len; va += 0x1000U) {
-        void* frame = pmm_alloc_page();
-        if (!frame) return (uintptr_t)-ENOMEM;
-        vmm_map_page((uint64_t)(uintptr_t)frame, (uint64_t)va, vmm_flags);
-        memset((void*)va, 0, 0x1000U);
+        for (uintptr_t va = base; va < base + aligned_len; va += 0x1000U) {
+            void* frame = pmm_alloc_page();
+            if (!frame) return (uintptr_t)-ENOMEM;
+            vmm_map_page((uint64_t)(uintptr_t)frame, (uint64_t)va, vmm_flags);
+            memset((void*)va, 0, 0x1000U);
+        }
     }
 
     current_process->mmaps[slot].base = base;