virtgpu_kms.c 8.2 KB
Newer Older
D
Dave Airlie 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
/*
 * Copyright (C) 2015 Red Hat, Inc.
 * All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <linux/virtio.h>
#include <linux/virtio_config.h>
S
Sam Ravnborg 已提交
28 29 30

#include <drm/drm_file.h>

D
Dave Airlie 已提交
31 32 33 34 35 36 37 38 39 40 41 42 43
#include "virtgpu_drv.h"

static void virtio_gpu_config_changed_work_func(struct work_struct *work)
{
	struct virtio_gpu_device *vgdev =
		container_of(work, struct virtio_gpu_device,
			     config_changed_work);
	u32 events_read, events_clear = 0;

	/* read the config space */
	virtio_cread(vgdev->vdev, struct virtio_gpu_config,
		     events_read, &events_read);
	if (events_read & VIRTIO_GPU_EVENT_DISPLAY) {
G
Gerd Hoffmann 已提交
44 45
		if (vgdev->has_edid)
			virtio_gpu_cmd_get_edids(vgdev);
D
Dave Airlie 已提交
46 47 48 49 50 51 52 53
		virtio_gpu_cmd_get_display_info(vgdev);
		drm_helper_hpd_irq_event(vgdev->ddev);
		events_clear |= VIRTIO_GPU_EVENT_DISPLAY;
	}
	virtio_cwrite(vgdev->vdev, struct virtio_gpu_config,
		      events_clear, &events_clear);
}

54 55
static int virtio_gpu_context_create(struct virtio_gpu_device *vgdev,
				      uint32_t nlen, const char *name)
G
Gerd Hoffmann 已提交
56
{
57
	int handle = ida_alloc(&vgdev->ctx_id_ida, GFP_KERNEL);
G
Gerd Hoffmann 已提交
58

59 60
	if (handle < 0)
		return handle;
61
	handle += 1;
62 63
	virtio_gpu_cmd_context_create(vgdev, handle, nlen, name);
	return handle;
G
Gerd Hoffmann 已提交
64 65 66 67 68 69
}

static void virtio_gpu_context_destroy(struct virtio_gpu_device *vgdev,
				      uint32_t ctx_id)
{
	virtio_gpu_cmd_context_destroy(vgdev, ctx_id);
70
	ida_free(&vgdev->ctx_id_ida, ctx_id - 1);
G
Gerd Hoffmann 已提交
71 72
}

D
Dave Airlie 已提交
73 74 75 76 77 78 79 80
static void virtio_gpu_init_vq(struct virtio_gpu_queue *vgvq,
			       void (*work_func)(struct work_struct *work))
{
	spin_lock_init(&vgvq->qlock);
	init_waitqueue_head(&vgvq->ack_queue);
	INIT_WORK(&vgvq->dequeue_work, work_func);
}

G
Gerd Hoffmann 已提交
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
static void virtio_gpu_get_capsets(struct virtio_gpu_device *vgdev,
				   int num_capsets)
{
	int i, ret;

	vgdev->capsets = kcalloc(num_capsets,
				 sizeof(struct virtio_gpu_drv_capset),
				 GFP_KERNEL);
	if (!vgdev->capsets) {
		DRM_ERROR("failed to allocate cap sets\n");
		return;
	}
	for (i = 0; i < num_capsets; i++) {
		virtio_gpu_cmd_get_capset_info(vgdev, i);
		ret = wait_event_timeout(vgdev->resp_wq,
					 vgdev->capsets[i].id > 0, 5 * HZ);
		if (ret == 0) {
			DRM_ERROR("timed out waiting for cap set %d\n", i);
			kfree(vgdev->capsets);
			vgdev->capsets = NULL;
			return;
		}
		DRM_INFO("cap set %d: id %d, max-version %d, max-size %d\n",
			 i, vgdev->capsets[i].id,
			 vgdev->capsets[i].max_version,
			 vgdev->capsets[i].max_size);
	}
	vgdev->num_capsets = num_capsets;
}

111
int virtio_gpu_init(struct drm_device *dev)
D
Dave Airlie 已提交
112 113 114 115
{
	static vq_callback_t *callbacks[] = {
		virtio_gpu_ctrl_ack, virtio_gpu_cursor_ack
	};
116
	static const char * const names[] = { "control", "cursor" };
D
Dave Airlie 已提交
117 118 119 120

	struct virtio_gpu_device *vgdev;
	/* this will expand later */
	struct virtqueue *vqs[2];
G
Gerd Hoffmann 已提交
121
	u32 num_scanouts, num_capsets;
D
Dave Airlie 已提交
122 123
	int ret;

D
Daniel Vetter 已提交
124
	if (!virtio_has_feature(dev_to_virtio(dev->dev), VIRTIO_F_VERSION_1))
D
Dave Airlie 已提交
125 126 127 128 129 130 131 132
		return -ENODEV;

	vgdev = kzalloc(sizeof(struct virtio_gpu_device), GFP_KERNEL);
	if (!vgdev)
		return -ENOMEM;

	vgdev->ddev = dev;
	dev->dev_private = vgdev;
D
Daniel Vetter 已提交
133
	vgdev->vdev = dev_to_virtio(dev->dev);
D
Dave Airlie 已提交
134 135 136
	vgdev->dev = dev->dev;

	spin_lock_init(&vgdev->display_info_lock);
137 138
	ida_init(&vgdev->ctx_id_ida);
	ida_init(&vgdev->resource_ida);
D
Dave Airlie 已提交
139 140 141 142
	init_waitqueue_head(&vgdev->resp_wq);
	virtio_gpu_init_vq(&vgdev->ctrlq, virtio_gpu_dequeue_ctrl_func);
	virtio_gpu_init_vq(&vgdev->cursorq, virtio_gpu_dequeue_cursor_func);

143
	vgdev->fence_drv.context = dma_fence_context_alloc(1);
D
Dave Airlie 已提交
144 145
	spin_lock_init(&vgdev->fence_drv.lock);
	INIT_LIST_HEAD(&vgdev->fence_drv.fences);
G
Gerd Hoffmann 已提交
146
	INIT_LIST_HEAD(&vgdev->cap_cache);
D
Dave Airlie 已提交
147 148 149
	INIT_WORK(&vgdev->config_changed_work,
		  virtio_gpu_config_changed_work_func);

150 151 152 153 154
	INIT_WORK(&vgdev->obj_free_work,
		  virtio_gpu_array_put_free_work);
	INIT_LIST_HEAD(&vgdev->obj_free_list);
	spin_lock_init(&vgdev->obj_free_lock);

155
#ifdef __LITTLE_ENDIAN
G
Gerd Hoffmann 已提交
156 157
	if (virtio_has_feature(vgdev->vdev, VIRTIO_GPU_F_VIRGL))
		vgdev->has_virgl_3d = true;
158
#endif
G
Gerd Hoffmann 已提交
159 160 161
	if (virtio_has_feature(vgdev->vdev, VIRTIO_GPU_F_EDID)) {
		vgdev->has_edid = true;
	}
G
Gerd Hoffmann 已提交
162 163 164
	if (virtio_has_feature(vgdev->vdev, VIRTIO_RING_F_INDIRECT_DESC)) {
		vgdev->has_indirect = true;
	}
G
Gerd Hoffmann 已提交
165

166 167 168 169
	DRM_INFO("features: %cvirgl %cedid\n",
		 vgdev->has_virgl_3d ? '+' : '-',
		 vgdev->has_edid     ? '+' : '-');

M
Michael S. Tsirkin 已提交
170
	ret = virtio_find_vqs(vgdev->vdev, 2, vqs, callbacks, names, NULL);
D
Dave Airlie 已提交
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
	if (ret) {
		DRM_ERROR("failed to find virt queues\n");
		goto err_vqs;
	}
	vgdev->ctrlq.vq = vqs[0];
	vgdev->cursorq.vq = vqs[1];
	ret = virtio_gpu_alloc_vbufs(vgdev);
	if (ret) {
		DRM_ERROR("failed to alloc vbufs\n");
		goto err_vbufs;
	}

	/* get display info */
	virtio_cread(vgdev->vdev, struct virtio_gpu_config,
		     num_scanouts, &num_scanouts);
	vgdev->num_scanouts = min_t(uint32_t, num_scanouts,
				    VIRTIO_GPU_MAX_SCANOUTS);
	if (!vgdev->num_scanouts) {
		DRM_ERROR("num_scanouts is zero\n");
		ret = -EINVAL;
		goto err_scanouts;
	}
G
Gerd Hoffmann 已提交
193 194 195 196 197
	DRM_INFO("number of scanouts: %d\n", num_scanouts);

	virtio_cread(vgdev->vdev, struct virtio_gpu_config,
		     num_capsets, &num_capsets);
	DRM_INFO("number of cap sets: %d\n", num_capsets);
D
Dave Airlie 已提交
198

199
	virtio_gpu_modeset_init(vgdev);
D
Dave Airlie 已提交
200 201 202

	virtio_device_ready(vgdev->vdev);

G
Gerd Hoffmann 已提交
203 204
	if (num_capsets)
		virtio_gpu_get_capsets(vgdev, num_capsets);
G
Gerd Hoffmann 已提交
205 206
	if (vgdev->has_edid)
		virtio_gpu_cmd_get_edids(vgdev);
207 208 209
	virtio_gpu_cmd_get_display_info(vgdev);
	wait_event_timeout(vgdev->resp_wq, !vgdev->display_info_pending,
			   5 * HZ);
D
Dave Airlie 已提交
210 211 212 213 214 215 216 217 218 219 220
	return 0;

err_scanouts:
	virtio_gpu_free_vbufs(vgdev);
err_vbufs:
	vgdev->vdev->config->del_vqs(vgdev->vdev);
err_vqs:
	kfree(vgdev);
	return ret;
}

G
Gerd Hoffmann 已提交
221 222 223 224 225 226 227 228 229 230
static void virtio_gpu_cleanup_cap_cache(struct virtio_gpu_device *vgdev)
{
	struct virtio_gpu_drv_cap_cache *cache_ent, *tmp;

	list_for_each_entry_safe(cache_ent, tmp, &vgdev->cap_cache, head) {
		kfree(cache_ent->caps_cache);
		kfree(cache_ent);
	}
}

231
void virtio_gpu_deinit(struct drm_device *dev)
D
Dave Airlie 已提交
232 233 234
{
	struct virtio_gpu_device *vgdev = dev->dev_private;

235
	flush_work(&vgdev->obj_free_work);
D
Dave Airlie 已提交
236 237 238
	flush_work(&vgdev->ctrlq.dequeue_work);
	flush_work(&vgdev->cursorq.dequeue_work);
	flush_work(&vgdev->config_changed_work);
239
	vgdev->vdev->config->reset(vgdev->vdev);
D
Dave Airlie 已提交
240
	vgdev->vdev->config->del_vqs(vgdev->vdev);
241 242 243 244 245
}

void virtio_gpu_release(struct drm_device *dev)
{
	struct virtio_gpu_device *vgdev = dev->dev_private;
D
Dave Airlie 已提交
246 247 248

	virtio_gpu_modeset_fini(vgdev);
	virtio_gpu_free_vbufs(vgdev);
G
Gerd Hoffmann 已提交
249 250
	virtio_gpu_cleanup_cap_cache(vgdev);
	kfree(vgdev->capsets);
D
Dave Airlie 已提交
251 252
	kfree(vgdev);
}
G
Gerd Hoffmann 已提交
253 254 255 256 257

int virtio_gpu_driver_open(struct drm_device *dev, struct drm_file *file)
{
	struct virtio_gpu_device *vgdev = dev->dev_private;
	struct virtio_gpu_fpriv *vfpriv;
258
	int id;
259
	char dbgname[TASK_COMM_LEN];
G
Gerd Hoffmann 已提交
260 261 262 263 264 265 266 267 268 269

	/* can't create contexts without 3d renderer */
	if (!vgdev->has_virgl_3d)
		return 0;

	/* allocate a virt GPU context for this opener */
	vfpriv = kzalloc(sizeof(*vfpriv), GFP_KERNEL);
	if (!vfpriv)
		return -ENOMEM;

270
	get_task_comm(dbgname, current);
271
	id = virtio_gpu_context_create(vgdev, strlen(dbgname), dbgname);
272 273
	if (id < 0) {
		kfree(vfpriv);
274
		return id;
275
	}
G
Gerd Hoffmann 已提交
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295

	vfpriv->ctx_id = id;
	file->driver_priv = vfpriv;
	return 0;
}

void virtio_gpu_driver_postclose(struct drm_device *dev, struct drm_file *file)
{
	struct virtio_gpu_device *vgdev = dev->dev_private;
	struct virtio_gpu_fpriv *vfpriv;

	if (!vgdev->has_virgl_3d)
		return;

	vfpriv = file->driver_priv;

	virtio_gpu_context_destroy(vgdev, vfpriv->ctx_id);
	kfree(vfpriv);
	file->driver_priv = NULL;
}