virtio-gpu: add 3d/virgl support

Add the bits needed for opengl rendering support: query
capabilities, new virtio commands, drm ioctls.

Signed-off-by: Dave Airlie <airlied@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
diff --git a/drivers/gpu/drm/virtio/virtgpu_kms.c b/drivers/gpu/drm/virtio/virtgpu_kms.c
index 782766c..06496a1 100644
--- a/drivers/gpu/drm/virtio/virtgpu_kms.c
+++ b/drivers/gpu/drm/virtio/virtgpu_kms.c
@@ -52,6 +52,41 @@
 		      events_clear, &events_clear);
 }
 
+static void virtio_gpu_ctx_id_get(struct virtio_gpu_device *vgdev,
+				  uint32_t *resid)
+{
+	int handle;
+
+	idr_preload(GFP_KERNEL);
+	spin_lock(&vgdev->ctx_id_idr_lock);
+	handle = idr_alloc(&vgdev->ctx_id_idr, NULL, 1, 0, 0);
+	spin_unlock(&vgdev->ctx_id_idr_lock);
+	idr_preload_end();
+	*resid = handle;
+}
+
+static void virtio_gpu_ctx_id_put(struct virtio_gpu_device *vgdev, uint32_t id)
+{
+	spin_lock(&vgdev->ctx_id_idr_lock);
+	idr_remove(&vgdev->ctx_id_idr, id);
+	spin_unlock(&vgdev->ctx_id_idr_lock);
+}
+
+static void virtio_gpu_context_create(struct virtio_gpu_device *vgdev,
+				      uint32_t nlen, const char *name,
+				      uint32_t *ctx_id)
+{
+	virtio_gpu_ctx_id_get(vgdev, ctx_id);
+	virtio_gpu_cmd_context_create(vgdev, *ctx_id, nlen, name);
+}
+
+static void virtio_gpu_context_destroy(struct virtio_gpu_device *vgdev,
+				      uint32_t ctx_id)
+{
+	virtio_gpu_cmd_context_destroy(vgdev, ctx_id);
+	virtio_gpu_ctx_id_put(vgdev, ctx_id);
+}
+
 static void virtio_gpu_init_vq(struct virtio_gpu_queue *vgvq,
 			       void (*work_func)(struct work_struct *work))
 {
@@ -60,6 +95,36 @@
 	INIT_WORK(&vgvq->dequeue_work, work_func);
 }
 
+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;
+}
+
 int virtio_gpu_driver_load(struct drm_device *dev, unsigned long flags)
 {
 	static vq_callback_t *callbacks[] = {
@@ -70,7 +135,7 @@
 	struct virtio_gpu_device *vgdev;
 	/* this will expand later */
 	struct virtqueue *vqs[2];
-	u32 num_scanouts;
+	u32 num_scanouts, num_capsets;
 	int ret;
 
 	if (!virtio_has_feature(dev->virtdev, VIRTIO_F_VERSION_1))
@@ -96,9 +161,15 @@
 
 	spin_lock_init(&vgdev->fence_drv.lock);
 	INIT_LIST_HEAD(&vgdev->fence_drv.fences);
+	INIT_LIST_HEAD(&vgdev->cap_cache);
 	INIT_WORK(&vgdev->config_changed_work,
 		  virtio_gpu_config_changed_work_func);
 
+	if (virtio_has_feature(vgdev->vdev, VIRTIO_GPU_F_VIRGL))
+		vgdev->has_virgl_3d = true;
+	DRM_INFO("virgl 3d acceleration %s\n",
+		 vgdev->has_virgl_3d ? "enabled" : "not available");
+
 	ret = vgdev->vdev->config->find_vqs(vgdev->vdev, 2, vqs,
 					    callbacks, names);
 	if (ret) {
@@ -129,6 +200,11 @@
 		ret = -EINVAL;
 		goto err_scanouts;
 	}
+	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);
 
 	ret = virtio_gpu_modeset_init(vgdev);
 	if (ret)
@@ -137,6 +213,8 @@
 	virtio_device_ready(vgdev->vdev);
 	vgdev->vqs_ready = true;
 
+	if (num_capsets)
+		virtio_gpu_get_capsets(vgdev, num_capsets);
 	virtio_gpu_cmd_get_display_info(vgdev);
 	wait_event_timeout(vgdev->resp_wq, !vgdev->display_info_pending,
 			   5 * HZ);
@@ -157,6 +235,16 @@
 	return ret;
 }
 
+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);
+	}
+}
+
 int virtio_gpu_driver_unload(struct drm_device *dev)
 {
 	struct virtio_gpu_device *vgdev = dev->dev_private;
@@ -170,6 +258,49 @@
 	virtio_gpu_modeset_fini(vgdev);
 	virtio_gpu_ttm_fini(vgdev);
 	virtio_gpu_free_vbufs(vgdev);
+	virtio_gpu_cleanup_cap_cache(vgdev);
+	kfree(vgdev->capsets);
 	kfree(vgdev);
 	return 0;
 }
+
+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;
+	uint32_t id;
+	char dbgname[64], tmpname[TASK_COMM_LEN];
+
+	/* can't create contexts without 3d renderer */
+	if (!vgdev->has_virgl_3d)
+		return 0;
+
+	get_task_comm(tmpname, current);
+	snprintf(dbgname, sizeof(dbgname), "%s", tmpname);
+	dbgname[63] = 0;
+	/* allocate a virt GPU context for this opener */
+	vfpriv = kzalloc(sizeof(*vfpriv), GFP_KERNEL);
+	if (!vfpriv)
+		return -ENOMEM;
+
+	virtio_gpu_context_create(vgdev, strlen(dbgname), dbgname, &id);
+
+	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;
+}