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
验证方法
- 设备节点检查:加载后执行
ls -l /dev/fb0,应显示主设备号29、次设备号0。 - 帧缓冲信息读取:执行
fbset -i或cat /sys/class/graphics/fb0/virtual_size,输出应包含800x600、32bpp。 - 写操作验证:执行
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)可验证读写一致性)。 - dmesg检查:执行
dmesg | grep myfb,应看到注册成功的日志输出。
以上步骤全部通过,即表示帧缓冲驱动注册成功且基本读写操作正常。