1 | // SPDX-License-Identifier: GPL-2.0 OR MIT |
2 | |
3 | /* |
4 | * Xen para-virtual DRM device |
5 | * |
6 | * Copyright (C) 2016-2018 EPAM Systems Inc. |
7 | * |
8 | * Author: Oleksandr Andrushchenko <oleksandr_andrushchenko@epam.com> |
9 | */ |
10 | |
11 | #include <drm/drm_atomic.h> |
12 | #include <drm/drm_atomic_helper.h> |
13 | #include <drm/drm_drv.h> |
14 | #include <drm/drm_fourcc.h> |
15 | #include <drm/drm_framebuffer.h> |
16 | #include <drm/drm_gem.h> |
17 | #include <drm/drm_gem_atomic_helper.h> |
18 | #include <drm/drm_gem_framebuffer_helper.h> |
19 | #include <drm/drm_probe_helper.h> |
20 | #include <drm/drm_vblank.h> |
21 | |
22 | #include "xen_drm_front.h" |
23 | #include "xen_drm_front_conn.h" |
24 | #include "xen_drm_front_kms.h" |
25 | |
26 | /* |
27 | * Timeout in ms to wait for frame done event from the backend: |
28 | * must be a bit more than IO time-out |
29 | */ |
30 | #define FRAME_DONE_TO_MS (XEN_DRM_FRONT_WAIT_BACK_MS + 100) |
31 | |
32 | static struct xen_drm_front_drm_pipeline * |
33 | to_xen_drm_pipeline(struct drm_simple_display_pipe *pipe) |
34 | { |
35 | return container_of(pipe, struct xen_drm_front_drm_pipeline, pipe); |
36 | } |
37 | |
38 | static void fb_destroy(struct drm_framebuffer *fb) |
39 | { |
40 | struct xen_drm_front_drm_info *drm_info = fb->dev->dev_private; |
41 | int idx; |
42 | |
43 | if (drm_dev_enter(dev: fb->dev, idx: &idx)) { |
44 | xen_drm_front_fb_detach(front_info: drm_info->front_info, |
45 | fb_cookie: xen_drm_front_fb_to_cookie(fb)); |
46 | drm_dev_exit(idx); |
47 | } |
48 | drm_gem_fb_destroy(fb); |
49 | } |
50 | |
51 | static const struct drm_framebuffer_funcs fb_funcs = { |
52 | .destroy = fb_destroy, |
53 | }; |
54 | |
55 | static struct drm_framebuffer * |
56 | fb_create(struct drm_device *dev, struct drm_file *filp, |
57 | const struct drm_mode_fb_cmd2 *mode_cmd) |
58 | { |
59 | struct xen_drm_front_drm_info *drm_info = dev->dev_private; |
60 | struct drm_framebuffer *fb; |
61 | struct drm_gem_object *gem_obj; |
62 | int ret; |
63 | |
64 | fb = drm_gem_fb_create_with_funcs(dev, file: filp, mode_cmd, funcs: &fb_funcs); |
65 | if (IS_ERR(ptr: fb)) |
66 | return fb; |
67 | |
68 | gem_obj = fb->obj[0]; |
69 | |
70 | ret = xen_drm_front_fb_attach(front_info: drm_info->front_info, |
71 | dbuf_cookie: xen_drm_front_dbuf_to_cookie(gem_obj), |
72 | fb_cookie: xen_drm_front_fb_to_cookie(fb), |
73 | width: fb->width, height: fb->height, |
74 | pixel_format: fb->format->format); |
75 | if (ret < 0) { |
76 | DRM_ERROR("Back failed to attach FB %p: %d\n" , fb, ret); |
77 | goto fail; |
78 | } |
79 | |
80 | return fb; |
81 | |
82 | fail: |
83 | drm_gem_fb_destroy(fb); |
84 | return ERR_PTR(error: ret); |
85 | } |
86 | |
87 | static const struct drm_mode_config_funcs mode_config_funcs = { |
88 | .fb_create = fb_create, |
89 | .atomic_check = drm_atomic_helper_check, |
90 | .atomic_commit = drm_atomic_helper_commit, |
91 | }; |
92 | |
93 | static void send_pending_event(struct xen_drm_front_drm_pipeline *pipeline) |
94 | { |
95 | struct drm_crtc *crtc = &pipeline->pipe.crtc; |
96 | struct drm_device *dev = crtc->dev; |
97 | unsigned long flags; |
98 | |
99 | spin_lock_irqsave(&dev->event_lock, flags); |
100 | if (pipeline->pending_event) |
101 | drm_crtc_send_vblank_event(crtc, e: pipeline->pending_event); |
102 | pipeline->pending_event = NULL; |
103 | spin_unlock_irqrestore(lock: &dev->event_lock, flags); |
104 | } |
105 | |
106 | static void display_enable(struct drm_simple_display_pipe *pipe, |
107 | struct drm_crtc_state *crtc_state, |
108 | struct drm_plane_state *plane_state) |
109 | { |
110 | struct xen_drm_front_drm_pipeline *pipeline = |
111 | to_xen_drm_pipeline(pipe); |
112 | struct drm_crtc *crtc = &pipe->crtc; |
113 | struct drm_framebuffer *fb = plane_state->fb; |
114 | int ret, idx; |
115 | |
116 | if (!drm_dev_enter(dev: pipe->crtc.dev, idx: &idx)) |
117 | return; |
118 | |
119 | ret = xen_drm_front_mode_set(pipeline, x: crtc->x, y: crtc->y, |
120 | width: fb->width, height: fb->height, |
121 | bpp: fb->format->cpp[0] * 8, |
122 | fb_cookie: xen_drm_front_fb_to_cookie(fb)); |
123 | |
124 | if (ret) { |
125 | DRM_ERROR("Failed to enable display: %d\n" , ret); |
126 | pipeline->conn_connected = false; |
127 | } |
128 | |
129 | drm_dev_exit(idx); |
130 | } |
131 | |
132 | static void display_disable(struct drm_simple_display_pipe *pipe) |
133 | { |
134 | struct xen_drm_front_drm_pipeline *pipeline = |
135 | to_xen_drm_pipeline(pipe); |
136 | int ret = 0, idx; |
137 | |
138 | if (drm_dev_enter(dev: pipe->crtc.dev, idx: &idx)) { |
139 | ret = xen_drm_front_mode_set(pipeline, x: 0, y: 0, width: 0, height: 0, bpp: 0, |
140 | fb_cookie: xen_drm_front_fb_to_cookie(NULL)); |
141 | drm_dev_exit(idx); |
142 | } |
143 | if (ret) |
144 | DRM_ERROR("Failed to disable display: %d\n" , ret); |
145 | |
146 | /* Make sure we can restart with enabled connector next time */ |
147 | pipeline->conn_connected = true; |
148 | |
149 | /* release stalled event if any */ |
150 | send_pending_event(pipeline); |
151 | } |
152 | |
153 | void xen_drm_front_kms_on_frame_done(struct xen_drm_front_drm_pipeline *pipeline, |
154 | u64 fb_cookie) |
155 | { |
156 | /* |
157 | * This runs in interrupt context, e.g. under |
158 | * drm_info->front_info->io_lock, so we cannot call _sync version |
159 | * to cancel the work |
160 | */ |
161 | cancel_delayed_work(dwork: &pipeline->pflip_to_worker); |
162 | |
163 | send_pending_event(pipeline); |
164 | } |
165 | |
166 | static void pflip_to_worker(struct work_struct *work) |
167 | { |
168 | struct delayed_work *delayed_work = to_delayed_work(work); |
169 | struct xen_drm_front_drm_pipeline *pipeline = |
170 | container_of(delayed_work, |
171 | struct xen_drm_front_drm_pipeline, |
172 | pflip_to_worker); |
173 | |
174 | DRM_ERROR("Frame done timed-out, releasing" ); |
175 | send_pending_event(pipeline); |
176 | } |
177 | |
178 | static bool display_send_page_flip(struct drm_simple_display_pipe *pipe, |
179 | struct drm_plane_state *old_plane_state) |
180 | { |
181 | struct drm_plane_state *plane_state = |
182 | drm_atomic_get_new_plane_state(state: old_plane_state->state, |
183 | plane: &pipe->plane); |
184 | |
185 | /* |
186 | * If old_plane_state->fb is NULL and plane_state->fb is not, |
187 | * then this is an atomic commit which will enable display. |
188 | * If old_plane_state->fb is not NULL and plane_state->fb is, |
189 | * then this is an atomic commit which will disable display. |
190 | * Ignore these and do not send page flip as this framebuffer will be |
191 | * sent to the backend as a part of display_set_config call. |
192 | */ |
193 | if (old_plane_state->fb && plane_state->fb) { |
194 | struct xen_drm_front_drm_pipeline *pipeline = |
195 | to_xen_drm_pipeline(pipe); |
196 | struct xen_drm_front_drm_info *drm_info = pipeline->drm_info; |
197 | int ret; |
198 | |
199 | schedule_delayed_work(dwork: &pipeline->pflip_to_worker, |
200 | delay: msecs_to_jiffies(FRAME_DONE_TO_MS)); |
201 | |
202 | ret = xen_drm_front_page_flip(front_info: drm_info->front_info, |
203 | conn_idx: pipeline->index, |
204 | fb_cookie: xen_drm_front_fb_to_cookie(fb: plane_state->fb)); |
205 | if (ret) { |
206 | DRM_ERROR("Failed to send page flip request to backend: %d\n" , ret); |
207 | |
208 | pipeline->conn_connected = false; |
209 | /* |
210 | * Report the flip not handled, so pending event is |
211 | * sent, unblocking user-space. |
212 | */ |
213 | return false; |
214 | } |
215 | /* |
216 | * Signal that page flip was handled, pending event will be sent |
217 | * on frame done event from the backend. |
218 | */ |
219 | return true; |
220 | } |
221 | |
222 | return false; |
223 | } |
224 | |
225 | static int display_check(struct drm_simple_display_pipe *pipe, |
226 | struct drm_plane_state *plane_state, |
227 | struct drm_crtc_state *crtc_state) |
228 | { |
229 | /* |
230 | * Xen doesn't initialize vblanking via drm_vblank_init(), so |
231 | * DRM helpers assume that it doesn't handle vblanking and start |
232 | * sending out fake VBLANK events automatically. |
233 | * |
234 | * As xen contains it's own logic for sending out VBLANK events |
235 | * in send_pending_event(), disable no_vblank (i.e., the xen |
236 | * driver has vblanking support). |
237 | */ |
238 | crtc_state->no_vblank = false; |
239 | |
240 | return 0; |
241 | } |
242 | |
243 | static void display_update(struct drm_simple_display_pipe *pipe, |
244 | struct drm_plane_state *old_plane_state) |
245 | { |
246 | struct xen_drm_front_drm_pipeline *pipeline = |
247 | to_xen_drm_pipeline(pipe); |
248 | struct drm_crtc *crtc = &pipe->crtc; |
249 | struct drm_pending_vblank_event *event; |
250 | int idx; |
251 | |
252 | event = crtc->state->event; |
253 | if (event) { |
254 | struct drm_device *dev = crtc->dev; |
255 | unsigned long flags; |
256 | |
257 | WARN_ON(pipeline->pending_event); |
258 | |
259 | spin_lock_irqsave(&dev->event_lock, flags); |
260 | crtc->state->event = NULL; |
261 | |
262 | pipeline->pending_event = event; |
263 | spin_unlock_irqrestore(lock: &dev->event_lock, flags); |
264 | } |
265 | |
266 | if (!drm_dev_enter(dev: pipe->crtc.dev, idx: &idx)) { |
267 | send_pending_event(pipeline); |
268 | return; |
269 | } |
270 | |
271 | /* |
272 | * Send page flip request to the backend *after* we have event cached |
273 | * above, so on page flip done event from the backend we can |
274 | * deliver it and there is no race condition between this code and |
275 | * event from the backend. |
276 | * If this is not a page flip, e.g. no flip done event from the backend |
277 | * is expected, then send now. |
278 | */ |
279 | if (!display_send_page_flip(pipe, old_plane_state)) |
280 | send_pending_event(pipeline); |
281 | |
282 | drm_dev_exit(idx); |
283 | } |
284 | |
285 | static enum drm_mode_status |
286 | display_mode_valid(struct drm_simple_display_pipe *pipe, |
287 | const struct drm_display_mode *mode) |
288 | { |
289 | struct xen_drm_front_drm_pipeline *pipeline = |
290 | container_of(pipe, struct xen_drm_front_drm_pipeline, |
291 | pipe); |
292 | |
293 | if (mode->hdisplay != pipeline->width) |
294 | return MODE_ERROR; |
295 | |
296 | if (mode->vdisplay != pipeline->height) |
297 | return MODE_ERROR; |
298 | |
299 | return MODE_OK; |
300 | } |
301 | |
302 | static const struct drm_simple_display_pipe_funcs display_funcs = { |
303 | .mode_valid = display_mode_valid, |
304 | .enable = display_enable, |
305 | .disable = display_disable, |
306 | .check = display_check, |
307 | .update = display_update, |
308 | }; |
309 | |
310 | static int display_pipe_init(struct xen_drm_front_drm_info *drm_info, |
311 | int index, struct xen_drm_front_cfg_connector *cfg, |
312 | struct xen_drm_front_drm_pipeline *pipeline) |
313 | { |
314 | struct drm_device *dev = drm_info->drm_dev; |
315 | const u32 *formats; |
316 | int format_count; |
317 | int ret; |
318 | |
319 | pipeline->drm_info = drm_info; |
320 | pipeline->index = index; |
321 | pipeline->height = cfg->height; |
322 | pipeline->width = cfg->width; |
323 | |
324 | INIT_DELAYED_WORK(&pipeline->pflip_to_worker, pflip_to_worker); |
325 | |
326 | ret = xen_drm_front_conn_init(drm_info, connector: &pipeline->conn); |
327 | if (ret) |
328 | return ret; |
329 | |
330 | formats = xen_drm_front_conn_get_formats(format_count: &format_count); |
331 | |
332 | return drm_simple_display_pipe_init(dev, pipe: &pipeline->pipe, |
333 | funcs: &display_funcs, formats, |
334 | format_count, NULL, |
335 | connector: &pipeline->conn); |
336 | } |
337 | |
338 | int xen_drm_front_kms_init(struct xen_drm_front_drm_info *drm_info) |
339 | { |
340 | struct drm_device *dev = drm_info->drm_dev; |
341 | int i, ret; |
342 | |
343 | drm_mode_config_init(dev); |
344 | |
345 | dev->mode_config.min_width = 0; |
346 | dev->mode_config.min_height = 0; |
347 | dev->mode_config.max_width = 4095; |
348 | dev->mode_config.max_height = 2047; |
349 | dev->mode_config.funcs = &mode_config_funcs; |
350 | |
351 | for (i = 0; i < drm_info->front_info->cfg.num_connectors; i++) { |
352 | struct xen_drm_front_cfg_connector *cfg = |
353 | &drm_info->front_info->cfg.connectors[i]; |
354 | struct xen_drm_front_drm_pipeline *pipeline = |
355 | &drm_info->pipeline[i]; |
356 | |
357 | ret = display_pipe_init(drm_info, index: i, cfg, pipeline); |
358 | if (ret) { |
359 | drm_mode_config_cleanup(dev); |
360 | return ret; |
361 | } |
362 | } |
363 | |
364 | drm_mode_config_reset(dev); |
365 | drm_kms_helper_poll_init(dev); |
366 | return 0; |
367 | } |
368 | |
369 | void xen_drm_front_kms_fini(struct xen_drm_front_drm_info *drm_info) |
370 | { |
371 | int i; |
372 | |
373 | for (i = 0; i < drm_info->front_info->cfg.num_connectors; i++) { |
374 | struct xen_drm_front_drm_pipeline *pipeline = |
375 | &drm_info->pipeline[i]; |
376 | |
377 | cancel_delayed_work_sync(dwork: &pipeline->pflip_to_worker); |
378 | |
379 | send_pending_event(pipeline); |
380 | } |
381 | } |
382 | |