From: Dongwon Kim <[email protected]>

virtio device needs to delete before VM suspend happens
then reinitialize all virtqueues again upon resume

Suggested-by: Dmitry Osipenko <[email protected]>
Tested-by: Dmitry Osipenko <[email protected]>
Cc: Vivek Kasireddy <[email protected]>
Cc: Nirmoy Das <[email protected]>
Signed-off-by: Dongwon Kim <[email protected]>
---
v2: 10ms sleep was added in virtgpu_freeze to avoid the situation
    the driver is locked up during resumption.

v3: Plain 10ms delay was replaced with wait calls which wait until
    the virtio queue is empty.
    (Dmitry Osipenko)

v4: Change wait_event to wait_event_timeout to prevent permanent wait
    (Nirmoy Das)

v5: Move duplicate wait_event_timeout blocks into a single
    virtio_gpu_wait_queue helper function.
    (Dmitry Osipenko)

    Handle timeouts gracefully by returning -ETIMEDOUT to abort
    the freeze operation if the host hangs.
    (Dmitry Osipenko)
---
 drivers/gpu/drm/virtio/virtgpu_drv.c | 68 +++++++++++++++++++++++++++-
 drivers/gpu/drm/virtio/virtgpu_drv.h |  2 +
 drivers/gpu/drm/virtio/virtgpu_kms.c | 23 +++++++---
 drivers/gpu/drm/virtio/virtgpu_vq.c  | 17 +++++++
 4 files changed, 103 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/drm/virtio/virtgpu_drv.c 
b/drivers/gpu/drm/virtio/virtgpu_drv.c
index 812ee3f5e4aa..fd3fbc7474dd 100644
--- a/drivers/gpu/drm/virtio/virtgpu_drv.c
+++ b/drivers/gpu/drm/virtio/virtgpu_drv.c
@@ -165,6 +165,68 @@ static unsigned int features[] = {
        VIRTIO_GPU_F_CONTEXT_INIT,
        VIRTIO_GPU_F_BLOB_ALIGNMENT,
 };
+
+#ifdef CONFIG_PM_SLEEP
+static int virtgpu_freeze(struct virtio_device *vdev)
+{
+       struct drm_device *dev = vdev->priv;
+       struct virtio_gpu_device *vgdev = dev->dev_private;
+       int error;
+
+       error = drm_mode_config_helper_suspend(dev);
+       if (error) {
+               DRM_ERROR("suspend error %d\n", error);
+               return error;
+       }
+
+       flush_work(&vgdev->obj_free_work);
+       flush_work(&vgdev->ctrlq.dequeue_work);
+       flush_work(&vgdev->cursorq.dequeue_work);
+       flush_work(&vgdev->config_changed_work);
+
+       error = virtio_gpu_wait_queue(&vgdev->ctrlq, vgdev->ctrlq.vq->num_max);
+
+       if (error) {
+               DRM_ERROR("suspend error %d\n", error);
+               return error;
+       }
+
+       error = virtio_gpu_wait_queue(&vgdev->cursorq, 
vgdev->cursorq.vq->num_max);
+
+       if (error) {
+               DRM_ERROR("suspend error %d\n", error);
+               return error;
+       }
+
+       vdev->config->del_vqs(vdev);
+
+       return 0;
+}
+
+static int virtgpu_restore(struct virtio_device *vdev)
+{
+       struct drm_device *dev = vdev->priv;
+       struct virtio_gpu_device *vgdev = dev->dev_private;
+       int error;
+
+       error = virtio_gpu_find_vqs(vgdev);
+       if (error) {
+               DRM_ERROR("failed to find virt queues\n");
+               return error;
+       }
+
+       virtio_device_ready(vdev);
+
+       error = drm_mode_config_helper_resume(dev);
+       if (error) {
+               DRM_ERROR("resume error %d\n", error);
+               return error;
+       }
+
+       return 0;
+}
+#endif
+
 static struct virtio_driver virtio_gpu_driver = {
        .feature_table = features,
        .feature_table_size = ARRAY_SIZE(features),
@@ -173,7 +235,11 @@ static struct virtio_driver virtio_gpu_driver = {
        .probe = virtio_gpu_probe,
        .remove = virtio_gpu_remove,
        .shutdown = virtio_gpu_shutdown,
-       .config_changed = virtio_gpu_config_changed
+       .config_changed = virtio_gpu_config_changed,
+#ifdef CONFIG_PM_SLEEP
+       .freeze = virtgpu_freeze,
+       .restore = virtgpu_restore,
+#endif
 };
 
 static int __init virtio_gpu_driver_init(void)
diff --git a/drivers/gpu/drm/virtio/virtgpu_drv.h 
b/drivers/gpu/drm/virtio/virtgpu_drv.h
index 7449907754a4..c630c75e308b 100644
--- a/drivers/gpu/drm/virtio/virtgpu_drv.h
+++ b/drivers/gpu/drm/virtio/virtgpu_drv.h
@@ -303,6 +303,7 @@ void virtio_gpu_deinit(struct drm_device *dev);
 void virtio_gpu_release(struct drm_device *dev);
 int virtio_gpu_driver_open(struct drm_device *dev, struct drm_file *file);
 void virtio_gpu_driver_postclose(struct drm_device *dev, struct drm_file 
*file);
+int virtio_gpu_find_vqs(struct virtio_gpu_device *vgdev);
 
 /* virtgpu_gem.c */
 int virtio_gpu_gem_object_open(struct drm_gem_object *obj,
@@ -424,6 +425,7 @@ void virtio_gpu_dequeue_ctrl_func(struct work_struct *work);
 void virtio_gpu_dequeue_cursor_func(struct work_struct *work);
 void virtio_gpu_panic_notify(struct virtio_gpu_device *vgdev);
 void virtio_gpu_notify(struct virtio_gpu_device *vgdev);
+int virtio_gpu_wait_queue(struct virtio_gpu_queue *vgvq, int num_elem);
 
 int
 virtio_gpu_cmd_resource_assign_uuid(struct virtio_gpu_device *vgdev,
diff --git a/drivers/gpu/drm/virtio/virtgpu_kms.c 
b/drivers/gpu/drm/virtio/virtgpu_kms.c
index cfde9f573df6..82baf1b3717e 100644
--- a/drivers/gpu/drm/virtio/virtgpu_kms.c
+++ b/drivers/gpu/drm/virtio/virtgpu_kms.c
@@ -115,15 +115,28 @@ static void virtio_gpu_get_capsets(struct 
virtio_gpu_device *vgdev,
        vgdev->num_capsets = num_capsets;
 }
 
-int virtio_gpu_init(struct virtio_device *vdev, struct drm_device *dev)
+int virtio_gpu_find_vqs(struct virtio_gpu_device *vgdev)
 {
        struct virtqueue_info vqs_info[] = {
                { "control", virtio_gpu_ctrl_ack },
                { "cursor", virtio_gpu_cursor_ack },
        };
-       struct virtio_gpu_device *vgdev;
-       /* this will expand later */
        struct virtqueue *vqs[2];
+       int ret;
+
+       ret = virtio_find_vqs(vgdev->vdev, 2, vqs, vqs_info, NULL);
+       if (ret)
+               return ret;
+
+       vgdev->ctrlq.vq = vqs[0];
+       vgdev->cursorq.vq = vqs[1];
+
+       return 0;
+}
+
+int virtio_gpu_init(struct virtio_device *vdev, struct drm_device *dev)
+{
+       struct virtio_gpu_device *vgdev;
        u32 num_scanouts, num_capsets, blob_alignment;
        int ret = 0;
 
@@ -215,13 +228,11 @@ int virtio_gpu_init(struct virtio_device *vdev, struct 
drm_device *dev)
                 vgdev->has_context_init ? '+' : '-',
                 vgdev->has_blob_alignment ? '+' : '-');
 
-       ret = virtio_find_vqs(vgdev->vdev, 2, vqs, vqs_info, NULL);
+       ret = virtio_gpu_find_vqs(vgdev);
        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");
diff --git a/drivers/gpu/drm/virtio/virtgpu_vq.c 
b/drivers/gpu/drm/virtio/virtgpu_vq.c
index 67865810a2e7..22bf11497f16 100644
--- a/drivers/gpu/drm/virtio/virtgpu_vq.c
+++ b/drivers/gpu/drm/virtio/virtgpu_vq.c
@@ -369,6 +369,23 @@ static int virtio_gpu_panic_queue_ctrl_sgs(struct 
virtio_gpu_device *vgdev,
        return 0;
 }
 
+int virtio_gpu_wait_queue(struct virtio_gpu_queue *vgvq, int num_elem)
+{
+       int ret;
+
+       /* Cap num_elem at the queue's maximum capacity */
+       num_elem = min(num_elem, (int)vgvq->vq->num_max);
+
+       /* Wait up to 5 seconds for enough free slots to become available */
+       ret = wait_event_timeout(vgvq->ack_queue,
+                                vgvq->vq->num_free >= num_elem,
+                                5 * HZ);
+       if (ret == 0)
+               return -ETIMEDOUT;
+
+       return 0;
+}
+
 static int virtio_gpu_queue_ctrl_sgs(struct virtio_gpu_device *vgdev,
                                     struct virtio_gpu_vbuffer *vbuf,
                                     struct virtio_gpu_fence *fence,
-- 
2.34.1

Reply via email to