2. 注册一个基本的帧缓冲驱动

任务目标

在Linux内核中实现一个最小帧缓冲驱动,完成平台设备注册、fb_info结构体初始化、fb_ops操作集实现,最终创建/dev/fb0设备节点并支持基本的read/write操作。

环境与依赖

  • 内核源码树已配置并编译通过(建议Linux 5.10+)
  • 内核配置启用:CONFIG_FB=y 或 CONFIG_FB=m
  • 开发板或QEMU虚拟机,具备LCD控制器或简单内存映射显示区域

核心实现

驱动使用内核帧缓冲框架,通过platform_driver匹配设备树节点,在probe中分配fb_info、设置固定与可变参数、注册帧缓冲设备。显存使用devm_kzalloc分配,避免额外IO映射复杂度,聚焦帧缓冲注册流程本身。

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/dma-mapping.h>

#define FB_WIDTH   800
#define FB_HEIGHT  600
#define FB_BPP     32

static struct fb_info *fb_info;
static void *fake_screen_base;
static u32 pseudo_palette[16];

static int myfb_setcolreg(unsigned regno, unsigned red, unsigned green,
                          unsigned blue, unsigned transp, struct fb_info *info)
{
    u32 v;
    if (regno > 15)
        return -EINVAL;

    red   >>= 8;
    green >>= 8;
    blue  >>= 8;
    v = (red << 16) | (green << 8) | blue;
    ((u32 *)info->pseudo_palette)[regno] = v;
    return 0;
}

static int myfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
    if (var->xres != FB_WIDTH || var->yres != FB_HEIGHT)
        return -EINVAL;
    if (var->bits_per_pixel != FB_BPP)
        return -EINVAL;
    return 0;
}

static struct fb_ops myfb_ops = {
    .owner        = THIS_MODULE,
    .fb_read      = fb_sys_read,
    .fb_write     = fb_sys_write,
    .fb_fillrect  = sys_fillrect,
    .fb_copyarea  = sys_copyarea,
    .fb_imageblit = sys_imageblit,
    .fb_setcolreg = myfb_setcolreg,
    .fb_check_var = myfb_check_var,
};

static int myfb_probe(struct platform_device *pdev)
{
    int ret;

    fb_info = framebuffer_alloc(sizeof(u32) * 16, &pdev->dev);
    if (!fb_info)
        return -ENOMEM;

    fake_screen_base = devm_kzalloc(&pdev->dev,
                                    FB_WIDTH * FB_HEIGHT * FB_BPP / 8,
                                    GFP_KERNEL);
    if (!fake_screen_base) {
        framebuffer_release(fb_info);
        return -ENOMEM;
    }

    strcpy(fb_info->fix.id, "myfb");
    fb_info->fix.smem_start = virt_to_phys(fake_screen_base);
    fb_info->fix.smem_len   = FB_WIDTH * FB_HEIGHT * FB_BPP / 8;
    fb_info->fix.type       = FB_TYPE_PACKED_PIXELS;
    fb_info->fix.visual     = FB_VISUAL_TRUECOLOR;
    fb_info->fix.line_length = FB_WIDTH * FB_BPP / 8;

    fb_info->var.xres           = FB_WIDTH;
    fb_info->var.yres           = FB_HEIGHT;
    fb_info->var.xres_virtual   = FB_WIDTH;
    fb_info->var.yres_virtual   = FB_HEIGHT;
    fb_info->var.bits_per_pixel = FB_BPP;
    fb_info->var.red.offset     = 16;
    fb_info->var.red.length     = 8;
    fb_info->var.green.offset   = 8;
    fb_info->var.green.length   = 8;
    fb_info->var.blue.offset    = 0;
    fb_info->var.blue.length    = 8;
    fb_info->var.activate       = FB_ACTIVATE_NOW;

    fb_info->screen_base = fake_screen_base;
    fb_info->pseudo_palette = pseudo_palette;
    fb_info->fbops = &myfb_ops;

    ret = register_framebuffer(fb_info);
    if (ret) {
        dev_err(&pdev->dev, "register_framebuffer failed: %d\n", ret);
        framebuffer_release(fb_info);
        return ret;
    }

    platform_set_drvdata(pdev, fb_info);
    dev_info(&pdev->dev, "myfb registered: %dx%d %dbpp\n",
             FB_WIDTH, FB_HEIGHT, FB_BPP);
    return 0;
}

static void myfb_remove(struct platform_device *pdev)
{
    struct fb_info *info = platform_get_drvdata(pdev);
    if (info) {
        unregister_framebuffer(info);
        framebuffer_release(info);
    }
}

static const struct of_device_id myfb_of_match[] = {
    { .compatible = "vendor,myfb" },
    { }
};
MODULE_DEVICE_TABLE(of, myfb_of_match);

static struct platform_driver myfb_driver = {
    .probe  = myfb_probe,
    .remove = myfb_remove,
    .driver = {
        .name = "myfb",
        .of_match_table = myfb_of_match,
    },
};
module_platform_driver(myfb_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kernel Driver Lab");
MODULE_DESCRIPTION("Minimal framebuffer driver example");

设备树节点

/ {
    myfb {
        compatible = "vendor,myfb";
        status = "okay";
    };
};

编译与加载

# 内核树内编译(放入 drivers/video/fbdev/ 后)
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfig
# 启用 CONFIG_FB 与 CONFIG_FB_MYFB

# 或作为模块单独编译
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- M=$PWD modules

# 加载模块
insmod myfb.ko

验证方法

  1. 设备节点检查:加载后执行 ls -l /dev/fb0,应显示主设备号29、次设备号0。
  2. 帧缓冲信息读取:执行 fbset -i 或 cat /sys/class/graphics/fb0/virtual_size,输出应包含800x600、32bpp。
  3. 写操作验证:执行 dd if=/dev/urandom of=/dev/fb0 bs=1024 count=100,应无错误返回;随后 dd if=/dev/fb0 of=/tmp/fb_dump bs=1024 count=100,比较两次数据应一致(cmp /tmp/fb_dump <(dd if=/dev/urandom of=/dev/fb0 bs=1024 count=100 2>/dev/null; dd if=/dev/fb0 bs=1024 count=100 2>/dev/null) 可验证读写一致性)。
  4. dmesg检查:执行 dmesg | grep myfb,应看到注册成功的日志输出。

以上步骤全部通过,即表示帧缓冲驱动注册成功且基本读写操作正常。