1. Linux内核帧缓冲¶
帧缓冲(FrameBuffer,简称fb)是Linux内核中面向图形显示设备的抽象层,它将显示硬件的帧缓冲区封装为统一的设备接口,为用户空间和内核空间提供了一套标准化的图形操作方式。 不同于早期的字符显示终端,帧缓冲屏蔽了不同显示硬件的底层差异,使得应用程序无需直接操作硬件寄存器,只需通过读写帧缓冲内存,即可实现图形的绘制、显示与更新。
在嵌入式Linux系统中,帧缓冲是图形显示子系统的核心基础,无论是简单的字符界面、图形桌面,还是嵌入式GUI(如Qt),都依赖帧缓冲提供的底层支撑。
1.1. 帧缓冲核心概念¶
1.1.1. 帧缓冲的定义¶
帧缓冲是Linux内核为显示设备提供的一种抽象表示,本质上是一块映射到内核地址空间的连续内存区域,该内存区域与显示硬件的帧缓冲区(Frame Buffer Memory)物理地址一一对应。 显示硬件会周期性地从这块内存中读取像素数据,渲染到屏幕上,因此,应用程序或内核驱动只需修改这块内存中的像素值,就能实现屏幕显示内容的更新。
帧缓冲的作用是隔离应用程序与显示硬件,应用程序无需关心硬件的具体型号、寄存器配置,只需通过标准的帧缓冲接口(如open、read、write、mmap)操作内存即可; 而驱动程序则负责将帧缓冲的抽象操作,映射到底层硬件的具体控制(如内存地址配置、显示时序、像素格式转换)。
1.1.2. 帧缓冲与DRM的关系¶
在Linux内核中,帧缓冲(fbdev)与DRM(Direct Rendering Manager,直接渲染管理器)是两种并行的显示子系统架构:
帧缓冲:设计简单,适用于简单的图形显示场景,接口简洁,但缺乏对3D渲染、多图层叠加、VSYNC(垂直同步)等高级特性的支持。
DRM:为复杂图形场景设计,支持3D渲染、多图层、原子操作、垂直同步等高级特性,目前主流嵌入式芯片的显示驱动均基于DRM架构实现。
需要注意的是,现代DRM驱动中,通常会通过fbdev-emulator模块兼容帧缓冲接口,即通过DRM驱动模拟帧缓冲设备,确保依赖帧缓冲接口的legacy应用程序能够正常运行。
1.1.3. 帧缓冲与DRM帧缓冲的区别¶
帧缓冲是Linux内核早期的显示抽象,而DRM帧缓冲是基于DRM架构的现代帧缓冲实现,二者虽均用于图形显示,但在架构设计、功能支持、内存管理等方面存在显著差异,区别如下:
架构依赖不同:帧缓冲是独立的显示子系统,不依赖其他框架,直接通过驱动操作硬件寄存器和帧缓冲内存;DRM帧缓冲依赖DRM框架,需与DRM的GEM内存管理、原子操作等模块配合使用。
内存管理方式不同:传统fbdev的帧缓冲内存通常为物理连续内存,由驱动直接分配和管理,灵活性较差;DRM帧缓冲的内存由DRM GEM模块管理,支持物理连续内存、IOMMU映射等多种方式,且支持多平面(如YUV420格式的Y、U、V平面)内存管理。
功能支持不同:传统fbdev仅支持基础的像素读写、屏幕空白等简单功能,不支持3D渲染、多图层叠加、垂直同步等高级特性;DRM帧缓冲支持多图层、原子操作、垂直同步、硬件加速等高级功能,适配复杂图形场景。
接口与兼容性不同:传统fbdev通过/dev/fbX设备文件提供接口,应用程序通过open、read、write、ioctl等系统调用操作;DRM帧缓冲通过DRM IOCTL命令(如DRM_IOCTL_MODE_ADDFB2)提供接口,同时可通过fbdev-emulator模块兼容传统fbdev接口,实现向下兼容。
现代嵌入式系统中,DRM帧缓冲已逐渐替代传统fbdev,瑞芯微DRM驱动中的帧缓冲实现,就是基于DRM架构,同时兼容传统fbdev接口,兼顾高级功能与兼容性。
1.2. 帧缓冲基础知识¶
1.2.1. 帧缓冲大小计算¶
显示硬件的核心是帧缓冲区,其大小由屏幕分辨率和像素格式决定,计算公式为:
帧缓冲大小 = 屏幕宽度 * 屏幕高度 * 每个像素占用字节数
例如:1080P(1920x1080)分辨率、RGB888格式(每个像素3字节)的帧缓冲大小为1920*1080*3 = 6220800字节。
1.2.2. 帧缓冲的像素格式¶
像素格式决定了每个像素在帧缓冲内存中的存储方式,Linux内核支持多种标准像素格式,常见的有:
RGB565:每个像素占用2字节,R(红)占5位、G(绿)占6位、B(蓝)占5位,适合低功耗、低分辨率场景。
RGB888:每个像素占用3字节,R、G、B各占8位,色彩还原度高,是主流的像素格式。
ARGB8888:每个像素占用4字节,增加了A(透明度)通道,适合需要透明效果的场景(如GUI叠加)。
YUV420:每个像素占用1.5字节,采用亮度(Y)和色度(U、V)分离的存储方式,适合视频显示场景(如HDMI播放)。
内核通过像素格式描述结构体,统一管理不同格式的参数,如每个像素字节数、颜色通道排列顺序等,后续会详细介绍该结构体。
1.3. 帧缓冲核心结构体¶
1.3.1. 帧缓冲设备结构体¶
帧缓冲设备结构体(struct fb_info)是帧缓冲驱动的核心结构体,用于描述一个帧缓冲设备的所有信息(硬件配置、内存信息、操作函数等),是驱动程序与内核帧缓冲抽象层交互的核心载体。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | struct fb_info {
refcount_t count; /* 设备引用计数,用于同步管理 */
int node; /* 设备节点号 */
int flags; /* 帧缓冲设备标志 */
int fbcon_rotate_hint; /* 屏幕旋转提示,默认-1,驱动可配置 */
struct mutex lock; /* 保护open/release/ioctl等操作的互斥锁 */
struct mutex mm_lock; /* 保护fb_mmap及内存相关字段的互斥锁 */
struct fb_var_screeninfo var; /* 可变参数:屏幕分辨率、像素格式等,用户可修改 */
struct fb_fix_screeninfo fix; /* 固定参数:帧缓冲内存地址、大小等,硬件决定 */
struct fb_monspecs monspecs; /* 显示器规格信息 */
struct fb_pixmap pixmap; /* 图像硬件映射器 */
struct fb_cmap cmap; /* 当前颜色映射表 */
struct list_head modelist; /* 支持的显示模式列表 */
struct fb_videomode *mode; /* 当前使用的显示模式 */
const struct fb_ops *fbops; /* 帧缓冲操作函数集 */
struct device *device; /* 父设备结构体 */
struct device *dev; /* 帧缓冲设备自身结构体 */
int class_flag; /* 私有sysfs标志 */
union {
char __iomem *screen_base; /* 帧缓冲内存虚拟地址 */
char *screen_buffer;
};
unsigned long screen_size; /* 帧缓冲内存大小,字节 */
void *pseudo_palette; /* 伪调色板 */
u32 state; /* 硬件状态 */
void *par; /* 私有数据指针,驱动可扩展存储硬件相关信息 */
/* 其他成员省略 */
};
|
1.3.2. 可变屏幕参数结构体¶
可变屏幕参数结构体(struct fb_var_screeninfo)用于描述帧缓冲的可变参数,这些参数可由用户通过ioctl接口修改(如修改屏幕分辨率、像素格式),驱动程序需根据修改后的参数重新配置硬件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | struct fb_var_screeninfo {
__u32 xres; /* 水平像素数 */
__u32 yres; /* 垂直像素数 */
__u32 xres_virtual; /* 水平虚拟分辨率 */
__u32 yres_virtual; /* 垂直虚拟分辨率 */
__u32 xoffset; /* 虚拟到可见区域的水平偏移 */
__u32 yoffset; /* 虚拟到可见区域的垂直偏移 */
__u32 bits_per_pixel; /* 每个像素占用位数 */
__u32 grayscale; /* 灰度模式(0=彩色,1=灰度) */
struct fb_bitfield red; /* 红色通道位域描述 */
struct fb_bitfield green; /* 绿色通道位域描述 */
struct fb_bitfield blue; /* 蓝色通道位域描述 */
struct fb_bitfield transp;/* 透明度通道位域描述 */
__u32 activate; /* 参数生效方式 */
__u32 pixclock; /* 像素时钟周期 */
__u32 rotate; /* 屏幕逆时针旋转角度 */
__u32 colorspace; /* 色彩空间 */
/* 其他成员省略 */
};
|
1.3.3. 固定屏幕参数结构体¶
固定屏幕参数结构体(struct fb_fix_screeninfo)用于描述帧缓冲的固定参数,这些参数由硬件决定,用户无法修改,驱动程序在初始化时填充。
1 2 3 4 5 6 7 8 9 10 11 12 | struct fb_fix_screeninfo {
char id[16]; /* 设备标识字符串 */
unsigned long smem_start; /* 帧缓冲内存起始地址 */
__u32 smem_len; /* 帧缓冲内存大小 */
__u32 type; /* 显示设备类型 */
__u32 visual; /* 视觉模式 */
__u32 line_length; /* 每行像素占用的字节数 */
unsigned long mmio_start; /* 内存映射I/O起始地址 */
__u32 mmio_len; /* 内存映射I/O区域大小 */
__u16 capabilities; /* 设备能力 */
/* 其他成员省略 */
};
|
1.3.4. 帧缓冲操作函数集结构体¶
帧缓冲操作函数集结构体(struct fb_ops)是帧缓冲驱动的核心操作函数集,驱动程序需实现该结构体中的函数,以响应应用程序的操作(如open、read、ioctl等),内核通过该结构体调用驱动的具体实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | struct fb_ops {
/* 模块所有者 */
struct module *owner;
/* 打开帧缓冲设备 */
int (*fb_open)(struct fb_info *info, int user);
/* 关闭帧缓冲设备 */
int (*fb_release)(struct fb_info *info, int user);
/* 读取像素数据 */
ssize_t (*fb_read)(struct fb_info *info, char __user *buf, size_t count, loff_t *ppos);
/* 写入像素数据 */
ssize_t (*fb_write)(struct fb_info *info, const char __user *buf, size_t count, loff_t *ppos);
/* 检查并修正可变参数 */
int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);
/* 根据var设置显示模式 */
int (*fb_set_par)(struct fb_info *info);
/* 屏幕息屏/亮屏 */
int (*fb_blank)(int blank, struct fb_info *info);
/* 像素填充 */
void (*fb_fillrect)(struct fb_info *info, const struct fb_fillrect *rect);
/* 图像复制 */
void (*fb_copyarea)(struct fb_info *info, const struct fb_copyarea *region);
/* 图像渲染 */
void (*fb_imageblit)(struct fb_info *info, const struct fb_image *image);
/* ioctl配置接口 */
int (*fb_ioctl)(struct fb_info *info, unsigned int cmd, unsigned long arg);
/* 内存映射操作 */
int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);
/* 其他成员省略 */
};
|
1.4. 帧缓冲驱动核心函数¶
1.4.1. 帧缓冲设备注册¶
1.4.1.1. register_framebuffer函数¶
register_framebuffer函数用于将驱动初始化好的struct fb_info结构体注册到内核帧缓冲抽象层,注册成功后,内核会创建对应的/dev/fbX设备文件,应用程序即可访问该帧缓冲设备。
函数原型:
1 | int register_framebuffer(struct fb_info *fb_info);
|
参数说明:
fb_info:指向驱动初始化好的帧缓冲设备结构体,需填充var、fix、fbops等核心成员。
返回值:
0:注册成功;
负数:注册失败,错误码对应具体失败原因,如参数无效、内存不足。
1.4.2. 帧缓冲设备注销¶
1.4.2.1. unregister_framebuffer函数¶
unregister_framebuffer函数用于将已注册的帧缓冲设备从内核中注销,释放相关资源(如设备编号、链表节点、字符设备文件),通常在驱动卸载时调用。
函数原型:
1 | void unregister_framebuffer(struct fb_info *fb_info);
|
参数说明:
fb_info:指向需要注销的帧缓冲设备结构体。
1.4.3. 帧缓冲内存分配¶
1.4.3.1. framebuffer_alloc函数¶
framebuffer_alloc函数用于分配struct fb_info结构体,并初始化其内部的互斥锁、工作队列等成员,简化驱动程序的初始化流程。
函数原型:
1 | struct fb_info *framebuffer_alloc(size_t size, struct device *dev);
|
参数说明:
size:私有数据的大小,即fb_info->par指向的内存大小,若无需私有数据,可设为0;
dev:关联的设备结构体(如platform_device),用于设备模型管理。
返回值:
非NULL:指向分配成功的fb_info结构体;
NULL:分配失败(内存不足)。
1.4.4. 帧缓冲内存释放¶
1.4.4.1. framebuffer_release函数¶
framebuffer_release函数用于释放由framebuffer_alloc分配的fb_info结构体,以及其关联的私有数据和资源。
函数原型:
1 | void framebuffer_release(struct fb_info *info);
|
参数说明:
fb_info:指向需要释放的由framebuffer_alloc分配的fb_info结构体。
1.4.5. ioctl核心处理函数¶
1.4.5.1. fb_ioctl函数¶
fb_ioctl函数是帧缓冲驱动的核心接口,用于处理应用程序发送的配置请求(如获取屏幕参数、修改像素格式、设置屏幕旋转等),驱动程序需实现该函数,并根据ioctl命令执行对应的操作。
常用ioctl命令:
FBIOPUT_VSCREENINFO:设置可变屏幕参数;
FBIOGET_VSCREENINFO:获取可变屏幕参数;
FBIOGET_FSCREENINFO:获取固定屏幕参数;
FBIOGETCMAP:获取颜色映射表;
FBIOPUTCMAP:设置颜色映射表;
FBIOBLANK:设置屏幕空白,如息屏、亮屏。
1.5. DRM帧缓冲核心结构体¶
1.5.1. DRM帧缓冲结构体¶
DRM帧缓冲结构体(struct drm_framebuffer)是DRM框架中帧缓冲的核心抽象结构体,用于描述DRM架构下的帧缓冲信息,与传统fbdev的struct fb_info对应,是现代DRM驱动中帧缓冲实现的核心载体,关联GEM内存对象和显示参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | struct drm_framebuffer {
struct drm_device *dev; /* 该帧缓冲所属的DRM设备 */
struct list_head head; /* 链表节点,加入DRM模式配置的帧缓冲链表,受fb_lock保护 */
struct drm_mode_object base; /* 基础模式设置对象结构体,包含引用计数 */
char comm[TASK_COMM_LEN]; /* 分配帧缓冲的进程名称,用于帧缓冲dump */
const struct drm_format_info *format; /* 帧缓冲像素格式信息 */
const struct drm_framebuffer_funcs *funcs; /* 帧缓冲操作函数集 */
unsigned int pitches[DRM_FORMAT_MAX_PLANES]; /* 每个平面的行跨度,来自drm_mode_fb_cmd2 */
unsigned int offsets[DRM_FORMAT_MAX_PLANES]; /* 每个平面的偏移量,标识像素数据在缓冲中的起始位置 */
uint64_t modifier; /* 数据布局修饰符,描述像素 tiling、压缩等特殊布局 */
unsigned int width; /* 帧缓冲可见区域的逻辑宽度 */
unsigned int height; /* 帧缓冲可见区域的逻辑高度 */
int flags; /* 帧缓冲标志,如DRM_MODE_FB_INTERLACED:隔行扫描 */
struct drm_gem_object *obj[DRM_FORMAT_MAX_PLANES]; /* 支撑帧缓冲的GEM对象,每个平面对应一个 */
/* 其他成员省略 */
};
|
该结构体与传统struct fb_info的核心区别的是,它依赖DRM框架的GEM内存管理,支持多平面(如YUV420格式的Y、U、V平面),适配多图层叠加、垂直同步等高级显示特性,是现代嵌入式DRM驱动帧缓冲实现的基础。
1.5.1.1. GEM对象与帧缓冲的关系¶
帧缓冲的像素数据存储在GEM(Graphics Execution Manager)对象中,drm_framebuffer的obj[]成员指向对应的GEM对象。
GEM负责管理帧缓冲的内存(分配、释放、映射),帧缓冲负责管理图像的格式和尺寸,二者协同完成图像数据的存储与显示。
1.5.1.1.1. GEM内存对象结构体¶
GEM内存对象结构体(struct drm_gem_object)是DRM框架中GEM(Graphics Execution Manager)内存管理的核心结构体,用于描述DRM帧缓冲关联的内存对象,是DRM帧缓冲(struct drm_framebuffer)的内存支撑,负责管理帧缓冲的内存分配、映射、释放等。
1 2 3 4 5 6 7 8 9 10 11 12 | struct drm_gem_object {
struct kref refcount; // 对象引用计数
unsigned handle_count; // GEM对象的文件私有句柄计数
struct drm_device *dev; // 该GEM对象所属的DRM设备
struct file *filp; // 可交换缓冲的SHMEM文件节点,私有内存时为NULL
struct drm_vma_offset_node vma_node;// 支持mmap的映射信息,管理内存映射偏移
size_t size; // 对象大小,生命周期内不可修改
struct dma_resv *resv; // 关联的预留对象指针,通常指向_resv
struct dma_resv _resv; // 自身预留对象,导入GEM对象不使用
const struct drm_gem_object_funcs *funcs; // GEM对象操作函数集
/* 其他成员省略 */
};
|
1.5.2. DRM帧缓冲模式命令结构体¶
DRM帧缓冲模式命令结构体(struct drm_mode_fb_cmd2)是DRM用户空间与内核空间交互帧缓冲配置的核心数据载体,用于传递用户空间请求的帧缓冲参数(如分辨率、像素格式、GEM内存句柄等),是用户空间创建、配置DRM帧缓冲的关键接口。
1 2 3 4 5 6 7 8 9 10 11 12 | struct drm_mode_fb_cmd2 {
__u32 fb_id; // 帧缓冲对象ID
__u32 width; // 帧缓冲宽度
__u32 height; // 帧缓冲高度
__u32 pixel_format; // 像素格式,参考DRM_FORMAT_*常量,drm_fourcc.h中定义
__u32 flags; // 帧缓冲标志,如隔行扫描、格式修饰符标志等
__u32 handles[4]; // GEM缓冲区句柄,每个平面对应一个,未使用设为0
__u32 pitches[4]; // 每个平面的行跨度
__u32 offsets[4]; // 每个平面在缓冲区中的偏移量
/* 其他成员省略 */
};
|
用户空间通过填充该结构体的参数,向内核驱动发起帧缓冲创建请求,内核驱动则解析该结构体中的配置,完成帧缓冲的初始化与内存绑定,是DRM帧缓冲与用户空间通信的桥梁。
1.5.3. DRM帧缓冲操作函数集结构体¶
DRM帧缓冲操作函数集结构体(struct drm_framebuffer)是DRM帧缓冲的操作函数集,与传统fbdev的struct fb_ops对应,DRM驱动需实现该结构体中的核心函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 | struct drm_framebuffer_funcs {
/* 销毁帧缓冲资源,释放关联的GEM内存等,DRM核心会保证对每个成功创建的帧缓冲调用该函数 */
void (*destroy)(struct drm_framebuffer *framebuffer);
/* 为帧缓冲创建用户空间句柄,供用户空间访问帧缓冲内存 */
int (*create_handle)(struct drm_framebuffer *fb,
struct drm_file *file_priv,
unsigned int *handle);
/* 可选回调,处理用户空间通知的帧缓冲脏区域更新,需将脏区域刷新到显示硬件 */
int (*dirty)(struct drm_framebuffer *framebuffer,
struct drm_file *file_priv, unsigned flags,
unsigned color, struct drm_clip_rect *clips,
unsigned num_clips);
};
|
1.5.4. AFBC专用DRM帧缓冲结构体¶
AFBC专用DRM帧缓冲结构体(struct drm_afbc_framebuffer)是DRM框架中专门用于AFBC(Arm Frame Buffer Compression,Arm帧缓冲压缩)场景的帧缓冲结构体,继承自struct drm_framebuffer,适用于需要帧缓冲压缩的高端显示场景,如高清视频、复杂GUI显示,可减少内存带宽占用。
1 2 3 4 5 6 7 8 9 | struct drm_afbc_framebuffer {
struct drm_framebuffer base; /* 继承DRM帧缓冲基础结构体 */
u32 block_width; /* 单个AFBC压缩块的宽度 */
u32 block_height; /* 单个AFBC压缩块的高度 */
u32 aligned_width; /* AFBC帧缓冲的对齐宽度 */
u32 aligned_height; /* AFBC帧缓冲的对齐高度 */
u32 offset; /* 第一个AFBC压缩头部的偏移量 */
u32 afbc_size; /* AFBC压缩缓冲的最小大小 */
};
|
1.5.5. DRM fbdev兼容助手结构体¶
DRM fbdev兼容助手结构体(struct drm_fb_helper)是DRM框架中fbdev兼容助手的核心结构体,用于实现DRM帧缓冲到传统fbdev设备的兼容适配,是fbdev-emulator模块的核心载体。
1 2 3 4 5 6 7 8 9 10 11 12 13 | struct drm_fb_helper {
struct drm_client_dev client; // fbdev模拟使用的DRM客户端
struct drm_client_buffer *buffer;// fbdev模拟使用的帧缓冲缓冲区
struct drm_framebuffer *fb; // 关联的DRM帧缓冲对象
struct drm_device *dev; // 关联的DRM设备
const struct drm_fb_helper_funcs *funcs; // fbdev兼容助手操作函数集
struct fb_info *fbdev; // 模拟的传统fbdev设备结构体
u32 pseudo_palette[17]; // 伪调色板,适配传统fbdev调色板功能
struct mutex lock; // 顶层互斥锁,保护内部数据结构
bool deferred_setup; // 延迟初始化标志,无输出时延迟fbdev设置
int preferred_bpp; // 驱动首选的每个像素位数(BPP),用于延迟初始化
/* 其他成员省略 */
};
|
1.5.6. DRM fbdev兼容助手操作函数集结构体¶
DRM fbdev兼容助手操作函数集(struct drm_fb_helper_funcs),与struct drm_fb_helper配套使用。
1 2 3 4 5 | struct drm_fb_helper_funcs {
// 驱动回调函数:分配并初始化fbdev info结构体,同时分配支撑fbdev的DRM帧缓冲
int (*fb_probe)(struct drm_fb_helper *helper,
struct drm_fb_helper_surface_size *sizes);
};
|
1.5.7. DRM fbdev兼容助手表面大小配置结构体¶
fbdev兼容助手表面大小配置结构体(struct drm_fb_helper_surface_size)是DRM fbdev兼容助手的核心配置结构体,用于存储fbdev兼容模式下帧缓冲和显示表面的关键参数, 是drm_fb_helper_prepare、drm_fb_helper_fill_info等函数的核心参数,负责传递显示分辨率、像素相关配置,为fb_info初始化和帧缓冲分配提供参数支撑。
1 2 3 4 5 6 7 8 | struct drm_fb_helper_surface_size {
u32 fb_width; // 帧缓冲宽度,对应fbdev设备的实际显示宽度
u32 fb_height; // 帧缓冲高度,对应fbdev设备的实际显示高度
u32 surface_width; // 显示表面宽度,与帧缓冲宽度一致或适配硬件缩放
u32 surface_height; // 显示表面高度,与帧缓冲高度一致或适配硬件缩放
u32 surface_bpp; // 显示表面像素位数,如16、24、32,决定像素格式
u32 surface_depth; // 显示表面像素深度,通常与surface_bpp一致,描述像素色彩深度
};
|
1.6. DRM帧缓冲驱动核心函数¶
1.6.1. DRM帧缓冲初始化¶
1.6.1.1. drm_framebuffer_init函数¶
drm_framebuffer_init函数是DRM帧缓冲的核心初始化函数,用于将分配好的struct drm_framebuffer对象注册到DRM核心,关联DRM设备和帧缓冲操作函数集,是DRM帧缓冲能够被内核识别和使用的前提。
函数原型:
1 2 3 | int drm_framebuffer_init(struct drm_device *dev,
struct drm_framebuffer *fb,
const struct drm_framebuffer_funcs *funcs);
|
参数说明:
dev:指向该帧缓冲所属的DRM设备结构体(struct drm_device),用于关联设备上下文。
fb:指向需要初始化的struct drm_framebuffer对象,需提前分配内存并填充width、height、format等核心成员。
funcs:指向DRM帧缓冲操作函数集(struct drm_framebuffer_funcs),驱动需实现该函数集中的核心回调(如destroy、create_handle)。
返回值:
0:初始化成功,帧缓冲对象被注册到DRM核心,加入设备的帧缓冲链表。
负数:初始化失败,错误码对应具体原因。
1.6.2. DRM帧缓冲销毁¶
1.6.2.1. drm_framebuffer_cleanup函数¶
drm_framebuffer_cleanup函数用于清理DRM帧缓冲对象的核心资源,与drm_framebuffer_init配套使用,通常在驱动自定义的destroy回调中调用,释放帧缓冲关联的链表节点、模式对象等资源。
函数原型:
1 | void drm_framebuffer_cleanup(struct drm_framebuffer *fb);
|
参数说明:
fb:指向需要清理的struct drm_framebuffer对象。
1.6.3. DRM帧缓冲句柄创建¶
1.6.3.1. drm_gem_fb_create_handle函数¶
drm_gem_fb_create_handle函数是DRM框架提供的辅助函数,用于为GEM类型的DRM帧缓冲创建用户空间句柄,供用户空间通过句柄访问帧缓冲关联的GEM内存。
函数原型:
1 2 | int drm_gem_fb_create_handle(struct drm_framebuffer *fb, struct drm_file *file,
unsigned int *handle);
|
参数说明:
fb:指向DRM帧缓冲对象,需关联GEM内存对象(fb->obj数组非空)。
file_priv:指向DRM文件私有数据结构体,关联当前用户空间进程的文件描述符。
handle:输出参数,用于存储创建成功的用户空间句柄,供用户空间后续操作使用。
返回值:
0:句柄创建成功,handle指向创建的句柄值。
负数:创建失败,错误码对应具体原因。
1.6.4. DRM帧缓冲脏区域更新¶
1.6.4.1. drm_atomic_helper_dirtyfb函数¶
drm_atomic_helper_dirtyfb函数是DRM原子驱动的辅助函数,用于处理用户空间通知的帧缓冲脏区域更新,将脏区域的像素数据刷新到显示硬件,适用于原子操作架构的DRM驱动,可直接作为struct drm_framebuffer_funcs的dirty回调使用。
函数原型:
1 2 3 4 | int drm_atomic_helper_dirtyfb(struct drm_framebuffer *fb,
struct drm_file *file_priv, unsigned int flags,
unsigned int color, struct drm_clip_rect *clips,
unsigned int num_clips);
|
参数说明:
fb:指向需要更新的DRM帧缓冲对象。
file_priv:指向DRM文件私有数据结构体,关联用户空间进程。
flags:脏区域更新标志,用于指定更新方式(如是否同步更新、是否忽略颜色值)。
color:填充颜色值,当clips为空时,用该颜色填充整个帧缓冲。
clips:指向脏区域矩形数组,每个矩形描述一个需要更新的脏区域。
num_clips:脏区域矩形的数量。
返回值:
0:脏区域更新成功,数据已刷新到显示硬件。
负数:更新失败,错误码对应具体原因。
1.6.5. GEM帧缓冲初始化¶
drm_gem_fb_init_with_funcs函数是DRM框架提供的GEM类型帧缓冲初始化辅助函数,基于drm_framebuffer_init封装,专门用于初始化关联GEM内存的DRM帧缓冲, 简化驱动中GEM帧缓冲的初始化流程,无需手动填充帧缓冲的部分核心成员,可用于快速初始化普通GEM帧缓冲。
函数原型:
1 2 3 4 5 | int drm_gem_fb_init_with_funcs(struct drm_device *dev,
struct drm_framebuffer *fb,
struct drm_file *file,
const struct drm_mode_fb_cmd2 *mode_cmd,
const struct drm_framebuffer_funcs *funcs);
|
参数说明:
dev:指向该帧缓冲所属的DRM设备结构体,关联设备上下文。
fb:指向需要初始化的struct drm_framebuffer对象,需提前分配内存。
file:指向DRM文件私有数据结构体,关联当前用户空间进程的文件描述符,用于关联帧缓冲的用户空间访问权限。
mode_cmd:指向DRM帧缓冲模式命令结构体,包含帧缓冲的核心配置参数。
funcs:指向DRM帧缓冲操作函数集。
返回值:
0:初始化成功,帧缓冲对象注册到DRM核心,关联GEM内存、用户空间文件上下文和操作函数集,可正常被DRM框架调度使用。
负数:初始化失败,错误码对应具体原因。
1.6.6. GEM对象引用计数释放¶
1.6.6.1. drm_gem_object_put函数¶
函数原型:
1 | static inline void drm_gem_object_put(struct drm_gem_object *obj);
|
参数说明:
obj:指向需要释放引用计数的struct drm_gem_object对象,若obj为NULL,函数直接返回,不执行任何操作,避免空指针异常。
1.6.7. AFBC帧缓冲初始化¶
1.6.7.1. drm_gem_fb_afbc_init函数¶
drm_gem_fb_afbc_init函数是DRM框架提供的AFBC专用帧缓冲初始化函数,专门用于初始化基于AFBC压缩格式的DRM帧缓冲,适配struct drm_afbc_framebuffer结构体,是支持AFBC压缩特性的帧缓冲初始化核心函数。
函数原型:
1 2 3 | int drm_gem_fb_afbc_init(struct drm_device *dev,
const struct drm_mode_fb_cmd2 *mode_cmd,
struct drm_afbc_framebuffer *afbc_fb);
|
参数说明:
dev:指向该AFBC帧缓冲所属的DRM设备结构体,关联设备上下文,确保帧缓冲与设备硬件适配。
mode_cmd:指向DRM帧缓冲模式命令结构体,包含AFBC帧缓冲的核心配置参数,如分辨率、像素格式、行跨度、偏移量等,是初始化AFBC帧缓冲的参数来源。
afbc_fb:指向需要初始化的AFBC专用帧缓冲结构体,需提前分配内存。
返回值:
0:初始化成功,AFBC帧缓冲对象注册到DRM核心,关联AFBC压缩配置和GEM内存,可正常用于显示。
负数:初始化失败,错误码对应具体原因。
1.6.8. 帧缓冲参数填充¶
1.6.8.1. drm_helper_mode_fill_fb_struct函数¶
drm_helper_mode_fill_fb_struct函数是DRM框架提供的辅助函数,核心作用是从DRM帧缓冲模式命令结构体(struct drm_mode_fb_cmd2)中提取配置参数,自动填充到DRM帧缓冲结构体(struct drm_framebuffer)中,无需驱动手动解析和赋值,简化帧缓冲初始化流程。
函数原型:
1 2 3 | void drm_helper_mode_fill_fb_struct(struct drm_device *dev,
struct drm_framebuffer *fb,
const struct drm_mode_fb_cmd2 *mode_cmd);
|
参数说明:
dev:指向该帧缓冲所属的DRM设备结构体,关联设备上下文,确保参数填充符合设备硬件特性。
fb:指向需要填充参数的DRM帧缓冲结构体,需提前分配内存,函数将自动填充其核心成员。
mode_cmd:指向DRM帧缓冲模式命令结构体,是参数来源,包含用户空间请求的帧缓冲配置,如分辨率、像素格式、行跨度等。
1.6.9. DRM fbdev兼容准备函数¶
1.6.9.1. drm_fb_helper_prepare函数¶
drm_fb_helper_prepare函数是DRM框架中fbdev兼容助手的核心准备函数,用于在fbdev兼容模式启用前,完成必要的初始化准备工作,为后续fbdev设备的创建、帧缓冲关联奠定基础。
函数原型:
1 2 | void drm_fb_helper_prepare(struct drm_device *dev, struct drm_fb_helper *helper,
const struct drm_fb_helper_funcs *funcs);
|
参数说明:
dev:指向该帧缓冲所属的DRM设备结构体,关联设备上下文。
helper:指向DRM fbdev兼容助手结构体。
fbdev:指向兼容助手操作函数集结构体。
1.6.10. DRM fbdev兼容助手初始化¶
1.6.10.1. drm_fb_helper_init函数¶
drm_fb_helper_init函数是DRM框架中fbdev兼容助手的核心初始化函数,用于初始化struct drm_fb_helper结构体,关联DRM设备,完成fbdev兼容模式的基础配置,为后续fbdev设备创建、帧缓冲关联提供前提,是DRM帧缓冲实现fbdev向下兼容的核心入口函数。
函数原型:
1 | int drm_fb_helper_init(struct drm_device *dev, struct drm_fb_helper *helper);
|
参数说明:
dev:指向该帧缓冲所属的DRM设备结构体,关联设备上下文。
helper:指向需要初始化的DRM fbdev兼容助手结构体,需提前通过kzalloc等函数分配内存,初始化后将作为fbdev兼容模式的核心载体,存储兼容模式相关的所有状态和配置。
返回值:
0:初始化成功,helper已关联DRM设备,内部核心成员(如锁、客户端对象)已完成初始化,可正常进行后续fbdev兼容配置。
负数:初始化失败,错误码对应具体原因。
1.6.11. DRM fbdev兼容助手初始配置¶
1.6.11.1. drm_fb_helper_initial_config函数¶
drm_fb_helper_initial_config函数用于完成fbdev兼容助手的初始配置,核心是根据指定的像素位数(BPP),分配并初始化fbdev对应的DRM帧缓冲,创建模拟的fbdev设备(struct fb_info),完成fbdev接口与DRM帧缓冲的绑定,是fbdev兼容模式真正生效的核心函数。
函数原型:
1 | int drm_fb_helper_initial_config(struct drm_fb_helper *fb_helper, int bpp_sel);
|
参数说明:
fb_helper:指向已通过drm_fb_helper_init初始化的DRM fbdev兼容助手结构体,是配置的核心载体,需确保已关联DRM设备。
bpp_sel:指定fbdev设备的像素位数,即每个像素占用的位数,用于确定帧缓冲的像素格式,常用值为16(RGB565)、24(RGB888)、32(ARGB8888);若设为0,驱动会使用默认的BPP。
返回值:
0:初始配置成功,已创建fbdev设备(struct fb_info),并关联对应的DRM帧缓冲,fbdev接口已生效,可通过/dev/fbX访问。
负数:配置失败,错误码对应具体原因。
1.6.12. DRM fbdev兼容助手资源释放¶
1.6.12.1. drm_fb_helper_fini函数¶
drm_fb_helper_fini函数用于释放DRM fbdev兼容助手的所有资源,与drm_fb_helper_init配套使用,核心是注销模拟的fbdev设备、释放关联的DRM帧缓冲、清理内部状态和同步机制,通常在DRM驱动卸载时调用,确保资源彻底释放,避免内存泄漏,是fbdev兼容模式退出的核心函数。
函数原型:
1 | void drm_fb_helper_fini(struct drm_fb_helper *helper);
|
参数说明:
helper:指向已通过drm_fb_helper_init初始化的DRM fbdev兼容助手结构体,需释放其关联的所有资源;若helper为NULL,函数直接返回,不执行任何操作。
1.6.13. DRM fbdev兼容助手fb_info分配¶
1.6.13.1. drm_fb_helper_alloc_fbi函数¶
drm_fb_helper_alloc_fbi函数是DRM框架中fbdev兼容助手的专用内存分配函数,核心作用是为模拟的fbdev设备分配并初始化struct fb_info结构体,填充fb_info的核心成员(如操作函数集、私有数据等),关联fbdev兼容助手,避免驱动手动分配和初始化fb_info的繁琐操作,是fbdev设备创建的核心辅助函数。
函数原型:
1 | struct fb_info *drm_fb_helper_alloc_fbi(struct drm_fb_helper *fb_helper);
|
参数说明:
fb_helper:指向已通过drm_fb_helper_init初始化的DRM fbdev兼容助手结构体,用于关联分配的fb_info结构体,确保fb_info与fbdev兼容助手的上下文一致。
返回值说明:
非NULL:分配并初始化成功,返回指向struct fb_info结构体的指针,该结构体已关联fb_helper,可直接用于后续fbdev设备注册。
NULL:分配失败,通常是由于内存不足(ENOMEM)或fb_helper未初始化,无法完成fb_info的分配和初始化。
1.6.14. DRM fbdev兼容助手fb_info参数填充¶
drm_fb_helper_fill_info函数是DRM框架中fbdev兼容助手的专用参数填充函数,核心作用是将fbdev兼容模式的显示参数(如分辨率、像素格式、行跨度等)填充到已分配的struct fb_info结构体中,完善fb_info的配置,与drm_fb_helper_alloc_fbi函数配套使用,进一步简化fbdev设备的创建流程。
函数原型:
1 2 3 | void drm_fb_helper_fill_info(struct fb_info *info,
struct drm_fb_helper *fb_helper,
struct drm_fb_helper_surface_size *sizes);
|
参数说明:
info:指向已通过drm_fb_helper_alloc_fbi或手动分配的struct fb_info结构体,需填充核心显示参数,确保该结构体已完成基础初始化。
fb_helper:指向已通过drm_fb_helper_init初始化的DRM fbdev兼容助手结构体,关联DRM设备上下文,为参数填充提供设备相关配置。
sizes:指向struct drm_fb_helper_surface_size结构体,包含fbdev设备的核心显示参数(宽度、高度、像素位数BPP、像素格式、行跨度等),是参数填充的核心来源。
1.7. 瑞芯微DRM驱动中帧缓冲的实现解析¶
瑞芯微DRM驱动的帧缓冲实现,依赖3个核心源码文件,位于内核源码/drivers/gpu/drm/rockchip/目录:
rockchip_drm_fb.c:核心文件,实现帧缓冲的创建、销毁、格式检查等基础操作,定义帧缓冲的核心函数集。
rockchip_drm_fbdev.c:适配Linux传统fbdev子系统,将DRM帧缓冲封装为fbdev设备,兼容依赖fbdev的传统应用。
rockchip_drm_drv.c:驱动主文件,注册帧缓冲相关的回调函数,完成帧缓冲的整体初始化。
瑞芯微帧缓冲的实现流程可分为:帧缓冲初始化、帧缓冲创建、帧缓冲销毁、fbdev适配四大模块,以下以 瑞芯微6.1.99内核版本 结合源码函数逐一分析。
注解
以下仅对DRM驱动的帧缓冲实现进行详解,其他如DRM模式配置等涉及其他DRM内容不作展开,这些内容可学习后续DRM章节后再回头查看。
1.7.1. 帧缓冲初始化¶
驱动启动时,rockchip_drm_fb.c的rockchip_drm_bind函数调用rockchip_drm_mode_config_init函数初始化DRM模式配置,其中注册了帧缓冲的创建回调函数,为后续帧缓冲创建做准备。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | void rockchip_drm_mode_config_init(struct drm_device *dev)
{
dev->mode_config.min_width = 0;
dev->mode_config.min_height = 0;
// 设置帧缓冲最大尺寸,默认16384x16384,限制帧缓冲大小
dev->mode_config.max_width = 16384;
dev->mode_config.max_height = 16384;
dev->mode_config.async_page_flip = true; // 支持异步页翻转,提升显示流畅度
// 注册帧缓冲核心操作函数集
dev->mode_config.funcs = &rockchip_drm_mode_config_funcs;
// 注册帧缓冲辅助操作函数
dev->mode_config.helper_private = &rockchip_mode_config_helpers;
dev->mode_config.normalize_zpos = true;
}
|
其中rockchip_drm_mode_config_funcs结构体定义了帧缓冲的创建函数fb_create:
1 2 3 4 5 6 | static const struct drm_mode_config_funcs rockchip_drm_mode_config_funcs = {
.fb_create = rockchip_fb_create, // 帧缓冲创建回调
.output_poll_changed = rockchip_drm_output_poll_changed,
.atomic_check = rockchip_atomic_check,
.atomic_commit = drm_atomic_helper_commit,
};
|
当应用层调用drmModeAddFB2等接口创建帧缓冲时,DRM框架会自动调用rockchip_fb_create函数。
1.7.2. 帧缓冲创建¶
rockchip_fb_create函数是帧缓冲创建的入口,由DRM框架回调,负责检查像素格式、分配帧缓冲结构体、初始化GEM关联,支持AFBC格式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | static struct drm_framebuffer *
rockchip_fb_create(struct drm_device *dev, struct drm_file *file,
const struct drm_mode_fb_cmd2 *mode_cmd)
{
struct drm_afbc_framebuffer *afbc_fb;
const struct drm_format_info *info;
int ret, i;
// 1. 获取像素格式信息
info = drm_get_format_info(dev, mode_cmd);
if (!info)
return ERR_PTR(-ENOMEM);
// 2. 检查每一行像素的字节数,必须4字节对齐,硬件要求
for (i = 0; i < info->num_planes; ++i) {
if (mode_cmd->pitches[i] % 4) {
DRM_DEV_ERROR_RATELIMITED(dev->dev,
"fb pitch[%d] must be 4 byte aligned: %d\n", i, mode_cmd->pitches[i]);
return ERR_PTR(-EINVAL);
}
}
// 3. 分配AFBC帧缓冲结构体
afbc_fb = kzalloc(sizeof(*afbc_fb), GFP_KERNEL);
if (!afbc_fb)
return ERR_PTR(-ENOMEM);
// 4. 初始化GEM帧缓冲
ret = drm_gem_fb_init_with_funcs(dev, &afbc_fb->base, file, mode_cmd,
&rockchip_drm_fb_funcs);
if (ret) {
kfree(afbc_fb);
return ERR_PTR(ret);
}
// 5. 如果是AFBC格式,初始化AFBC相关参数
if (drm_is_afbc(mode_cmd->modifier[0])) {
ret = drm_gem_fb_afbc_init(dev, mode_cmd, afbc_fb);
if (ret) {
struct drm_gem_object **obj = afbc_fb->base.obj;
for (i = 0; i < info->num_planes; ++i)
drm_gem_object_put(obj[i]);
kfree(afbc_fb);
return ERR_PTR(ret);
}
}
// 返回初始化完成的帧缓冲,afbc_fb->base是标准drm_framebuffer结构体
return &afbc_fb->base;
}
|
1.7.3. 帧缓冲内存分配¶
rockchip_fb_alloc函数是普通帧缓冲内存分配的底层接口,被rockchip_drm_framebuffer_init调用,负责分配drm_framebuffer结构体,并关联GEM对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | struct drm_framebuffer *
rockchip_fb_alloc(struct drm_device *dev, const struct drm_mode_fb_cmd2 *mode_cmd,
struct drm_gem_object **obj, unsigned int num_planes)
{
struct drm_framebuffer *fb;
int ret;
int i;
// 1. 分配帧缓冲结构体
fb = kzalloc(sizeof(*fb), GFP_KERNEL);
if (!fb)
return ERR_PTR(-ENOMEM);
// 2. 填充帧缓冲基本信息,如尺寸、格式、行距等
drm_helper_mode_fill_fb_struct(dev, fb, mode_cmd);
// 3. 关联GEM对象
for (i = 0; i < num_planes; i++)
fb->obj[i] = obj[i];
// 4. 初始化帧缓冲,关联操作函数集
ret = drm_framebuffer_init(dev, fb, &rockchip_drm_fb_funcs);
if (ret) {
DRM_DEV_ERROR(dev->dev,
"Failed to initialize framebuffer: %d\n",
ret);
kfree(fb);
return ERR_PTR(ret);
}
return fb;
}
|
1.7.4. 帧缓冲销毁¶
帧缓冲销毁的核心是释放帧缓冲结构体、GEM对象,瑞芯微针对普通帧缓冲和LOGO帧缓冲做了差异化处理,避免LOGO显示时内存被误释放。
1 2 3 4 5 6 7 8 9 10 11 12 | static void rockchip_drm_fb_destroy(struct drm_framebuffer *fb)
{
// 区分LOGO帧缓冲和普通帧缓冲
if (is_rockchip_logo_fb(fb)) {
struct rockchip_drm_logo_fb *rockchip_logo_fb = to_rockchip_logo_fb(fb);
// LOGO帧缓冲:延迟1秒销毁
schedule_delayed_work(&rockchip_logo_fb->destroy_work, HZ);
} else {
// 普通帧缓冲:立即销毁
__rockchip_drm_fb_destroy(fb);
}
}
|
__rockchip_drm_fb_destroy函数是实际的销毁逻辑,负责释放帧缓冲资源。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | static void __rockchip_drm_fb_destroy(struct drm_framebuffer *fb)
{
int i = 0;
// 1. 清理帧缓冲
drm_framebuffer_cleanup(fb);
// 2. 处理LOGO帧缓冲:释放LOGO资源和GEM对象
if (is_rockchip_logo_fb(fb)) {
struct rockchip_drm_logo_fb *rockchip_logo_fb = to_rockchip_logo_fb(fb);
#ifndef MODULE
rockchip_free_loader_memory(fb->dev); // 释放BootLoader分配的LOGO内存
#endif
drm_gem_object_release(rockchip_logo_fb->fb.obj[0]); // 释放GEM对象
kfree(rockchip_logo_fb); // 释放LOGO帧缓冲扩展结构体
} else {
// 3. 处理普通帧缓冲:释放所有关联的GEM对象
for (i = 0; i < 4; i++) {
if (fb->obj[i])
drm_gem_object_put(fb->obj[i]);
}
kfree(fb); // 释放普通帧缓冲结构体
}
}
|
1.7.5. fbdev适配¶
Linux系统中,部分传统应用依赖fbdev子系统(如fbcon控制台),瑞芯微驱动通过rockchip_drm_fbdev.c实现DRM帧缓冲与fbdev的适配,将DRM帧缓冲封装为fbdev设备。
1.7.5.1. fbdev初始化¶
rockchip_drm_fbdev_init函数在驱动绑定阶段被调用,创建fbdev helper,初始化fbdev设备,关联DRM帧缓冲。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | int rockchip_drm_fbdev_init(struct drm_device *dev)
{
struct rockchip_drm_private *private = dev->dev_private;
struct drm_fb_helper *helper;
int ret;
// 检查DRM设备是否有CRTC和连接器
if (!dev->mode_config.num_crtc || !dev->mode_config.num_connector)
return -EINVAL;
// 1. 分配fbdev helper结构体,用于管理fbdev设备
helper = devm_kzalloc(dev->dev, sizeof(*helper), GFP_KERNEL);
if (!helper)
return -ENOMEM;
private->fbdev_helper = helper;
// 2. 准备fbdev helper,关联操作函数集
drm_fb_helper_prepare(dev, helper, &rockchip_drm_fb_helper_funcs);
// 3. 初始化fbdev helper
ret = drm_fb_helper_init(dev, helper);
if (ret < 0) {
DRM_DEV_ERROR(dev->dev,
"Failed to initialize drm fb helper - %d.\n",
ret);
return ret;
}
// 4. 设置fbdev初始配置,默认32位色深,匹配大多数显示场景
ret = drm_fb_helper_initial_config(helper, PREFERRED_BPP);
if (ret < 0) {
DRM_DEV_ERROR(dev->dev,
"Failed to set initial hw config - %d.\n",
ret);
goto err_drm_fb_helper_fini;
}
return 0;
err_drm_fb_helper_fini:
drm_fb_helper_fini(helper);
return ret;
}
|
1.7.5.2. fbdev帧缓冲创建¶
rockchip_drm_fbdev_create函数是fbdev helper的核心回调,负责创建fbdev对应的DRM帧缓冲,分配GEM内存,初始化fb_info结构体。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | static int rockchip_drm_fbdev_create(struct drm_fb_helper *helper,
struct drm_fb_helper_surface_size *sizes)
{
struct rockchip_drm_private *private = helper->dev->dev_private;
struct drm_mode_fb_cmd2 mode_cmd = { 0 };
struct drm_device *dev = helper->dev;
struct rockchip_gem_object *rk_obj;
struct drm_framebuffer *fb;
unsigned int bytes_per_pixel;
unsigned long offset;
struct fb_info *fbi;
size_t size;
int ret;
// 1. 根据色深计算每像素字节数
bytes_per_pixel = DIV_ROUND_UP(sizes->surface_bpp, 8);
// 2. 填充帧缓冲参数:尺寸、行距、像素格式
mode_cmd.width = sizes->surface_width;
mode_cmd.height = sizes->surface_height;
mode_cmd.pitches[0] = sizes->surface_width * bytes_per_pixel;
mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
sizes->surface_depth);
// 3. 计算帧缓冲内存大小:宽度x高度x每像素字节数
size = mode_cmd.pitches[0] * mode_cmd.height;
// 4. 分配GEM对象
rk_obj = rockchip_gem_create_object(dev, size, true, 0);
if (IS_ERR(rk_obj))
return -ENOMEM;
private->fbdev_bo = &rk_obj->base;
// 5. 分配fb_info结构体,用于兼容传统应用
fbi = drm_fb_helper_alloc_fbi(helper);
if (IS_ERR(fbi)) {
DRM_DEV_ERROR(dev->dev, "Failed to create framebuffer info.\n");
ret = PTR_ERR(fbi);
goto out;
}
// 6. 初始化DRM帧缓冲,关联GEM对象
helper->fb = rockchip_drm_framebuffer_init(dev, &mode_cmd,
private->fbdev_bo);
if (IS_ERR(helper->fb)) {
DRM_DEV_ERROR(dev->dev,
"Failed to allocate DRM framebuffer.\n");
ret = PTR_ERR(helper->fb);
goto out;
}
// 7. 配置fb_info结构体:关联fbdev操作函数、帧缓冲内存地址等
fbi->fbops = &rockchip_drm_fbdev_ops;
fb = helper->fb;
drm_fb_helper_fill_info(fbi, helper, sizes);
offset = fbi->var.xoffset * bytes_per_pixel;
offset += fbi->var.yoffset * fb->pitches[0];
dev->mode_config.fb_base = 0;
fbi->screen_base = rk_obj->kvaddr + offset; // 帧缓冲虚拟地址
fbi->screen_size = rk_obj->base.size; // 帧缓冲内存大小
fbi->fix.smem_len = rk_obj->base.size;
DRM_DEBUG_KMS("FB [%dx%d]-%d kvaddr=%p offset=%ld size=%zu\n",
fb->width, fb->height, fb->format->depth,
rk_obj->kvaddr,
offset, size);
return 0;
out:
drm_gem_object_put(&rk_obj->base);
return ret;
}
|
1.7.5.3. fbdev操作函数集¶
rockchip_drm_fbdev_ops结构体定义了fbdev设备的操作接口(如内存映射、填充矩形、图像复制),兼容传统fbdev应用。
1 2 3 4 5 6 7 8 | static const struct fb_ops rockchip_drm_fbdev_ops = {
.owner = THIS_MODULE,
DRM_FB_HELPER_DEFAULT_OPS, // 复用DRM框架默认fbdev操作
.fb_mmap = rockchip_fbdev_mmap, // 帧缓冲内存映射
.fb_fillrect = drm_fb_helper_cfb_fillrect, // 填充矩形
.fb_copyarea = drm_fb_helper_cfb_copyarea, // 复制区域
.fb_imageblit = drm_fb_helper_cfb_imageblit, // 图像绘制
};
|
1.7.6. DRM帧缓冲测试¶
使用传统fbdev程序进行测试,通过以下步骤实现帧缓冲操作与纯色填充:
打开帧缓冲设备/dev/fb0,获取文件描述符。
通过IOCTL命令获取帧缓冲固定信息和可变信息,打印硬件参数。
使用mmap()将显存映射到用户空间,获取显存指针。
调用纯色填充函数,循环向显存写入指定颜色值,实现全屏填充。
通过IOCTL命令刷新屏幕,显示填充颜色,间隔1秒切换颜色,观察效果。
完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | #include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
// 定义32位ARGB8888格式颜色常量(A=透明度 R=红 G=绿 B=蓝)
#define RED 0xFFFF0000 // 红色:透明度255,红色255,绿色0,蓝色0
#define GREEN 0xFF00FF00 // 绿色:透明度255,红色0,绿色255,蓝色0
#define BLUE 0xFF0000FF // 蓝色:透明度255,红色0,绿色0,蓝色255
#define YELLOW 0xFFFFFF00 // 黄色:透明度255,红色255,绿色255,蓝色0
#define WHITE 0xFFFFFFFF // 白色:透明度255,红/绿/蓝全255
#define BLACK 0xFF000000 // 黑色:透明度255,红/绿/蓝全0
/**
* @brief 全屏纯色填充函数(32位色深)
* @param fb_addr 帧缓冲显存指针
* @param color 要填充的颜色值
* @param screen_size 屏幕总字节数
*/
void fill_color(uint32_t *fb_addr, uint32_t color, int screen_size)
{
// 计算屏幕总像素数:32位像素 = 4字节,总字节数/4=像素个数
int pix_count = screen_size / 4;
// 循环遍历所有像素,填充指定颜色
for (int i = 0; i < pix_count; i++) {
fb_addr[i] = color; // 给每个像素点赋值
}
}
/**
* @brief 获取并打印帧缓冲硬件信息
* @param fp 帧缓冲设备文件描述符
* @param finfo 存储帧缓冲固定信息的结构体
* @param vinfo 存储帧缓冲可变信息的结构体
*/
void fb_get_info(int fp, struct fb_fix_screeninfo *finfo, struct fb_var_screeninfo *vinfo)
{
long screensize;
// IOCTL获取帧缓冲固定信息(显存地址、行宽等不可修改参数)
if (ioctl(fp, FBIOGET_FSCREENINFO, finfo)) {
perror("读取固定信息失败");
exit(2);
}
// IOCTL获取帧缓冲可变信息(分辨率、偏移等可修改参数)
if (ioctl(fp, FBIOGET_VSCREENINFO, vinfo)) {
perror("读取可变信息失败");
exit(3);
}
// 计算单屏字节大小 = 行宽 × 垂直分辨率
screensize = finfo->line_length * vinfo->yres;
// 打印帧缓冲硬件信息
printf("========== 帧缓冲信息 ==========\n");
printf("设备ID: %s\n", finfo->id); // 帧缓冲设备名称
printf("物理地址: 0x%lx, 总大小: %u 字节\n", (unsigned long)finfo->smem_start, finfo->smem_len); // 显存物理地址+总大小
printf("行宽: %u 字节\n", finfo->line_length); // 每行像素的字节数
printf("分辨率: %dx%d, 位深: %u 位\n", vinfo->xres, vinfo->yres, vinfo->bits_per_pixel); // 屏幕分辨率+色深
printf("虚拟分辨率: %dx%d\n", vinfo->xres_virtual, vinfo->yres_virtual); // 虚拟分辨率
printf("=================================\n");
}
int main()
{
int fb_fd; // 帧缓冲设备文件描述符
struct fb_var_screeninfo vinfo; // 帧缓冲可变参数结构体
struct fb_fix_screeninfo finfo; // 帧缓冲固定参数结构体
void *fb_base; // 显存映射到用户空间的基地址
uint32_t *fb_addr; // 32位像素格式的显存指针
int screen_size; // 单屏总字节大小
// 打开帧缓冲设备 /dev/fb0,读写模式
fb_fd = open("/dev/fb0", O_RDWR);
if (fb_fd < 0) {
perror("打开 /dev/fb0 失败");
exit(1);
}
// 获取并打印帧缓冲硬件参数
fb_get_info(fb_fd, &finfo, &vinfo);
// 将内核态显存映射到用户空间,方便直接操作
// 参数:NULL(系统自动分配地址)、显存总大小、读写权限、共享映射、设备fd、偏移0
fb_base = mmap(NULL, finfo.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fb_fd, 0);
if (fb_base == MAP_FAILED) {
perror("mmap 映射失败");
close(fb_fd);
exit(4);
}
// 强制转换为32位指针,匹配ARGB8888颜色格式
fb_addr = (uint32_t *)fb_base;
// 打印用户空间显存地址
printf("显存虚拟地址: %p\n", fb_addr);
// 计算单屏总字节大小 = 行宽 × 垂直分辨率
screen_size = finfo.line_length * vinfo.yres;
// 初始化显示:X/Y轴偏移量为0,全屏显示
vinfo.xoffset = 0;
vinfo.yoffset = 0;
// ========== 纯色循环填充测试 ==========
printf("\n开始填充黄色...\n");
fill_color(fb_addr, YELLOW, screen_size); // 填充黄色到显存
ioctl(fb_fd, FBIOPAN_DISPLAY, &vinfo); // 刷新屏幕,显示黄色
sleep(1);
printf("填充蓝色...\n");
fill_color(fb_addr, BLUE, screen_size); // 填充蓝色到显存
ioctl(fb_fd, FBIOPAN_DISPLAY, &vinfo); // 刷新屏幕,显示蓝色
sleep(1);
printf("填充红色...\n");
fill_color(fb_addr, RED, screen_size); // 填充红色到显存
ioctl(fb_fd, FBIOPAN_DISPLAY, &vinfo); // 刷新屏幕,显示红色
sleep(1);
printf("填充绿色...\n");
fill_color(fb_addr, GREEN, screen_size); // 填充绿色到显存
ioctl(fb_fd, FBIOPAN_DISPLAY, &vinfo); // 刷新屏幕,显示绿色
sleep(1);
printf("填充白色...\n");
fill_color(fb_addr, WHITE, screen_size); // 填充白色到显存
ioctl(fb_fd, FBIOPAN_DISPLAY, &vinfo); // 刷新屏幕,显示白色
sleep(1);
// 测试结束,填充黑色清屏
fill_color(fb_addr, BLACK, screen_size);
ioctl(fb_fd, FBIOPAN_DISPLAY, &vinfo);
// 释放用户空间映射的显存
munmap(fb_base, finfo.smem_len);
// 关闭帧缓冲设备文件
close(fb_fd);
printf("测试完成!\n");
return 0;
}
|
注意
传统fbdev程序不支持多屏异显,请不要接多块屏幕进行测试。
程序编译并运行测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | #编译程序
gcc frame_buffer_app.c -o frame_buffer_app
#运行程序
./frame_buffer_app
#以下为5.5寸MIPI屏幕信息打印
========== 帧缓冲信息 ==========
设备ID: rockchipdrmfb
物理地址: 0x0, 总大小: 16588800 字节
行宽: 4320 字节
分辨率: 1080x1920, 位深: 32 位
虚拟分辨率: 1080x1920
=================================
显存虚拟地址: 0x7f9fcee000
开始填充黄色...
填充蓝色...
填充红色...
填充绿色...
填充白色...
测试完成!
|
运行程序后终端会先打印帧缓冲硬件信息以及用户空间显存地址,然后可以观察到屏幕屏幕依次切换5种颜色,每种颜色全屏显示,测试结束后,屏幕清屏(显示黑色)。
1.8. 双缓冲驱动实验¶
如果学习过板卡配套的《Linux基础与应用开发实战指南》的framebuffer章节的同学有可能还会记得文档提供了一种实现双缓冲framebuffer设计的思路,但文档没有提供具体的驱动代码和应用程序。 因为仅仅学习了应用篇是不可能完成驱动修改支持双缓冲的,但对于如今已经学习了DRM帧缓冲驱动的我们来说,完全有能力实现双缓冲。
本实验基于 瑞芯微6.1.99内核版本 的源码进行修改实现双缓冲。
本章的示例代码目录为: linux_driver/35_frame_buffer
1.8.1. 双缓冲实现原理¶
双缓冲的核心是分配两块独立的显存区域(前台缓冲、后台缓冲),应用层在后台缓冲中绘制图像(不直接显示),绘制完成后通过“页翻转”切换显示区域,实现无闪烁显示。
瑞芯微DRM fbdev兼容层中,双缓冲可通过“虚拟分辨率”实现:将虚拟高度(yres_virtual)设置为物理高度(yres)的2倍,两块缓冲区域上下拼接,通过修改yoffset偏移量切换显示区域。
1.8.2. 驱动层双缓冲实现¶
修改内核源码/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c,修改内容如下:
定义双缓冲倍数
1 2 | //双缓冲倍数
#define DOUBLE_BUF 2
|
显存分配和虚拟分辨率设置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | static int rockchip_drm_fbdev_create(struct drm_fb_helper *helper,
struct drm_fb_helper_surface_size *sizes)
{
struct rockchip_drm_private *private = helper->dev->dev_private;
struct drm_mode_fb_cmd2 mode_cmd = { 0 };
struct drm_device *dev = helper->dev;
struct rockchip_gem_object *rk_obj;
struct drm_framebuffer *fb;
unsigned int bytes_per_pixel;
unsigned long offset;
struct fb_info *fbi;
size_t size;
int ret;
bytes_per_pixel = DIV_ROUND_UP(sizes->surface_bpp, 8);
mode_cmd.width = sizes->surface_width;
mode_cmd.height = sizes->surface_height * DOUBLE_BUF; //双缓冲:DRM帧缓冲高度设为2倍
mode_cmd.pitches[0] = sizes->surface_width * bytes_per_pixel;
mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
sizes->surface_depth);
size = mode_cmd.pitches[0] * mode_cmd.height;
rk_obj = rockchip_gem_create_object(dev, size, true, 0);
if (IS_ERR(rk_obj))
return -ENOMEM;
private->fbdev_bo = &rk_obj->base;
fbi = drm_fb_helper_alloc_fbi(helper);
if (IS_ERR(fbi)) {
DRM_DEV_ERROR(dev->dev, "Failed to create framebuffer info.\n");
ret = PTR_ERR(fbi);
goto out;
}
helper->fb = rockchip_drm_framebuffer_init(dev, &mode_cmd,
private->fbdev_bo);
if (IS_ERR(helper->fb)) {
DRM_DEV_ERROR(dev->dev,
"Failed to allocate DRM framebuffer.\n");
ret = PTR_ERR(helper->fb);
goto out;
}
fbi->fbops = &rockchip_drm_fbdev_ops;
fb = helper->fb;
drm_fb_helper_fill_info(fbi, helper, sizes);
fbi->var.yres_virtual = mode_cmd.height; //双缓冲:虚拟高度 = DRM帧缓冲总高度
offset = fbi->var.xoffset * bytes_per_pixel;
offset += fbi->var.yoffset * fb->pitches[0];
dev->mode_config.fb_base = 0;
fbi->screen_base = rk_obj->kvaddr + offset;
fbi->screen_size = rk_obj->base.size;
fbi->fix.smem_len = rk_obj->base.size;
DRM_DEBUG_KMS("FB [%dx%d]-%d kvaddr=%p offset=%ld size=%zu\n",
fb->width, fb->height, fb->format->depth,
rk_obj->kvaddr,
offset, size);
return 0;
out:
drm_gem_object_put(&rk_obj->base);
return ret;
}
|
第18行:DRM帧缓冲高度设为2倍,分配2倍物理高度的显存。
第52行:将虚拟高度同步到fbdev的可变参数中;
1.8.3. 应用层双缓冲实现¶
应用程序完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | #include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
// 定义32位ARGB8888格式颜色常量(A=透明度 R=红 G=绿 B=蓝)
#define RED 0xFFFF0000 // 红色:透明度255,红色255,绿色0,蓝色0
#define GREEN 0xFF00FF00 // 绿色:透明度255,红色0,绿色255,蓝色0
#define BLUE 0xFF0000FF // 蓝色:透明度255,红色0,绿色0,蓝色255
#define YELLOW 0xFFFFFF00 // 黄色:透明度255,红色255,绿色255,蓝色0
#define WHITE 0xFFFFFFFF // 白色:透明度255,红/绿/蓝全255
#define BLACK 0xFF000000 // 黑色:透明度255,红/绿/蓝全0
/**
* @brief 全屏纯色填充函数(32位色深)
* @param fb_addr 帧缓冲显存指针
* @param color 要填充的颜色值
* @param screen_size 屏幕总字节数
*/
void fill_color(uint32_t *fb_addr, uint32_t color, int screen_size)
{
// 计算屏幕总像素数:32位像素 = 4字节,总字节数/4=像素个数
int pix_count = screen_size / 4;
// 循环遍历所有像素,填充指定颜色
for (int i = 0; i < pix_count; i++) {
fb_addr[i] = color; // 给每个像素点赋值
}
}
/**
* @brief 获取并打印帧缓冲硬件信息
* @param fp 帧缓冲设备文件描述符
* @param finfo 存储帧缓冲固定信息的结构体
* @param vinfo 存储帧缓冲可变信息的结构体
*/
void fb_get_info(int fp, struct fb_fix_screeninfo *finfo, struct fb_var_screeninfo *vinfo)
{
long screensize;
// IOCTL获取帧缓冲固定信息(显存地址、行宽等不可修改参数)
if (ioctl(fp, FBIOGET_FSCREENINFO, finfo)) {
perror("读取固定信息失败");
exit(2);
}
// IOCTL获取帧缓冲可变信息(分辨率、偏移等可修改参数)
if (ioctl(fp, FBIOGET_VSCREENINFO, vinfo)) {
perror("读取可变信息失败");
exit(3);
}
// 计算单屏字节大小 = 行宽 × 垂直分辨率
screensize = finfo->line_length * vinfo->yres;
// 打印帧缓冲硬件信息
printf("========== 帧缓冲信息 ==========\n");
printf("设备ID: %s\n", finfo->id); // 帧缓冲设备名称
printf("物理地址: 0x%lx, 总大小: %u 字节\n", (unsigned long)finfo->smem_start, finfo->smem_len); // 显存物理地址+总大小
printf("行宽: %u 字节\n", finfo->line_length); // 每行像素的字节数
printf("分辨率: %dx%d, 位深: %u 位\n", vinfo->xres, vinfo->yres, vinfo->bits_per_pixel); // 屏幕分辨率+色深
printf("虚拟分辨率: %dx%d\n", vinfo->xres_virtual, vinfo->yres_virtual); // 虚拟分辨率
printf("=================================\n");
}
int main()
{
int fb_fd; // 帧缓冲设备文件描述符
struct fb_var_screeninfo vinfo; // 帧缓冲可变参数结构体
struct fb_fix_screeninfo finfo; // 帧缓冲固定参数结构体
void *fb_base; // 显存映射到用户空间的基地址
uint32_t *fb_addr; // 前台显示缓冲指针
uint32_t *fb_back_buf; // 后台显示缓冲指针
int screen_size; // 单屏总字节大小
int i; // 循环计数变量
// 打开帧缓冲设备 /dev/fb0,读写模式
fb_fd = open("/dev/fb0", O_RDWR);
if (fb_fd < 0) {
perror("打开 /dev/fb0 失败");
exit(1);
}
// 获取并打印帧缓冲硬件参数
fb_get_info(fb_fd, &finfo, &vinfo);
// 将内核态显存映射到用户空间,方便直接操作
// 参数:NULL(系统自动分配地址)、显存总大小、读写权限、共享映射、设备fd、偏移0
fb_base = mmap(NULL, finfo.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fb_fd, 0);
if (fb_base == MAP_FAILED) {
perror("mmap 映射失败");
close(fb_fd);
exit(4);
}
// 强制转换为32位指针,匹配ARGB8888颜色格式
fb_addr = (uint32_t *)fb_base;
// 打印用户空间显存地址
printf("显存虚拟地址: %p\n", fb_addr);
// 计算单屏总字节大小 = 行宽 × 垂直分辨率
screen_size = finfo.line_length * vinfo.yres;
// 后台缓冲地址 = 前台地址 + 单屏像素数
fb_back_buf = fb_addr + (screen_size / 4);
// 预填充两个缓冲:前台蓝色,后台红色
fill_color(fb_addr, BLUE, screen_size); // 物理屏填充蓝色
fill_color(fb_back_buf, RED, screen_size); // 虚拟屏填充红色
// 初始化显示:X/Y轴偏移量0,显示前台缓冲颜色
vinfo.xoffset = 0;
vinfo.yoffset = 0;
ioctl(fb_fd, FBIOPAN_DISPLAY, &vinfo); // 刷新屏幕
sleep(1);
// ===================== 双缓冲红蓝切换循环 =====================
printf("\n双缓冲红蓝切换5次测试开始!\n");
while (1) {
// 循环5次:红蓝交替切换
for(i=0;i<5;i++) {
// 切换Y轴偏移=屏幕高度,显示后台缓冲
vinfo.yoffset = vinfo.yres;
ioctl(fb_fd, FBIOPAN_DISPLAY, &vinfo);
printf("显示:红色\n");
sleep(1);
// 切换Y轴偏移=0,显示前台缓冲
vinfo.yoffset = 0;
ioctl(fb_fd, FBIOPAN_DISPLAY, &vinfo);
printf("显示:蓝色\n");
sleep(1);
}
break;
}
// 测试结束,填充黑色清屏
fill_color(fb_addr, BLACK, screen_size);
ioctl(fb_fd, FBIOPAN_DISPLAY, &vinfo);
// 释放用户空间映射的显存
munmap(fb_base, finfo.smem_len);
// 关闭帧缓冲设备文件
close(fb_fd);
printf("测试完成!\n");
return 0;
}
|
第77-78行:定义前台和后台显示缓冲指针,用来区分前台缓冲(基地址)和后台缓冲(基地址+单屏像素数)。
第110行:设置后台缓冲地址为前台地址 + 单屏像素数,其中单屏像素数 = 单屏总字节数 / 每个像素的字节数。
第113-114行:预填充两块缓冲,前台缓冲区填充蓝色,后台缓冲区填充红色。
第129、135行:通过FBIOPAN_DISPLAY指令修改yoffset偏移量,实现前台/后台缓冲的切换。
1.8.4. 编译内核并替换¶
由于本实验是直接修改内核源码/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c驱动的,而该驱动是直接编译进内核且与其他驱动有强依赖关系不能被编译成驱动模块, 因此,需要重新编译内核并替换板卡的内核。
1.8.4.1. 编译内核¶
修改内核自带的rockchip_drm_fbdev.c驱动后,在内核源码顶层目录执行以下命令编译内核:
1 2 3 4 5 6 7 8 | #清除之前生成的所有文件和配置
make mrproper
# 这里以rk356x系列6.1.99内核配置文件为例
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- lubancat_linux_rk356x_defconfig
# 编译内核,指定平台,指定交叉编译工具,使用8线程进行编译,线程可根据电脑性能自行确定
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j8
|
提示
其余系列板卡参考 驱动章节实验环境搭建章节的编译内核 小节进行编译。
编译完成后在 内核源码/arch/arm64/boot/ 目录生成的 Image 文件就是内核文件。
1.8.4.2. 替换内核¶
将内核先传到板卡,再拷贝到板卡的/boot/目录下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #先传输到板卡
#查看板卡原先内核
ls /boot/Image* -l
#信息打印如下,可以确认当前板卡实际内核就是/boot/Image-6.1.99-rk356x
lrwxrwxrwx 1 root root 19 5月19日 10:15 /boot/Image -> Image-6.1.99-rk356x
-rw-r--r-- 1 root root 42213888 5月19日 10:15 /boot/Image-6.1.99-rk356x
#替换内核,根据实际内核名字而定
sudo cp -f Image /boot/Image-6.1.99-rk356x
#重启系统
sudo reboot
#查看内核版本
cat /proc/version
#信息打印如下
Linux version 6.1.99-rk356x (guest@dev107) (aarch64-linux-gnu-gcc (Ubuntu/Linaro 7.5.0-3ubuntu1~18.04) 7.5.0, GNU ld (GNU Binutils for Ubuntu) 2.30) #20 SMP Wed May 20 07:39:29 UTC 2026
|
可以从编译的主机名和编译时间确认内核是否替换成功,如以上信息中“guest@dev107”就是作者的服务器主机,“Wed May 20 07:39:29 UTC 2026”就是作者编译驱动的UTC世界时间,说明内核替换成功。
1.8.5. 程序运行结果¶
如出现 Permission denied 或类似字样,请注意用户权限,大部分操作硬件外设的功能,几乎都需要root用户权限,简单的解决方案是在执行语句前加入sudo或以root用户运行程序。
1.8.5.1. 实验操作¶
注意
传统fbdev程序不支持多屏异显,请不要接多块屏幕进行测试。
确认内核替换成功并连接屏幕后,使用以下命令编译并运行双缓冲测试程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | #编译程序
gcc frame_buffer_double_app.c -o frame_buffer_double_app
#运行程序
./frame_buffer_double_app
#以下为5.5寸MIPI屏幕信息打印
========== 帧缓冲信息 ==========
设备ID: rockchipdrmfb
物理地址: 0x0, 总大小: 16588800 字节
行宽: 4320 字节
分辨率: 1080x1920, 位深: 32 位
虚拟分辨率: 1080x3840
=================================
显存虚拟地址: 0x7f82cae000
双缓冲红蓝颜色切换5次测试开始!
显示:红色
显示:蓝色
显示:红色
显示:蓝色
显示:红色
显示:蓝色
显示:红色
显示:蓝色
显示:红色
显示:蓝色
测试完成!
|
可以从信息打印看到虚拟分辨率的高度是硬件实际分辨率的2倍,说明驱动修改成功; 屏幕依次切换红蓝颜色,每种颜色全屏显示,切换流畅,无卡顿、无花屏现象说明通过修改yoffset偏移量,实现前台/后台缓冲的切换成功。
1.8.6. 实验注意事项¶
双缓冲倍数设置:驱动层DOUBLE_BUF宏必须设为2,否则虚拟分辨率不满足双缓冲要求,应用层会报错;
显示刷新:应用层切换yoffset后,必须调用FBIOPAN_DISPLAY接口刷新,否则屏幕不会更新;
屏幕连接个数:勿连接多个屏幕,传统fbdev程序不支持多屏异显,若连接多屏幕会因识别到的实际分辨率和虚拟分辨率错误导致显示错位问题。