drm/msm: shrinker support
authorRob Clark <robdclark@gmail.com>
Tue, 17 May 2016 20:19:32 +0000 (16:19 -0400)
committerRob Clark <robdclark@gmail.com>
Sat, 16 Jul 2016 14:09:06 +0000 (10:09 -0400)
For a first step, only purge obj->madv==DONTNEED objects.  We could be
more agressive and next try unpinning inactive objects..  but that is
only useful if you have swap.

Signed-off-by: Rob Clark <robdclark@gmail.com>
drivers/gpu/drm/msm/Makefile
drivers/gpu/drm/msm/msm_drv.c
drivers/gpu/drm/msm/msm_drv.h
drivers/gpu/drm/msm/msm_gem.c
drivers/gpu/drm/msm/msm_gem.h
drivers/gpu/drm/msm/msm_gem_shrinker.c [new file with mode: 0644]

index 4727d04..4e2806c 100644 (file)
@@ -46,6 +46,7 @@ msm-y := \
        msm_fence.o \
        msm_gem.o \
        msm_gem_prime.o \
+       msm_gem_shrinker.o \
        msm_gem_submit.o \
        msm_gpu.o \
        msm_iommu.o \
index 00881f3..f3b8f69 100644 (file)
@@ -195,6 +195,8 @@ static int msm_drm_uninit(struct device *dev)
                kfree(vbl_ev);
        }
 
+       msm_gem_shrinker_cleanup(ddev);
+
        drm_kms_helper_poll_fini(ddev);
 
        drm_dev_unregister(ddev);
@@ -350,6 +352,7 @@ static int msm_drm_init(struct device *dev, struct drm_driver *drv)
        }
 
        ddev->dev_private = priv;
+       priv->dev = ddev;
 
        ret = msm_mdss_init(ddev);
        if (ret) {
@@ -382,6 +385,8 @@ static int msm_drm_init(struct device *dev, struct drm_driver *drv)
        if (ret)
                goto fail;
 
+       msm_gem_shrinker_init(ddev);
+
        switch (get_mdp_ver(pdev)) {
        case 4:
                kms = mdp4_kms_init(ddev);
index a49d7fd..4755894 100644 (file)
@@ -78,6 +78,8 @@ struct msm_vblank_ctrl {
 
 struct msm_drm_private {
 
+       struct drm_device *dev;
+
        struct msm_kms *kms;
 
        /* subordinate devices, if present: */
@@ -151,6 +153,8 @@ struct msm_drm_private {
                struct drm_mm mm;
        } vram;
 
+       struct shrinker shrinker;
+
        struct msm_vblank_ctrl vblank_ctrl;
 };
 
@@ -169,6 +173,9 @@ void msm_gem_submit_free(struct msm_gem_submit *submit);
 int msm_ioctl_gem_submit(struct drm_device *dev, void *data,
                struct drm_file *file);
 
+void msm_gem_shrinker_init(struct drm_device *dev);
+void msm_gem_shrinker_cleanup(struct drm_device *dev);
+
 int msm_gem_mmap_obj(struct drm_gem_object *obj,
                        struct vm_area_struct *vma);
 int msm_gem_mmap(struct file *filp, struct vm_area_struct *vma);
@@ -196,6 +203,7 @@ void msm_gem_prime_unpin(struct drm_gem_object *obj);
 void *msm_gem_vaddr_locked(struct drm_gem_object *obj);
 void *msm_gem_vaddr(struct drm_gem_object *obj);
 int msm_gem_madvise(struct drm_gem_object *obj, unsigned madv);
+void msm_gem_purge(struct drm_gem_object *obj);
 int msm_gem_sync_object(struct drm_gem_object *obj,
                struct msm_fence_context *fctx, bool exclusive);
 void msm_gem_move_to_active(struct drm_gem_object *obj,
index 2636c27..444d0b5 100644 (file)
@@ -448,6 +448,38 @@ int msm_gem_madvise(struct drm_gem_object *obj, unsigned madv)
        return (msm_obj->madv != __MSM_MADV_PURGED);
 }
 
+void msm_gem_purge(struct drm_gem_object *obj)
+{
+       struct drm_device *dev = obj->dev;
+       struct msm_gem_object *msm_obj = to_msm_bo(obj);
+
+       WARN_ON(!mutex_is_locked(&dev->struct_mutex));
+       WARN_ON(!is_purgeable(msm_obj));
+       WARN_ON(obj->import_attach);
+
+       put_iova(obj);
+
+       vunmap(msm_obj->vaddr);
+       msm_obj->vaddr = NULL;
+
+       put_pages(obj);
+
+       msm_obj->madv = __MSM_MADV_PURGED;
+
+       drm_vma_node_unmap(&obj->vma_node, dev->anon_inode->i_mapping);
+       drm_gem_free_mmap_offset(obj);
+
+       /* Our goal here is to return as much of the memory as
+        * is possible back to the system as we are called from OOM.
+        * To do this we must instruct the shmfs to drop all of its
+        * backing pages, *now*.
+        */
+       shmem_truncate_range(file_inode(obj->filp), 0, (loff_t)-1);
+
+       invalidate_mapping_pages(file_inode(obj->filp)->i_mapping,
+                       0, (loff_t)-1);
+}
+
 /* must be called before _move_to_active().. */
 int msm_gem_sync_object(struct drm_gem_object *obj,
                struct msm_fence_context *fctx, bool exclusive)
index fa8e1f1..631dab5 100644 (file)
@@ -77,6 +77,12 @@ static inline bool is_active(struct msm_gem_object *msm_obj)
        return msm_obj->gpu != NULL;
 }
 
+static inline bool is_purgeable(struct msm_gem_object *msm_obj)
+{
+       return (msm_obj->madv == MSM_MADV_DONTNEED) && msm_obj->sgt &&
+                       !msm_obj->base.dma_buf && !msm_obj->base.import_attach;
+}
+
 #define MAX_CMDS 4
 
 /* Created per submit-ioctl, to track bo's and cmdstream bufs, etc,
diff --git a/drivers/gpu/drm/msm/msm_gem_shrinker.c b/drivers/gpu/drm/msm/msm_gem_shrinker.c
new file mode 100644 (file)
index 0000000..70fba9b
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2016 Red Hat
+ * Author: Rob Clark <robdclark@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "msm_drv.h"
+#include "msm_gem.h"
+
+static bool mutex_is_locked_by(struct mutex *mutex, struct task_struct *task)
+{
+       if (!mutex_is_locked(mutex))
+               return false;
+
+#if defined(CONFIG_SMP) || defined(CONFIG_DEBUG_MUTEXES)
+       return mutex->owner == task;
+#else
+       /* Since UP may be pre-empted, we cannot assume that we own the lock */
+       return false;
+#endif
+}
+
+static bool msm_gem_shrinker_lock(struct drm_device *dev, bool *unlock)
+{
+       if (!mutex_trylock(&dev->struct_mutex)) {
+               if (!mutex_is_locked_by(&dev->struct_mutex, current))
+                       return false;
+               *unlock = false;
+       } else {
+               *unlock = true;
+       }
+
+       return true;
+}
+
+
+static unsigned long
+msm_gem_shrinker_count(struct shrinker *shrinker, struct shrink_control *sc)
+{
+       struct msm_drm_private *priv =
+               container_of(shrinker, struct msm_drm_private, shrinker);
+       struct drm_device *dev = priv->dev;
+       struct msm_gem_object *msm_obj;
+       unsigned long count = 0;
+       bool unlock;
+
+       if (!msm_gem_shrinker_lock(dev, &unlock))
+               return 0;
+
+       list_for_each_entry(msm_obj, &priv->inactive_list, mm_list) {
+               if (is_purgeable(msm_obj))
+                       count += msm_obj->base.size >> PAGE_SHIFT;
+       }
+
+       if (unlock)
+               mutex_unlock(&dev->struct_mutex);
+
+       return count;
+}
+
+static unsigned long
+msm_gem_shrinker_scan(struct shrinker *shrinker, struct shrink_control *sc)
+{
+       struct msm_drm_private *priv =
+               container_of(shrinker, struct msm_drm_private, shrinker);
+       struct drm_device *dev = priv->dev;
+       struct msm_gem_object *msm_obj;
+       unsigned long freed = 0;
+       bool unlock;
+
+       if (!msm_gem_shrinker_lock(dev, &unlock))
+               return SHRINK_STOP;
+
+       list_for_each_entry(msm_obj, &priv->inactive_list, mm_list) {
+               if (freed >= sc->nr_to_scan)
+                       break;
+               if (is_purgeable(msm_obj)) {
+                       msm_gem_purge(&msm_obj->base);
+                       freed += msm_obj->base.size >> PAGE_SHIFT;
+               }
+       }
+
+       if (unlock)
+               mutex_unlock(&dev->struct_mutex);
+
+       if (freed > 0)
+               pr_info_ratelimited("Purging %lu bytes\n", freed << PAGE_SHIFT);
+
+       return freed;
+}
+
+/**
+ * msm_gem_shrinker_init - Initialize msm shrinker
+ * @dev_priv: msm device
+ *
+ * This function registers and sets up the msm shrinker.
+ */
+void msm_gem_shrinker_init(struct drm_device *dev)
+{
+       struct msm_drm_private *priv = dev->dev_private;
+       priv->shrinker.count_objects = msm_gem_shrinker_count;
+       priv->shrinker.scan_objects = msm_gem_shrinker_scan;
+       priv->shrinker.seeks = DEFAULT_SEEKS;
+       WARN_ON(register_shrinker(&priv->shrinker));
+}
+
+/**
+ * msm_gem_shrinker_cleanup - Clean up msm shrinker
+ * @dev_priv: msm device
+ *
+ * This function unregisters the msm shrinker.
+ */
+void msm_gem_shrinker_cleanup(struct drm_device *dev)
+{
+       struct msm_drm_private *priv = dev->dev_private;
+       unregister_shrinker(&priv->shrinker);
+}