1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | |
3 | #include <linux/dma-fence.h> |
4 | |
5 | #include <drm/drm_atomic.h> |
6 | #include <drm/drm_atomic_helper.h> |
7 | #include <drm/drm_probe_helper.h> |
8 | #include <drm/drm_vblank.h> |
9 | |
10 | #include "vkms_drv.h" |
11 | |
12 | static enum hrtimer_restart vkms_vblank_simulate(struct hrtimer *timer) |
13 | { |
14 | struct vkms_output *output = container_of(timer, struct vkms_output, |
15 | vblank_hrtimer); |
16 | struct drm_crtc *crtc = &output->crtc; |
17 | struct vkms_crtc_state *state; |
18 | u64 ret_overrun; |
19 | bool ret, fence_cookie; |
20 | |
21 | fence_cookie = dma_fence_begin_signalling(); |
22 | |
23 | ret_overrun = hrtimer_forward_now(timer: &output->vblank_hrtimer, |
24 | interval: output->period_ns); |
25 | if (ret_overrun != 1) |
26 | pr_warn("%s: vblank timer overrun\n" , __func__); |
27 | |
28 | spin_lock(lock: &output->lock); |
29 | ret = drm_crtc_handle_vblank(crtc); |
30 | if (!ret) |
31 | DRM_ERROR("vkms failure on handling vblank" ); |
32 | |
33 | state = output->composer_state; |
34 | spin_unlock(lock: &output->lock); |
35 | |
36 | if (state && output->composer_enabled) { |
37 | u64 frame = drm_crtc_accurate_vblank_count(crtc); |
38 | |
39 | /* update frame_start only if a queued vkms_composer_worker() |
40 | * has read the data |
41 | */ |
42 | spin_lock(lock: &output->composer_lock); |
43 | if (!state->crc_pending) |
44 | state->frame_start = frame; |
45 | else |
46 | DRM_DEBUG_DRIVER("crc worker falling behind, frame_start: %llu, frame_end: %llu\n" , |
47 | state->frame_start, frame); |
48 | state->frame_end = frame; |
49 | state->crc_pending = true; |
50 | spin_unlock(lock: &output->composer_lock); |
51 | |
52 | ret = queue_work(wq: output->composer_workq, work: &state->composer_work); |
53 | if (!ret) |
54 | DRM_DEBUG_DRIVER("Composer worker already queued\n" ); |
55 | } |
56 | |
57 | dma_fence_end_signalling(cookie: fence_cookie); |
58 | |
59 | return HRTIMER_RESTART; |
60 | } |
61 | |
62 | static int vkms_enable_vblank(struct drm_crtc *crtc) |
63 | { |
64 | struct drm_device *dev = crtc->dev; |
65 | unsigned int pipe = drm_crtc_index(crtc); |
66 | struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; |
67 | struct vkms_output *out = drm_crtc_to_vkms_output(crtc); |
68 | |
69 | drm_calc_timestamping_constants(crtc, mode: &crtc->mode); |
70 | |
71 | hrtimer_init(timer: &out->vblank_hrtimer, CLOCK_MONOTONIC, mode: HRTIMER_MODE_REL); |
72 | out->vblank_hrtimer.function = &vkms_vblank_simulate; |
73 | out->period_ns = ktime_set(secs: 0, nsecs: vblank->framedur_ns); |
74 | hrtimer_start(timer: &out->vblank_hrtimer, tim: out->period_ns, mode: HRTIMER_MODE_REL); |
75 | |
76 | return 0; |
77 | } |
78 | |
79 | static void vkms_disable_vblank(struct drm_crtc *crtc) |
80 | { |
81 | struct vkms_output *out = drm_crtc_to_vkms_output(crtc); |
82 | |
83 | hrtimer_cancel(timer: &out->vblank_hrtimer); |
84 | } |
85 | |
86 | static bool vkms_get_vblank_timestamp(struct drm_crtc *crtc, |
87 | int *max_error, ktime_t *vblank_time, |
88 | bool in_vblank_irq) |
89 | { |
90 | struct drm_device *dev = crtc->dev; |
91 | unsigned int pipe = crtc->index; |
92 | struct vkms_device *vkmsdev = drm_device_to_vkms_device(dev); |
93 | struct vkms_output *output = &vkmsdev->output; |
94 | struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; |
95 | |
96 | if (!READ_ONCE(vblank->enabled)) { |
97 | *vblank_time = ktime_get(); |
98 | return true; |
99 | } |
100 | |
101 | *vblank_time = READ_ONCE(output->vblank_hrtimer.node.expires); |
102 | |
103 | if (WARN_ON(*vblank_time == vblank->time)) |
104 | return true; |
105 | |
106 | /* |
107 | * To prevent races we roll the hrtimer forward before we do any |
108 | * interrupt processing - this is how real hw works (the interrupt is |
109 | * only generated after all the vblank registers are updated) and what |
110 | * the vblank core expects. Therefore we need to always correct the |
111 | * timestampe by one frame. |
112 | */ |
113 | *vblank_time -= output->period_ns; |
114 | |
115 | return true; |
116 | } |
117 | |
118 | static struct drm_crtc_state * |
119 | vkms_atomic_crtc_duplicate_state(struct drm_crtc *crtc) |
120 | { |
121 | struct vkms_crtc_state *vkms_state; |
122 | |
123 | if (WARN_ON(!crtc->state)) |
124 | return NULL; |
125 | |
126 | vkms_state = kzalloc(size: sizeof(*vkms_state), GFP_KERNEL); |
127 | if (!vkms_state) |
128 | return NULL; |
129 | |
130 | __drm_atomic_helper_crtc_duplicate_state(crtc, state: &vkms_state->base); |
131 | |
132 | INIT_WORK(&vkms_state->composer_work, vkms_composer_worker); |
133 | |
134 | return &vkms_state->base; |
135 | } |
136 | |
137 | static void vkms_atomic_crtc_destroy_state(struct drm_crtc *crtc, |
138 | struct drm_crtc_state *state) |
139 | { |
140 | struct vkms_crtc_state *vkms_state = to_vkms_crtc_state(state); |
141 | |
142 | __drm_atomic_helper_crtc_destroy_state(state); |
143 | |
144 | WARN_ON(work_pending(&vkms_state->composer_work)); |
145 | kfree(objp: vkms_state->active_planes); |
146 | kfree(objp: vkms_state); |
147 | } |
148 | |
149 | static void vkms_atomic_crtc_reset(struct drm_crtc *crtc) |
150 | { |
151 | struct vkms_crtc_state *vkms_state = |
152 | kzalloc(size: sizeof(*vkms_state), GFP_KERNEL); |
153 | |
154 | if (crtc->state) |
155 | vkms_atomic_crtc_destroy_state(crtc, state: crtc->state); |
156 | |
157 | __drm_atomic_helper_crtc_reset(crtc, state: &vkms_state->base); |
158 | if (vkms_state) |
159 | INIT_WORK(&vkms_state->composer_work, vkms_composer_worker); |
160 | } |
161 | |
162 | static const struct drm_crtc_funcs vkms_crtc_funcs = { |
163 | .set_config = drm_atomic_helper_set_config, |
164 | .page_flip = drm_atomic_helper_page_flip, |
165 | .reset = vkms_atomic_crtc_reset, |
166 | .atomic_duplicate_state = vkms_atomic_crtc_duplicate_state, |
167 | .atomic_destroy_state = vkms_atomic_crtc_destroy_state, |
168 | .enable_vblank = vkms_enable_vblank, |
169 | .disable_vblank = vkms_disable_vblank, |
170 | .get_vblank_timestamp = vkms_get_vblank_timestamp, |
171 | .get_crc_sources = vkms_get_crc_sources, |
172 | .set_crc_source = vkms_set_crc_source, |
173 | .verify_crc_source = vkms_verify_crc_source, |
174 | }; |
175 | |
176 | static int vkms_crtc_atomic_check(struct drm_crtc *crtc, |
177 | struct drm_atomic_state *state) |
178 | { |
179 | struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, |
180 | crtc); |
181 | struct vkms_crtc_state *vkms_state = to_vkms_crtc_state(crtc_state); |
182 | struct drm_plane *plane; |
183 | struct drm_plane_state *plane_state; |
184 | int i = 0, ret; |
185 | |
186 | if (vkms_state->active_planes) |
187 | return 0; |
188 | |
189 | ret = drm_atomic_add_affected_planes(state: crtc_state->state, crtc); |
190 | if (ret < 0) |
191 | return ret; |
192 | |
193 | drm_for_each_plane_mask(plane, crtc->dev, crtc_state->plane_mask) { |
194 | plane_state = drm_atomic_get_existing_plane_state(state: crtc_state->state, |
195 | plane); |
196 | WARN_ON(!plane_state); |
197 | |
198 | if (!plane_state->visible) |
199 | continue; |
200 | |
201 | i++; |
202 | } |
203 | |
204 | vkms_state->active_planes = kcalloc(n: i, size: sizeof(plane), GFP_KERNEL); |
205 | if (!vkms_state->active_planes) |
206 | return -ENOMEM; |
207 | vkms_state->num_active_planes = i; |
208 | |
209 | i = 0; |
210 | drm_for_each_plane_mask(plane, crtc->dev, crtc_state->plane_mask) { |
211 | plane_state = drm_atomic_get_existing_plane_state(state: crtc_state->state, |
212 | plane); |
213 | |
214 | if (!plane_state->visible) |
215 | continue; |
216 | |
217 | vkms_state->active_planes[i++] = |
218 | to_vkms_plane_state(plane_state); |
219 | } |
220 | |
221 | return 0; |
222 | } |
223 | |
224 | static void vkms_crtc_atomic_enable(struct drm_crtc *crtc, |
225 | struct drm_atomic_state *state) |
226 | { |
227 | drm_crtc_vblank_on(crtc); |
228 | } |
229 | |
230 | static void vkms_crtc_atomic_disable(struct drm_crtc *crtc, |
231 | struct drm_atomic_state *state) |
232 | { |
233 | drm_crtc_vblank_off(crtc); |
234 | } |
235 | |
236 | static void vkms_crtc_atomic_begin(struct drm_crtc *crtc, |
237 | struct drm_atomic_state *state) |
238 | { |
239 | struct vkms_output *vkms_output = drm_crtc_to_vkms_output(crtc); |
240 | |
241 | /* This lock is held across the atomic commit to block vblank timer |
242 | * from scheduling vkms_composer_worker until the composer is updated |
243 | */ |
244 | spin_lock_irq(lock: &vkms_output->lock); |
245 | } |
246 | |
247 | static void vkms_crtc_atomic_flush(struct drm_crtc *crtc, |
248 | struct drm_atomic_state *state) |
249 | { |
250 | struct vkms_output *vkms_output = drm_crtc_to_vkms_output(crtc); |
251 | |
252 | if (crtc->state->event) { |
253 | spin_lock(lock: &crtc->dev->event_lock); |
254 | |
255 | if (drm_crtc_vblank_get(crtc) != 0) |
256 | drm_crtc_send_vblank_event(crtc, e: crtc->state->event); |
257 | else |
258 | drm_crtc_arm_vblank_event(crtc, e: crtc->state->event); |
259 | |
260 | spin_unlock(lock: &crtc->dev->event_lock); |
261 | |
262 | crtc->state->event = NULL; |
263 | } |
264 | |
265 | vkms_output->composer_state = to_vkms_crtc_state(crtc->state); |
266 | |
267 | spin_unlock_irq(lock: &vkms_output->lock); |
268 | } |
269 | |
270 | static const struct drm_crtc_helper_funcs vkms_crtc_helper_funcs = { |
271 | .atomic_check = vkms_crtc_atomic_check, |
272 | .atomic_begin = vkms_crtc_atomic_begin, |
273 | .atomic_flush = vkms_crtc_atomic_flush, |
274 | .atomic_enable = vkms_crtc_atomic_enable, |
275 | .atomic_disable = vkms_crtc_atomic_disable, |
276 | }; |
277 | |
278 | int vkms_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, |
279 | struct drm_plane *primary, struct drm_plane *cursor) |
280 | { |
281 | struct vkms_output *vkms_out = drm_crtc_to_vkms_output(crtc); |
282 | int ret; |
283 | |
284 | ret = drmm_crtc_init_with_planes(dev, crtc, primary, cursor, |
285 | funcs: &vkms_crtc_funcs, NULL); |
286 | if (ret) { |
287 | DRM_ERROR("Failed to init CRTC\n" ); |
288 | return ret; |
289 | } |
290 | |
291 | drm_crtc_helper_add(crtc, funcs: &vkms_crtc_helper_funcs); |
292 | |
293 | drm_mode_crtc_set_gamma_size(crtc, VKMS_LUT_SIZE); |
294 | drm_crtc_enable_color_mgmt(crtc, degamma_lut_size: 0, has_ctm: false, VKMS_LUT_SIZE); |
295 | |
296 | spin_lock_init(&vkms_out->lock); |
297 | spin_lock_init(&vkms_out->composer_lock); |
298 | |
299 | vkms_out->composer_workq = alloc_ordered_workqueue("vkms_composer" , 0); |
300 | if (!vkms_out->composer_workq) |
301 | return -ENOMEM; |
302 | |
303 | return ret; |
304 | } |
305 | |