Viewing: vbe.c
📄 vbe.c (Read Only) ⬅ To go back
#include "vbe.h"
#include "vmm.h"
#include "pmm.h"
#include "devfs.h"
#include "fb.h"
#include "uaccess.h"
#include "console.h"
#include "utils.h"
#include "arch/x86/kernel_va_map.h"

#include <stddef.h>

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) {
        kprintf("[VBE] No framebuffer provided by bootloader.\n");
        return -1;
    }

    g_vbe.phys_addr = bi->fb_addr;
    g_vbe.pitch = bi->fb_pitch;
    g_vbe.width = bi->fb_width;
    g_vbe.height = bi->fb_height;
    g_vbe.bpp = bi->fb_bpp;
    g_vbe.size = g_vbe.pitch * g_vbe.height;

    uint32_t pages = (g_vbe.size + 0xFFF) >> 12;
    uintptr_t virt_base = KVA_FRAMEBUFFER;

    for (uint32_t i = 0; i < pages; i++) {
        vmm_map_page((uint64_t)(g_vbe.phys_addr + i * 0x1000),
                     (uint64_t)(virt_base + i * 0x1000),
                     VMM_FLAG_PRESENT | VMM_FLAG_RW);
    }

    g_vbe.virt_addr = (volatile uint8_t*)virt_base;
    g_vbe_ready = 1;

    kprintf("[VBE] Framebuffer %ux%ux%u @ 0x%x mapped to 0x%x\n",
            (unsigned)g_vbe.width, (unsigned)g_vbe.height,
            (unsigned)g_vbe.bpp, (unsigned)g_vbe.phys_addr,
            (unsigned)virt_base);

    return 0;
}

int vbe_available(void) {
    return g_vbe_ready;
}

const struct vbe_info* vbe_get_info(void) {
    if (!g_vbe_ready) return NULL;
    return &g_vbe;
}

void vbe_put_pixel(uint32_t x, uint32_t y, uint32_t color) {
    if (!g_vbe_ready) return;
    if (x >= g_vbe.width || y >= g_vbe.height) return;

    uint32_t offset = y * g_vbe.pitch + x * (g_vbe.bpp / 8);
    volatile uint8_t* pixel = g_vbe.virt_addr + offset;

    if (g_vbe.bpp == 32) {
        *(volatile uint32_t*)pixel = color;
    } else if (g_vbe.bpp == 24) {
        pixel[0] = (uint8_t)(color & 0xFF);
        pixel[1] = (uint8_t)((color >> 8) & 0xFF);
        pixel[2] = (uint8_t)((color >> 16) & 0xFF);
    } else if (g_vbe.bpp == 16) {
        *(volatile uint16_t*)pixel = (uint16_t)color;
    }
}

void vbe_fill_rect(uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint32_t color) {
    if (!g_vbe_ready) return;

    uint32_t x_end = x + w;
    uint32_t y_end = y + h;
    if (x_end > g_vbe.width) x_end = g_vbe.width;
    if (y_end > g_vbe.height) y_end = g_vbe.height;

    uint32_t bytes_pp = g_vbe.bpp / 8;

    for (uint32_t row = y; row < y_end; row++) {
        volatile uint8_t* row_ptr = g_vbe.virt_addr + row * g_vbe.pitch + x * bytes_pp;
        if (g_vbe.bpp == 32) {
            volatile uint32_t* p = (volatile uint32_t*)row_ptr;
            for (uint32_t col = x; col < x_end; col++) {
                *p++ = color;
            }
        } else {
            for (uint32_t col = x; col < x_end; col++) {
                uint32_t off = (col - x) * bytes_pp;
                if (bytes_pp == 3) {
                    row_ptr[off]     = (uint8_t)(color & 0xFF);
                    row_ptr[off + 1] = (uint8_t)((color >> 8) & 0xFF);
                    row_ptr[off + 2] = (uint8_t)((color >> 16) & 0xFF);
                } else if (bytes_pp == 2) {
                    *(volatile uint16_t*)(row_ptr + off) = (uint16_t)color;
                }
            }
        }
    }
}

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;

    static const struct file_operations fb0_fops = {
        .read  = fb0_read,
        .write = fb0_write,
        .ioctl = fb0_ioctl,
        .mmap  = fb0_mmap,
    };

    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.f_ops = &fb0_fops;
    devfs_register_device(&g_dev_fb0_node);

    kprintf("[VBE] Registered /dev/fb0\n");
}