LCD DRM Driver Framework Analysis II

This article analyzes the LCD DRM framework from the LCD code level based on the rk3566 / rk3568 platform.

1. The uboot stage

1. The driver files involved

5a821d904c9f4a7bb2f853dd7779a774.png

 2. uboot code flow analysis

2.1) Loading of each probe function

2.1.1) Each bind function is loaded when the device and driver match (uboot startup phase).

2.1.2) Each probe function is loaded by uclass_get_device_by_xxx (uclass_get_device_by_ofnode/uclass_get_device_by_phandle) series functions when parsing the device tree in rockchip_display_probe

->rockchip_display_bind
->rockchip_vop_bind
->dw_mipi_dsi_bind
    ->rockchip_display_probe
        ->uclass_get_device_by_ofnode(UCLASS_VIDEO_CRTC, np_to_ofnode(vop_node), &crtc_dev)             
    ->rockchip_vop_probe    
        ->rockchip_of_find_connector        
            ->uclass_get_device_by_ofnode(UCLASS_DISPLAY, conn, &dev)            
                ->dw_mipi_dsi_probe    
        ->rockchip_of_find_phy        
            ->uclass_get_device_by_phandle(UCLASS_PHY, dev, "phys", &phy_dev)            
                ->inno_video_phy_probe    
        ->rockchip_of_find_bridge        
            ->uclass_get_device_by_ofnode(UCLASS_VIDEO_BRIDGE, node, &dev)    
        ->rockchip_of_find_panel        
            ->uclass_get_device_by_ofnode(UCLASS_PANEL, panel_node, &panel_dev)        
                ->rockchip_panel_probe

2.2) The initialization process of the LCD in the uboot stage after booting

2.2.1) rockchip_show_logo is to display U-Boot logo and kernel logo.

2.2.2) load_bmp_logo loads logo data, including finding logo data (find_or_alloc_logo_cache) and obtaining the displayed cache buffer (get_display_buffer)

2.2.3) display_logo display to display the logo (parameter is the logo image data)

board_late_init    
    ->rockchip_show_logo        
        ->load_bmp_logo            
            ->find_or_alloc_logo_cache                
                ->rockchip_read_resource_file                    
                    ->get_display_buffer        
                        ->display_logo

2.2.4) The display_init function initializes each module, including:

2.2.4.1) Panel_matching performs the screen matching process, mainly to power on each module to reach the link-connected state, and then decide to use the corresponding dtsi file by reading the hardware panel_id, so that the matched dtsi file will be used later (valid panel data) to initialize the screen, operate timing, etc.

2.2.4.2) display_set_plane setting layer

2.2.4.3) display_enable Power on again, enable each module, brighten the backlight and other operations.

	->board_late_init
		->rockchip_show_logo
			->display_logo
				->display_init
					->panel_matching
						->conn_funcs->init(state)
						->rockchip_panel_init
						->rockchip_panel_getId
							->panel->funcs->getId(panel)=rockchip_dsi_panel_getId //读取实际panel id和dtsi文件的id进行比较
								->mipi_dsi_dcs_read
						->conn_funcs->disable(state)
						->conn_funcs->unprepare(state)
					->crtc_funcs->preinit(state)
					->rockchip_panel_init
					->conn_funcs->init(state)
					->rockchip_phy_init
					->crtc_funcs->init
				->display_set_plane
					->crtc_funcs->set_plane(state)=rockchip_vop_set_plane
					->display_enable
						->crtc_funcs->prepare(state)=rockchip_vop_prepare
						->conn_funcs->prepare(state)=dw_mipi_dsi_connector_prepare
						->rockchip_bridge_pre_enable(conn_state->bridge)
						->rockchip_panel_prepare(panel_state->panel)=panel_simple_prepare
						->crtc_funcs->enable(state)=rockchip_vop_enable
						->conn_funcs->enable(state)=dw_mipi_dsi_connector_enable
						->rockchip_bridge_enable(conn_state->bridge)
        ->rockchip_panel_enable(panel_state->panel)=panel_simple_enable

2. Kernel stage

1. The driver files involved

bdd9387383a44511946337de3c83fc95.png

 2. Kernel LCD driver loading process

7e06deb92fcb4e50a0c220fe5ab17d0c.png

 3. Detailed analysis of LCD driver loading

The initialization of each module is realized by executing the probe function and bind function of each module, as follows:

rockchip_drm_init
->vop2_probe
->dw_hdmi_rockchip_probe
->dw_mipi_dsi_probe
->rockchip_drm_platform_probe
	->rockchip_drm_bind
		->rockchip_drm_mode_config_init
		->component_bind_all
		->show_loader_logo
		->rockchip_drm_fbdev_init
			->drm_fb_helper_prepare
		->drm_dev_register
	->vop2_bind
		->vop2_win_init
		->vop2_create_crtc
			->vop2_plane_init
			->drm_crtc_init_with_planes
			->drm_crtc_helper_add
			->rockchip_register_crtc_funcs
	->dw_hdmi_rockchip_bind
	->dw_mipi_dsi_bind
		->drm_encoder_init
		->drm_encoder_helper_add
		->drm_connector_init
		->drm_connector_helper_add
->panel_simple_dsi_probe
	->panel_simple_probe
->drm_panel_init

Note:

drm_deviceIt is used to abstract a complete DRM device, and the Mode Settingrelated parts are drm_mode_configmanaged by . In order for a drm_devicesupport KMSrelated API, the DRM framework requires the driver to:

1) drm_driverWhen registering, driver_featuresthe flag needs to existDRIVER_MODESET。

2) Call the function in the probe function drm_mode_config_initto initialize the KMS framework, which is essentially a structure drm_devicein the initialization.mode_config

3) Fill the values ​​of int min_width, min_height; int max_width, max_height in mode_config, these values ​​are the size limit of framebuffer.

4) Set the mode_config->funcs pointer, which is essentially a set of callback functions implemented by the driver, covering KMSsome fairly basic operations.

5) The , etc. objects drm_devicecontained in the final initialization .drm_connectordrm_crtc

4. DRM helper architecture

The basic idea is to abstract the operation of specific components through a set of callback functions. For example drm_connector_funcs, at the same time, another set of helper functions is used to give the general implementation of the original set of callback functions, so that developers can implement the callback functions abstracted by this set of helper functions That's it. It can ensure that developers have a high enough degree of freedom (no need for helper functions at all), and simplify the development of developers (using helper functions), while providing developers with the ability to hook specific helper functions. Take drm_connector as an example to illustrate the implementation and usage of the helper architecture.

Under normal circumstances, drm_connectoryou need to provide struct drm_connector_funcsa callback function group when creating an object, but when using a helper function, you can directly fill in the corresponding callback function with the helper function:

static const struct drm_connector_funcs dw_mipi_dsi_atomic_connector_funcs = {
	.fill_modes = drm_helper_probe_single_connector_modes,
	.destroy = dw_mipi_dsi_drm_connector_destroy,
	.reset = drm_atomic_helper_connector_reset,
	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
	.atomic_get_property = dw_mipi_dsi_atomic_connector_get_property,
};

In fact, the helper function is not omnipotent, it just abstracts the behavior that most drivers should share, and the hardware-specific part needs to be provided to the helper function in the form of a callback function. This callback function group is provided by struct drm_connector_helper_funcs. When creating drm_connector, it needs to drm_connector_helper_addbe registered through the function. The function saves the address of the corresponding callback function object in the pointer drm_connectorin helper_private, as follows:

drm_connector_helper_add(connector, &dw_mipi_dsi_connector_helper_funcs);
static inline void drm_connector_helper_add(struct drm_connector *connector,
					    const struct drm_connector_helper_funcs *funcs)
{
	connector->helper_private = funcs;
}

static struct drm_connector_helper_funcs dw_mipi_dsi_connector_helper_funcs = {
	.get_modes = dw_mipi_dsi_connector_get_modes,
};

5. Interaction between user mode and kernel mode

When the driver registers a supported KMSDRM device, it will create a file /dev/drm/under it card%d. The user mode can realize the corresponding function by opening the file and performing corresponding operations on the file descriptor. The file operation callback function ( filesystem_operations) corresponding to the file descriptor is located drm_driverin and filled by the driver. A typical example is as follows:

static const struct file_operations rockchip_drm_driver_fops = {
	.owner = THIS_MODULE,
	.open = drm_open,
	.mmap = rockchip_gem_mmap,
	.poll = drm_poll,
	.read = drm_read,
	.unlocked_ioctl = drm_ioctl,
	.compat_ioctl = drm_compat_ioctl,
	.release = drm_release,
};

long drm_ioctl(struct file *filp,
	      unsigned int cmd, unsigned long arg)
{
	struct drm_file *file_priv = filp->private_data;
	struct drm_device *dev;
	const struct drm_ioctl_desc *ioctl = NULL;
	drm_ioctl_t *func;
	unsigned int nr = DRM_IOCTL_NR(cmd);
	int retcode = -EINVAL;
	char stack_kdata[128];
	char *kdata = NULL;
	unsigned int in_size, out_size, drv_size, ksize;
	bool is_driver_ioctl;

	dev = file_priv->minor->dev;

	if (drm_dev_is_unplugged(dev))
		return -ENODEV;

	is_driver_ioctl = nr >= DRM_COMMAND_BASE && nr < DRM_COMMAND_END;

	if (is_driver_ioctl) {
		/* driver ioctl */
		unsigned int index = nr - DRM_COMMAND_BASE;

		if (index >= dev->driver->num_ioctls)
			goto err_i1;
		index = array_index_nospec(index, dev->driver->num_ioctls);
		ioctl = &dev->driver->ioctls[index];
	} else {
		/* core ioctl */
		if (nr >= DRM_CORE_IOCTL_COUNT)
			goto err_i1;
		nr = array_index_nospec(nr, DRM_CORE_IOCTL_COUNT);
		ioctl = &drm_ioctls[nr];
	}

	drv_size = _IOC_SIZE(ioctl->cmd);
	out_size = in_size = _IOC_SIZE(cmd);
	if ((cmd & ioctl->cmd & IOC_IN) == 0)
		in_size = 0;
	if ((cmd & ioctl->cmd & IOC_OUT) == 0)
		out_size = 0;
	ksize = max(max(in_size, out_size), drv_size);

	DRM_DEBUG("pid=%d, dev=0x%lx, auth=%d, %s\n",
		  task_pid_nr(current),
		  (long)old_encode_dev(file_priv->minor->kdev->devt),
		  file_priv->authenticated, ioctl->name);

	/* Do not trust userspace, use our own definition */
	func = ioctl->func;

	if (unlikely(!func)) {
		DRM_DEBUG("no function\n");
		retcode = -EINVAL;
		goto err_i1;
	}

	if (ksize <= sizeof(stack_kdata)) {
		kdata = stack_kdata;
	} else {
		kdata = kmalloc(ksize, GFP_KERNEL);
		if (!kdata) {
			retcode = -ENOMEM;
			goto err_i1;
		}
	}

	if (copy_from_user(kdata, (void __user *)arg, in_size) != 0) {
		retcode = -EFAULT;
		goto err_i1;
	}

	if (ksize > in_size)
		memset(kdata + in_size, 0, ksize - in_size);

	retcode = drm_ioctl_kernel(filp, func, kdata, ioctl->flags);
	if (copy_to_user((void __user *)arg, kdata, out_size) != 0)
		retcode = -EFAULT;

      err_i1:
	if (!ioctl)
		DRM_DEBUG("invalid ioctl: pid=%d, dev=0x%lx, auth=%d, cmd=0x%02x, nr=0x%02x\n",
			  task_pid_nr(current),
			  (long)old_encode_dev(file_priv->minor->kdev->devt),
			  file_priv->authenticated, cmd, nr);

	if (kdata != stack_kdata)
		kfree(kdata);
	if (retcode)
		DRM_DEBUG("pid=%d, ret = %d\n", task_pid_nr(current), retcode);
	return retcode;
}

通过访问drmModeSetCrtcThe relevant legacy interface is called to drm_ioctl_kernel -> IOCTL:

return DRM_IOCTL(fd, DRM_IOCTL_MODE_SETCRTC, &crtc);

while all drm related definitions are in drivers/gpu/drm/drm_ioctl.c:

DRM_IOCTL_DEF(DRM_IOCTL_MODE_SETCRTC, drm_mode_setcrtc, DRM_MASTER),

That is, the final handler function is drm_mode_setcrtc. The function first checks the feature of the DRM device:

 if (!drm_core_check_feature(dev, DRIVER_MODESET))                
        return -EOPNOTSUPP;

The final call is drm_crtc_funcs->set_configthe callback function, that is, drm_atomic_helper_set_configthe function:

if (drm_drv_uses_atomic_modeset(dev))
    ret = crtc->funcs->set_config(&set, &ctx);
else
    ret = __drm_mode_set_config_internal(&set, &ctx);

struct drm_crtc_funcs structure:

static const struct drm_crtc_funcs vop2_crtc_funcs = 
{
    .gamma_set = vop2_crtc_legacy_gamma_set,
    .set_config = drm_atomic_helper_set_config,
    .page_flip = drm_atomic_helper_page_flip,
    .destroy = vop2_crtc_destroy,
    .reset = vop2_crtc_reset,
    .atomic_get_property = vop2_crtc_atomic_get_property,
    .atomic_set_property = vop2_crtc_atomic_set_property,
    .atomic_duplicate_state = vop2_crtc_duplicate_state,
    .atomic_destroy_state = vop2_crtc_destroy_state,
    .enable_vblank = vop2_crtc_enable_vblank,
    .disable_vblank = vop2_crtc_disable_vblank,
    .set_crc_source = vop2_crtc_set_crc_source,
    .verify_crc_source = vop2_crtc_verify_crc_source,
};

drm_atomic_helper_set_config implementation:

int drm_atomic_helper_set_config(struct drm_mode_set *set,
				 struct drm_modeset_acquire_ctx *ctx)
{
	struct drm_atomic_state *state;
	struct drm_crtc *crtc = set->crtc;
	int ret = 0;

	state = drm_atomic_state_alloc(crtc->dev);
	if (!state)
		return -ENOMEM;

	state->acquire_ctx = ctx;
	ret = __drm_atomic_helper_set_config(set, state);
	if (ret != 0)
		goto fail;

	ret = handle_conflicting_encoders(state, true);
	if (ret)
		goto fail;

	ret = drm_atomic_commit(state);

fail:
	drm_atomic_state_put(state);
	return ret;
}

The entry function called by A-KMS in user mode drmModeAtomicCommituses different IOCTL calls:

 ret = DRM_IOCTL(fd, DRM_IOCTL_MODE_ATOMIC, &atomic);

The corresponding kernel state is:

DRM_IOCTL_DEF(DRM_IOCTL_MODE_ATOMIC, drm_mode_atomic_ioctl, DRM_MASTER),

drm_mode_atomic_ioctl is implemented as:

int drm_mode_atomic_ioctl(struct drm_device *dev,
			  void *data, struct drm_file *file_priv)
{
    //省略无关代码

	drm_modeset_acquire_init(&ctx, DRM_MODESET_ACQUIRE_INTERRUPTIBLE);

	state = drm_atomic_state_alloc(dev);
	if (!state)
		return -ENOMEM;

	state->acquire_ctx = &ctx;
	state->allow_modeset = !!(arg->flags & DRM_MODE_ATOMIC_ALLOW_MODESET);

retry:
	copied_objs = 0;
	copied_props = 0;
	fence_state = NULL;
	num_fences = 0;

	for (i = 0; i < arg->count_objs; i++) {
		uint32_t obj_id, count_props;
		struct drm_mode_object *obj;

		if (get_user(obj_id, objs_ptr + copied_objs)) {
			ret = -EFAULT;
			goto out;
		}

		obj = drm_mode_object_find(dev, file_priv, obj_id, DRM_MODE_OBJECT_ANY);
		if (!obj) {
			ret = -ENOENT;
			goto out;
		}

		if (!obj->properties) {
			drm_mode_object_put(obj);
			ret = -ENOENT;
			goto out;
		}

		if (get_user(count_props, count_props_ptr + copied_objs)) {
			drm_mode_object_put(obj);
			ret = -EFAULT;
			goto out;
		}

		copied_objs++;

		for (j = 0; j < count_props; j++) {
			uint32_t prop_id;
			uint64_t prop_value;
			struct drm_property *prop;

			if (get_user(prop_id, props_ptr + copied_props)) {
				drm_mode_object_put(obj);
				ret = -EFAULT;
				goto out;
			}

			prop = drm_mode_obj_find_prop_id(obj, prop_id);
			if (!prop) {
				drm_mode_object_put(obj);
				ret = -ENOENT;
				goto out;
			}

			if (copy_from_user(&prop_value,
					   prop_values_ptr + copied_props,
					   sizeof(prop_value))) {
				drm_mode_object_put(obj);
				ret = -EFAULT;
				goto out;
			}

			ret = drm_atomic_set_property(state, obj, prop,
						      prop_value);
			if (ret) {
				drm_mode_object_put(obj);
				goto out;
			}

			copied_props++;
		}

		drm_mode_object_put(obj);
	}

	ret = prepare_signaling(dev, state, arg, file_priv, &fence_state,
				&num_fences);
	if (ret)
		goto out;

	if (arg->flags & DRM_MODE_ATOMIC_TEST_ONLY) {
		ret = drm_atomic_check_only(state);
	} else if (arg->flags & DRM_MODE_ATOMIC_NONBLOCK) {
		ret = drm_atomic_nonblocking_commit(state);
	} else {
		if (unlikely(drm_debug & DRM_UT_STATE))
			drm_atomic_print_state(state);

		ret = drm_atomic_commit(state);
	}
    //省略无关代码
	return ret;
}

6. Atomic KMS Architecture

Atomic Mode Setting (subsequent abbreviation A-KMS). This architecture will make up for the shortcomings of the previous API. Since the original API does not support updating the status of the entire DRM display pipeline at the same time, there will be some intermediate states in the KMS process, which may easily cause results that developers do not want to see and affect user experience. At the same time, the original KMS interface does not support rollback, requiring the application to record the original configuration status. Atomic Mode Setting also solves this problem. Atomic commit means: this commit operation is either successful, or the original state remains unchanged. That is, if the operation fails in the middle, those configurations that have already taken effect need to be restored to the previous state, as if the commit operation did not occur. And Commit, because this operation may modify multiple parameters, and after modifying these parameters, initiate an operation request at one time, which is similar to the meaning of "submitting" materials after filling out the form

From the perspective of user mode, the Atomic Mode Setting interface changes the state of each KMS object from implicitly updated through API to explicit object attribute. User-mode programs can read and write attributes on the KMS object through the common attribute operation interface. The changes will not take effect immediately, but will be cached. When the application has updated all the properties it wants to update, it can use the Commit operation to notify the KMS layer to actually update the hardware status. At this time, the driver needs to verify whether the modification required by the application program is legal. If it is legal, the modification of the entire display state can be completed at one time. A-KMS also implements interfaces that are only used to check whether the new state is valid.

The KMS framework provides a set of helper functions to help driver authors implement the original Legacy KMS interface. In essence, the original legacy-related interfaces are A-KMSimplemented through the helper functions implemented by the compatibility layer. In essence, drm_atomic_helperthe helper functions with prefixes are used. Implement the original legacy interface.

User mode related interfaces:

typedef struct _drmModeAtomicReq drmModeAtomicReq, *drmModeAtomicReqPtr;

extern drmModeAtomicReqPtr drmModeAtomicAlloc(void);
extern drmModeAtomicReqPtr drmModeAtomicDuplicate(drmModeAtomicReqPtr req);
extern int drmModeAtomicMerge(drmModeAtomicReqPtr base,
                              drmModeAtomicReqPtr augment);
extern void drmModeAtomicFree(drmModeAtomicReqPtr req);
extern int drmModeAtomicGetCursor(drmModeAtomicReqPtr req);
extern void drmModeAtomicSetCursor(drmModeAtomicReqPtr req, int cursor);
extern int drmModeAtomicAddProperty(drmModeAtomicReqPtr req,
                                    uint32_t object_id,
                                    uint32_t property_id,
                                    uint64_t value);
extern int drmModeAtomicCommit(int fd,
                               drmModeAtomicReqPtr req,
                               uint32_t flags,
                               void *user_data);

The essence of the user mode interface is to extend the original attribute interface, allowing the user to describe a state set, and then drmModeAtomicCommitperform the commit operation through the function. As can be seen from libdrmthe code, the commit operation is actually called at the end DRM_IOCTL_MODE_ATOMIC, ioctlwhich is the only entry of the Native interface. As can be seen from the kernel code, the ioctlprocessing function is drm_mode_atomic_ioctl. Asdrm_mode_atomic_ioctl a clue, many related implementations can be found.

1) State object

The core of A-KMS is the state collection of the entire display controller, which is represented by an independent state object. A DRM shows the overall status of the pipeline struct drm_atomic_staterepresented by:

struct drm_atomic_state {
  struct kref ref;
  struct drm_device *dev;
  bool allow_modeset : 1;
  bool legacy_cursor_update : 1;
  bool async_update : 1;
  bool duplicated : 1;
  struct __drm_planes_state *planes;
  struct __drm_crtcs_state *crtcs;
  int num_connector;
  struct __drm_connnectors_state *connectors;
  int num_private_objs;
  struct __drm_private_objs_state *private_objs;
  struct drm_modeset_acquire_ctx *acquire_ctx;
  struct drm_crtc_commit *fake_commit;
  struct work_struct commit_work;
};

You can see that it consists of a state object for each individual component (ie drm_mode_object.

2) The creation of state

drm_atomic_stateThe creation of is drm_atomic_state_allocimplemented by . As you can see in the function, a drm_mode_config_funcsnamed hook is provided in the function atomic_state_alloc, which allows us to realize the creation of the state object by ourselves. By default, the function call simply allocates memory and then drm_atomic_state_initinitializes with . The initialization function simply allocates drm_atomic_statethe memory region pointed to by several pointers in the allocation.

For the state corresponding to each drm object, its creation operation is implemented by its corresponding drm_{object}_funcs->atomic_duplicate_state. If the driver is not extended drm_atomic_state, this callback function is generally filled in drm_atomic_helper_{object}_duplicate_state. In the commit process, drm_atomic_get_{object}_statethe creation operation is triggered by the function. After the function triggers the copy state operation, it will also fill in the copied state and the original state drm_atomic_statein the corresponding __drm_{object}_state.

struct __drm_{object}_state {
        struct drm_{object} *ptr;
        struct drm_{object}_state *state, *old_state, *new_state;
    
        /* extra fields may exist */
};

old_stateSave drm_{object}the existing state here , stateand new_statesave our copied state.

Finally, describe the simple process of creating state when committing:

2.1) The drm_mode_atomic_ioctl function will update the property passed in from the user mode and call drm_atomic_set_property to write the previously createddrm_atomic_state

2.2) The drm_atomic_set_property function will call the corresponding drm_atomic_get_{object}_statefunction according to the type of the object passed in, and get the value corresponding to the object type drm_{object}_state. In this call, if drm_atomic_modethe corresponding state __drm_{object}_statedoes not exist, copy the original state and fill in

2.3) Then drm_atomic_set_propertyit will call drm_atomic_{object}_set_propertyto write the attribute update into the new state

2.4) Finally, drm_mode_atomic_ioctl calls the corresponding function ( drm_atomic_commitand its non-blocking version) to perform the commit operation (the premise of this operation is that the TEST_ONLY flag is not set)

3) state update

The state update drm_atomic_{object}_set_propertyis implemented by a function, and the overall process has been analyzed earlier. At present, the state update we see appears as a whole, that is, it is triggered by the commit operation in user mode. In fact DRM also supports partial update. atomic_duplicate_stateThe purpose of the and atomic_state_allocother hooks is to allow driver developers to add their own state to the original state, usually using the existing helper.

4) State detection drm_atomic_check_only

Since the entire interface is atomicvalid, this requires the driver that implements this set of interfaces to be able to detect whether a specific display pipeline (mode) state is legal (that is, it can be accepted by the hardware and run correctly). drm_atomic_check_onlyThe function is the entry point of the summary driver for this function. The user mode API of DRM provides DRM_MODE_ATOMIC_TEST_ONLYflag bits, the purpose of which is to allow the user mode to directly request the driver to detect the validity of the configuration without committing the configuration.

The main operation of the function is as follows:

4.1) For all CRTCs, Connectors and Planes, call drm_atomic_{crtc,connector,plane}_checkthem to perform basic legality checks. Note that this check does not involve the callback implemented by the driver, and it is completely the DRM framework's own check.

4.2) If mode_config->funcs->atomic_checkthe callback function exists, it is called to check. Note that this function is generally drm_atomic_helper_check, or the wrapper of the function implemented by the driver itself.

4.3) If state->allow_modesetit is false, that means no modeset operation is required, then all CRTCcalling drm_atomic_crtc_needs_modesetfunctions are checked.

5)drm_atomic_helper_check

The main operations of A-KMS are mainly divided into two types:

5.1) Check the legitimacy of the display mode to confirm that the hardware does work normally in this mode

5.2) commit operation, completely set the hardware to the corresponding state

drm_atomic_helper_checkdrm_mode_config_funcs->atomic_checkIt is the callback function under normal circumstances . It mainly includes two major function points:

5.3)drm_atomic_helper_check_modeset

5.4)drm_atomic_helper_check_planes

The former calls atomic_checkthe callback function of the components below the CRTC step by step to confirm whether the modeset is legal.

6) commit operation

6.1)drm_crtc_commit

The commit operation is conceptually based on each CRTC, so each commit operation is drm_crtc_commitabstracted by:

struct drm_crtc_commit {
    struct drm_crtc *crtc;
    struct kref ref;
    struct completion flip_done;
    struct completion hw_done;
    struct completion cleanup_done;
    struct list_head commit_entry;
    struct drm_pending_vblank_event *event;
    bool abort_completion;
};

drm_crtc_commitwill be put into drm_crtc->commit_list, and drm_crtc_commitessentially only plays a synchronous role, corresponding to three events:

6.1.1)flip_down

6.1.2)hw_down

6.1.3)cleanup_down

6.2)drm_atomic_commit

The real commit operation drm_atomic_commitis implemented by a function, as follows:

int drm_atomic_commit(struct drm_atomic_state *state)
{
        struct drm_mode_config *config = &state->dev->mode_config;
        int ret;

        ret = drm_atomic_check_only(state);
        if (ret)
                return ret;

        DRM_DEBUG_ATOMIC("committing %p\n", state);

        return config->funcs->atomic_commit(state->dev, state, false);
}

It is mainly divided into checking the legality of the state and calling drm_mode_config_funcs->atomic_committhe function to perform the commit operation. By default, the functionality of the atomic_commit callback function is drm_atomic_helper_commitimplemented by . There are two code paths inside the function: blocking and non-blocking. The blocking situation is analyzed here, because as seen above, drm_atomic_committhe non-blocking implementation is called.

6.3)drm_atomic_helper_commit

In most cases, drivers will use the default implementation provided in the DRM framework. The DRM framework atomic_commitprovides a default implementation for the callback function drm_atomic_helper_commit, and then analyzes the function. The implementation of the function is obviously divided into two sections drm_atomic_stateby the parameters async_update, as follows:

        if (state->async_update) {
                ret = drm_atomic_helper_prepare_planes(dev, state);
                if (ret)
                        return ret;

                drm_atomic_helper_async_commit(dev, state);
                drm_atomic_helper_cleanup_planes(dev, state);

                return 0;
        }

In non-asynchronous mode, the function is first invoked drm_atomic_helper_setup_commitfor validation and creation drm_crtc_commit. Then the function is initialized state->commit_work, and the subsequent corresponding operations may workqueuebe completed in it.

The function finally calls the callback function drm_atomic_helper_prepare_planesin the helper for all the newly appeared planes in the state in turn prepare_fb. For non-blocking cases, the call drm_atomic_helper_wait_for_fencesperforms a wait operation. Finally, call the core drm_atomic_helper_swap_statefunction of the software layer to update the new state to the old state. Note that this is an update at the software level, simply modifying the state object.

If the non-blocking mode is used when the function is called, the workqueue is directly scheduled to perform subsequent operations, otherwise, commit_tailthe function is called directly, as follows:

       if (nonblock)
                queue_work(system_unbound_wq, &state->commit_work);
        else
                commit_tail(state);

The actual state->commit_workprocessing function is also called directly commit_tail:

static void commit_work(struct work_struct *work)
{
        struct drm_atomic_state *state = container_of(work,
                                                      struct drm_atomic_state,
                                                      commit_work);
        commit_tail(state);
}

And commit_tailthe implementation uses multiple helpers:

static void commit_tail(struct drm_atomic_state *old_state)
{
        struct drm_device *dev = old_state->dev;
        const struct drm_mode_config_helper_funcs *funcs;

        funcs = dev->mode_config.helper_private;

        drm_atomic_helper_wait_for_fences(dev, old_state, false);

        drm_atomic_helper_wait_for_dependencies(old_state);

        if (funcs && funcs->atomic_commit_tail)
                funcs->atomic_commit_tail(old_state);
        else
                drm_atomic_helper_commit_tail(old_state);

        drm_atomic_helper_commit_cleanup_done(old_state);

        drm_atomic_state_put(old_state);
}

6.4)drm_atomic_helper_setup_commit

The function first traverses all the objects whose status has changed CRTC, and then creates them drm_crtc_commit. As seen earlier, this object is used to track the progress of the Commit operation. After creation, drm_crtc_commitit is saved in new_crtc_state->commit. Then the function will be called stall_checksto check whether there is a stagnant commit in the current commit queue:

        list_for_each_entry(commit, &crtc->commit_list, commit_entry) {
                if (i == 0) {
                        completed = try_wait_for_completion(&commit->flip_done);
                        /* Userspace is not allowed to get ahead of the previous
                         * commit with nonblocking ones. */
                        if (!completed && nonblock) {
                                spin_unlock(&crtc->commit_lock);
                                return -EBUSY;
                        }
                } else if (i == 1) {
                        stall_commit = drm_crtc_commit_get(commit);
                        break;
                }

                i++;
        }
        spin_unlock(&crtc->commit_lock);

From a logical point of view, remove the first one from the commit queue drm_commit, and then check whether it flip_donehas completionbeen completed. If it is not completed, and the runtime drm_atomic_helper_commitis in non-blocking mode ( nonblockthe parameter is true), the entire commit operation will be returned directly -EBUSY. Then, remove the second one drm_commit(if it exists), and consider it as a stall, and wait for it to cleanup_donecomplete with a timeout of 10 seconds.

The function then makes two simple optimizations:

                /* Drivers only send out events when at least either current or
                 * new CRTC state is active. Complete right away if everything
                 * stays off. */
                if (!old_crtc_state->active && !new_crtc_state->active) {
                        complete_all(&commit->flip_done);
                        continue;
                }

                /* Legacy cursor updates are fully unsynced. */
                if (state->legacy_cursor_update) {
                        complete_all(&commit->flip_done);
                        continue;
                }

When the CRTCs in the old and new states are both closed, flip_done must be completed directly. And when using Legacy Cursor-related API, because the API itself is not synchronized, it can be directly regarded as completed flip_done. Then the function is created for each CRTC drm_pending_eventand placed in it. Note that the newly new_crtc_state->eventcreated pointer directly points to the previously created one , that is, when this is processed, it will directly complete the corresponding .drm_pending_eventcompletiondrm_commit->flip_eventdrm_pending_eventflip_done

6.5)drm_atomic_helper_wait_for_fences

new_plane_state->fenceThis function will be called sequentially for all the planes involved in the new state dma_wait_fence. That is to say, it is meaningless to simply study this function, and it needs to be analyzed in conjunction with the implementation related to the plane.

6.6)drm_atomic_helper_wait_for_dependencies

This function in turn waits for the corresponding events in the old state of this commit, that is, the commit corresponding to the original state (the commit that sets the display controller to oldstate). Simply put, it is the sum old_{crtc,plane,connector}_state->commit of waiting .hw_doneflip_done

6.7)drm_atomic_helper_commit_tail

commit_tailThe callback function is called in drm_cmode_config_helpers->atomic_commit_tail, and if it is empty, it is called directlydrm_atomic_helper_commit_tail

void drm_atomic_helper_commit_tail(struct drm_atomic_state *old_state)
{
        struct drm_device *dev = old_state->dev;

        drm_atomic_helper_commit_modeset_disables(dev, old_state);
        drm_atomic_helper_commit_planes(dev, old_state, 0);
        drm_atomic_helper_commit_modeset_enables(dev, old_state);
        drm_atomic_helper_fake_vblank(old_state);
        drm_atomic_helper_commit_hw_done(old_state);
        drm_atomic_helper_wait_for_vblanks(dev, old_state);
        drm_atomic_helper_cleanup_planes(dev, old_state);
}

Guess you like

Origin blog.csdn.net/qq_33782617/article/details/126214791