From: Tulio A M Mendes Date: Thu, 12 Feb 2026 07:26:30 +0000 (-0300) Subject: feat: /dev/fb0 framebuffer device + fd-backed mmap support X-Git-Url: https://projects.tadryanom.me/docs/static/git-logo.png?a=commitdiff_plain;h=89fbda518d76fd4b7cf245d27ff0eb0a568f6df4;p=AdrOS.git feat: /dev/fb0 framebuffer device + fd-backed mmap support 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). --- diff --git a/include/fb.h b/include/fb.h new file mode 100644 index 0000000..17102b7 --- /dev/null +++ b/include/fb.h @@ -0,0 +1,24 @@ +#ifndef FB_H +#define FB_H + +#include + +/* + * 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 diff --git a/include/vbe.h b/include/vbe.h index 15c1848..ae87c6a 100644 --- a/include/vbe.h +++ b/include/vbe.h @@ -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 diff --git a/src/drivers/vbe.c b/src/drivers/vbe.c index c638ea3..3d18076 100644 --- a/src/drivers/vbe.c +++ b/src/drivers/vbe.c @@ -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"); +} diff --git a/src/kernel/init.c b/src/kernel/init.c index 97a6c70..2ef9cfa 100644 --- a/src/kernel/init.c +++ b/src/kernel/init.c @@ -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); diff --git a/src/kernel/syscall.c b/src/kernel/syscall.c index d73841c..c32c9c9 100644 --- a/src/kernel/syscall.c +++ b/src/kernel/syscall.c @@ -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;