DRM Framework (vkms) Analysis - drm-driven creation fbdevice analysis (rockchip_drm_drv example)

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

Keywords: drm

Added by theycallmepj on Sun, 27 Feb 2022 20:11:13 +0200