3. 实现固定显示参数
任务说明
本案例目标是在Linux帧缓冲驱动中正确填充fb_fix_screeninfo结构体。该结构体描述显存的固定属性(不可动态改变),包括物理起始地址、显存总大小、一行的字节长度等。内核通过fb_ops.fb_get_fix_screeninfo回调将该结构体返回给用户空间,供FBIOGET_FSCREENINFO ioctl使用。
核心实现
以下代码展示了一个最小但完整的帧缓冲驱动中fb_fix_screeninfo的填充逻辑。驱动假设使用系统内存作为显存(通过dma_alloc_coherent分配),分辨率固定为800×600,32位色深(4字节/像素)。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fb.h>
#include <linux/dma-mapping.h>
#include <linux/platform_device.h>
#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 600
#define BYTES_PER_PIXEL 4
static struct fb_info *fb_info;
static dma_addr_t dma_handle;
static void *framebuffer;
static int myfb_get_fix(struct fb_info *info, struct fb_fix_screeninfo *fix)
{
/* 填充固定显示参数 */
strscpy(fix->id, "myfb", sizeof(fix->id));
fix->smem_start = dma_handle; /* 显存物理起始地址 */
fix->smem_len = SCREEN_WIDTH * SCREEN_HEIGHT * BYTES_PER_PIXEL;
fix->type = FB_TYPE_PACKED_PIXELS;
fix->visual = FB_VISUAL_TRUECOLOR;
fix->line_length = SCREEN_WIDTH * BYTES_PER_PIXEL; /* 一行字节数 */
fix->accel = FB_ACCEL_NONE;
return 0;
}
static const struct fb_ops myfb_ops = {
.owner = THIS_MODULE,
.fb_get_fix_screeninfo = myfb_get_fix,
/* 其他必要回调(fb_read/fb_write/fb_set_var等)此处省略,
本案例聚焦fix结构体填充 */
};
static int myfb_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
size_t fb_size = SCREEN_WIDTH * SCREEN_HEIGHT * BYTES_PER_PIXEL;
/* 分配连续物理内存作为显存 */
framebuffer = dma_alloc_coherent(dev, fb_size, &dma_handle, GFP_KERNEL);
if (!framebuffer)
return -ENOMEM;
/* 分配fb_info并注册 */
fb_info = framebuffer_alloc(0, dev);
if (!fb_info) {
dma_free_coherent(dev, fb_size, framebuffer, dma_handle);
return -ENOMEM;
}
fb_info->fbops = &myfb_ops;
fb_info->screen_base = framebuffer;
fb_info->screen_size = fb_size;
fb_info->var.xres = SCREEN_WIDTH;
fb_info->var.yres = SCREEN_HEIGHT;
fb_info->var.bits_per_pixel = BYTES_PER_PIXEL * 8;
fb_info->fix = (struct fb_fix_screeninfo){0}; /* 清零 */
if (register_framebuffer(fb_info) != 0) {
framebuffer_release(fb_info);
dma_free_coherent(dev, fb_size, framebuffer, dma_handle);
return -EINVAL;
}
return 0;
}
static int myfb_remove(struct platform_device *pdev)
{
unregister_framebuffer(fb_info);
framebuffer_release(fb_info);
dma_free_coherent(&pdev->dev, SCREEN_WIDTH * SCREEN_HEIGHT * BYTES_PER_PIXEL,
framebuffer, dma_handle);
return 0;
}
static struct platform_driver myfb_driver = {
.probe = myfb_probe,
.remove = myfb_remove,
.driver = {
.name = "myfb",
},
};
module_platform_driver(myfb_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Minimal framebuffer driver demonstrating fb_fix_screeninfo");
关键字段说明
- smem_start:显存物理起始地址,此处来自
dma_alloc_coherent返回的DMA地址,用户空间mmap时需要该值。 - smem_len:显存总字节数,必须与实际分配大小一致。
- line_length:显示一行所需的字节数,等于
xres * bits_per_pixel / 8。若存在行对齐填充,需按硬件实际计算。 - type/visual:分别表示像素排列方式(连续排列)和颜色模式(真彩)。
- accel:硬件加速类型,无加速时设为
FB_ACCEL_NONE。
编译与加载
# 编译(内核源码树环境下)
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
# 加载驱动(需先注册platform device,此处省略device注册代码)
insmod myfb.ko
# 查看已注册的帧缓冲设备
ls /dev/fb*
验证方法
加载驱动后,通过以下方式确认fb_fix_screeninfo填充正确:
# 使用fbset查看固定参数(若已安装)
fbset -i /dev/fb1
# 或使用小型C程序调用FBIOGET_FSCREENINFO ioctl
# 关键检查点:
# 1. smem_start 非0,且与dma_alloc_coherent分配地址一致
# 2. smem_len = 800*600*4 = 1920000
# 3. line_length = 800*4 = 3200
# 4. type = 0 (FB_TYPE_PACKED_PIXELS)
# 5. visual = 2 (FB_VISUAL_TRUECOLOR)
通过条件:ioctl调用返回0,且上述字段值与预期完全一致。若smem_start为0或line_length计算错误,说明填充逻辑有误。
常见问题
- smem_start为0:检查
dma_alloc_coherent是否成功,以及是否将dma_handle而非虚拟地址赋给该字段。 - line_length错误:当xres不是8的倍数或存在硬件对齐要求时,需按
PAGE_ALIGN或硬件手册计算实际行字节数,不能简单用xres * bpp / 8。 - 用户空间mmap失败:确认
smem_start和smem_len与驱动实际分配区域一致,且驱动实现了fb_mmap回调。