2. 设置可变显示参数
任务说明
本案例实现Linux帧缓冲驱动中的 fb_set_var 回调,使应用层通过 FBIOPUT_VSCREENINFO ioctl 动态修改分辨率与像素格式时,驱动能校验参数、更新 fb_info 中的可变参数,并重新计算显存布局。完成目标是:用户程序修改分辨率后,重新mmap映射即可看到新尺寸的显示效果。
核心实现
#include <linux/fb.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/mm.h>
#define DRV_NAME "demo_fb"
struct demo_fb_par {
u32 pseudo_palette[16];
void __iomem *vram_base;
size_t vram_size;
u32 xres, yres, bits_per_pixel;
};
static int demo_fb_set_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
struct demo_fb_par *par = info->par;
size_t new_size;
u32 line_len;
/* 1. 校验分辨率范围 */
if (var->xres < 16 || var->xres > 1920 ||
var->yres < 16 || var->yres > 1080)
return -EINVAL;
/* 2. 校验像素格式(仅支持 RGB565 和 XRGB8888) */
if (var->bits_per_pixel == 16) {
if (var->red.offset != 11 || var->red.length != 5 ||
var->green.offset != 5 || var->green.length != 6 ||
var->blue.offset != 0 || var->blue.length != 5)
return -EINVAL;
} else if (var->bits_per_pixel == 32) {
if (var->red.offset != 16 || var->red.length != 8 ||
var->green.offset != 8 || var->green.length != 8 ||
var->blue.offset != 0 || var->blue.length != 8)
return -EINVAL;
} else {
return -EINVAL;
}
/* 3. 计算新显存需求并检查是否超出分配大小 */
line_len = var->xres * var->bits_per_pixel / 8;
new_size = line_len * var->yres;
if (new_size > par->vram_size)
return -ENOMEM;
/* 4. 同步到 fb_info 的可变参数区 */
info->var.xres = var->xres;
info->var.yres = var->yres;
info->var.xres_virtual = var->xres;
info->var.yres_virtual = var->yres;
info->var.bits_per_pixel = var->bits_per_pixel;
info->var.red = var->red;
info->var.green = var->green;
info->var.blue = var->blue;
info->var.activate = FB_ACTIVATE_NOW;
/* 5. 更新固定参数中的行长度 */
info->fix.line_length = line_len;
/* 6. 记录当前参数到私有结构 */
par->xres = var->xres;
par->yres = var->yres;
par->bits_per_pixel = var->bits_per_pixel;
return 0;
}
static int demo_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
/* 复用 set_var 的校验逻辑,但不实际修改参数 */
struct fb_var_screeninfo tmp = *var;
return demo_fb_set_var(&tmp, info);
}
static int demo_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
struct demo_fb_par *par = info->par;
unsigned long start = (unsigned long)par->vram_base;
unsigned long size = vma->vm_end - vma->vm_start;
if (size > par->vram_size)
return -EINVAL;
vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
if (remap_pfn_range(vma, vma->vm_start, start >> PAGE_SHIFT,
size, vma->vm_page_prot))
return -EAGAIN;
return 0;
}
static struct fb_ops demo_fb_ops = {
.owner = THIS_MODULE,
.fb_check_var = demo_fb_check_var,
.fb_set_var = demo_fb_set_var,
.fb_mmap = demo_fb_mmap,
.fb_fillrect = sys_fillrect,
.fb_copyarea = sys_copyarea,
.fb_imageblit = sys_imageblit,
};
static int demo_fb_probe(struct platform_device *pdev)
{
struct fb_info *info;
struct demo_fb_par *par;
struct resource *res;
int ret;
info = framebuffer_alloc(sizeof(*par), &pdev->dev);
if (!info)
return -ENOMEM;
par = info->par;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
ret = -ENODEV;
goto err_release;
}
par->vram_size = resource_size(res);
par->vram_base = ioremap(res->start, par->vram_size);
if (!par->vram_base) {
ret = -ENOMEM;
goto err_release;
}
/* 默认参数:800x600 RGB565 */
info->var.xres = 800;
info->var.yres = 600;
info->var.xres_virtual = 800;
info->var.yres_virtual = 600;
info->var.bits_per_pixel = 16;
info->var.red.offset = 11; info->var.red.length = 5;
info->var.green.offset = 5; info->var.green.length = 6;
info->var.blue.offset = 0; info->var.blue.length = 5;
info->var.activate = FB_ACTIVATE_NOW;
info->fix.smem_start = res->start;
info->fix.smem_len = par->vram_size;
info->fix.line_length = 800 * 2;
info->fix.type = FB_TYPE_PACKED_PIXELS;
info->fix.visual = FB_VISUAL_TRUECOLOR;
strcpy(info->fix.id, DRV_NAME);
info->fbops = &demo_fb_ops;
info->pseudo_palette = par->pseudo_palette;
info->screen_base = par->vram_base;
info->screen_size = par->vram_size;
ret = register_framebuffer(info);
if (ret)
goto err_unmap;
platform_set_drvdata(pdev, info);
return 0;
err_unmap:
iounmap(par->vram_base);
err_release:
framebuffer_release(info);
return ret;
}
static int demo_fb_remove(struct platform_device *pdev)
{
struct fb_info *info = platform_get_drvdata(pdev);
struct demo_fb_par *par = info->par;
unregister_framebuffer(info);
iounmap(par->vram_base);
framebuffer_release(info);
return 0;
}
static struct platform_driver demo_fb_driver = {
.probe = demo_fb_probe,
.remove = demo_fb_remove,
.driver = {
.name = DRV_NAME,
},
};
module_platform_driver(demo_fb_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Demo FB driver with fb_set_var support");
关键说明
- 参数校验:
demo_fb_set_var首先检查分辨率范围(16×16 至 1920×1080)和像素格式的位域布局,防止非法参数破坏显存。 - 显存容量检查:根据新分辨率计算所需字节数,若超过
par->vram_size(由平台资源决定)则返回-ENOMEM,避免越界访问。 - 同步更新:校验通过后同步更新
info->var、info->fix.line_length和私有参数,保证后续fb_read/fb_write和 mmap 使用新布局。 - check_var 复用:
fb_check_var调用同一套校验逻辑但不实际修改参数,供应用层查询可行性。
编译与加载
# 内核树外编译(需内核头文件)
make -C /lib/modules/$(uname -r)/build M=$PWD modules
# 加载驱动(需先注册平台设备,或使用设备树 compatible="demo_fb")
insmod demo_fb.ko
用户态验证
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
int fd = open("/dev/fb0", O_RDWR);
struct fb_var_screeninfo var;
ioctl(fd, FBIOGET_VSCREENINFO, &var);
printf("Before: %ux%u %ubpp\n", var.xres, var.yres, var.bits_per_pixel);
/* 修改为 1024x768 RGB565 */
var.xres = 1024;
var.yres = 768;
var.bits_per_pixel = 16;
var.red.offset = 11; var.red.length = 5;
var.green.offset = 5; var.green.length = 6;
var.blue.offset = 0; var.blue.length = 5;
if (ioctl(fd, FBIOPUT_VSCREENINFO, &var) == 0) {
ioctl(fd, FBIOGET_VSCREENINFO, &var);
printf("After: %ux%u %ubpp\n", var.xres, var.yres, var.bits_per_pixel);
} else {
perror("FBIOPUT_VSCREENINFO");
}
close(fd);
return 0;
}
通过条件:编译运行上述用户程序,输出显示分辨率从 800x600 16bpp 变为 1024x768 16bpp,且 ioctl 返回 0。若尝试设置非法分辨率(如 2000×2000)或错误位域(如 24bpp),ioctl 应返回 -EINVAL。