Home | History | Annotate | Line # | Download | only in drm
      1 /*	$NetBSD: drm_damage_helper.c,v 1.2 2021/12/18 23:44:57 riastradh Exp $	*/
      2 
      3 // SPDX-License-Identifier: GPL-2.0 OR MIT
      4 /**************************************************************************
      5  *
      6  * Copyright (c) 2018 VMware, Inc., Palo Alto, CA., USA
      7  * All Rights Reserved.
      8  *
      9  * Permission is hereby granted, free of charge, to any person obtaining a
     10  * copy of this software and associated documentation files (the
     11  * "Software"), to deal in the Software without restriction, including
     12  * without limitation the rights to use, copy, modify, merge, publish,
     13  * distribute, sub license, and/or sell copies of the Software, and to
     14  * permit persons to whom the Software is furnished to do so, subject to
     15  * the following conditions:
     16  *
     17  * The above copyright notice and this permission notice (including the
     18  * next paragraph) shall be included in all copies or substantial portions
     19  * of the Software.
     20  *
     21  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     22  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     23  * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
     24  * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
     25  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
     26  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
     27  * USE OR OTHER DEALINGS IN THE SOFTWARE.
     28  *
     29  * Authors:
     30  * Deepak Rawat <drawat (at) vmware.com>
     31  * Rob Clark <robdclark (at) gmail.com>
     32  *
     33  **************************************************************************/
     34 
     35 #include <sys/cdefs.h>
     36 __KERNEL_RCSID(0, "$NetBSD: drm_damage_helper.c,v 1.2 2021/12/18 23:44:57 riastradh Exp $");
     37 
     38 #include <drm/drm_atomic.h>
     39 #include <drm/drm_damage_helper.h>
     40 #include <drm/drm_device.h>
     41 
     42 /**
     43  * DOC: overview
     44  *
     45  * FB_DAMAGE_CLIPS is an optional plane property which provides a means to
     46  * specify a list of damage rectangles on a plane in framebuffer coordinates of
     47  * the framebuffer attached to the plane. In current context damage is the area
     48  * of plane framebuffer that has changed since last plane update (also called
     49  * page-flip), irrespective of whether currently attached framebuffer is same as
     50  * framebuffer attached during last plane update or not.
     51  *
     52  * FB_DAMAGE_CLIPS is a hint to kernel which could be helpful for some drivers
     53  * to optimize internally especially for virtual devices where each framebuffer
     54  * change needs to be transmitted over network, usb, etc.
     55  *
     56  * Since FB_DAMAGE_CLIPS is a hint so it is an optional property. User-space can
     57  * ignore damage clips property and in that case driver will do a full plane
     58  * update. In case damage clips are provided then it is guaranteed that the area
     59  * inside damage clips will be updated to plane. For efficiency driver can do
     60  * full update or can update more than specified in damage clips. Since driver
     61  * is free to read more, user-space must always render the entire visible
     62  * framebuffer. Otherwise there can be corruptions. Also, if a user-space
     63  * provides damage clips which doesn't encompass the actual damage to
     64  * framebuffer (since last plane update) can result in incorrect rendering.
     65  *
     66  * FB_DAMAGE_CLIPS is a blob property with the layout of blob data is simply an
     67  * array of &drm_mode_rect. Unlike plane &drm_plane_state.src coordinates,
     68  * damage clips are not in 16.16 fixed point. Similar to plane src in
     69  * framebuffer, damage clips cannot be negative. In damage clip, x1/y1 are
     70  * inclusive and x2/y2 are exclusive. While kernel does not error for overlapped
     71  * damage clips, it is strongly discouraged.
     72  *
     73  * Drivers that are interested in damage interface for plane should enable
     74  * FB_DAMAGE_CLIPS property by calling drm_plane_enable_fb_damage_clips().
     75  * Drivers implementing damage can use drm_atomic_helper_damage_iter_init() and
     76  * drm_atomic_helper_damage_iter_next() helper iterator function to get damage
     77  * rectangles clipped to &drm_plane_state.src.
     78  */
     79 
     80 static void convert_clip_rect_to_rect(const struct drm_clip_rect *src,
     81 				      struct drm_mode_rect *dest,
     82 				      uint32_t num_clips, uint32_t src_inc)
     83 {
     84 	while (num_clips > 0) {
     85 		dest->x1 = src->x1;
     86 		dest->y1 = src->y1;
     87 		dest->x2 = src->x2;
     88 		dest->y2 = src->y2;
     89 		src += src_inc;
     90 		dest++;
     91 		num_clips--;
     92 	}
     93 }
     94 
     95 /**
     96  * drm_plane_enable_fb_damage_clips - Enables plane fb damage clips property.
     97  * @plane: Plane on which to enable damage clips property.
     98  *
     99  * This function lets driver to enable the damage clips property on a plane.
    100  */
    101 void drm_plane_enable_fb_damage_clips(struct drm_plane *plane)
    102 {
    103 	struct drm_device *dev = plane->dev;
    104 	struct drm_mode_config *config = &dev->mode_config;
    105 
    106 	drm_object_attach_property(&plane->base, config->prop_fb_damage_clips,
    107 				   0);
    108 }
    109 EXPORT_SYMBOL(drm_plane_enable_fb_damage_clips);
    110 
    111 /**
    112  * drm_atomic_helper_check_plane_damage - Verify plane damage on atomic_check.
    113  * @state: The driver state object.
    114  * @plane_state: Plane state for which to verify damage.
    115  *
    116  * This helper function makes sure that damage from plane state is discarded
    117  * for full modeset. If there are more reasons a driver would want to do a full
    118  * plane update rather than processing individual damage regions, then those
    119  * cases should be taken care of here.
    120  *
    121  * Note that &drm_plane_state.fb_damage_clips == NULL in plane state means that
    122  * full plane update should happen. It also ensure helper iterator will return
    123  * &drm_plane_state.src as damage.
    124  */
    125 void drm_atomic_helper_check_plane_damage(struct drm_atomic_state *state,
    126 					  struct drm_plane_state *plane_state)
    127 {
    128 	struct drm_crtc_state *crtc_state;
    129 
    130 	if (plane_state->crtc) {
    131 		crtc_state = drm_atomic_get_new_crtc_state(state,
    132 							   plane_state->crtc);
    133 
    134 		if (WARN_ON(!crtc_state))
    135 			return;
    136 
    137 		if (drm_atomic_crtc_needs_modeset(crtc_state)) {
    138 			drm_property_blob_put(plane_state->fb_damage_clips);
    139 			plane_state->fb_damage_clips = NULL;
    140 		}
    141 	}
    142 }
    143 EXPORT_SYMBOL(drm_atomic_helper_check_plane_damage);
    144 
    145 /**
    146  * drm_atomic_helper_dirtyfb - Helper for dirtyfb.
    147  * @fb: DRM framebuffer.
    148  * @file_priv: Drm file for the ioctl call.
    149  * @flags: Dirty fb annotate flags.
    150  * @color: Color for annotate fill.
    151  * @clips: Dirty region.
    152  * @num_clips: Count of clip in clips.
    153  *
    154  * A helper to implement &drm_framebuffer_funcs.dirty using damage interface
    155  * during plane update. If num_clips is 0 then this helper will do a full plane
    156  * update. This is the same behaviour expected by DIRTFB IOCTL.
    157  *
    158  * Note that this helper is blocking implementation. This is what current
    159  * drivers and userspace expect in their DIRTYFB IOCTL implementation, as a way
    160  * to rate-limit userspace and make sure its rendering doesn't get ahead of
    161  * uploading new data too much.
    162  *
    163  * Return: Zero on success, negative errno on failure.
    164  */
    165 int drm_atomic_helper_dirtyfb(struct drm_framebuffer *fb,
    166 			      struct drm_file *file_priv, unsigned int flags,
    167 			      unsigned int color, struct drm_clip_rect *clips,
    168 			      unsigned int num_clips)
    169 {
    170 	struct drm_modeset_acquire_ctx ctx;
    171 	struct drm_property_blob *damage = NULL;
    172 	struct drm_mode_rect *rects = NULL;
    173 	struct drm_atomic_state *state;
    174 	struct drm_plane *plane;
    175 	int ret = 0;
    176 
    177 	/*
    178 	 * When called from ioctl, we are interruptable, but not when called
    179 	 * internally (ie. defio worker)
    180 	 */
    181 	drm_modeset_acquire_init(&ctx,
    182 		file_priv ? DRM_MODESET_ACQUIRE_INTERRUPTIBLE : 0);
    183 
    184 	state = drm_atomic_state_alloc(fb->dev);
    185 	if (!state) {
    186 		ret = -ENOMEM;
    187 		goto out_drop_locks;
    188 	}
    189 	state->acquire_ctx = &ctx;
    190 
    191 	if (clips) {
    192 		uint32_t inc = 1;
    193 
    194 		if (flags & DRM_MODE_FB_DIRTY_ANNOTATE_COPY) {
    195 			inc = 2;
    196 			num_clips /= 2;
    197 		}
    198 
    199 		rects = kcalloc(num_clips, sizeof(*rects), GFP_KERNEL);
    200 		if (!rects) {
    201 			ret = -ENOMEM;
    202 			goto out;
    203 		}
    204 
    205 		convert_clip_rect_to_rect(clips, rects, num_clips, inc);
    206 		damage = drm_property_create_blob(fb->dev,
    207 						  num_clips * sizeof(*rects),
    208 						  rects);
    209 		if (IS_ERR(damage)) {
    210 			ret = PTR_ERR(damage);
    211 			damage = NULL;
    212 			goto out;
    213 		}
    214 	}
    215 
    216 retry:
    217 	drm_for_each_plane(plane, fb->dev) {
    218 		struct drm_plane_state *plane_state;
    219 
    220 		ret = drm_modeset_lock(&plane->mutex, state->acquire_ctx);
    221 		if (ret)
    222 			goto out;
    223 
    224 		if (plane->state->fb != fb) {
    225 			drm_modeset_unlock(&plane->mutex);
    226 			continue;
    227 		}
    228 
    229 		plane_state = drm_atomic_get_plane_state(state, plane);
    230 		if (IS_ERR(plane_state)) {
    231 			ret = PTR_ERR(plane_state);
    232 			goto out;
    233 		}
    234 
    235 		drm_property_replace_blob(&plane_state->fb_damage_clips,
    236 					  damage);
    237 	}
    238 
    239 	ret = drm_atomic_commit(state);
    240 
    241 out:
    242 	if (ret == -EDEADLK) {
    243 		drm_atomic_state_clear(state);
    244 		ret = drm_modeset_backoff(&ctx);
    245 		if (!ret)
    246 			goto retry;
    247 	}
    248 
    249 	drm_property_blob_put(damage);
    250 	kfree(rects);
    251 	drm_atomic_state_put(state);
    252 
    253 out_drop_locks:
    254 	drm_modeset_drop_locks(&ctx);
    255 	drm_modeset_acquire_fini(&ctx);
    256 
    257 	return ret;
    258 
    259 }
    260 EXPORT_SYMBOL(drm_atomic_helper_dirtyfb);
    261 
    262 /**
    263  * drm_atomic_helper_damage_iter_init - Initialize the damage iterator.
    264  * @iter: The iterator to initialize.
    265  * @old_state: Old plane state for validation.
    266  * @state: Plane state from which to iterate the damage clips.
    267  *
    268  * Initialize an iterator, which clips plane damage
    269  * &drm_plane_state.fb_damage_clips to plane &drm_plane_state.src. This iterator
    270  * returns full plane src in case damage is not present because either
    271  * user-space didn't sent or driver discarded it (it want to do full plane
    272  * update). Currently this iterator returns full plane src in case plane src
    273  * changed but that can be changed in future to return damage.
    274  *
    275  * For the case when plane is not visible or plane update should not happen the
    276  * first call to iter_next will return false. Note that this helper use clipped
    277  * &drm_plane_state.src, so driver calling this helper should have called
    278  * drm_atomic_helper_check_plane_state() earlier.
    279  */
    280 void
    281 drm_atomic_helper_damage_iter_init(struct drm_atomic_helper_damage_iter *iter,
    282 				   const struct drm_plane_state *old_state,
    283 				   const struct drm_plane_state *state)
    284 {
    285 	memset(iter, 0, sizeof(*iter));
    286 
    287 	if (!state || !state->crtc || !state->fb || !state->visible)
    288 		return;
    289 
    290 	iter->clips = drm_helper_get_plane_damage_clips(state);
    291 	iter->num_clips = drm_plane_get_damage_clips_count(state);
    292 
    293 	/* Round down for x1/y1 and round up for x2/y2 to catch all pixels */
    294 	iter->plane_src.x1 = state->src.x1 >> 16;
    295 	iter->plane_src.y1 = state->src.y1 >> 16;
    296 	iter->plane_src.x2 = (state->src.x2 >> 16) + !!(state->src.x2 & 0xFFFF);
    297 	iter->plane_src.y2 = (state->src.y2 >> 16) + !!(state->src.y2 & 0xFFFF);
    298 
    299 	if (!iter->clips || !drm_rect_equals(&state->src, &old_state->src)) {
    300 		iter->clips = NULL;
    301 		iter->num_clips = 0;
    302 		iter->full_update = true;
    303 	}
    304 }
    305 EXPORT_SYMBOL(drm_atomic_helper_damage_iter_init);
    306 
    307 /**
    308  * drm_atomic_helper_damage_iter_next - Advance the damage iterator.
    309  * @iter: The iterator to advance.
    310  * @rect: Return a rectangle in fb coordinate clipped to plane src.
    311  *
    312  * Since plane src is in 16.16 fixed point and damage clips are whole number,
    313  * this iterator round off clips that intersect with plane src. Round down for
    314  * x1/y1 and round up for x2/y2 for the intersected coordinate. Similar rounding
    315  * off for full plane src, in case it's returned as damage. This iterator will
    316  * skip damage clips outside of plane src.
    317  *
    318  * Return: True if the output is valid, false if reached the end.
    319  *
    320  * If the first call to iterator next returns false then it means no need to
    321  * update the plane.
    322  */
    323 bool
    324 drm_atomic_helper_damage_iter_next(struct drm_atomic_helper_damage_iter *iter,
    325 				   struct drm_rect *rect)
    326 {
    327 	bool ret = false;
    328 
    329 	if (iter->full_update) {
    330 		*rect = iter->plane_src;
    331 		iter->full_update = false;
    332 		return true;
    333 	}
    334 
    335 	while (iter->curr_clip < iter->num_clips) {
    336 		*rect = iter->clips[iter->curr_clip];
    337 		iter->curr_clip++;
    338 
    339 		if (drm_rect_intersect(rect, &iter->plane_src)) {
    340 			ret = true;
    341 			break;
    342 		}
    343 	}
    344 
    345 	return ret;
    346 }
    347 EXPORT_SYMBOL(drm_atomic_helper_damage_iter_next);
    348 
    349 /**
    350  * drm_atomic_helper_damage_merged - Merged plane damage
    351  * @old_state: Old plane state for validation.
    352  * @state: Plane state from which to iterate the damage clips.
    353  * @rect: Returns the merged damage rectangle
    354  *
    355  * This function merges any valid plane damage clips into one rectangle and
    356  * returns it in @rect.
    357  *
    358  * For details see: drm_atomic_helper_damage_iter_init() and
    359  * drm_atomic_helper_damage_iter_next().
    360  *
    361  * Returns:
    362  * True if there is valid plane damage otherwise false.
    363  */
    364 bool drm_atomic_helper_damage_merged(const struct drm_plane_state *old_state,
    365 				     struct drm_plane_state *state,
    366 				     struct drm_rect *rect)
    367 {
    368 	struct drm_atomic_helper_damage_iter iter;
    369 	struct drm_rect clip;
    370 	bool valid = false;
    371 
    372 	rect->x1 = INT_MAX;
    373 	rect->y1 = INT_MAX;
    374 	rect->x2 = 0;
    375 	rect->y2 = 0;
    376 
    377 	drm_atomic_helper_damage_iter_init(&iter, old_state, state);
    378 	drm_atomic_for_each_plane_damage(&iter, &clip) {
    379 		rect->x1 = min(rect->x1, clip.x1);
    380 		rect->y1 = min(rect->y1, clip.y1);
    381 		rect->x2 = max(rect->x2, clip.x2);
    382 		rect->y2 = max(rect->y2, clip.y2);
    383 		valid = true;
    384 	}
    385 
    386 	return valid;
    387 }
    388 EXPORT_SYMBOL(drm_atomic_helper_damage_merged);
    389