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回调。