本文主要介绍DRM框架里的fbdev兼容逻辑
一 framebuffer框架简单介绍
framebuffer框架下fbdev的注册主要三步步:
(1)创建fbdev操作函数,以rockchip为例:
static const struct fb_ops rockchip_drm_fbdev_ops = {
.owner = THIS_MODULE,
DRM_FB_HELPER_DEFAULT_OPS,
.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,
};
(2)填充struct fb_info* fbi实例的各个参数,其中就包括赋值操作函数
fbi->fbops = &rockchip_drm_fbdev_ops
(3)注册fb设备
register_framebuffer(fbi)
二 DRM注册接口
drm驱动中注册fbdev, 主要是选择主要有以下几个方面:
1)创建显存drm_framebuffer 实例fb
2)填充到fb_info中 并注册fbdev
3)将fb绑定到对应的crtc plane
经过上述操作,就可以通过fbdev的FBIOGET_FSCREENINFO/FBIOGET_VSCREEENINFO、mmap/FBIOPAN_DISPLAY/FBIO_WAITFORVSYNC等操作显存, 而fb/crtc/plane/fb_info之间是通过drm_fb_helper来关联的,其结构体如下:
struct drm_fb_helper {
//client用来关联crtc/plane/mode_set等参数
struct drm_client_dev client;
struct drm_client_buffer *buffer;
//fb显存
struct drm_framebuffer *fb;
struct drm_device *dev;
//fb显存的创建hook,以及部分fb_info fbdev成员的填充,该hook由各drm驱动创建
const struct drm_fb_helper_funcs *funcs;
//fbdev
struct fb_info *fbdev;
// ....
};
(1)fb_helper->fb的创建
helper->fb是显存drm_framebuffer实例,其创建主要是通过调用drm_fb_helper_funcs实例中的fb_probe hook,如下:
static const struct drm_fb_helper_funcs rockchip_drm_fb_helper_funcs = {
.fb_probe = rockchip_drm_fbdev_create,
};
static int rockchip_drm_fbdev_create(struct drm_fb_helper *helper,
struct drm_fb_helper_surface_size *sizes)
{
struct rockchip_drm_private *private = to_drm_private(helper);
struct drm_mode_fb_cmd2 mode_cmd = { 0 };
struct drm_device *dev = helper->dev;
;
struct drm_framebuffer *fb;
unsigned int bytes_per_pixel;
unsigned long offset;
//根据size创建drm_gem_obj对象
struct rockchip_gem_object *rk_obj rk_obj;
rk_obj = rockchip_gem_create_object(dev, size, true);
rok_obj = rockchip_gem_alloc_object
rockchip_gem_alloc_buf
//drm_gem_obj对象
private->fbdev_bo = &rk_obj->base;
//创建fb_info对象并保存到fb_helper->fbdev中
struct fb_info *fbi;
fbi = drm_fb_helper_alloc_fbi(helper);
//利用private->fbdev_bo(drm_gem_obj对象)创建drm_frame_buffer对象并保存到helper->fb中
helper->fb = rockchip_drm_framebuffer_init(dev, &mode_cmd,
private->fbdev_bo);
//初始化fbdev的操作函数
fbi->fbops = &rockchip_drm_fbdev_ops;
//填充fb_info的参数
fb = helper->fb;
drm_fb_helper_fill_info(fbi, helper, sizes);
drm_fb_helper_fill_fix //填充固定参数
drm_fb_helper_fill_var //填充可变参数
//将fb_helper保存到par中,fbdev操作函数(rockchip_drm_fbdev_ops)会根据
//jnfo->par找到fb_helper
info->par = fb_helper;
//继续填充fb_info的参数
offset = fbi->var.xoffset * bytes_per_pixel;
offset += fbi->var.yoffset * fb->pitches[0];
fbi->screen_base = rk_obj->kvaddr + offset;
fbi->screen_size = rk_obj->base.size;
fbi->fix.smem_len = rk_obj->base.size;
}
(2)fb_helper->client的初始化
fb_helper->client是drm_client_dev对象, 其成员modesets(drm_mode_set类型)关联了fb/crtc/connector/displaymode等
rockchip_drm_fbdev_init
helper = &private->fbdev_helper
/*将rockchip_drm_fb_helper_funcs保存到helper->funcs
*后续会调用其fb_probe,用来创建fb
*/
drm_fb_helper_prepare(dev, helper, &rockchip_drm_fb_helper_funcs)
helper->funcs = funcs; //rockchip_drm_fb_helper_funcs
//初始化drm_fb_helper对象
drm_fb_helper_init(dev, helper)
/*创建drm_client_dev对象fb_helper->client,这个对象很重要,
*其成员modesets(drm_mode_set类型)
*关联fb/crtc/connector/displaymode等
*/
drm_client_init(dev, &fb_helper->client)
drm_client_modeset_create(client)
drm_for_each_crtc(crtc, dev)
根据drm设备的crtc数目创建modeset
client->modesets[i++].crtc = crtc
(3)fb_helper->fbdev的注册
注册逻辑如下:
rockchip_drm_fbdev_init
drm_fb_helper_prepare
drm_fb_helper_init
drm_fb_helper_initial_config(helper, ...)
__drm_fb_helper_initial_config_and_unlock(fb_helper, bpp_sel)
//
drm_client_modeset_probe(&fb_helper->client, width, height)
//根据connector count申请crtcs/modes/offsets/enabled等变量
//....
//找到status为connected的connectors以及其连接的crtc和显示模式mode
drm_client_firmware_config(...,connectors, crtcs, modes, offsets, enabled)
//初始化client->modesets[]
for(i = 0; i < connector_count;i++)
{
/*从client->modesets[]中找到匹配的crtc
* client->modesets[].crtc在drm_client_modeset_create中初始化
* 其位于drm_fb_helper_init接口中调用
*/
drm_mode_set* modeset = drm_client_find_modeset(client, crtc)
//赋值modesets的mode/connectors/x/y等成员
modeset->mode = drm_mode_dupicate(dev, mode)
modeset->connectors[modeset->num_connectors++] = connector;
modeset->x = offset->x
modeset->y = offset->y
}
//创建单显存fb,所以这里应该不支持双缓冲
ret = drm_fb_helper_single_fb_probe(fb_helper, bpp_sel);
/*根据connector/plane调整size,最后调用
*fb_probe(即 rockchip_drm_fbdev_create)创建显存
*/
ret = (*fb_helper->funcs->fb_probe)(fb_helper, &sizes);
//设置显存fb到modeset->fb
drm_setup_crtcs_fb(fb_helper)
drm_client_for_each_modeset(modeset, client)
{
//这里将创建的显存fb赋给所有的modeset
modeset->fb = fb_helper->fb;
}
注册fb_info
info = fb_helper->fbdev;
ret = register_framebuffer(info);
从上边的逻辑drm_fb_helper_initial_config触发所有的操作:
drm_client_modeset_probe探测所有的modesets
drm_fb_helper_single_fb_probe从所有的modesets中确定合适的fb大小并调用fb_probe创建显存fb
drm_setup_crtcs_fb将创建的显存fb保存到client所有的modeset->fb中
最后调用register_framebuffer注册fbdev
(4)显存fb绑定到crtc(plane)
我们注意到drm_setup_crtcs_fb接口只是将显存fb保存的client下所有的modeset->fb中。那么显存fb是如何绑定到crtc中的呢?
其实显存fb(modeset->fb)通过drm_client_modeset_commit_atomic接口绑定到crtc上的,如下:
总结:显存fb通过atomic modeset绑定到所有crtc的primary plane(这里是否是所有crtc? 存疑)
drm_client_modeset_commit_atomic
//遍历client的所有modeset
drm_client_for_each_modeset(mode_set, client)
{
//找到mode_set绑定的crtc的primary plane
struct drm_plane* primary = mode_set->crtc->primary
ret = derm_atomic_helper_set_config(mode_set, state)
//将显存mode_set->fb赋值给plane_state
drm_atomic_set_fb_for_plane(primary_state, set->fb)
}
//通过atomic modeset 提交显存fb到crtc的primary plane
ret = drm_atomic_commit(state)
drm_client_modeset_commit_atomic是何时被调用的呢?梳理代码发现, 在fbdev进行FBIOGET_VSCREENINFO时被调用,逻辑如下:
//用户态
fd_fb = open("/dev/fb0")
ioctl(fd_fb, FBIOPUT_VSCREENINFO, &var)
//内核态
do_fb_ioctl
case FBIOPUT_VSCREENINFO:
ret = fb_set_var(info, &var)
ret = info->fbos->fb_set_par(info)
//drm_fb_helper_set_par
__drm_fb_helper_restore_fbdev_mode_unlocked(fb_helper, force)
//调用drm_client_modeset_commit
ret = drm_client_modeset_commit(&fb_helper->client)
drm_client_modeset_commit_locked
//调用drm_client_modeset_commit_atomic
drm_client_modeset_commit_atomic(client, ...)
需要注意的是,drm_client_modeset_commit_atomic接口其他地方也会调用,待梳理。
三 fbdev操作
fbdev操作按顺序来主要有FBIOGET_FSCREENINFO/FBIOGET_VSCREENINFO/mmap/FBIO_PUT_VSCREENINFO/
FBIOPAN_DISPLAY/FBIO_WAITFORVSYNC, 对应的操作函数如下:
#define DRM_FB_HELPER_DEFAULT_OPS \
.fb_check_var = drm_fb_helper_check_var, \
.fb_set_par = drm_fb_helper_set_par, \
.fb_setcmap = drm_fb_helper_setcmap, \
.fb_blank = drm_fb_helper_blank, \
.fb_pan_display = drm_fb_helper_pan_display, \
.fb_debug_enter = drm_fb_helper_debug_enter, \
.fb_debug_leave = drm_fb_helper_debug_leave, \
.fb_ioctl = drm_fb_helper_ioctl
static const struct fb_ops rockchip_drm_fbdev_ops = {
.owner = THIS_MODULE,
DRM_FB_HELPER_DEFAULT_OPS,
.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) FBIOGET_FSCREENINFO
获取固定参数,比较简单不做介绍
(2)FBIOGET_VSCREENINFO
获取可变参数,比较简单不做介绍
(3)mmap
映射显存fb虚拟地址,供用户空间使用,内核态对应接口fb_mmap
static int rockchip_fbdev_mmap(struct fb_info *info,
struct vm_area_struct *vma)
{
//根据info->par找到helper
struct drm_fb_helper *helper = info->par;
struct rockchip_drm_private *private = to_drm_private(helper);
//fbdev_bo就是显存fb对应的drm_gem_obj实例
rockchip_gem_mmap_buf(private->fbdev_bo, vma);
//映射操作
drm_gem_mmap_obj
}
(4)FBIO_PUT_VSCREENINFO
设置可变参数,其用法之一就是设置多显存buffer, 用户态设置方法如下:
ioctl(fd, FBIOGET_FSCREENINFO, &fix);
ioctl(fd, FBIOGET_VSCREENINFO, &var);
//获取当前显存buffer个数
curr_num = fix.smem_len/screen_size;
//设置双缓冲
var.yres_virtual = 2 * var.yes
ioctl(fd, FBIOPUT_VSCREENINFO, &var);
内核态对应接口fb_set_par ,这个接口我们在前文已经分析过,他会触发drm_client_modeset_commit_atomic接口绑定fb到plane, 当我们没有分析双缓冲的逻辑,这个后续补充
(5)FBIOPAN_DISPLAY
双缓冲时,切换缓冲使用,对用内核态接口fb_pan_display
//用户态
var.yoffset = buf_idx*var.yes
ioctl(fd, FBIOPAN_DISPLAY, &var)
//内核态
drm_fb_helper_pan_display(fb_var_screeninfo*var, fb_info* info)
pan_display_atomic(var, info)
//设置modeset->x/y
pan_set(fb_helper, var->xoffset, var->yoffset);
modeset->x = x; //var->xoffset
modeset->y = y; // var->yoffset
//更新plane的参数x,y
drm_client_modeset_commit_locked(&fb_helper->client);
从(4)(5)可以看出,所谓的双缓冲,在内核态实际上申请一块大的显存(2倍的单缓冲大小),然后通过var.yoffset(内核态对应modeset->y)来切换plan的显示区域
(6)FBIO_WAITFORVSYNC
等待vsync,内核态对应接口drm_fb_helper_ioctl:
drm_fb_helper_ioctl
switch(cmd)
{
case FBIO_WAITFORVSYNC:
//仅支持第一个crtc的vsync等待
crtc = fb_helper->client.modeset[0].crtc;
drm_crtc_wait_one_vblank(crtc)
}
(7)测试程序
static int fd_fb;
static struct fb_fix_screeninfo fix; /* Current fix */
static struct fb_var_screeninfo var; /* Current var */
static int screen_size;
static unsigned char *fb_base;
static unsigned int line_width;
static unsigned int pixel_width;
int main(int argc, char **argv)
{
int i;
int ret;
int buffer_num;
int buf_idx = 1;
char *buf_next;
unsigned int colors[] = {0x00FF0000, 0x0000FF00, 0x000000FF, 0, 0x00FFFFFF}; /* 0x00RRGGBB */
struct timespec time;
...
fd_fb = open("/dev/fb0", O_RDWR);
ioctl(fd_fb, FBIOGET_FSCREENINFO, &fix);
ioctl(fd_fb, FBIOGET_VSCREENINFO, &var);
line_width = var.xres * var.bits_per_pixel / 8;
pixel_width = var.bits_per_pixel / 8;
screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
// 1. 获得 buffer 个数
buffer_num = fix.smem_len / screen_size;
printf("buffer_num = %d\n", buffer_num);
fb_base = (unsigned char *)mmap(NULL , fix.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
if (fb_base == (unsigned char *)-1) {
printf("can't mmap\n");
return -1;
}
if ((argv[1][0] == 's') || (buffer_num == 1)) {
printf("single buffer:\n");
while (1) {
for (i = 0; i < sizeof(colors)/sizeof(colors[0]); i++) {
lcd_draw_screen(fb_base, colors[i]);
nanosleep(&time, NULL);
}
}
} else {
printf("double buffer:\n");
// 2. 使能多 buffer
var.yres_virtual = buffer_num * var.yres;
ioctl(fd_fb, FBIOPUT_VSCREENINFO, &var);
while (1) {
for (i = 0; i < sizeof(colors)/sizeof(colors[0]); i++) {
// 3. 更新 buffer 里的数据
buf_next = fb_base + buf_idx * screen_size;
lcd_draw_screen(buf_next, colors[i]);
// 4. 通知驱动切换 buffer
var.yoffset = buf_idx * var.yres;
ret = ioctl(fd_fb, FBIOPAN_DISPLAY, &var);
if (ret < 0) {
perror("ioctl() / FBIOPAN_DISPLAY");
}
// 5. 等待帧同步完成
ret = 0;
ioctl(fd_fb, FBIO_WAITFORVSYNC, &ret);
if (ret < 0) {
perror("ioctl() / FBIO_WAITFORVSYNC");
}
buf_idx = !buf_idx;
nanosleep(&time, NULL);
}
}
}
munmap(fb_base , screen_size);
close(fd_fb);
return 0;
}
关于fbdev双缓冲可以参考: