Commit d8c4bddc authored by Thomas Zimmermann's avatar Thomas Zimmermann
Browse files

drm/fb-helper: Synchronize dirty worker with vblank



Before updating the display from the console's shadow buffer, the dirty
worker now waits for a vblank. This allows several screen updates to pile
up and acts as a rate limiter. If a DRM master is present, it could
interfere with the vblank. Don't wait in this case.

v4:
	* share code with WAITFORVSYNC ioctl (Emil)
	* use lock guard
v3:
	* add back helper->lock
	* acquire DRM master status while waiting for vblank
v2:
	* don't hold helper->lock while waiting for vblank

Signed-off-by: default avatarThomas Zimmermann <tzimmermann@suse.de>
Acked-by: default avatarSam Ravnborg <sam@ravnborg.org>
Link: https://lore.kernel.org/r/20250829091447.46719-1-tzimmermann@suse.de
parent cf207ea2
Loading
Loading
Loading
Loading
+44 −0
Original line number Diff line number Diff line
@@ -1293,6 +1293,50 @@ int drm_client_modeset_dpms(struct drm_client_dev *client, int mode)
}
EXPORT_SYMBOL(drm_client_modeset_dpms);

/**
 * drm_client_modeset_wait_for_vblank() - Wait for the next VBLANK to occur
 * @client: DRM client
 * @crtc_index: The ndex of the CRTC to wait on
 *
 * Block the caller until the given CRTC has seen a VBLANK. Do nothing
 * if the CRTC is disabled. If there's another DRM master present, fail
 * with -EBUSY.
 *
 * Returns:
 * 0 on success, or negative error code otherwise.
 */
int drm_client_modeset_wait_for_vblank(struct drm_client_dev *client, unsigned int crtc_index)
{
	struct drm_device *dev = client->dev;
	struct drm_crtc *crtc;
	int ret;

	/*
	 * Rate-limit update frequency to vblank. If there's a DRM master
	 * present, it could interfere while we're waiting for the vblank
	 * event. Don't wait in this case.
	 */
	if (!drm_master_internal_acquire(dev))
		return -EBUSY;

	crtc = client->modesets[crtc_index].crtc;

	/*
	 * Only wait for a vblank event if the CRTC is enabled, otherwise
	 * just don't do anything, not even report an error.
	 */
	ret = drm_crtc_vblank_get(crtc);
	if (!ret) {
		drm_crtc_wait_one_vblank(crtc);
		drm_crtc_vblank_put(crtc);
	}

	drm_master_internal_release(dev);

	return 0;
}
EXPORT_SYMBOL(drm_client_modeset_wait_for_vblank);

#ifdef CONFIG_DRM_KUNIT_TEST
#include "tests/drm_client_modeset_test.c"
#endif
+6 −24
Original line number Diff line number Diff line
@@ -368,6 +368,10 @@ static void drm_fb_helper_fb_dirty(struct drm_fb_helper *helper)
	unsigned long flags;
	int ret;

	mutex_lock(&helper->lock);
	drm_client_modeset_wait_for_vblank(&helper->client, 0);
	mutex_unlock(&helper->lock);

	if (drm_WARN_ON_ONCE(dev, !helper->funcs->fb_dirty))
		return;

@@ -1068,15 +1072,9 @@ int drm_fb_helper_ioctl(struct fb_info *info, unsigned int cmd,
			unsigned long arg)
{
	struct drm_fb_helper *fb_helper = info->par;
	struct drm_device *dev = fb_helper->dev;
	struct drm_crtc *crtc;
	int ret = 0;

	mutex_lock(&fb_helper->lock);
	if (!drm_master_internal_acquire(dev)) {
		ret = -EBUSY;
		goto unlock;
	}
	guard(mutex)(&fb_helper->lock);

	switch (cmd) {
	case FBIO_WAITFORVSYNC:
@@ -1096,28 +1094,12 @@ int drm_fb_helper_ioctl(struct fb_info *info, unsigned int cmd,
		 * make. If we're not smart enough here, one should
		 * just consider switch the userspace to KMS.
		 */
		crtc = fb_helper->client.modesets[0].crtc;

		/*
		 * Only wait for a vblank event if the CRTC is
		 * enabled, otherwise just don't do anythintg,
		 * not even report an error.
		 */
		ret = drm_crtc_vblank_get(crtc);
		if (!ret) {
			drm_crtc_wait_one_vblank(crtc);
			drm_crtc_vblank_put(crtc);
		}

		ret = 0;
		ret = drm_client_modeset_wait_for_vblank(&fb_helper->client, 0);
		break;
	default:
		ret = -ENOTTY;
	}

	drm_master_internal_release(dev);
unlock:
	mutex_unlock(&fb_helper->lock);
	return ret;
}
EXPORT_SYMBOL(drm_fb_helper_ioctl);
+1 −0
Original line number Diff line number Diff line
@@ -220,6 +220,7 @@ int drm_client_modeset_check(struct drm_client_dev *client);
int drm_client_modeset_commit_locked(struct drm_client_dev *client);
int drm_client_modeset_commit(struct drm_client_dev *client);
int drm_client_modeset_dpms(struct drm_client_dev *client, int mode);
int drm_client_modeset_wait_for_vblank(struct drm_client_dev *client, unsigned int crtc_index);

/**
 * drm_client_for_each_modeset() - Iterate over client modesets