vmwgfx_ldu.c revision 1.3
1/*	$NetBSD: vmwgfx_ldu.c,v 1.3 2021/12/18 23:45:45 riastradh Exp $	*/
2
3// SPDX-License-Identifier: GPL-2.0 OR MIT
4/**************************************************************************
5 *
6 * Copyright 2009-2015 VMware, Inc., Palo Alto, CA., USA
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the
10 * "Software"), to deal in the Software without restriction, including
11 * without limitation the rights to use, copy, modify, merge, publish,
12 * distribute, sub license, and/or sell copies of the Software, and to
13 * permit persons to whom the Software is furnished to do so, subject to
14 * the following conditions:
15 *
16 * The above copyright notice and this permission notice (including the
17 * next paragraph) shall be included in all copies or substantial portions
18 * of the Software.
19 *
20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
23 * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
24 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
25 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
26 * USE OR OTHER DEALINGS IN THE SOFTWARE.
27 *
28 **************************************************************************/
29
30#include <sys/cdefs.h>
31__KERNEL_RCSID(0, "$NetBSD: vmwgfx_ldu.c,v 1.3 2021/12/18 23:45:45 riastradh Exp $");
32
33#include <drm/drm_atomic.h>
34#include <drm/drm_atomic_helper.h>
35#include <drm/drm_fourcc.h>
36#include <drm/drm_plane_helper.h>
37#include <drm/drm_vblank.h>
38
39#include "vmwgfx_kms.h"
40
41#define vmw_crtc_to_ldu(x) \
42	container_of(x, struct vmw_legacy_display_unit, base.crtc)
43#define vmw_encoder_to_ldu(x) \
44	container_of(x, struct vmw_legacy_display_unit, base.encoder)
45#define vmw_connector_to_ldu(x) \
46	container_of(x, struct vmw_legacy_display_unit, base.connector)
47
48struct vmw_legacy_display {
49	struct list_head active;
50
51	unsigned num_active;
52	unsigned last_num_active;
53
54	struct vmw_framebuffer *fb;
55};
56
57/**
58 * Display unit using the legacy register interface.
59 */
60struct vmw_legacy_display_unit {
61	struct vmw_display_unit base;
62
63	struct list_head active;
64};
65
66static void vmw_ldu_destroy(struct vmw_legacy_display_unit *ldu)
67{
68	list_del_init(&ldu->active);
69	vmw_du_cleanup(&ldu->base);
70	kfree(ldu);
71}
72
73
74/*
75 * Legacy Display Unit CRTC functions
76 */
77
78static void vmw_ldu_crtc_destroy(struct drm_crtc *crtc)
79{
80	vmw_ldu_destroy(vmw_crtc_to_ldu(crtc));
81}
82
83static int vmw_ldu_commit_list(struct vmw_private *dev_priv)
84{
85	struct vmw_legacy_display *lds = dev_priv->ldu_priv;
86	struct vmw_legacy_display_unit *entry;
87	struct drm_framebuffer *fb = NULL;
88	struct drm_crtc *crtc = NULL;
89	int i = 0;
90
91	/* If there is no display topology the host just assumes
92	 * that the guest will set the same layout as the host.
93	 */
94	if (!(dev_priv->capabilities & SVGA_CAP_DISPLAY_TOPOLOGY)) {
95		int w = 0, h = 0;
96		list_for_each_entry(entry, &lds->active, active) {
97			crtc = &entry->base.crtc;
98			w = max(w, crtc->x + crtc->mode.hdisplay);
99			h = max(h, crtc->y + crtc->mode.vdisplay);
100			i++;
101		}
102
103		if (crtc == NULL)
104			return 0;
105		fb = entry->base.crtc.primary->state->fb;
106
107		return vmw_kms_write_svga(dev_priv, w, h, fb->pitches[0],
108					  fb->format->cpp[0] * 8,
109					  fb->format->depth);
110	}
111
112	if (!list_empty(&lds->active)) {
113		entry = list_entry(lds->active.next, typeof(*entry), active);
114		fb = entry->base.crtc.primary->state->fb;
115
116		vmw_kms_write_svga(dev_priv, fb->width, fb->height, fb->pitches[0],
117				   fb->format->cpp[0] * 8, fb->format->depth);
118	}
119
120	/* Make sure we always show something. */
121	vmw_write(dev_priv, SVGA_REG_NUM_GUEST_DISPLAYS,
122		  lds->num_active ? lds->num_active : 1);
123
124	i = 0;
125	list_for_each_entry(entry, &lds->active, active) {
126		crtc = &entry->base.crtc;
127
128		vmw_write(dev_priv, SVGA_REG_DISPLAY_ID, i);
129		vmw_write(dev_priv, SVGA_REG_DISPLAY_IS_PRIMARY, !i);
130		vmw_write(dev_priv, SVGA_REG_DISPLAY_POSITION_X, crtc->x);
131		vmw_write(dev_priv, SVGA_REG_DISPLAY_POSITION_Y, crtc->y);
132		vmw_write(dev_priv, SVGA_REG_DISPLAY_WIDTH, crtc->mode.hdisplay);
133		vmw_write(dev_priv, SVGA_REG_DISPLAY_HEIGHT, crtc->mode.vdisplay);
134		vmw_write(dev_priv, SVGA_REG_DISPLAY_ID, SVGA_ID_INVALID);
135
136		i++;
137	}
138
139	BUG_ON(i != lds->num_active);
140
141	lds->last_num_active = lds->num_active;
142
143	return 0;
144}
145
146static int vmw_ldu_del_active(struct vmw_private *vmw_priv,
147			      struct vmw_legacy_display_unit *ldu)
148{
149	struct vmw_legacy_display *ld = vmw_priv->ldu_priv;
150	if (list_empty(&ldu->active))
151		return 0;
152
153	/* Must init otherwise list_empty(&ldu->active) will not work. */
154	list_del_init(&ldu->active);
155	if (--(ld->num_active) == 0) {
156		BUG_ON(!ld->fb);
157		if (ld->fb->unpin)
158			ld->fb->unpin(ld->fb);
159		ld->fb = NULL;
160	}
161
162	return 0;
163}
164
165static int vmw_ldu_add_active(struct vmw_private *vmw_priv,
166			      struct vmw_legacy_display_unit *ldu,
167			      struct vmw_framebuffer *vfb)
168{
169	struct vmw_legacy_display *ld = vmw_priv->ldu_priv;
170	struct vmw_legacy_display_unit *entry;
171	struct list_head *at;
172
173	BUG_ON(!ld->num_active && ld->fb);
174	if (vfb != ld->fb) {
175		if (ld->fb && ld->fb->unpin)
176			ld->fb->unpin(ld->fb);
177		vmw_svga_enable(vmw_priv);
178		if (vfb->pin)
179			vfb->pin(vfb);
180		ld->fb = vfb;
181	}
182
183	if (!list_empty(&ldu->active))
184		return 0;
185
186	at = &ld->active;
187	list_for_each_entry(entry, &ld->active, active) {
188		if (entry->base.unit > ldu->base.unit)
189			break;
190
191		at = &entry->active;
192	}
193
194	list_add(&ldu->active, at);
195
196	ld->num_active++;
197
198	return 0;
199}
200
201/**
202 * vmw_ldu_crtc_mode_set_nofb - Enable svga
203 *
204 * @crtc: CRTC associated with the new screen
205 *
206 * For LDU, just enable the svga
207 */
208static void vmw_ldu_crtc_mode_set_nofb(struct drm_crtc *crtc)
209{
210}
211
212/**
213 * vmw_ldu_crtc_atomic_enable - Noop
214 *
215 * @crtc: CRTC associated with the new screen
216 *
217 * This is called after a mode set has been completed.  Here's
218 * usually a good place to call vmw_ldu_add_active/vmw_ldu_del_active
219 * but since for LDU the display plane is closely tied to the
220 * CRTC, it makes more sense to do those at plane update time.
221 */
222static void vmw_ldu_crtc_atomic_enable(struct drm_crtc *crtc,
223				       struct drm_crtc_state *old_state)
224{
225}
226
227/**
228 * vmw_ldu_crtc_atomic_disable - Turns off CRTC
229 *
230 * @crtc: CRTC to be turned off
231 */
232static void vmw_ldu_crtc_atomic_disable(struct drm_crtc *crtc,
233					struct drm_crtc_state *old_state)
234{
235}
236
237static const struct drm_crtc_funcs vmw_legacy_crtc_funcs = {
238	.gamma_set = vmw_du_crtc_gamma_set,
239	.destroy = vmw_ldu_crtc_destroy,
240	.reset = vmw_du_crtc_reset,
241	.atomic_duplicate_state = vmw_du_crtc_duplicate_state,
242	.atomic_destroy_state = vmw_du_crtc_destroy_state,
243	.set_config = drm_atomic_helper_set_config,
244};
245
246
247/*
248 * Legacy Display Unit encoder functions
249 */
250
251static void vmw_ldu_encoder_destroy(struct drm_encoder *encoder)
252{
253	vmw_ldu_destroy(vmw_encoder_to_ldu(encoder));
254}
255
256static const struct drm_encoder_funcs vmw_legacy_encoder_funcs = {
257	.destroy = vmw_ldu_encoder_destroy,
258};
259
260/*
261 * Legacy Display Unit connector functions
262 */
263
264static void vmw_ldu_connector_destroy(struct drm_connector *connector)
265{
266	vmw_ldu_destroy(vmw_connector_to_ldu(connector));
267}
268
269static const struct drm_connector_funcs vmw_legacy_connector_funcs = {
270	.dpms = vmw_du_connector_dpms,
271	.detect = vmw_du_connector_detect,
272	.fill_modes = vmw_du_connector_fill_modes,
273	.destroy = vmw_ldu_connector_destroy,
274	.reset = vmw_du_connector_reset,
275	.atomic_duplicate_state = vmw_du_connector_duplicate_state,
276	.atomic_destroy_state = vmw_du_connector_destroy_state,
277};
278
279static const struct
280drm_connector_helper_funcs vmw_ldu_connector_helper_funcs = {
281};
282
283/*
284 * Legacy Display Plane Functions
285 */
286
287static void
288vmw_ldu_primary_plane_atomic_update(struct drm_plane *plane,
289				    struct drm_plane_state *old_state)
290{
291	struct vmw_private *dev_priv;
292	struct vmw_legacy_display_unit *ldu;
293	struct vmw_framebuffer *vfb;
294	struct drm_framebuffer *fb;
295	struct drm_crtc *crtc = plane->state->crtc ?: old_state->crtc;
296
297
298	ldu = vmw_crtc_to_ldu(crtc);
299	dev_priv = vmw_priv(plane->dev);
300	fb       = plane->state->fb;
301
302	vfb = (fb) ? vmw_framebuffer_to_vfb(fb) : NULL;
303
304	if (vfb)
305		vmw_ldu_add_active(dev_priv, ldu, vfb);
306	else
307		vmw_ldu_del_active(dev_priv, ldu);
308
309	vmw_ldu_commit_list(dev_priv);
310}
311
312
313static const struct drm_plane_funcs vmw_ldu_plane_funcs = {
314	.update_plane = drm_atomic_helper_update_plane,
315	.disable_plane = drm_atomic_helper_disable_plane,
316	.destroy = vmw_du_primary_plane_destroy,
317	.reset = vmw_du_plane_reset,
318	.atomic_duplicate_state = vmw_du_plane_duplicate_state,
319	.atomic_destroy_state = vmw_du_plane_destroy_state,
320};
321
322static const struct drm_plane_funcs vmw_ldu_cursor_funcs = {
323	.update_plane = drm_atomic_helper_update_plane,
324	.disable_plane = drm_atomic_helper_disable_plane,
325	.destroy = vmw_du_cursor_plane_destroy,
326	.reset = vmw_du_plane_reset,
327	.atomic_duplicate_state = vmw_du_plane_duplicate_state,
328	.atomic_destroy_state = vmw_du_plane_destroy_state,
329};
330
331/*
332 * Atomic Helpers
333 */
334static const struct
335drm_plane_helper_funcs vmw_ldu_cursor_plane_helper_funcs = {
336	.atomic_check = vmw_du_cursor_plane_atomic_check,
337	.atomic_update = vmw_du_cursor_plane_atomic_update,
338	.prepare_fb = vmw_du_cursor_plane_prepare_fb,
339	.cleanup_fb = vmw_du_plane_cleanup_fb,
340};
341
342static const struct
343drm_plane_helper_funcs vmw_ldu_primary_plane_helper_funcs = {
344	.atomic_check = vmw_du_primary_plane_atomic_check,
345	.atomic_update = vmw_ldu_primary_plane_atomic_update,
346};
347
348static const struct drm_crtc_helper_funcs vmw_ldu_crtc_helper_funcs = {
349	.mode_set_nofb = vmw_ldu_crtc_mode_set_nofb,
350	.atomic_check = vmw_du_crtc_atomic_check,
351	.atomic_begin = vmw_du_crtc_atomic_begin,
352	.atomic_flush = vmw_du_crtc_atomic_flush,
353	.atomic_enable = vmw_ldu_crtc_atomic_enable,
354	.atomic_disable = vmw_ldu_crtc_atomic_disable,
355};
356
357
358static int vmw_ldu_init(struct vmw_private *dev_priv, unsigned unit)
359{
360	struct vmw_legacy_display_unit *ldu;
361	struct drm_device *dev = dev_priv->dev;
362	struct drm_connector *connector;
363	struct drm_encoder *encoder;
364	struct drm_plane *primary, *cursor;
365	struct drm_crtc *crtc;
366	int ret;
367
368	ldu = kzalloc(sizeof(*ldu), GFP_KERNEL);
369	if (!ldu)
370		return -ENOMEM;
371
372	ldu->base.unit = unit;
373	crtc = &ldu->base.crtc;
374	encoder = &ldu->base.encoder;
375	connector = &ldu->base.connector;
376	primary = &ldu->base.primary;
377	cursor = &ldu->base.cursor;
378
379	INIT_LIST_HEAD(&ldu->active);
380
381	ldu->base.pref_active = (unit == 0);
382	ldu->base.pref_width = dev_priv->initial_width;
383	ldu->base.pref_height = dev_priv->initial_height;
384	ldu->base.pref_mode = NULL;
385
386	/*
387	 * Remove this after enabling atomic because property values can
388	 * only exist in a state object
389	 */
390	ldu->base.is_implicit = true;
391
392	/* Initialize primary plane */
393	vmw_du_plane_reset(primary);
394
395	ret = drm_universal_plane_init(dev, &ldu->base.primary,
396				       0, &vmw_ldu_plane_funcs,
397				       vmw_primary_plane_formats,
398				       ARRAY_SIZE(vmw_primary_plane_formats),
399				       NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
400	if (ret) {
401		DRM_ERROR("Failed to initialize primary plane");
402		goto err_free;
403	}
404
405	drm_plane_helper_add(primary, &vmw_ldu_primary_plane_helper_funcs);
406
407	/* Initialize cursor plane */
408	vmw_du_plane_reset(cursor);
409
410	ret = drm_universal_plane_init(dev, &ldu->base.cursor,
411			0, &vmw_ldu_cursor_funcs,
412			vmw_cursor_plane_formats,
413			ARRAY_SIZE(vmw_cursor_plane_formats),
414			NULL, DRM_PLANE_TYPE_CURSOR, NULL);
415	if (ret) {
416		DRM_ERROR("Failed to initialize cursor plane");
417		drm_plane_cleanup(&ldu->base.primary);
418		goto err_free;
419	}
420
421	drm_plane_helper_add(cursor, &vmw_ldu_cursor_plane_helper_funcs);
422
423	vmw_du_connector_reset(connector);
424	ret = drm_connector_init(dev, connector, &vmw_legacy_connector_funcs,
425				 DRM_MODE_CONNECTOR_VIRTUAL);
426	if (ret) {
427		DRM_ERROR("Failed to initialize connector\n");
428		goto err_free;
429	}
430
431	drm_connector_helper_add(connector, &vmw_ldu_connector_helper_funcs);
432	connector->status = vmw_du_connector_detect(connector, true);
433
434	ret = drm_encoder_init(dev, encoder, &vmw_legacy_encoder_funcs,
435			       DRM_MODE_ENCODER_VIRTUAL, NULL);
436	if (ret) {
437		DRM_ERROR("Failed to initialize encoder\n");
438		goto err_free_connector;
439	}
440
441	(void) drm_connector_attach_encoder(connector, encoder);
442	encoder->possible_crtcs = (1 << unit);
443	encoder->possible_clones = 0;
444
445	ret = drm_connector_register(connector);
446	if (ret) {
447		DRM_ERROR("Failed to register connector\n");
448		goto err_free_encoder;
449	}
450
451	vmw_du_crtc_reset(crtc);
452	ret = drm_crtc_init_with_planes(dev, crtc, &ldu->base.primary,
453					&ldu->base.cursor,
454					&vmw_legacy_crtc_funcs, NULL);
455	if (ret) {
456		DRM_ERROR("Failed to initialize CRTC\n");
457		goto err_free_unregister;
458	}
459
460	drm_crtc_helper_add(crtc, &vmw_ldu_crtc_helper_funcs);
461
462	drm_mode_crtc_set_gamma_size(crtc, 256);
463
464	drm_object_attach_property(&connector->base,
465				   dev_priv->hotplug_mode_update_property, 1);
466	drm_object_attach_property(&connector->base,
467				   dev->mode_config.suggested_x_property, 0);
468	drm_object_attach_property(&connector->base,
469				   dev->mode_config.suggested_y_property, 0);
470	if (dev_priv->implicit_placement_property)
471		drm_object_attach_property
472			(&connector->base,
473			 dev_priv->implicit_placement_property,
474			 1);
475
476	return 0;
477
478err_free_unregister:
479	drm_connector_unregister(connector);
480err_free_encoder:
481	drm_encoder_cleanup(encoder);
482err_free_connector:
483	drm_connector_cleanup(connector);
484err_free:
485	kfree(ldu);
486	return ret;
487}
488
489int vmw_kms_ldu_init_display(struct vmw_private *dev_priv)
490{
491	struct drm_device *dev = dev_priv->dev;
492	int i, ret;
493
494	if (dev_priv->ldu_priv) {
495		DRM_INFO("ldu system already on\n");
496		return -EINVAL;
497	}
498
499	dev_priv->ldu_priv = kmalloc(sizeof(*dev_priv->ldu_priv), GFP_KERNEL);
500	if (!dev_priv->ldu_priv)
501		return -ENOMEM;
502
503	INIT_LIST_HEAD(&dev_priv->ldu_priv->active);
504	dev_priv->ldu_priv->num_active = 0;
505	dev_priv->ldu_priv->last_num_active = 0;
506	dev_priv->ldu_priv->fb = NULL;
507
508	/* for old hardware without multimon only enable one display */
509	if (dev_priv->capabilities & SVGA_CAP_MULTIMON)
510		ret = drm_vblank_init(dev, VMWGFX_NUM_DISPLAY_UNITS);
511	else
512		ret = drm_vblank_init(dev, 1);
513	if (ret != 0)
514		goto err_free;
515
516	vmw_kms_create_implicit_placement_property(dev_priv);
517
518	if (dev_priv->capabilities & SVGA_CAP_MULTIMON)
519		for (i = 0; i < VMWGFX_NUM_DISPLAY_UNITS; ++i)
520			vmw_ldu_init(dev_priv, i);
521	else
522		vmw_ldu_init(dev_priv, 0);
523
524	dev_priv->active_display_unit = vmw_du_legacy;
525
526	DRM_INFO("Legacy Display Unit initialized\n");
527
528	return 0;
529
530err_free:
531	kfree(dev_priv->ldu_priv);
532	dev_priv->ldu_priv = NULL;
533	return ret;
534}
535
536int vmw_kms_ldu_close_display(struct vmw_private *dev_priv)
537{
538	if (!dev_priv->ldu_priv)
539		return -ENOSYS;
540
541	BUG_ON(!list_empty(&dev_priv->ldu_priv->active));
542
543	kfree(dev_priv->ldu_priv);
544
545	return 0;
546}
547
548
549int vmw_kms_ldu_do_bo_dirty(struct vmw_private *dev_priv,
550			    struct vmw_framebuffer *framebuffer,
551			    unsigned int flags, unsigned int color,
552			    struct drm_clip_rect *clips,
553			    unsigned int num_clips, int increment)
554{
555	size_t fifo_size;
556	int i;
557
558	struct {
559		uint32_t header;
560		SVGAFifoCmdUpdate body;
561	} *cmd;
562
563	fifo_size = sizeof(*cmd) * num_clips;
564	cmd = VMW_FIFO_RESERVE(dev_priv, fifo_size);
565	if (unlikely(cmd == NULL))
566		return -ENOMEM;
567
568	memset(cmd, 0, fifo_size);
569	for (i = 0; i < num_clips; i++, clips += increment) {
570		cmd[i].header = SVGA_CMD_UPDATE;
571		cmd[i].body.x = clips->x1;
572		cmd[i].body.y = clips->y1;
573		cmd[i].body.width = clips->x2 - clips->x1;
574		cmd[i].body.height = clips->y2 - clips->y1;
575	}
576
577	vmw_fifo_commit(dev_priv, fifo_size);
578	return 0;
579}
580