drm/vmwgfx: Improve on hibernation
authorThomas Hellstrom <thellstrom@vmware.com>
Thu, 22 Mar 2018 09:26:37 +0000 (10:26 +0100)
committerThomas Hellstrom <thellstrom@vmware.com>
Thu, 22 Mar 2018 11:08:23 +0000 (12:08 +0100)
Make it possible to hibernate also with masters that don't switch VT at
hibernation time. We save and restore modesetting state unless fbdev is
active and enabled at hibernation time.

Signed-off-by: Thomas Hellstrom <thellstrom@vmware.com>
Reviewed-by: Sinclair Yeh <syeh@vmware.com>
drivers/gpu/drm/vmwgfx/vmwgfx_drv.c
drivers/gpu/drm/vmwgfx/vmwgfx_drv.h
drivers/gpu/drm/vmwgfx/vmwgfx_fb.c
drivers/gpu/drm/vmwgfx/vmwgfx_kms.c

index 5055e5f68c4f219210008764453dcdfc69267032..c66f32a6a9d9b0869527d895426241e8b02f0ce8 100644 (file)
@@ -1279,8 +1279,7 @@ static void vmw_master_drop(struct drm_device *dev,
        ttm_lock_set_kill(&dev_priv->fbdev_master.lock, false, SIGTERM);
        ttm_vt_unlock(&dev_priv->fbdev_master.lock);
 
-       if (dev_priv->enable_fb)
-               vmw_fb_on(dev_priv);
+       vmw_fb_refresh(dev_priv);
 }
 
 /**
@@ -1370,28 +1369,23 @@ static int vmwgfx_pm_notifier(struct notifier_block *nb, unsigned long val,
 
        switch (val) {
        case PM_HIBERNATION_PREPARE:
-               if (dev_priv->enable_fb)
-                       vmw_fb_off(dev_priv);
-               ttm_suspend_lock(&dev_priv->reservation_sem);
-
                /*
-                * This empties VRAM and unbinds all GMR bindings.
-                * Buffer contents is moved to swappable memory.
+                * Take the reservation sem in write mode, which will make sure
+                * there are no other processes holding a buffer object
+                * reservation, meaning we should be able to evict all buffer
+                * objects if needed.
+                * Once user-space processes have been frozen, we can release
+                * the lock again.
                 */
-               vmw_execbuf_release_pinned_bo(dev_priv);
-               vmw_resource_evict_all(dev_priv);
-               vmw_release_device_early(dev_priv);
-               ttm_bo_swapout_all(&dev_priv->bdev);
-               vmw_fence_fifo_down(dev_priv->fman);
+               ttm_suspend_lock(&dev_priv->reservation_sem);
+               dev_priv->suspend_locked = true;
                break;
        case PM_POST_HIBERNATION:
        case PM_POST_RESTORE:
-               vmw_fence_fifo_up(dev_priv->fman);
-               ttm_suspend_unlock(&dev_priv->reservation_sem);
-               if (dev_priv->enable_fb)
-                       vmw_fb_on(dev_priv);
-               break;
-       case PM_RESTORE_PREPARE:
+               if (READ_ONCE(dev_priv->suspend_locked)) {
+                       dev_priv->suspend_locked = false;
+                       ttm_suspend_unlock(&dev_priv->reservation_sem);
+               }
                break;
        default:
                break;
@@ -1442,25 +1436,50 @@ static int vmw_pm_freeze(struct device *kdev)
        struct pci_dev *pdev = to_pci_dev(kdev);
        struct drm_device *dev = pci_get_drvdata(pdev);
        struct vmw_private *dev_priv = vmw_priv(dev);
+       int ret;
 
+       /*
+        * Unlock for vmw_kms_suspend.
+        * No user-space processes should be running now.
+        */
+       ttm_suspend_unlock(&dev_priv->reservation_sem);
+       ret = vmw_kms_suspend(dev_priv->dev);
+       if (ret) {
+               ttm_suspend_lock(&dev_priv->reservation_sem);
+               DRM_ERROR("Failed to freeze modesetting.\n");
+               return ret;
+       }
        dev_priv->suspended = true;
        if (dev_priv->enable_fb)
-               vmw_fifo_resource_dec(dev_priv);
+               vmw_fb_off(dev_priv);
 
+       ttm_suspend_lock(&dev_priv->reservation_sem);
+       vmw_execbuf_release_pinned_bo(dev_priv);
+       vmw_resource_evict_all(dev_priv);
+       vmw_release_device_early(dev_priv);
+       ttm_bo_swapout_all(&dev_priv->bdev);
+       if (dev_priv->enable_fb)
+               vmw_fifo_resource_dec(dev_priv);
        if (atomic_read(&dev_priv->num_fifo_resources) != 0) {
                DRM_ERROR("Can't hibernate while 3D resources are active.\n");
                if (dev_priv->enable_fb)
                        vmw_fifo_resource_inc(dev_priv);
                WARN_ON(vmw_request_device_late(dev_priv));
+               dev_priv->suspend_locked = false;
+               ttm_suspend_unlock(&dev_priv->reservation_sem);
+               if (dev_priv->suspend_state)
+                       vmw_kms_resume(dev);
+               if (dev_priv->enable_fb)
+                       vmw_fb_on(dev_priv);
                dev_priv->suspended = false;
+               vmw_fb_refresh(dev_priv);
                return -EBUSY;
        }
 
-       if (dev_priv->enable_fb)
-               __vmw_svga_disable(dev_priv);
+       vmw_fence_fifo_down(dev_priv->fman);
+       __vmw_svga_disable(dev_priv);
        
        vmw_release_device_late(dev_priv);
-
        return 0;
 }
 
@@ -1484,7 +1503,17 @@ static int vmw_pm_restore(struct device *kdev)
        if (dev_priv->enable_fb)
                __vmw_svga_enable(dev_priv);
 
+       vmw_fence_fifo_up(dev_priv->fman);
+       dev_priv->suspend_locked = false;
+       ttm_suspend_unlock(&dev_priv->reservation_sem);
+       if (dev_priv->suspend_state)
+               vmw_kms_resume(dev_priv->dev);
+
+       if (dev_priv->enable_fb)
+               vmw_fb_on(dev_priv);
+
        dev_priv->suspended = false;
+       vmw_fb_refresh(dev_priv);
 
        return 0;
 }
index 5c2a36ae1bbe323d55d566a6aa58133ced4255fb..0bf28a6528bfbee9dfbcd6c6f188b71121d2211b 100644 (file)
@@ -425,6 +425,7 @@ struct vmw_private {
        struct vmw_framebuffer *implicit_fb;
        struct mutex global_kms_state_mutex;
        spinlock_t cursor_lock;
+       struct drm_atomic_state *suspend_state;
 
        /*
         * Context and surface management.
@@ -498,6 +499,7 @@ struct vmw_private {
        struct notifier_block pm_nb;
        bool suspended;
        bool refuse_hibernation;
+       bool suspend_locked;
 
        struct mutex release_mutex;
        atomic_t num_fifo_resources;
@@ -909,6 +911,7 @@ int vmw_fb_init(struct vmw_private *vmw_priv);
 int vmw_fb_close(struct vmw_private *dev_priv);
 int vmw_fb_off(struct vmw_private *vmw_priv);
 int vmw_fb_on(struct vmw_private *vmw_priv);
+void vmw_fb_refresh(struct vmw_private *vmw_priv);
 
 /**
  * Kernel modesetting - vmwgfx_kms.c
@@ -945,6 +948,8 @@ int vmw_kms_present(struct vmw_private *dev_priv,
 int vmw_kms_update_layout_ioctl(struct drm_device *dev, void *data,
                                struct drm_file *file_priv);
 void vmw_kms_legacy_hotspot_clear(struct vmw_private *dev_priv);
+int vmw_kms_suspend(struct drm_device *dev);
+int vmw_kms_resume(struct drm_device *dev);
 
 int vmw_dumb_create(struct drm_file *file_priv,
                    struct drm_device *dev,
index fb4e59ee26c77df60c445fcc7e7541e9a4e2bc2c..e85c1868ef12f65fe156df001d2193872b01c4dc 100644 (file)
@@ -161,10 +161,17 @@ static int vmw_fb_blank(int blank, struct fb_info *info)
        return 0;
 }
 
-/*
- * Dirty code
+/**
+ * vmw_fb_dirty_flush - flush dirty regions to the kms framebuffer
+ *
+ * @work: The struct work_struct associated with this task.
+ *
+ * This function flushes the dirty regions of the vmalloc framebuffer to the
+ * kms framebuffer, and if the kms framebuffer is visible, also updated the
+ * corresponding displays. Note that this function runs even if the kms
+ * framebuffer is not bound to a crtc and thus not visible, but it's turned
+ * off during hibernation using the par->dirty.active bool.
  */
-
 static void vmw_fb_dirty_flush(struct work_struct *work)
 {
        struct vmw_fb_par *par = container_of(work, struct vmw_fb_par,
@@ -852,12 +859,6 @@ int vmw_fb_off(struct vmw_private *vmw_priv)
        flush_delayed_work(&info->deferred_work);
        flush_delayed_work(&par->local_work);
 
-       mutex_lock(&par->bo_mutex);
-       drm_modeset_lock_all(vmw_priv->dev);
-       (void) vmw_fb_kms_detach(par, true, false);
-       drm_modeset_unlock_all(vmw_priv->dev);
-       mutex_unlock(&par->bo_mutex);
-
        return 0;
 }
 
@@ -873,10 +874,24 @@ int vmw_fb_on(struct vmw_private *vmw_priv)
        info = vmw_priv->fb_info;
        par = info->par;
 
-       vmw_fb_set_par(info);
        spin_lock_irqsave(&par->dirty.lock, flags);
        par->dirty.active = true;
        spin_unlock_irqrestore(&par->dirty.lock, flags);
  
        return 0;
 }
+
+/**
+ * vmw_fb_refresh - Refresh fb display
+ *
+ * @vmw_priv: Pointer to device private
+ *
+ * Call into kms to show the fbdev display(s).
+ */
+void vmw_fb_refresh(struct vmw_private *vmw_priv)
+{
+       if (!vmw_priv->fb_info)
+               return;
+
+       vmw_fb_set_par(vmw_priv->fb_info);
+}
index 63159674bf924dd602a55b5b8267e8096b079320..3628a9fe705fc4ac416c2baa58587e87f5eff9c0 100644 (file)
@@ -2848,3 +2848,51 @@ int vmw_kms_set_config(struct drm_mode_set *set,
 
        return drm_atomic_helper_set_config(set, ctx);
 }
+
+
+/**
+ * vmw_kms_suspend - Save modesetting state and turn modesetting off.
+ *
+ * @dev: Pointer to the drm device
+ * Return: 0 on success. Negative error code on failure.
+ */
+int vmw_kms_suspend(struct drm_device *dev)
+{
+       struct vmw_private *dev_priv = vmw_priv(dev);
+
+       dev_priv->suspend_state = drm_atomic_helper_suspend(dev);
+       if (IS_ERR(dev_priv->suspend_state)) {
+               int ret = PTR_ERR(dev_priv->suspend_state);
+
+               DRM_ERROR("Failed kms suspend: %d\n", ret);
+               dev_priv->suspend_state = NULL;
+
+               return ret;
+       }
+
+       return 0;
+}
+
+
+/**
+ * vmw_kms_resume - Re-enable modesetting and restore state
+ *
+ * @dev: Pointer to the drm device
+ * Return: 0 on success. Negative error code on failure.
+ *
+ * State is resumed from a previous vmw_kms_suspend(). It's illegal
+ * to call this function without a previous vmw_kms_suspend().
+ */
+int vmw_kms_resume(struct drm_device *dev)
+{
+       struct vmw_private *dev_priv = vmw_priv(dev);
+       int ret;
+
+       if (WARN_ON(!dev_priv->suspend_state))
+               return 0;
+
+       ret = drm_atomic_helper_resume(dev, dev_priv->suspend_state);
+       dev_priv->suspend_state = NULL;
+
+       return ret;
+}