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
		virtio_gpu_cmd_get_display_info(vgdev);
G
Gerd Hoffmann 已提交
47
		virtio_gpu_notify(vgdev);
D
Dave Airlie 已提交
48 49 50 51 52 53 54
		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);
}

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

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

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

D
Dave Airlie 已提交
74 75 76 77 78 79 80 81
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 已提交
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 111
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;
}

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

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

D
Daniel Vetter 已提交
125
	if (!virtio_has_feature(dev_to_virtio(dev->dev), VIRTIO_F_VERSION_1))
D
Dave Airlie 已提交
126 127 128 129 130 131 132 133
		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 已提交
134
	vgdev->vdev = dev_to_virtio(dev->dev);
D
Dave Airlie 已提交
135 136 137
	vgdev->dev = dev->dev;

	spin_lock_init(&vgdev->display_info_lock);
138 139
	ida_init(&vgdev->ctx_id_ida);
	ida_init(&vgdev->resource_ida);
D
Dave Airlie 已提交
140 141 142 143
	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);

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

151 152 153 154 155
	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);

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

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

M
Michael S. Tsirkin 已提交
171
	ret = virtio_find_vqs(vgdev->vdev, 2, vqs, callbacks, names, NULL);
D
Dave Airlie 已提交
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
	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 已提交
194 195 196 197 198
	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 已提交
199

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

	virtio_device_ready(vgdev->vdev);

G
Gerd Hoffmann 已提交
204 205
	if (num_capsets)
		virtio_gpu_get_capsets(vgdev, num_capsets);
G
Gerd Hoffmann 已提交
206 207
	if (vgdev->has_edid)
		virtio_gpu_cmd_get_edids(vgdev);
208
	virtio_gpu_cmd_get_display_info(vgdev);
G
Gerd Hoffmann 已提交
209
	virtio_gpu_notify(vgdev);
210 211
	wait_event_timeout(vgdev->resp_wq, !vgdev->display_info_pending,
			   5 * HZ);
D
Dave Airlie 已提交
212 213 214 215 216 217 218 219 220 221 222
	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 已提交
223 224 225 226 227 228 229 230 231 232
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);
	}
}

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

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

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

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

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;
260
	int id;
261
	char dbgname[TASK_COMM_LEN];
G
Gerd Hoffmann 已提交
262 263 264 265 266 267 268 269 270 271

	/* 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;

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

	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;
}