This paper mainly introduces fbdev compatibility logic in DRM framework
A brief introduction to framebuffer framework
There are three main steps to register fbdev under the framework of framebuffer:
(1) Create an fbdev operation function, taking rockchip as an example:
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) Fill struct fb_ Parameters of the info* FBI instance, including assignment operation functions
fbi->fbops = &rockchip_drm_fbdev_ops
(3) Register fb devices
register_framebuffer(fbi)
Two DRM Registration Interface
Registering fbdev in drm driver mainly includes the following aspects:
1) Create Video Memory drm_framebuffer instance fb
2) Fill in fb_ In info and register fbdev
3) Bind fb to the corresponding crtc plane
By doing this, you can use FBIOGET_of fbdev FSCREENINFO/FBIOGET_ VSCREEENINFO, mmap/FBIOPAN_ DISPLAY/FBIO_ Operation memory such as WAITFORVSYNC, and fb/crtc/plane/fb_info to info via drm_fb_helper is associated with the following structure:
struct drm_fb_helper { //client is used to associate crtc/plane/mode_set and other parameters struct drm_client_dev client; struct drm_client_buffer *buffer; //fb video memory struct drm_framebuffer *fb; struct drm_device *dev; //Create hook for FB memory, and part of fb_ Fill-in of info fbdev member, which is created by each drm driver const struct drm_fb_helper_funcs *funcs; //fbdev struct fb_info *fbdev; // .... };
(1) fb_ Creation of helper->fb
Helper->fb is video drm_framebuffer instance, created primarily by calling drm_ Fb_ Helper_ Fb_in FuncS instance Probe hook, as follows:
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; //Create drm_from size Gem_ Obj object 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 object private->fbdev_bo = &rk_obj->base; //Create fb_info object and save to fb_ Helper->in fbdev struct fb_info *fbi; fbi = drm_fb_helper_alloc_fbi(helper); //Using private->fbdev_ Bo (drm_gem_obj object) Create drm_frame_buffer object and saved to helper->fb helper->fb = rockchip_drm_framebuffer_init(dev, &mode_cmd, private->fbdev_bo); //Operational functions to initialize fbdev fbi->fbops = &rockchip_drm_fbdev_ops; //Fill fb_ Parameters of info fb = helper->fb; drm_fb_helper_fill_info(fbi, helper, sizes); drm_fb_helper_fill_fix //Fill in fixed parameters drm_fb_helper_fill_var //Filling in variable parameters //Will fb_ The helper is saved in par, and the fbdev operation function (rockchip_drm_fbdev_ops) will operate on the //Jnfo->par found fb_helper info->par = fb_helper; //Continue filling fb_ Parameters of 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_ Initialization of helper->client
Fb_ Helper->client is drm_client_dev object whose members modesets (drm_mode_set type) are associated with fb/crtc/connector/displaymode, etc.
rockchip_drm_fbdev_init helper = &private->fbdev_helper /*Will rockchip_drm_fb_helper_funcs saved to helper->funcs *It will be called fb_later Probe, used to create FB */ drm_fb_helper_prepare(dev, helper, &rockchip_drm_fb_helper_funcs) helper->funcs = funcs; //rockchip_drm_fb_helper_funcs //Initialize drm_fb_helper object drm_fb_helper_init(dev, helper) /*Create drm_client_dev object fb_ Helper->client, this object is important, *Its member modesets(drm_mode_set type) *Associate fb/crtc/connector/displaymode l, etc. */ drm_client_init(dev, &fb_helper->client) drm_client_modeset_create(client) drm_for_each_crtc(crtc, dev) according to drm Equipment crtc Number creation modeset client->modesets[i++].crtc = crtc
(3) fb_ Registration of helper->fbdev
The registration logic is as follows:
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) //Request variables such as crtcs/modes/offsets/enabled based on connector count //.... //Find the connectors whose status are connected and their connected crtc and display mode drm_client_firmware_config(...,connectors, crtcs, modes, offsets, enabled) //Initialize client->modesets[] for(i = 0; i < connector_count;i++) { /*Find a matching crtc from client->modesets[] * client->modesets[].crtc In drm_ Client_ Modeset_ Initialize in create * It is located in drm_ Fb_ Helper_ Invoked in init interface */ drm_mode_set* modeset = drm_client_find_modeset(client, crtc) //Assign mode/connectors/x/y and other members of modesets modeset->mode = drm_mode_dupicate(dev, mode) modeset->connectors[modeset->num_connectors++] = connector; modeset->x = offset->x modeset->y = offset->y } //Create a single memory fb, so double buffering should not be supported here ret = drm_fb_helper_single_fb_probe(fb_helper, bpp_sel); /*Adjust size according to connector/plane s, and call last *fb_probe(That is rockchip_drm_fbdev_create) Create Video Memory */ ret = (*fb_helper->funcs->fb_probe)(fb_helper, &sizes); //Set video memory FB to modeset->fb drm_setup_crtcs_fb(fb_helper) drm_client_for_each_modeset(modeset, client) { //The created memory fb is assigned to all modeset s here modeset->fb = fb_helper->fb; } register fb_info info = fb_helper->fbdev; ret = register_framebuffer(info);
Logical drm_from top Fb_ Helper_ Initial_ Cong triggers all actions:
drm_client_modeset_probe detects all modesets
drm_fb_helper_single_fb_probe determines the appropriate FB size from all modesets and calls fb_probe Create Video Memory FB
drm_setup_crtcs_fb saves the created memory FB to all modesets->fb of the client
Last call to register_framebuffer registers fbdev
(4) Video memory fb bound to crtc(plane)
We noticed drm_ Setup_ Crtcs_ The FB interface simply saves the display in all modesets->fb under the client saved by fb. So how is video memory FB bound to crtc?
Actually, the video memory FB (modeset->fb) passes through drm_ Client_ Modeset_ Commit_ The atomic interface is bound to crtc as follows:
Summary: Video memory fb is bound to the primary plane of all crtcs via atomic modeset (is this all crtcs here?)
drm_client_modeset_commit_atomic //Traverse all modeset s of the client drm_client_for_each_modeset(mode_set, client) { //Find mode_ primary plane of set-bound crtc struct drm_plane* primary = mode_set->crtc->primary ret = derm_atomic_helper_set_config(mode_set, state) //Will display mode_ Set->fb assigned to plane_state drm_atomic_set_fb_for_plane(primary_state, set->fb) } //Submit primary plane of video memory fb to crtc via atomic modeset ret = drm_atomic_commit(state)
Drm_ Client_ Modeset_ Commit_ When was atomic called? Combing the code found that FBIOGET_was done in fbdev VSCREENINFO is called with the following logic:
//User State fd_fb = open("/dev/fb0") ioctl(fd_fb, FBIOPUT_VSCREENINFO, &var) //Kernel State 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) //Call drm_client_modeset_commit ret = drm_client_modeset_commit(&fb_helper->client) drm_client_modeset_commit_locked //Call drm_client_modeset_commit_atomic drm_client_modeset_commit_atomic(client, ...)
Notice that drm_ Client_ Modeset_ Commit_ The atomic interface is also called elsewhere and needs to be combed.
Three fbdev operations
The main fbdev operations in sequence are FBIOGET_FSCREENINFO/FBIOGET_VSCREENINFO/mmap/FBIO_PUT_VSCREENINFO/
FBIOPAN_DISPLAY/FBIO_WAITFORVSYNC, corresponding operation functions are as follows:
#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
Getting fixed parameters is simpler and not introduced
(2)FBIOGET_VSCREENINFO
Getting variable parameters is simpler and not introduced
(3)mmap
Map display memory FB virtual address for user space use, kernel state corresponding interface fb_mmap
static int rockchip_fbdev_mmap(struct fb_info *info, struct vm_area_struct *vma) { //Find helper from info->par struct drm_fb_helper *helper = info->par; struct rockchip_drm_private *private = to_drm_private(helper); //fbdev_bo is the drm_corresponding to the display memory fb Gem_ Obj instance rockchip_gem_mmap_buf(private->fbdev_bo, vma); //Mapping operation drm_gem_mmap_obj }
(4)FBIO_PUT_VSCREENINFO
Set variable parameters, one of its uses is to set up multiple memory buffer s, user-state settings are as follows:
ioctl(fd, FBIOGET_FSCREENINFO, &fix); ioctl(fd, FBIOGET_VSCREENINFO, &var); //Get the current number of video buffer s curr_num = fix.smem_len/screen_size; //Set Double Buffer var.yres_virtual = 2 * var.yes ioctl(fd, FBIOPUT_VSCREENINFO, &var);
Kernel State Corresponding Interface fb_set_par, this interface, which we have analyzed earlier, will trigger drm_ Client_ Modeset_ Commit_ The atomic interface binds FB to plane, which is a follow-up when we don't analyze the logic of double buffering
(5)FBIOPAN_DISPLAY
Switch buffer usage when double buffering, for kernel state interface fb_pan_display
//User State var.yoffset = buf_idx*var.yes ioctl(fd, FBIOPAN_DISPLAY, &var) //Kernel State drm_fb_helper_pan_display(fb_var_screeninfo*var, fb_info* info) pan_display_atomic(var, info) //Set modeset->x/y pan_set(fb_helper, var->xoffset, var->yoffset); modeset->x = x; //var->xoffset modeset->y = y; // var->yoffset //Update the parameters x,y for plane s drm_client_modeset_commit_locked(&fb_helper->client);
From (4) (5), you can see that the so-called double buffer actually requests a large amount of memory (twice the size of the single buffer) in the kernel state, and then switches the display area of the plan by var.yoffset (the kernel state corresponds to modeset->y).
(6)FBIO_WAITFORVSYNC
Wait for vsync, kernel state corresponding interface drm_fb_helper_ioctl:
drm_fb_helper_ioctl switch(cmd) { case FBIO_WAITFORVSYNC: //Only vsync wait for the first crtc is supported crtc = fb_helper->client.modeset[0].crtc; drm_crtc_wait_one_vblank(crtc) }
(7) Test procedures
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. Get the number of buffer s 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. Enable multiple buffer s 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. Update the data in the buffer buf_next = fb_base + buf_idx * screen_size; lcd_draw_screen(buf_next, colors[i]); // 4. Notification-driven switch buffer var.yoffset = buf_idx * var.yres; ret = ioctl(fd_fb, FBIOPAN_DISPLAY, &var); if (ret < 0) { perror("ioctl() / FBIOPAN_DISPLAY"); } // 5. Wait for frame synchronization to complete 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; }
For fbdev double buffering, you can refer to:
Linux Driven Development / fbdev Dual Cache / Quick Start_ Embedded Hacker-CSDN Blog