1 // SPDX-License-Identifier: GPL-2.0+
3 #include <linux/crc32.h>
5 #include <drm/drm_atomic.h>
6 #include <drm/drm_atomic_helper.h>
7 #include <drm/drm_gem_framebuffer_helper.h>
8 #include <drm/drm_vblank.h>
13 * compute_crc - Compute CRC value on output frame
15 * @vaddr_out: address to final framebuffer
16 * @composer: framebuffer's metadata
18 * returns CRC value computed using crc32 on the visible portion of
19 * the final framebuffer at vaddr_out
21 static uint32_t compute_crc(void *vaddr_out
, struct vkms_composer
*composer
)
24 int x_src
= composer
->src
.x1
>> 16;
25 int y_src
= composer
->src
.y1
>> 16;
26 int h_src
= drm_rect_height(&composer
->src
) >> 16;
27 int w_src
= drm_rect_width(&composer
->src
) >> 16;
30 for (i
= y_src
; i
< y_src
+ h_src
; ++i
) {
31 for (j
= x_src
; j
< x_src
+ w_src
; ++j
) {
32 src_offset
= composer
->offset
33 + (i
* composer
->pitch
)
34 + (j
* composer
->cpp
);
35 /* XRGB format ignores Alpha channel */
36 memset(vaddr_out
+ src_offset
+ 24, 0, 8);
37 crc
= crc32_le(crc
, vaddr_out
+ src_offset
,
46 * blend - blend value at vaddr_src with value at vaddr_dst
47 * @vaddr_dst: destination address
48 * @vaddr_src: source address
49 * @dest_composer: destination framebuffer's metadata
50 * @src_composer: source framebuffer's metadata
52 * Blend value at vaddr_src with value at vaddr_dst.
53 * Currently, this function write value of vaddr_src on value
54 * at vaddr_dst using buffer's metadata to locate the new values
55 * from vaddr_src and their destination at vaddr_dst.
57 * TODO: Use the alpha value to blend vaddr_src with vaddr_dst
58 * instead of overwriting it.
60 static void blend(void *vaddr_dst
, void *vaddr_src
,
61 struct vkms_composer
*dest_composer
,
62 struct vkms_composer
*src_composer
)
64 int i
, j
, j_dst
, i_dst
;
65 int offset_src
, offset_dst
;
67 int x_src
= src_composer
->src
.x1
>> 16;
68 int y_src
= src_composer
->src
.y1
>> 16;
70 int x_dst
= src_composer
->dst
.x1
;
71 int y_dst
= src_composer
->dst
.y1
;
72 int h_dst
= drm_rect_height(&src_composer
->dst
);
73 int w_dst
= drm_rect_width(&src_composer
->dst
);
75 int y_limit
= y_src
+ h_dst
;
76 int x_limit
= x_src
+ w_dst
;
78 for (i
= y_src
, i_dst
= y_dst
; i
< y_limit
; ++i
) {
79 for (j
= x_src
, j_dst
= x_dst
; j
< x_limit
; ++j
) {
80 offset_dst
= dest_composer
->offset
81 + (i_dst
* dest_composer
->pitch
)
82 + (j_dst
++ * dest_composer
->cpp
);
83 offset_src
= src_composer
->offset
84 + (i
* src_composer
->pitch
)
85 + (j
* src_composer
->cpp
);
87 memcpy(vaddr_dst
+ offset_dst
,
88 vaddr_src
+ offset_src
, sizeof(u32
));
94 static void compose_cursor(struct vkms_composer
*cursor_composer
,
95 struct vkms_composer
*primary_composer
,
98 struct drm_gem_object
*cursor_obj
;
99 struct vkms_gem_object
*cursor_vkms_obj
;
101 cursor_obj
= drm_gem_fb_get_obj(&cursor_composer
->fb
, 0);
102 cursor_vkms_obj
= drm_gem_to_vkms_gem(cursor_obj
);
104 if (WARN_ON(!cursor_vkms_obj
->vaddr
))
107 blend(vaddr_out
, cursor_vkms_obj
->vaddr
,
108 primary_composer
, cursor_composer
);
111 static uint32_t _vkms_get_crc(struct vkms_composer
*primary_composer
,
112 struct vkms_composer
*cursor_composer
)
114 struct drm_framebuffer
*fb
= &primary_composer
->fb
;
115 struct drm_gem_object
*gem_obj
= drm_gem_fb_get_obj(fb
, 0);
116 struct vkms_gem_object
*vkms_obj
= drm_gem_to_vkms_gem(gem_obj
);
117 void *vaddr_out
= kzalloc(vkms_obj
->gem
.size
, GFP_KERNEL
);
121 DRM_ERROR("Failed to allocate memory for output frame.");
125 if (WARN_ON(!vkms_obj
->vaddr
)) {
130 memcpy(vaddr_out
, vkms_obj
->vaddr
, vkms_obj
->gem
.size
);
133 compose_cursor(cursor_composer
, primary_composer
, vaddr_out
);
135 crc
= compute_crc(vaddr_out
, primary_composer
);
143 * vkms_composer_worker - ordered work_struct to compute CRC
147 * Work handler for composing and computing CRCs. work_struct scheduled in
148 * an ordered workqueue that's periodically scheduled to run by
149 * _vblank_handle() and flushed at vkms_atomic_crtc_destroy_state().
151 void vkms_composer_worker(struct work_struct
*work
)
153 struct vkms_crtc_state
*crtc_state
= container_of(work
,
154 struct vkms_crtc_state
,
156 struct drm_crtc
*crtc
= crtc_state
->base
.crtc
;
157 struct vkms_output
*out
= drm_crtc_to_vkms_output(crtc
);
158 struct vkms_composer
*primary_composer
= NULL
;
159 struct vkms_composer
*cursor_composer
= NULL
;
161 u64 frame_start
, frame_end
;
164 spin_lock_irq(&out
->composer_lock
);
165 frame_start
= crtc_state
->frame_start
;
166 frame_end
= crtc_state
->frame_end
;
167 crc_pending
= crtc_state
->crc_pending
;
168 crtc_state
->frame_start
= 0;
169 crtc_state
->frame_end
= 0;
170 crtc_state
->crc_pending
= false;
171 spin_unlock_irq(&out
->composer_lock
);
174 * We raced with the vblank hrtimer and previous work already computed
175 * the crc, nothing to do.
180 if (crtc_state
->num_active_planes
>= 1)
181 primary_composer
= crtc_state
->active_planes
[0]->composer
;
183 if (crtc_state
->num_active_planes
== 2)
184 cursor_composer
= crtc_state
->active_planes
[1]->composer
;
186 if (primary_composer
)
187 crc32
= _vkms_get_crc(primary_composer
, cursor_composer
);
190 * The worker can fall behind the vblank hrtimer, make sure we catch up.
192 while (frame_start
<= frame_end
)
193 drm_crtc_add_crc_entry(crtc
, true, frame_start
++, &crc32
);
196 static const char * const pipe_crc_sources
[] = {"auto"};
198 const char *const *vkms_get_crc_sources(struct drm_crtc
*crtc
,
201 *count
= ARRAY_SIZE(pipe_crc_sources
);
202 return pipe_crc_sources
;
205 static int vkms_crc_parse_source(const char *src_name
, bool *enabled
)
211 } else if (strcmp(src_name
, "auto") == 0) {
221 int vkms_verify_crc_source(struct drm_crtc
*crtc
, const char *src_name
,
226 if (vkms_crc_parse_source(src_name
, &enabled
) < 0) {
227 DRM_DEBUG_DRIVER("unknown source %s\n", src_name
);
236 int vkms_set_crc_source(struct drm_crtc
*crtc
, const char *src_name
)
238 struct vkms_output
*out
= drm_crtc_to_vkms_output(crtc
);
239 bool enabled
= false;
242 ret
= vkms_crc_parse_source(src_name
, &enabled
);
244 spin_lock_irq(&out
->lock
);
245 out
->composer_enabled
= enabled
;
246 spin_unlock_irq(&out
->lock
);