1 | // SPDX-License-Identifier: GPL-2.0 OR MIT |
2 | /****************************************************************************** |
3 | * |
4 | * COPYRIGHT (C) 2014-2023 VMware, Inc., Palo Alto, CA., USA |
5 | * |
6 | * Permission is hereby granted, free of charge, to any person obtaining a |
7 | * copy of this software and associated documentation files (the |
8 | * "Software"), to deal in the Software without restriction, including |
9 | * without limitation the rights to use, copy, modify, merge, publish, |
10 | * distribute, sub license, and/or sell copies of the Software, and to |
11 | * permit persons to whom the Software is furnished to do so, subject to |
12 | * the following conditions: |
13 | * |
14 | * The above copyright notice and this permission notice (including the |
15 | * next paragraph) shall be included in all copies or substantial portions |
16 | * of the Software. |
17 | * |
18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
20 | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL |
21 | * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, |
22 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
23 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE |
24 | * USE OR OTHER DEALINGS IN THE SOFTWARE. |
25 | * |
26 | ******************************************************************************/ |
27 | |
28 | #include "vmwgfx_bo.h" |
29 | #include "vmwgfx_kms.h" |
30 | #include "vmw_surface_cache.h" |
31 | |
32 | #include <drm/drm_atomic.h> |
33 | #include <drm/drm_atomic_helper.h> |
34 | #include <drm/drm_damage_helper.h> |
35 | #include <drm/drm_fourcc.h> |
36 | |
37 | #define vmw_crtc_to_stdu(x) \ |
38 | container_of(x, struct vmw_screen_target_display_unit, base.crtc) |
39 | #define vmw_encoder_to_stdu(x) \ |
40 | container_of(x, struct vmw_screen_target_display_unit, base.encoder) |
41 | #define vmw_connector_to_stdu(x) \ |
42 | container_of(x, struct vmw_screen_target_display_unit, base.connector) |
43 | |
44 | |
45 | |
46 | enum stdu_content_type { |
47 | SAME_AS_DISPLAY = 0, |
48 | SEPARATE_SURFACE, |
49 | SEPARATE_BO |
50 | }; |
51 | |
52 | /** |
53 | * struct vmw_stdu_dirty - closure structure for the update functions |
54 | * |
55 | * @base: The base type we derive from. Used by vmw_kms_helper_dirty(). |
56 | * @left: Left side of bounding box. |
57 | * @right: Right side of bounding box. |
58 | * @top: Top side of bounding box. |
59 | * @bottom: Bottom side of bounding box. |
60 | * @fb_left: Left side of the framebuffer/content bounding box |
61 | * @fb_top: Top of the framebuffer/content bounding box |
62 | * @pitch: framebuffer pitch (stride) |
63 | * @buf: buffer object when DMA-ing between buffer and screen targets. |
64 | * @sid: Surface ID when copying between surface and screen targets. |
65 | */ |
66 | struct vmw_stdu_dirty { |
67 | struct vmw_kms_dirty base; |
68 | s32 left, right, top, bottom; |
69 | s32 fb_left, fb_top; |
70 | u32 pitch; |
71 | union { |
72 | struct vmw_bo *buf; |
73 | u32 sid; |
74 | }; |
75 | }; |
76 | |
77 | /* |
78 | * SVGA commands that are used by this code. Please see the device headers |
79 | * for explanation. |
80 | */ |
81 | struct vmw_stdu_update { |
82 | SVGA3dCmdHeader ; |
83 | SVGA3dCmdUpdateGBScreenTarget body; |
84 | }; |
85 | |
86 | struct vmw_stdu_dma { |
87 | SVGA3dCmdHeader ; |
88 | SVGA3dCmdSurfaceDMA body; |
89 | }; |
90 | |
91 | struct vmw_stdu_surface_copy { |
92 | SVGA3dCmdHeader ; |
93 | SVGA3dCmdSurfaceCopy body; |
94 | }; |
95 | |
96 | struct vmw_stdu_update_gb_image { |
97 | SVGA3dCmdHeader ; |
98 | SVGA3dCmdUpdateGBImage body; |
99 | }; |
100 | |
101 | /** |
102 | * struct vmw_screen_target_display_unit - conglomerated STDU structure |
103 | * |
104 | * @base: VMW specific DU structure |
105 | * @display_srf: surface to be displayed. The dimension of this will always |
106 | * match the display mode. If the display mode matches |
107 | * content_vfbs dimensions, then this is a pointer into the |
108 | * corresponding field in content_vfbs. If not, then this |
109 | * is a separate buffer to which content_vfbs will blit to. |
110 | * @content_fb_type: content_fb type |
111 | * @display_width: display width |
112 | * @display_height: display height |
113 | * @defined: true if the current display unit has been initialized |
114 | * @cpp: Bytes per pixel |
115 | */ |
116 | struct vmw_screen_target_display_unit { |
117 | struct vmw_display_unit base; |
118 | struct vmw_surface *display_srf; |
119 | enum stdu_content_type content_fb_type; |
120 | s32 display_width, display_height; |
121 | |
122 | bool defined; |
123 | |
124 | /* For CPU Blit */ |
125 | unsigned int cpp; |
126 | }; |
127 | |
128 | |
129 | |
130 | static void vmw_stdu_destroy(struct vmw_screen_target_display_unit *stdu); |
131 | |
132 | |
133 | |
134 | /****************************************************************************** |
135 | * Screen Target Display Unit CRTC Functions |
136 | *****************************************************************************/ |
137 | |
138 | /** |
139 | * vmw_stdu_crtc_destroy - cleans up the STDU |
140 | * |
141 | * @crtc: used to get a reference to the containing STDU |
142 | */ |
143 | static void vmw_stdu_crtc_destroy(struct drm_crtc *crtc) |
144 | { |
145 | vmw_stdu_destroy(vmw_crtc_to_stdu(crtc)); |
146 | } |
147 | |
148 | /** |
149 | * vmw_stdu_define_st - Defines a Screen Target |
150 | * |
151 | * @dev_priv: VMW DRM device |
152 | * @stdu: display unit to create a Screen Target for |
153 | * @mode: The mode to set. |
154 | * @crtc_x: X coordinate of screen target relative to framebuffer origin. |
155 | * @crtc_y: Y coordinate of screen target relative to framebuffer origin. |
156 | * |
157 | * Creates a STDU that we can used later. This function is called whenever the |
158 | * framebuffer size changes. |
159 | * |
160 | * RETURNs: |
161 | * 0 on success, error code on failure |
162 | */ |
163 | static int vmw_stdu_define_st(struct vmw_private *dev_priv, |
164 | struct vmw_screen_target_display_unit *stdu, |
165 | struct drm_display_mode *mode, |
166 | int crtc_x, int crtc_y) |
167 | { |
168 | struct { |
169 | SVGA3dCmdHeader ; |
170 | SVGA3dCmdDefineGBScreenTarget body; |
171 | } *cmd; |
172 | |
173 | cmd = VMW_CMD_RESERVE(dev_priv, sizeof(*cmd)); |
174 | if (unlikely(cmd == NULL)) |
175 | return -ENOMEM; |
176 | |
177 | cmd->header.id = SVGA_3D_CMD_DEFINE_GB_SCREENTARGET; |
178 | cmd->header.size = sizeof(cmd->body); |
179 | |
180 | cmd->body.stid = stdu->base.unit; |
181 | cmd->body.width = mode->hdisplay; |
182 | cmd->body.height = mode->vdisplay; |
183 | cmd->body.flags = (0 == cmd->body.stid) ? SVGA_STFLAG_PRIMARY : 0; |
184 | cmd->body.dpi = 0; |
185 | cmd->body.xRoot = crtc_x; |
186 | cmd->body.yRoot = crtc_y; |
187 | |
188 | stdu->base.set_gui_x = cmd->body.xRoot; |
189 | stdu->base.set_gui_y = cmd->body.yRoot; |
190 | |
191 | vmw_cmd_commit(dev_priv, bytes: sizeof(*cmd)); |
192 | |
193 | stdu->defined = true; |
194 | stdu->display_width = mode->hdisplay; |
195 | stdu->display_height = mode->vdisplay; |
196 | |
197 | return 0; |
198 | } |
199 | |
200 | |
201 | |
202 | /** |
203 | * vmw_stdu_bind_st - Binds a surface to a Screen Target |
204 | * |
205 | * @dev_priv: VMW DRM device |
206 | * @stdu: display unit affected |
207 | * @res: Buffer to bind to the screen target. Set to NULL to blank screen. |
208 | * |
209 | * Binding a surface to a Screen Target the same as flipping |
210 | * |
211 | * Returns: %0 on success or -errno code on failure |
212 | */ |
213 | static int vmw_stdu_bind_st(struct vmw_private *dev_priv, |
214 | struct vmw_screen_target_display_unit *stdu, |
215 | const struct vmw_resource *res) |
216 | { |
217 | SVGA3dSurfaceImageId image; |
218 | |
219 | struct { |
220 | SVGA3dCmdHeader ; |
221 | SVGA3dCmdBindGBScreenTarget body; |
222 | } *cmd; |
223 | |
224 | |
225 | if (!stdu->defined) { |
226 | DRM_ERROR("No screen target defined\n" ); |
227 | return -EINVAL; |
228 | } |
229 | |
230 | /* Set up image using information in vfb */ |
231 | memset(&image, 0, sizeof(image)); |
232 | image.sid = res ? res->id : SVGA3D_INVALID_ID; |
233 | |
234 | cmd = VMW_CMD_RESERVE(dev_priv, sizeof(*cmd)); |
235 | if (unlikely(cmd == NULL)) |
236 | return -ENOMEM; |
237 | |
238 | cmd->header.id = SVGA_3D_CMD_BIND_GB_SCREENTARGET; |
239 | cmd->header.size = sizeof(cmd->body); |
240 | |
241 | cmd->body.stid = stdu->base.unit; |
242 | cmd->body.image = image; |
243 | |
244 | vmw_cmd_commit(dev_priv, bytes: sizeof(*cmd)); |
245 | |
246 | return 0; |
247 | } |
248 | |
249 | /** |
250 | * vmw_stdu_populate_update - populate an UPDATE_GB_SCREENTARGET command with a |
251 | * bounding box. |
252 | * |
253 | * @cmd: Pointer to command stream. |
254 | * @unit: Screen target unit. |
255 | * @left: Left side of bounding box. |
256 | * @right: Right side of bounding box. |
257 | * @top: Top side of bounding box. |
258 | * @bottom: Bottom side of bounding box. |
259 | */ |
260 | static void vmw_stdu_populate_update(void *cmd, int unit, |
261 | s32 left, s32 right, s32 top, s32 bottom) |
262 | { |
263 | struct vmw_stdu_update *update = cmd; |
264 | |
265 | update->header.id = SVGA_3D_CMD_UPDATE_GB_SCREENTARGET; |
266 | update->header.size = sizeof(update->body); |
267 | |
268 | update->body.stid = unit; |
269 | update->body.rect.x = left; |
270 | update->body.rect.y = top; |
271 | update->body.rect.w = right - left; |
272 | update->body.rect.h = bottom - top; |
273 | } |
274 | |
275 | /** |
276 | * vmw_stdu_update_st - Full update of a Screen Target |
277 | * |
278 | * @dev_priv: VMW DRM device |
279 | * @stdu: display unit affected |
280 | * |
281 | * This function needs to be called whenever the content of a screen |
282 | * target has changed completely. Typically as a result of a backing |
283 | * surface change. |
284 | * |
285 | * RETURNS: |
286 | * 0 on success, error code on failure |
287 | */ |
288 | static int vmw_stdu_update_st(struct vmw_private *dev_priv, |
289 | struct vmw_screen_target_display_unit *stdu) |
290 | { |
291 | struct vmw_stdu_update *cmd; |
292 | |
293 | if (!stdu->defined) { |
294 | DRM_ERROR("No screen target defined" ); |
295 | return -EINVAL; |
296 | } |
297 | |
298 | cmd = VMW_CMD_RESERVE(dev_priv, sizeof(*cmd)); |
299 | if (unlikely(cmd == NULL)) |
300 | return -ENOMEM; |
301 | |
302 | vmw_stdu_populate_update(cmd, unit: stdu->base.unit, |
303 | left: 0, right: stdu->display_width, |
304 | top: 0, bottom: stdu->display_height); |
305 | |
306 | vmw_cmd_commit(dev_priv, bytes: sizeof(*cmd)); |
307 | |
308 | return 0; |
309 | } |
310 | |
311 | |
312 | |
313 | /** |
314 | * vmw_stdu_destroy_st - Destroy a Screen Target |
315 | * |
316 | * @dev_priv: VMW DRM device |
317 | * @stdu: display unit to destroy |
318 | * |
319 | * Returns: %0 on success, negative error code on failure. -ERESTARTSYS if |
320 | * interrupted. |
321 | */ |
322 | static int vmw_stdu_destroy_st(struct vmw_private *dev_priv, |
323 | struct vmw_screen_target_display_unit *stdu) |
324 | { |
325 | int ret; |
326 | |
327 | struct { |
328 | SVGA3dCmdHeader ; |
329 | SVGA3dCmdDestroyGBScreenTarget body; |
330 | } *cmd; |
331 | |
332 | |
333 | /* Nothing to do if not successfully defined */ |
334 | if (unlikely(!stdu->defined)) |
335 | return 0; |
336 | |
337 | cmd = VMW_CMD_RESERVE(dev_priv, sizeof(*cmd)); |
338 | if (unlikely(cmd == NULL)) |
339 | return -ENOMEM; |
340 | |
341 | cmd->header.id = SVGA_3D_CMD_DESTROY_GB_SCREENTARGET; |
342 | cmd->header.size = sizeof(cmd->body); |
343 | |
344 | cmd->body.stid = stdu->base.unit; |
345 | |
346 | vmw_cmd_commit(dev_priv, bytes: sizeof(*cmd)); |
347 | |
348 | /* Force sync */ |
349 | ret = vmw_fallback_wait(dev_priv, lazy: false, fifo_idle: true, seqno: 0, interruptible: false, timeout: 3*HZ); |
350 | if (unlikely(ret != 0)) |
351 | DRM_ERROR("Failed to sync with HW" ); |
352 | |
353 | stdu->defined = false; |
354 | stdu->display_width = 0; |
355 | stdu->display_height = 0; |
356 | |
357 | return ret; |
358 | } |
359 | |
360 | |
361 | /** |
362 | * vmw_stdu_crtc_mode_set_nofb - Updates screen target size |
363 | * |
364 | * @crtc: CRTC associated with the screen target |
365 | * |
366 | * This function defines/destroys a screen target |
367 | * |
368 | */ |
369 | static void vmw_stdu_crtc_mode_set_nofb(struct drm_crtc *crtc) |
370 | { |
371 | struct vmw_private *dev_priv; |
372 | struct vmw_screen_target_display_unit *stdu; |
373 | struct drm_connector_state *conn_state; |
374 | struct vmw_connector_state *vmw_conn_state; |
375 | int x, y, ret; |
376 | |
377 | stdu = vmw_crtc_to_stdu(crtc); |
378 | dev_priv = vmw_priv(dev: crtc->dev); |
379 | conn_state = stdu->base.connector.state; |
380 | vmw_conn_state = vmw_connector_state_to_vcs(conn_state); |
381 | |
382 | if (stdu->defined) { |
383 | ret = vmw_stdu_bind_st(dev_priv, stdu, NULL); |
384 | if (ret) |
385 | DRM_ERROR("Failed to blank CRTC\n" ); |
386 | |
387 | (void) vmw_stdu_update_st(dev_priv, stdu); |
388 | |
389 | ret = vmw_stdu_destroy_st(dev_priv, stdu); |
390 | if (ret) |
391 | DRM_ERROR("Failed to destroy Screen Target\n" ); |
392 | |
393 | stdu->content_fb_type = SAME_AS_DISPLAY; |
394 | } |
395 | |
396 | if (!crtc->state->enable) |
397 | return; |
398 | |
399 | x = vmw_conn_state->gui_x; |
400 | y = vmw_conn_state->gui_y; |
401 | |
402 | vmw_svga_enable(dev_priv); |
403 | ret = vmw_stdu_define_st(dev_priv, stdu, mode: &crtc->mode, crtc_x: x, crtc_y: y); |
404 | |
405 | if (ret) |
406 | DRM_ERROR("Failed to define Screen Target of size %dx%d\n" , |
407 | crtc->x, crtc->y); |
408 | } |
409 | |
410 | |
411 | static void vmw_stdu_crtc_helper_prepare(struct drm_crtc *crtc) |
412 | { |
413 | } |
414 | |
415 | static void vmw_stdu_crtc_atomic_enable(struct drm_crtc *crtc, |
416 | struct drm_atomic_state *state) |
417 | { |
418 | } |
419 | |
420 | static void vmw_stdu_crtc_atomic_disable(struct drm_crtc *crtc, |
421 | struct drm_atomic_state *state) |
422 | { |
423 | struct vmw_private *dev_priv; |
424 | struct vmw_screen_target_display_unit *stdu; |
425 | int ret; |
426 | |
427 | |
428 | if (!crtc) { |
429 | DRM_ERROR("CRTC is NULL\n" ); |
430 | return; |
431 | } |
432 | |
433 | stdu = vmw_crtc_to_stdu(crtc); |
434 | dev_priv = vmw_priv(dev: crtc->dev); |
435 | |
436 | if (stdu->defined) { |
437 | ret = vmw_stdu_bind_st(dev_priv, stdu, NULL); |
438 | if (ret) |
439 | DRM_ERROR("Failed to blank CRTC\n" ); |
440 | |
441 | (void) vmw_stdu_update_st(dev_priv, stdu); |
442 | |
443 | ret = vmw_stdu_destroy_st(dev_priv, stdu); |
444 | if (ret) |
445 | DRM_ERROR("Failed to destroy Screen Target\n" ); |
446 | |
447 | stdu->content_fb_type = SAME_AS_DISPLAY; |
448 | } |
449 | } |
450 | |
451 | /** |
452 | * vmw_stdu_bo_cpu_clip - Callback to encode a CPU blit |
453 | * |
454 | * @dirty: The closure structure. |
455 | * |
456 | * This function calculates the bounding box for all the incoming clips. |
457 | */ |
458 | static void vmw_stdu_bo_cpu_clip(struct vmw_kms_dirty *dirty) |
459 | { |
460 | struct vmw_stdu_dirty *ddirty = |
461 | container_of(dirty, struct vmw_stdu_dirty, base); |
462 | |
463 | dirty->num_hits = 1; |
464 | |
465 | /* Calculate destination bounding box */ |
466 | ddirty->left = min_t(s32, ddirty->left, dirty->unit_x1); |
467 | ddirty->top = min_t(s32, ddirty->top, dirty->unit_y1); |
468 | ddirty->right = max_t(s32, ddirty->right, dirty->unit_x2); |
469 | ddirty->bottom = max_t(s32, ddirty->bottom, dirty->unit_y2); |
470 | |
471 | /* |
472 | * Calculate content bounding box. We only need the top-left |
473 | * coordinate because width and height will be the same as the |
474 | * destination bounding box above |
475 | */ |
476 | ddirty->fb_left = min_t(s32, ddirty->fb_left, dirty->fb_x); |
477 | ddirty->fb_top = min_t(s32, ddirty->fb_top, dirty->fb_y); |
478 | } |
479 | |
480 | |
481 | /** |
482 | * vmw_stdu_bo_cpu_commit - Callback to do a CPU blit from buffer object |
483 | * |
484 | * @dirty: The closure structure. |
485 | * |
486 | * For the special case when we cannot create a proxy surface in a |
487 | * 2D VM, we have to do a CPU blit ourselves. |
488 | */ |
489 | static void vmw_stdu_bo_cpu_commit(struct vmw_kms_dirty *dirty) |
490 | { |
491 | struct vmw_stdu_dirty *ddirty = |
492 | container_of(dirty, struct vmw_stdu_dirty, base); |
493 | struct vmw_screen_target_display_unit *stdu = |
494 | container_of(dirty->unit, typeof(*stdu), base); |
495 | s32 width, height; |
496 | s32 src_pitch, dst_pitch; |
497 | struct ttm_buffer_object *src_bo, *dst_bo; |
498 | u32 src_offset, dst_offset; |
499 | struct vmw_diff_cpy diff = VMW_CPU_BLIT_DIFF_INITIALIZER(stdu->cpp); |
500 | |
501 | if (!dirty->num_hits) |
502 | return; |
503 | |
504 | width = ddirty->right - ddirty->left; |
505 | height = ddirty->bottom - ddirty->top; |
506 | |
507 | if (width == 0 || height == 0) |
508 | return; |
509 | |
510 | /* Assume we are blitting from Guest (bo) to Host (display_srf) */ |
511 | src_pitch = stdu->display_srf->metadata.base_size.width * stdu->cpp; |
512 | src_bo = &stdu->display_srf->res.guest_memory_bo->tbo; |
513 | src_offset = ddirty->top * src_pitch + ddirty->left * stdu->cpp; |
514 | |
515 | dst_pitch = ddirty->pitch; |
516 | dst_bo = &ddirty->buf->tbo; |
517 | dst_offset = ddirty->fb_top * dst_pitch + ddirty->fb_left * stdu->cpp; |
518 | |
519 | (void) vmw_bo_cpu_blit(dst: dst_bo, dst_offset, dst_stride: dst_pitch, |
520 | src: src_bo, src_offset, src_stride: src_pitch, |
521 | w: width * stdu->cpp, h: height, diff: &diff); |
522 | } |
523 | |
524 | /** |
525 | * vmw_kms_stdu_readback - Perform a readback from a buffer-object backed |
526 | * framebuffer and the screen target system. |
527 | * |
528 | * @dev_priv: Pointer to the device private structure. |
529 | * @file_priv: Pointer to a struct drm-file identifying the caller. May be |
530 | * set to NULL, but then @user_fence_rep must also be set to NULL. |
531 | * @vfb: Pointer to the buffer-object backed framebuffer. |
532 | * @user_fence_rep: User-space provided structure for fence information. |
533 | * @clips: Array of clip rects. Either @clips or @vclips must be NULL. |
534 | * @vclips: Alternate array of clip rects. Either @clips or @vclips must |
535 | * be NULL. |
536 | * @num_clips: Number of clip rects in @clips or @vclips. |
537 | * @increment: Increment to use when looping over @clips or @vclips. |
538 | * @crtc: If crtc is passed, perform stdu dma on that crtc only. |
539 | * |
540 | * If DMA-ing till the screen target system, the function will also notify |
541 | * the screen target system that a bounding box of the cliprects has been |
542 | * updated. |
543 | * |
544 | * Returns: %0 on success, negative error code on failure. -ERESTARTSYS if |
545 | * interrupted. |
546 | */ |
547 | int vmw_kms_stdu_readback(struct vmw_private *dev_priv, |
548 | struct drm_file *file_priv, |
549 | struct vmw_framebuffer *vfb, |
550 | struct drm_vmw_fence_rep __user *user_fence_rep, |
551 | struct drm_clip_rect *clips, |
552 | struct drm_vmw_rect *vclips, |
553 | uint32_t num_clips, |
554 | int increment, |
555 | struct drm_crtc *crtc) |
556 | { |
557 | struct vmw_bo *buf = |
558 | container_of(vfb, struct vmw_framebuffer_bo, base)->buffer; |
559 | struct vmw_stdu_dirty ddirty; |
560 | int ret; |
561 | DECLARE_VAL_CONTEXT(val_ctx, NULL, 0); |
562 | |
563 | /* |
564 | * The GMR domain might seem confusing because it might seem like it should |
565 | * never happen with screen targets but e.g. the xorg vmware driver issues |
566 | * CMD_SURFACE_DMA for various pixmap updates which might transition our bo to |
567 | * a GMR. Instead of forcing another transition we can optimize the readback |
568 | * by reading directly from the GMR. |
569 | */ |
570 | vmw_bo_placement_set(bo: buf, |
571 | domain: VMW_BO_DOMAIN_MOB | VMW_BO_DOMAIN_SYS | VMW_BO_DOMAIN_GMR, |
572 | busy_domain: VMW_BO_DOMAIN_MOB | VMW_BO_DOMAIN_SYS | VMW_BO_DOMAIN_GMR); |
573 | ret = vmw_validation_add_bo(ctx: &val_ctx, vbo: buf); |
574 | if (ret) |
575 | return ret; |
576 | |
577 | ret = vmw_validation_prepare(ctx: &val_ctx, NULL, intr: true); |
578 | if (ret) |
579 | goto out_unref; |
580 | |
581 | ddirty.left = ddirty.top = S32_MAX; |
582 | ddirty.right = ddirty.bottom = S32_MIN; |
583 | ddirty.fb_left = ddirty.fb_top = S32_MAX; |
584 | ddirty.pitch = vfb->base.pitches[0]; |
585 | ddirty.buf = buf; |
586 | |
587 | ddirty.base.fifo_commit = vmw_stdu_bo_cpu_commit; |
588 | ddirty.base.clip = vmw_stdu_bo_cpu_clip; |
589 | ddirty.base.fifo_reserve_size = 0; |
590 | |
591 | ddirty.base.crtc = crtc; |
592 | |
593 | ret = vmw_kms_helper_dirty(dev_priv, framebuffer: vfb, clips, vclips, |
594 | dest_x: 0, dest_y: 0, num_clips, increment, dirty: &ddirty.base); |
595 | |
596 | vmw_kms_helper_validation_finish(dev_priv, file_priv, ctx: &val_ctx, NULL, |
597 | user_fence_rep); |
598 | return ret; |
599 | |
600 | out_unref: |
601 | vmw_validation_unref_lists(ctx: &val_ctx); |
602 | return ret; |
603 | } |
604 | |
605 | /** |
606 | * vmw_kms_stdu_surface_clip - Callback to encode a surface copy command cliprect |
607 | * |
608 | * @dirty: The closure structure. |
609 | * |
610 | * Encodes a surface copy command cliprect and updates the bounding box |
611 | * for the copy. |
612 | */ |
613 | static void vmw_kms_stdu_surface_clip(struct vmw_kms_dirty *dirty) |
614 | { |
615 | struct vmw_stdu_dirty *sdirty = |
616 | container_of(dirty, struct vmw_stdu_dirty, base); |
617 | struct vmw_stdu_surface_copy *cmd = dirty->cmd; |
618 | struct vmw_screen_target_display_unit *stdu = |
619 | container_of(dirty->unit, typeof(*stdu), base); |
620 | |
621 | if (sdirty->sid != stdu->display_srf->res.id) { |
622 | struct SVGA3dCopyBox *blit = (struct SVGA3dCopyBox *) &cmd[1]; |
623 | |
624 | blit += dirty->num_hits; |
625 | blit->srcx = dirty->fb_x; |
626 | blit->srcy = dirty->fb_y; |
627 | blit->x = dirty->unit_x1; |
628 | blit->y = dirty->unit_y1; |
629 | blit->d = 1; |
630 | blit->w = dirty->unit_x2 - dirty->unit_x1; |
631 | blit->h = dirty->unit_y2 - dirty->unit_y1; |
632 | } |
633 | |
634 | dirty->num_hits++; |
635 | |
636 | /* Destination bounding box */ |
637 | sdirty->left = min_t(s32, sdirty->left, dirty->unit_x1); |
638 | sdirty->top = min_t(s32, sdirty->top, dirty->unit_y1); |
639 | sdirty->right = max_t(s32, sdirty->right, dirty->unit_x2); |
640 | sdirty->bottom = max_t(s32, sdirty->bottom, dirty->unit_y2); |
641 | } |
642 | |
643 | /** |
644 | * vmw_kms_stdu_surface_fifo_commit - Callback to fill in and submit a surface |
645 | * copy command. |
646 | * |
647 | * @dirty: The closure structure. |
648 | * |
649 | * Fills in the missing fields in a surface copy command, and encodes a screen |
650 | * target update command. |
651 | */ |
652 | static void vmw_kms_stdu_surface_fifo_commit(struct vmw_kms_dirty *dirty) |
653 | { |
654 | struct vmw_stdu_dirty *sdirty = |
655 | container_of(dirty, struct vmw_stdu_dirty, base); |
656 | struct vmw_screen_target_display_unit *stdu = |
657 | container_of(dirty->unit, typeof(*stdu), base); |
658 | struct vmw_stdu_surface_copy *cmd = dirty->cmd; |
659 | struct vmw_stdu_update *update; |
660 | size_t blit_size = sizeof(SVGA3dCopyBox) * dirty->num_hits; |
661 | size_t commit_size; |
662 | |
663 | if (!dirty->num_hits) { |
664 | vmw_cmd_commit(dev_priv: dirty->dev_priv, bytes: 0); |
665 | return; |
666 | } |
667 | |
668 | if (sdirty->sid != stdu->display_srf->res.id) { |
669 | struct SVGA3dCopyBox *blit = (struct SVGA3dCopyBox *) &cmd[1]; |
670 | |
671 | cmd->header.id = SVGA_3D_CMD_SURFACE_COPY; |
672 | cmd->header.size = sizeof(cmd->body) + blit_size; |
673 | cmd->body.src.sid = sdirty->sid; |
674 | cmd->body.dest.sid = stdu->display_srf->res.id; |
675 | update = (struct vmw_stdu_update *) &blit[dirty->num_hits]; |
676 | commit_size = sizeof(*cmd) + blit_size + sizeof(*update); |
677 | stdu->display_srf->res.res_dirty = true; |
678 | } else { |
679 | update = dirty->cmd; |
680 | commit_size = sizeof(*update); |
681 | } |
682 | |
683 | vmw_stdu_populate_update(cmd: update, unit: stdu->base.unit, left: sdirty->left, |
684 | right: sdirty->right, top: sdirty->top, bottom: sdirty->bottom); |
685 | |
686 | vmw_cmd_commit(dev_priv: dirty->dev_priv, bytes: commit_size); |
687 | |
688 | sdirty->left = sdirty->top = S32_MAX; |
689 | sdirty->right = sdirty->bottom = S32_MIN; |
690 | } |
691 | |
692 | /** |
693 | * vmw_kms_stdu_surface_dirty - Dirty part of a surface backed framebuffer |
694 | * |
695 | * @dev_priv: Pointer to the device private structure. |
696 | * @framebuffer: Pointer to the surface-buffer backed framebuffer. |
697 | * @clips: Array of clip rects. Either @clips or @vclips must be NULL. |
698 | * @vclips: Alternate array of clip rects. Either @clips or @vclips must |
699 | * be NULL. |
700 | * @srf: Pointer to surface to blit from. If NULL, the surface attached |
701 | * to @framebuffer will be used. |
702 | * @dest_x: X coordinate offset to align @srf with framebuffer coordinates. |
703 | * @dest_y: Y coordinate offset to align @srf with framebuffer coordinates. |
704 | * @num_clips: Number of clip rects in @clips. |
705 | * @inc: Increment to use when looping over @clips. |
706 | * @out_fence: If non-NULL, will return a ref-counted pointer to a |
707 | * struct vmw_fence_obj. The returned fence pointer may be NULL in which |
708 | * case the device has already synchronized. |
709 | * @crtc: If crtc is passed, perform surface dirty on that crtc only. |
710 | * |
711 | * Returns: %0 on success, negative error code on failure. -ERESTARTSYS if |
712 | * interrupted. |
713 | */ |
714 | int vmw_kms_stdu_surface_dirty(struct vmw_private *dev_priv, |
715 | struct vmw_framebuffer *framebuffer, |
716 | struct drm_clip_rect *clips, |
717 | struct drm_vmw_rect *vclips, |
718 | struct vmw_resource *srf, |
719 | s32 dest_x, |
720 | s32 dest_y, |
721 | unsigned num_clips, int inc, |
722 | struct vmw_fence_obj **out_fence, |
723 | struct drm_crtc *crtc) |
724 | { |
725 | struct vmw_framebuffer_surface *vfbs = |
726 | container_of(framebuffer, typeof(*vfbs), base); |
727 | struct vmw_stdu_dirty sdirty; |
728 | DECLARE_VAL_CONTEXT(val_ctx, NULL, 0); |
729 | int ret; |
730 | |
731 | if (!srf) |
732 | srf = &vfbs->surface->res; |
733 | |
734 | ret = vmw_validation_add_resource(ctx: &val_ctx, res: srf, priv_size: 0, VMW_RES_DIRTY_NONE, |
735 | NULL, NULL); |
736 | if (ret) |
737 | return ret; |
738 | |
739 | ret = vmw_validation_prepare(ctx: &val_ctx, mutex: &dev_priv->cmdbuf_mutex, intr: true); |
740 | if (ret) |
741 | goto out_unref; |
742 | |
743 | if (vfbs->is_bo_proxy) { |
744 | ret = vmw_kms_update_proxy(res: srf, clips, num_clips, increment: inc); |
745 | if (ret) |
746 | goto out_finish; |
747 | } |
748 | |
749 | sdirty.base.fifo_commit = vmw_kms_stdu_surface_fifo_commit; |
750 | sdirty.base.clip = vmw_kms_stdu_surface_clip; |
751 | sdirty.base.fifo_reserve_size = sizeof(struct vmw_stdu_surface_copy) + |
752 | sizeof(SVGA3dCopyBox) * num_clips + |
753 | sizeof(struct vmw_stdu_update); |
754 | sdirty.base.crtc = crtc; |
755 | sdirty.sid = srf->id; |
756 | sdirty.left = sdirty.top = S32_MAX; |
757 | sdirty.right = sdirty.bottom = S32_MIN; |
758 | |
759 | ret = vmw_kms_helper_dirty(dev_priv, framebuffer, clips, vclips, |
760 | dest_x, dest_y, num_clips, increment: inc, |
761 | dirty: &sdirty.base); |
762 | out_finish: |
763 | vmw_kms_helper_validation_finish(dev_priv, NULL, ctx: &val_ctx, out_fence, |
764 | NULL); |
765 | |
766 | return ret; |
767 | |
768 | out_unref: |
769 | vmw_validation_unref_lists(ctx: &val_ctx); |
770 | return ret; |
771 | } |
772 | |
773 | |
774 | /* |
775 | * Screen Target CRTC dispatch table |
776 | */ |
777 | static const struct drm_crtc_funcs vmw_stdu_crtc_funcs = { |
778 | .gamma_set = vmw_du_crtc_gamma_set, |
779 | .destroy = vmw_stdu_crtc_destroy, |
780 | .reset = vmw_du_crtc_reset, |
781 | .atomic_duplicate_state = vmw_du_crtc_duplicate_state, |
782 | .atomic_destroy_state = vmw_du_crtc_destroy_state, |
783 | .set_config = drm_atomic_helper_set_config, |
784 | .page_flip = drm_atomic_helper_page_flip, |
785 | }; |
786 | |
787 | |
788 | |
789 | /****************************************************************************** |
790 | * Screen Target Display Unit Encoder Functions |
791 | *****************************************************************************/ |
792 | |
793 | /** |
794 | * vmw_stdu_encoder_destroy - cleans up the STDU |
795 | * |
796 | * @encoder: used the get the containing STDU |
797 | * |
798 | * vmwgfx cleans up crtc/encoder/connector all at the same time so technically |
799 | * this can be a no-op. Nevertheless, it doesn't hurt of have this in case |
800 | * the common KMS code changes and somehow vmw_stdu_crtc_destroy() doesn't |
801 | * get called. |
802 | */ |
803 | static void vmw_stdu_encoder_destroy(struct drm_encoder *encoder) |
804 | { |
805 | vmw_stdu_destroy(vmw_encoder_to_stdu(encoder)); |
806 | } |
807 | |
808 | static const struct drm_encoder_funcs vmw_stdu_encoder_funcs = { |
809 | .destroy = vmw_stdu_encoder_destroy, |
810 | }; |
811 | |
812 | |
813 | |
814 | /****************************************************************************** |
815 | * Screen Target Display Unit Connector Functions |
816 | *****************************************************************************/ |
817 | |
818 | /** |
819 | * vmw_stdu_connector_destroy - cleans up the STDU |
820 | * |
821 | * @connector: used to get the containing STDU |
822 | * |
823 | * vmwgfx cleans up crtc/encoder/connector all at the same time so technically |
824 | * this can be a no-op. Nevertheless, it doesn't hurt of have this in case |
825 | * the common KMS code changes and somehow vmw_stdu_crtc_destroy() doesn't |
826 | * get called. |
827 | */ |
828 | static void vmw_stdu_connector_destroy(struct drm_connector *connector) |
829 | { |
830 | vmw_stdu_destroy(vmw_connector_to_stdu(connector)); |
831 | } |
832 | |
833 | |
834 | |
835 | static const struct drm_connector_funcs vmw_stdu_connector_funcs = { |
836 | .dpms = vmw_du_connector_dpms, |
837 | .detect = vmw_du_connector_detect, |
838 | .fill_modes = drm_helper_probe_single_connector_modes, |
839 | .destroy = vmw_stdu_connector_destroy, |
840 | .reset = vmw_du_connector_reset, |
841 | .atomic_duplicate_state = vmw_du_connector_duplicate_state, |
842 | .atomic_destroy_state = vmw_du_connector_destroy_state, |
843 | }; |
844 | |
845 | |
846 | static const struct |
847 | drm_connector_helper_funcs vmw_stdu_connector_helper_funcs = { |
848 | .get_modes = vmw_connector_get_modes, |
849 | .mode_valid = vmw_connector_mode_valid |
850 | }; |
851 | |
852 | |
853 | |
854 | /****************************************************************************** |
855 | * Screen Target Display Plane Functions |
856 | *****************************************************************************/ |
857 | |
858 | |
859 | |
860 | /** |
861 | * vmw_stdu_primary_plane_cleanup_fb - Unpins the display surface |
862 | * |
863 | * @plane: display plane |
864 | * @old_state: Contains the FB to clean up |
865 | * |
866 | * Unpins the display surface |
867 | * |
868 | * Returns 0 on success |
869 | */ |
870 | static void |
871 | vmw_stdu_primary_plane_cleanup_fb(struct drm_plane *plane, |
872 | struct drm_plane_state *old_state) |
873 | { |
874 | struct vmw_plane_state *vps = vmw_plane_state_to_vps(old_state); |
875 | |
876 | if (vps->surf) |
877 | WARN_ON(!vps->pinned); |
878 | |
879 | vmw_du_plane_cleanup_fb(plane, old_state); |
880 | |
881 | vps->content_fb_type = SAME_AS_DISPLAY; |
882 | vps->cpp = 0; |
883 | } |
884 | |
885 | |
886 | |
887 | /** |
888 | * vmw_stdu_primary_plane_prepare_fb - Readies the display surface |
889 | * |
890 | * @plane: display plane |
891 | * @new_state: info on the new plane state, including the FB |
892 | * |
893 | * This function allocates a new display surface if the content is |
894 | * backed by a buffer object. The display surface is pinned here, and it'll |
895 | * be unpinned in .cleanup_fb() |
896 | * |
897 | * Returns: %0 on success |
898 | */ |
899 | static int |
900 | vmw_stdu_primary_plane_prepare_fb(struct drm_plane *plane, |
901 | struct drm_plane_state *new_state) |
902 | { |
903 | struct vmw_private *dev_priv = vmw_priv(dev: plane->dev); |
904 | struct drm_framebuffer *new_fb = new_state->fb; |
905 | struct vmw_framebuffer *vfb; |
906 | struct vmw_plane_state *vps = vmw_plane_state_to_vps(new_state); |
907 | enum stdu_content_type new_content_type; |
908 | struct vmw_framebuffer_surface *new_vfbs; |
909 | uint32_t hdisplay = new_state->crtc_w, vdisplay = new_state->crtc_h; |
910 | int ret; |
911 | |
912 | /* No FB to prepare */ |
913 | if (!new_fb) { |
914 | if (vps->surf) { |
915 | WARN_ON(vps->pinned != 0); |
916 | vmw_surface_unreference(srf: &vps->surf); |
917 | } |
918 | |
919 | return 0; |
920 | } |
921 | |
922 | vfb = vmw_framebuffer_to_vfb(new_fb); |
923 | new_vfbs = (vfb->bo) ? NULL : vmw_framebuffer_to_vfbs(new_fb); |
924 | |
925 | if (new_vfbs && |
926 | new_vfbs->surface->metadata.base_size.width == hdisplay && |
927 | new_vfbs->surface->metadata.base_size.height == vdisplay) |
928 | new_content_type = SAME_AS_DISPLAY; |
929 | else if (vfb->bo) |
930 | new_content_type = SEPARATE_BO; |
931 | else |
932 | new_content_type = SEPARATE_SURFACE; |
933 | |
934 | if (new_content_type != SAME_AS_DISPLAY) { |
935 | struct vmw_surface_metadata metadata = {0}; |
936 | |
937 | /* |
938 | * If content buffer is a buffer object, then we have to |
939 | * construct surface info |
940 | */ |
941 | if (new_content_type == SEPARATE_BO) { |
942 | |
943 | switch (new_fb->format->cpp[0]*8) { |
944 | case 32: |
945 | metadata.format = SVGA3D_X8R8G8B8; |
946 | break; |
947 | |
948 | case 16: |
949 | metadata.format = SVGA3D_R5G6B5; |
950 | break; |
951 | |
952 | case 8: |
953 | metadata.format = SVGA3D_P8; |
954 | break; |
955 | |
956 | default: |
957 | DRM_ERROR("Invalid format\n" ); |
958 | return -EINVAL; |
959 | } |
960 | |
961 | metadata.mip_levels[0] = 1; |
962 | metadata.num_sizes = 1; |
963 | metadata.scanout = true; |
964 | } else { |
965 | metadata = new_vfbs->surface->metadata; |
966 | } |
967 | |
968 | metadata.base_size.width = hdisplay; |
969 | metadata.base_size.height = vdisplay; |
970 | metadata.base_size.depth = 1; |
971 | |
972 | if (vps->surf) { |
973 | struct drm_vmw_size cur_base_size = |
974 | vps->surf->metadata.base_size; |
975 | |
976 | if (cur_base_size.width != metadata.base_size.width || |
977 | cur_base_size.height != metadata.base_size.height || |
978 | vps->surf->metadata.format != metadata.format) { |
979 | WARN_ON(vps->pinned != 0); |
980 | vmw_surface_unreference(srf: &vps->surf); |
981 | } |
982 | |
983 | } |
984 | |
985 | if (!vps->surf) { |
986 | ret = vmw_gb_surface_define(dev_priv, req: &metadata, |
987 | srf_out: &vps->surf); |
988 | if (ret != 0) { |
989 | DRM_ERROR("Couldn't allocate STDU surface.\n" ); |
990 | return ret; |
991 | } |
992 | } |
993 | } else { |
994 | /* |
995 | * prepare_fb and clean_fb should only take care of pinning |
996 | * and unpinning. References are tracked by state objects. |
997 | * The only time we add a reference in prepare_fb is if the |
998 | * state object doesn't have a reference to begin with |
999 | */ |
1000 | if (vps->surf) { |
1001 | WARN_ON(vps->pinned != 0); |
1002 | vmw_surface_unreference(srf: &vps->surf); |
1003 | } |
1004 | |
1005 | vps->surf = vmw_surface_reference(srf: new_vfbs->surface); |
1006 | } |
1007 | |
1008 | if (vps->surf) { |
1009 | |
1010 | /* Pin new surface before flipping */ |
1011 | ret = vmw_resource_pin(res: &vps->surf->res, interruptible: false); |
1012 | if (ret) |
1013 | goto out_srf_unref; |
1014 | |
1015 | vps->pinned++; |
1016 | } |
1017 | |
1018 | vps->content_fb_type = new_content_type; |
1019 | |
1020 | /* |
1021 | * This should only happen if the buffer object is too large to create a |
1022 | * proxy surface for. |
1023 | */ |
1024 | if (vps->content_fb_type == SEPARATE_BO) |
1025 | vps->cpp = new_fb->pitches[0] / new_fb->width; |
1026 | |
1027 | return 0; |
1028 | |
1029 | out_srf_unref: |
1030 | vmw_surface_unreference(srf: &vps->surf); |
1031 | return ret; |
1032 | } |
1033 | |
1034 | static uint32_t vmw_stdu_bo_fifo_size_cpu(struct vmw_du_update_plane *update, |
1035 | uint32_t num_hits) |
1036 | { |
1037 | return sizeof(struct vmw_stdu_update_gb_image) + |
1038 | sizeof(struct vmw_stdu_update); |
1039 | } |
1040 | |
1041 | static uint32_t vmw_stdu_bo_pre_clip_cpu(struct vmw_du_update_plane *update, |
1042 | void *cmd, uint32_t num_hits) |
1043 | { |
1044 | struct vmw_du_update_plane_buffer *bo_update = |
1045 | container_of(update, typeof(*bo_update), base); |
1046 | |
1047 | bo_update->fb_left = INT_MAX; |
1048 | bo_update->fb_top = INT_MAX; |
1049 | |
1050 | return 0; |
1051 | } |
1052 | |
1053 | static uint32_t vmw_stdu_bo_clip_cpu(struct vmw_du_update_plane *update, |
1054 | void *cmd, struct drm_rect *clip, |
1055 | uint32_t fb_x, uint32_t fb_y) |
1056 | { |
1057 | struct vmw_du_update_plane_buffer *bo_update = |
1058 | container_of(update, typeof(*bo_update), base); |
1059 | |
1060 | bo_update->fb_left = min_t(int, bo_update->fb_left, fb_x); |
1061 | bo_update->fb_top = min_t(int, bo_update->fb_top, fb_y); |
1062 | |
1063 | return 0; |
1064 | } |
1065 | |
1066 | static uint32_t |
1067 | vmw_stdu_bo_populate_update_cpu(struct vmw_du_update_plane *update, void *cmd, |
1068 | struct drm_rect *bb) |
1069 | { |
1070 | struct vmw_du_update_plane_buffer *bo_update; |
1071 | struct vmw_screen_target_display_unit *stdu; |
1072 | struct vmw_framebuffer_bo *vfbbo; |
1073 | struct vmw_diff_cpy diff = VMW_CPU_BLIT_DIFF_INITIALIZER(0); |
1074 | struct vmw_stdu_update_gb_image *cmd_img = cmd; |
1075 | struct vmw_stdu_update *cmd_update; |
1076 | struct ttm_buffer_object *src_bo, *dst_bo; |
1077 | u32 src_offset, dst_offset; |
1078 | s32 src_pitch, dst_pitch; |
1079 | s32 width, height; |
1080 | |
1081 | bo_update = container_of(update, typeof(*bo_update), base); |
1082 | stdu = container_of(update->du, typeof(*stdu), base); |
1083 | vfbbo = container_of(update->vfb, typeof(*vfbbo), base); |
1084 | |
1085 | width = bb->x2 - bb->x1; |
1086 | height = bb->y2 - bb->y1; |
1087 | |
1088 | diff.cpp = stdu->cpp; |
1089 | |
1090 | dst_bo = &stdu->display_srf->res.guest_memory_bo->tbo; |
1091 | dst_pitch = stdu->display_srf->metadata.base_size.width * stdu->cpp; |
1092 | dst_offset = bb->y1 * dst_pitch + bb->x1 * stdu->cpp; |
1093 | |
1094 | src_bo = &vfbbo->buffer->tbo; |
1095 | src_pitch = update->vfb->base.pitches[0]; |
1096 | src_offset = bo_update->fb_top * src_pitch + bo_update->fb_left * |
1097 | stdu->cpp; |
1098 | |
1099 | (void) vmw_bo_cpu_blit(dst: dst_bo, dst_offset, dst_stride: dst_pitch, src: src_bo, |
1100 | src_offset, src_stride: src_pitch, w: width * stdu->cpp, h: height, |
1101 | diff: &diff); |
1102 | |
1103 | if (drm_rect_visible(r: &diff.rect)) { |
1104 | SVGA3dBox *box = &cmd_img->body.box; |
1105 | |
1106 | cmd_img->header.id = SVGA_3D_CMD_UPDATE_GB_IMAGE; |
1107 | cmd_img->header.size = sizeof(cmd_img->body); |
1108 | cmd_img->body.image.sid = stdu->display_srf->res.id; |
1109 | cmd_img->body.image.face = 0; |
1110 | cmd_img->body.image.mipmap = 0; |
1111 | |
1112 | box->x = diff.rect.x1; |
1113 | box->y = diff.rect.y1; |
1114 | box->z = 0; |
1115 | box->w = drm_rect_width(r: &diff.rect); |
1116 | box->h = drm_rect_height(r: &diff.rect); |
1117 | box->d = 1; |
1118 | |
1119 | cmd_update = (struct vmw_stdu_update *)&cmd_img[1]; |
1120 | vmw_stdu_populate_update(cmd: cmd_update, unit: stdu->base.unit, |
1121 | left: diff.rect.x1, right: diff.rect.x2, |
1122 | top: diff.rect.y1, bottom: diff.rect.y2); |
1123 | |
1124 | return sizeof(*cmd_img) + sizeof(*cmd_update); |
1125 | } |
1126 | |
1127 | return 0; |
1128 | } |
1129 | |
1130 | /** |
1131 | * vmw_stdu_plane_update_bo - Update display unit for bo backed fb. |
1132 | * @dev_priv: device private. |
1133 | * @plane: plane state. |
1134 | * @old_state: old plane state. |
1135 | * @vfb: framebuffer which is blitted to display unit. |
1136 | * @out_fence: If non-NULL, will return a ref-counted pointer to vmw_fence_obj. |
1137 | * The returned fence pointer may be NULL in which case the device |
1138 | * has already synchronized. |
1139 | * |
1140 | * Return: 0 on success or a negative error code on failure. |
1141 | */ |
1142 | static int vmw_stdu_plane_update_bo(struct vmw_private *dev_priv, |
1143 | struct drm_plane *plane, |
1144 | struct drm_plane_state *old_state, |
1145 | struct vmw_framebuffer *vfb, |
1146 | struct vmw_fence_obj **out_fence) |
1147 | { |
1148 | struct vmw_du_update_plane_buffer bo_update; |
1149 | |
1150 | memset(&bo_update, 0, sizeof(struct vmw_du_update_plane_buffer)); |
1151 | bo_update.base.plane = plane; |
1152 | bo_update.base.old_state = old_state; |
1153 | bo_update.base.dev_priv = dev_priv; |
1154 | bo_update.base.du = vmw_crtc_to_du(plane->state->crtc); |
1155 | bo_update.base.vfb = vfb; |
1156 | bo_update.base.out_fence = out_fence; |
1157 | bo_update.base.mutex = NULL; |
1158 | bo_update.base.intr = false; |
1159 | |
1160 | bo_update.base.calc_fifo_size = vmw_stdu_bo_fifo_size_cpu; |
1161 | bo_update.base.pre_clip = vmw_stdu_bo_pre_clip_cpu; |
1162 | bo_update.base.clip = vmw_stdu_bo_clip_cpu; |
1163 | bo_update.base.post_clip = vmw_stdu_bo_populate_update_cpu; |
1164 | |
1165 | return vmw_du_helper_plane_update(update: &bo_update.base); |
1166 | } |
1167 | |
1168 | static uint32_t |
1169 | vmw_stdu_surface_fifo_size_same_display(struct vmw_du_update_plane *update, |
1170 | uint32_t num_hits) |
1171 | { |
1172 | struct vmw_framebuffer_surface *vfbs; |
1173 | uint32_t size = 0; |
1174 | |
1175 | vfbs = container_of(update->vfb, typeof(*vfbs), base); |
1176 | |
1177 | if (vfbs->is_bo_proxy) |
1178 | size += sizeof(struct vmw_stdu_update_gb_image) * num_hits; |
1179 | |
1180 | size += sizeof(struct vmw_stdu_update); |
1181 | |
1182 | return size; |
1183 | } |
1184 | |
1185 | static uint32_t vmw_stdu_surface_fifo_size(struct vmw_du_update_plane *update, |
1186 | uint32_t num_hits) |
1187 | { |
1188 | struct vmw_framebuffer_surface *vfbs; |
1189 | uint32_t size = 0; |
1190 | |
1191 | vfbs = container_of(update->vfb, typeof(*vfbs), base); |
1192 | |
1193 | if (vfbs->is_bo_proxy) |
1194 | size += sizeof(struct vmw_stdu_update_gb_image) * num_hits; |
1195 | |
1196 | size += sizeof(struct vmw_stdu_surface_copy) + sizeof(SVGA3dCopyBox) * |
1197 | num_hits + sizeof(struct vmw_stdu_update); |
1198 | |
1199 | return size; |
1200 | } |
1201 | |
1202 | static uint32_t |
1203 | vmw_stdu_surface_update_proxy(struct vmw_du_update_plane *update, void *cmd) |
1204 | { |
1205 | struct vmw_framebuffer_surface *vfbs; |
1206 | struct drm_plane_state *state = update->plane->state; |
1207 | struct drm_plane_state *old_state = update->old_state; |
1208 | struct vmw_stdu_update_gb_image *cmd_update = cmd; |
1209 | struct drm_atomic_helper_damage_iter iter; |
1210 | struct drm_rect clip; |
1211 | uint32_t copy_size = 0; |
1212 | |
1213 | vfbs = container_of(update->vfb, typeof(*vfbs), base); |
1214 | |
1215 | /* |
1216 | * proxy surface is special where a buffer object type fb is wrapped |
1217 | * in a surface and need an update gb image command to sync with device. |
1218 | */ |
1219 | drm_atomic_helper_damage_iter_init(iter: &iter, old_state, new_state: state); |
1220 | drm_atomic_for_each_plane_damage(&iter, &clip) { |
1221 | SVGA3dBox *box = &cmd_update->body.box; |
1222 | |
1223 | cmd_update->header.id = SVGA_3D_CMD_UPDATE_GB_IMAGE; |
1224 | cmd_update->header.size = sizeof(cmd_update->body); |
1225 | cmd_update->body.image.sid = vfbs->surface->res.id; |
1226 | cmd_update->body.image.face = 0; |
1227 | cmd_update->body.image.mipmap = 0; |
1228 | |
1229 | box->x = clip.x1; |
1230 | box->y = clip.y1; |
1231 | box->z = 0; |
1232 | box->w = drm_rect_width(r: &clip); |
1233 | box->h = drm_rect_height(r: &clip); |
1234 | box->d = 1; |
1235 | |
1236 | copy_size += sizeof(*cmd_update); |
1237 | cmd_update++; |
1238 | } |
1239 | |
1240 | return copy_size; |
1241 | } |
1242 | |
1243 | static uint32_t |
1244 | vmw_stdu_surface_populate_copy(struct vmw_du_update_plane *update, void *cmd, |
1245 | uint32_t num_hits) |
1246 | { |
1247 | struct vmw_screen_target_display_unit *stdu; |
1248 | struct vmw_framebuffer_surface *vfbs; |
1249 | struct vmw_stdu_surface_copy *cmd_copy = cmd; |
1250 | |
1251 | stdu = container_of(update->du, typeof(*stdu), base); |
1252 | vfbs = container_of(update->vfb, typeof(*vfbs), base); |
1253 | |
1254 | cmd_copy->header.id = SVGA_3D_CMD_SURFACE_COPY; |
1255 | cmd_copy->header.size = sizeof(cmd_copy->body) + sizeof(SVGA3dCopyBox) * |
1256 | num_hits; |
1257 | cmd_copy->body.src.sid = vfbs->surface->res.id; |
1258 | cmd_copy->body.dest.sid = stdu->display_srf->res.id; |
1259 | |
1260 | return sizeof(*cmd_copy); |
1261 | } |
1262 | |
1263 | static uint32_t |
1264 | vmw_stdu_surface_populate_clip(struct vmw_du_update_plane *update, void *cmd, |
1265 | struct drm_rect *clip, uint32_t fb_x, |
1266 | uint32_t fb_y) |
1267 | { |
1268 | struct SVGA3dCopyBox *box = cmd; |
1269 | |
1270 | box->srcx = fb_x; |
1271 | box->srcy = fb_y; |
1272 | box->srcz = 0; |
1273 | box->x = clip->x1; |
1274 | box->y = clip->y1; |
1275 | box->z = 0; |
1276 | box->w = drm_rect_width(r: clip); |
1277 | box->h = drm_rect_height(r: clip); |
1278 | box->d = 1; |
1279 | |
1280 | return sizeof(*box); |
1281 | } |
1282 | |
1283 | static uint32_t |
1284 | vmw_stdu_surface_populate_update(struct vmw_du_update_plane *update, void *cmd, |
1285 | struct drm_rect *bb) |
1286 | { |
1287 | vmw_stdu_populate_update(cmd, unit: update->du->unit, left: bb->x1, right: bb->x2, top: bb->y1, |
1288 | bottom: bb->y2); |
1289 | |
1290 | return sizeof(struct vmw_stdu_update); |
1291 | } |
1292 | |
1293 | /** |
1294 | * vmw_stdu_plane_update_surface - Update display unit for surface backed fb |
1295 | * @dev_priv: Device private |
1296 | * @plane: Plane state |
1297 | * @old_state: Old plane state |
1298 | * @vfb: Framebuffer which is blitted to display unit |
1299 | * @out_fence: If non-NULL, will return a ref-counted pointer to vmw_fence_obj. |
1300 | * The returned fence pointer may be NULL in which case the device |
1301 | * has already synchronized. |
1302 | * |
1303 | * Return: 0 on success or a negative error code on failure. |
1304 | */ |
1305 | static int vmw_stdu_plane_update_surface(struct vmw_private *dev_priv, |
1306 | struct drm_plane *plane, |
1307 | struct drm_plane_state *old_state, |
1308 | struct vmw_framebuffer *vfb, |
1309 | struct vmw_fence_obj **out_fence) |
1310 | { |
1311 | struct vmw_du_update_plane srf_update; |
1312 | struct vmw_screen_target_display_unit *stdu; |
1313 | struct vmw_framebuffer_surface *vfbs; |
1314 | |
1315 | stdu = vmw_crtc_to_stdu(plane->state->crtc); |
1316 | vfbs = container_of(vfb, typeof(*vfbs), base); |
1317 | |
1318 | memset(&srf_update, 0, sizeof(struct vmw_du_update_plane)); |
1319 | srf_update.plane = plane; |
1320 | srf_update.old_state = old_state; |
1321 | srf_update.dev_priv = dev_priv; |
1322 | srf_update.du = vmw_crtc_to_du(plane->state->crtc); |
1323 | srf_update.vfb = vfb; |
1324 | srf_update.out_fence = out_fence; |
1325 | srf_update.mutex = &dev_priv->cmdbuf_mutex; |
1326 | srf_update.intr = true; |
1327 | |
1328 | if (vfbs->is_bo_proxy) |
1329 | srf_update.post_prepare = vmw_stdu_surface_update_proxy; |
1330 | |
1331 | if (vfbs->surface->res.id != stdu->display_srf->res.id) { |
1332 | srf_update.calc_fifo_size = vmw_stdu_surface_fifo_size; |
1333 | srf_update.pre_clip = vmw_stdu_surface_populate_copy; |
1334 | srf_update.clip = vmw_stdu_surface_populate_clip; |
1335 | } else { |
1336 | srf_update.calc_fifo_size = |
1337 | vmw_stdu_surface_fifo_size_same_display; |
1338 | } |
1339 | |
1340 | srf_update.post_clip = vmw_stdu_surface_populate_update; |
1341 | |
1342 | return vmw_du_helper_plane_update(update: &srf_update); |
1343 | } |
1344 | |
1345 | /** |
1346 | * vmw_stdu_primary_plane_atomic_update - formally switches STDU to new plane |
1347 | * @plane: display plane |
1348 | * @state: Only used to get crtc info |
1349 | * |
1350 | * Formally update stdu->display_srf to the new plane, and bind the new |
1351 | * plane STDU. This function is called during the commit phase when |
1352 | * all the preparation have been done and all the configurations have |
1353 | * been checked. |
1354 | */ |
1355 | static void |
1356 | vmw_stdu_primary_plane_atomic_update(struct drm_plane *plane, |
1357 | struct drm_atomic_state *state) |
1358 | { |
1359 | struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, plane); |
1360 | struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, plane); |
1361 | struct vmw_plane_state *vps = vmw_plane_state_to_vps(new_state); |
1362 | struct drm_crtc *crtc = new_state->crtc; |
1363 | struct vmw_screen_target_display_unit *stdu; |
1364 | struct vmw_fence_obj *fence = NULL; |
1365 | struct vmw_private *dev_priv; |
1366 | int ret; |
1367 | |
1368 | /* If case of device error, maintain consistent atomic state */ |
1369 | if (crtc && new_state->fb) { |
1370 | struct vmw_framebuffer *vfb = |
1371 | vmw_framebuffer_to_vfb(new_state->fb); |
1372 | stdu = vmw_crtc_to_stdu(crtc); |
1373 | dev_priv = vmw_priv(dev: crtc->dev); |
1374 | |
1375 | stdu->display_srf = vps->surf; |
1376 | stdu->content_fb_type = vps->content_fb_type; |
1377 | stdu->cpp = vps->cpp; |
1378 | |
1379 | ret = vmw_stdu_bind_st(dev_priv, stdu, res: &stdu->display_srf->res); |
1380 | if (ret) |
1381 | DRM_ERROR("Failed to bind surface to STDU.\n" ); |
1382 | |
1383 | if (vfb->bo) |
1384 | ret = vmw_stdu_plane_update_bo(dev_priv, plane, |
1385 | old_state, vfb, out_fence: &fence); |
1386 | else |
1387 | ret = vmw_stdu_plane_update_surface(dev_priv, plane, |
1388 | old_state, vfb, |
1389 | out_fence: &fence); |
1390 | if (ret) |
1391 | DRM_ERROR("Failed to update STDU.\n" ); |
1392 | } else { |
1393 | crtc = old_state->crtc; |
1394 | stdu = vmw_crtc_to_stdu(crtc); |
1395 | dev_priv = vmw_priv(dev: crtc->dev); |
1396 | |
1397 | /* Blank STDU when fb and crtc are NULL */ |
1398 | if (!stdu->defined) |
1399 | return; |
1400 | |
1401 | ret = vmw_stdu_bind_st(dev_priv, stdu, NULL); |
1402 | if (ret) |
1403 | DRM_ERROR("Failed to blank STDU\n" ); |
1404 | |
1405 | ret = vmw_stdu_update_st(dev_priv, stdu); |
1406 | if (ret) |
1407 | DRM_ERROR("Failed to update STDU.\n" ); |
1408 | |
1409 | return; |
1410 | } |
1411 | |
1412 | if (fence) |
1413 | vmw_fence_obj_unreference(fence_p: &fence); |
1414 | } |
1415 | |
1416 | |
1417 | static const struct drm_plane_funcs vmw_stdu_plane_funcs = { |
1418 | .update_plane = drm_atomic_helper_update_plane, |
1419 | .disable_plane = drm_atomic_helper_disable_plane, |
1420 | .destroy = vmw_du_primary_plane_destroy, |
1421 | .reset = vmw_du_plane_reset, |
1422 | .atomic_duplicate_state = vmw_du_plane_duplicate_state, |
1423 | .atomic_destroy_state = vmw_du_plane_destroy_state, |
1424 | }; |
1425 | |
1426 | static const struct drm_plane_funcs vmw_stdu_cursor_funcs = { |
1427 | .update_plane = drm_atomic_helper_update_plane, |
1428 | .disable_plane = drm_atomic_helper_disable_plane, |
1429 | .destroy = vmw_du_cursor_plane_destroy, |
1430 | .reset = vmw_du_plane_reset, |
1431 | .atomic_duplicate_state = vmw_du_plane_duplicate_state, |
1432 | .atomic_destroy_state = vmw_du_plane_destroy_state, |
1433 | }; |
1434 | |
1435 | |
1436 | /* |
1437 | * Atomic Helpers |
1438 | */ |
1439 | static const struct |
1440 | drm_plane_helper_funcs vmw_stdu_cursor_plane_helper_funcs = { |
1441 | .atomic_check = vmw_du_cursor_plane_atomic_check, |
1442 | .atomic_update = vmw_du_cursor_plane_atomic_update, |
1443 | .prepare_fb = vmw_du_cursor_plane_prepare_fb, |
1444 | .cleanup_fb = vmw_du_cursor_plane_cleanup_fb, |
1445 | }; |
1446 | |
1447 | static const struct |
1448 | drm_plane_helper_funcs vmw_stdu_primary_plane_helper_funcs = { |
1449 | .atomic_check = vmw_du_primary_plane_atomic_check, |
1450 | .atomic_update = vmw_stdu_primary_plane_atomic_update, |
1451 | .prepare_fb = vmw_stdu_primary_plane_prepare_fb, |
1452 | .cleanup_fb = vmw_stdu_primary_plane_cleanup_fb, |
1453 | }; |
1454 | |
1455 | static const struct drm_crtc_helper_funcs vmw_stdu_crtc_helper_funcs = { |
1456 | .prepare = vmw_stdu_crtc_helper_prepare, |
1457 | .mode_set_nofb = vmw_stdu_crtc_mode_set_nofb, |
1458 | .atomic_check = vmw_du_crtc_atomic_check, |
1459 | .atomic_begin = vmw_du_crtc_atomic_begin, |
1460 | .atomic_flush = vmw_du_crtc_atomic_flush, |
1461 | .atomic_enable = vmw_stdu_crtc_atomic_enable, |
1462 | .atomic_disable = vmw_stdu_crtc_atomic_disable, |
1463 | }; |
1464 | |
1465 | |
1466 | /** |
1467 | * vmw_stdu_init - Sets up a Screen Target Display Unit |
1468 | * |
1469 | * @dev_priv: VMW DRM device |
1470 | * @unit: unit number range from 0 to VMWGFX_NUM_DISPLAY_UNITS |
1471 | * |
1472 | * This function is called once per CRTC, and allocates one Screen Target |
1473 | * display unit to represent that CRTC. Since the SVGA device does not separate |
1474 | * out encoder and connector, they are represented as part of the STDU as well. |
1475 | * |
1476 | * Returns: %0 on success or -errno code on failure |
1477 | */ |
1478 | static int vmw_stdu_init(struct vmw_private *dev_priv, unsigned unit) |
1479 | { |
1480 | struct vmw_screen_target_display_unit *stdu; |
1481 | struct drm_device *dev = &dev_priv->drm; |
1482 | struct drm_connector *connector; |
1483 | struct drm_encoder *encoder; |
1484 | struct drm_plane *primary; |
1485 | struct vmw_cursor_plane *cursor; |
1486 | struct drm_crtc *crtc; |
1487 | int ret; |
1488 | |
1489 | stdu = kzalloc(size: sizeof(*stdu), GFP_KERNEL); |
1490 | if (!stdu) |
1491 | return -ENOMEM; |
1492 | |
1493 | stdu->base.unit = unit; |
1494 | crtc = &stdu->base.crtc; |
1495 | encoder = &stdu->base.encoder; |
1496 | connector = &stdu->base.connector; |
1497 | primary = &stdu->base.primary; |
1498 | cursor = &stdu->base.cursor; |
1499 | |
1500 | stdu->base.pref_active = (unit == 0); |
1501 | stdu->base.pref_width = dev_priv->initial_width; |
1502 | stdu->base.pref_height = dev_priv->initial_height; |
1503 | stdu->base.is_implicit = false; |
1504 | |
1505 | /* Initialize primary plane */ |
1506 | ret = drm_universal_plane_init(dev, plane: primary, |
1507 | possible_crtcs: 0, funcs: &vmw_stdu_plane_funcs, |
1508 | formats: vmw_primary_plane_formats, |
1509 | ARRAY_SIZE(vmw_primary_plane_formats), |
1510 | NULL, type: DRM_PLANE_TYPE_PRIMARY, NULL); |
1511 | if (ret) { |
1512 | DRM_ERROR("Failed to initialize primary plane" ); |
1513 | goto err_free; |
1514 | } |
1515 | |
1516 | drm_plane_helper_add(plane: primary, funcs: &vmw_stdu_primary_plane_helper_funcs); |
1517 | drm_plane_enable_fb_damage_clips(plane: primary); |
1518 | |
1519 | /* Initialize cursor plane */ |
1520 | ret = drm_universal_plane_init(dev, plane: &cursor->base, |
1521 | possible_crtcs: 0, funcs: &vmw_stdu_cursor_funcs, |
1522 | formats: vmw_cursor_plane_formats, |
1523 | ARRAY_SIZE(vmw_cursor_plane_formats), |
1524 | NULL, type: DRM_PLANE_TYPE_CURSOR, NULL); |
1525 | if (ret) { |
1526 | DRM_ERROR("Failed to initialize cursor plane" ); |
1527 | drm_plane_cleanup(plane: &stdu->base.primary); |
1528 | goto err_free; |
1529 | } |
1530 | |
1531 | drm_plane_helper_add(plane: &cursor->base, funcs: &vmw_stdu_cursor_plane_helper_funcs); |
1532 | |
1533 | ret = drm_connector_init(dev, connector, funcs: &vmw_stdu_connector_funcs, |
1534 | DRM_MODE_CONNECTOR_VIRTUAL); |
1535 | if (ret) { |
1536 | DRM_ERROR("Failed to initialize connector\n" ); |
1537 | goto err_free; |
1538 | } |
1539 | |
1540 | drm_connector_helper_add(connector, funcs: &vmw_stdu_connector_helper_funcs); |
1541 | connector->status = vmw_du_connector_detect(connector, force: false); |
1542 | |
1543 | ret = drm_encoder_init(dev, encoder, funcs: &vmw_stdu_encoder_funcs, |
1544 | DRM_MODE_ENCODER_VIRTUAL, NULL); |
1545 | if (ret) { |
1546 | DRM_ERROR("Failed to initialize encoder\n" ); |
1547 | goto err_free_connector; |
1548 | } |
1549 | |
1550 | (void) drm_connector_attach_encoder(connector, encoder); |
1551 | encoder->possible_crtcs = (1 << unit); |
1552 | encoder->possible_clones = 0; |
1553 | |
1554 | ret = drm_connector_register(connector); |
1555 | if (ret) { |
1556 | DRM_ERROR("Failed to register connector\n" ); |
1557 | goto err_free_encoder; |
1558 | } |
1559 | |
1560 | ret = drm_crtc_init_with_planes(dev, crtc, primary, |
1561 | cursor: &cursor->base, |
1562 | funcs: &vmw_stdu_crtc_funcs, NULL); |
1563 | if (ret) { |
1564 | DRM_ERROR("Failed to initialize CRTC\n" ); |
1565 | goto err_free_unregister; |
1566 | } |
1567 | |
1568 | drm_crtc_helper_add(crtc, funcs: &vmw_stdu_crtc_helper_funcs); |
1569 | |
1570 | drm_mode_crtc_set_gamma_size(crtc, gamma_size: 256); |
1571 | |
1572 | drm_object_attach_property(obj: &connector->base, |
1573 | property: dev_priv->hotplug_mode_update_property, init_val: 1); |
1574 | drm_object_attach_property(obj: &connector->base, |
1575 | property: dev->mode_config.suggested_x_property, init_val: 0); |
1576 | drm_object_attach_property(obj: &connector->base, |
1577 | property: dev->mode_config.suggested_y_property, init_val: 0); |
1578 | return 0; |
1579 | |
1580 | err_free_unregister: |
1581 | drm_connector_unregister(connector); |
1582 | err_free_encoder: |
1583 | drm_encoder_cleanup(encoder); |
1584 | err_free_connector: |
1585 | drm_connector_cleanup(connector); |
1586 | err_free: |
1587 | kfree(objp: stdu); |
1588 | return ret; |
1589 | } |
1590 | |
1591 | |
1592 | |
1593 | /** |
1594 | * vmw_stdu_destroy - Cleans up a vmw_screen_target_display_unit |
1595 | * |
1596 | * @stdu: Screen Target Display Unit to be destroyed |
1597 | * |
1598 | * Clean up after vmw_stdu_init |
1599 | */ |
1600 | static void vmw_stdu_destroy(struct vmw_screen_target_display_unit *stdu) |
1601 | { |
1602 | vmw_du_cleanup(du: &stdu->base); |
1603 | kfree(objp: stdu); |
1604 | } |
1605 | |
1606 | |
1607 | |
1608 | /****************************************************************************** |
1609 | * Screen Target Display KMS Functions |
1610 | * |
1611 | * These functions are called by the common KMS code in vmwgfx_kms.c |
1612 | *****************************************************************************/ |
1613 | |
1614 | /** |
1615 | * vmw_kms_stdu_init_display - Initializes a Screen Target based display |
1616 | * |
1617 | * @dev_priv: VMW DRM device |
1618 | * |
1619 | * This function initialize a Screen Target based display device. It checks |
1620 | * the capability bits to make sure the underlying hardware can support |
1621 | * screen targets, and then creates the maximum number of CRTCs, a.k.a Display |
1622 | * Units, as supported by the display hardware. |
1623 | * |
1624 | * RETURNS: |
1625 | * 0 on success, error code otherwise |
1626 | */ |
1627 | int vmw_kms_stdu_init_display(struct vmw_private *dev_priv) |
1628 | { |
1629 | struct drm_device *dev = &dev_priv->drm; |
1630 | int i, ret; |
1631 | |
1632 | |
1633 | /* Do nothing if there's no support for MOBs */ |
1634 | if (!dev_priv->has_mob) |
1635 | return -ENOSYS; |
1636 | |
1637 | if (!(dev_priv->capabilities & SVGA_CAP_GBOBJECTS)) |
1638 | return -ENOSYS; |
1639 | |
1640 | dev_priv->active_display_unit = vmw_du_screen_target; |
1641 | |
1642 | for (i = 0; i < VMWGFX_NUM_DISPLAY_UNITS; ++i) { |
1643 | ret = vmw_stdu_init(dev_priv, unit: i); |
1644 | |
1645 | if (unlikely(ret != 0)) { |
1646 | drm_err(&dev_priv->drm, |
1647 | "Failed to initialize STDU %d" , i); |
1648 | return ret; |
1649 | } |
1650 | } |
1651 | |
1652 | drm_mode_config_reset(dev); |
1653 | |
1654 | return 0; |
1655 | } |
1656 | |