1 /* 2 * Copyright (C) 2016 Red Hat 3 * Author: Rob Clark <robdclark@gmail.com> 4 * 5 * This program is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 as published by 7 * the Free Software Foundation. 8 * 9 * This program is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 12 * more details. 13 * 14 * You should have received a copy of the GNU General Public License along with 15 * this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 #include "msm_drv.h" 19 #include "msm_gem.h" 20 21 static bool msm_gem_shrinker_lock(struct drm_device *dev, bool *unlock) 22 { 23 switch (mutex_trylock_recursive(&dev->struct_mutex)) { 24 case MUTEX_TRYLOCK_FAILED: 25 return false; 26 27 case MUTEX_TRYLOCK_SUCCESS: 28 *unlock = true; 29 return true; 30 31 case MUTEX_TRYLOCK_RECURSIVE: 32 *unlock = false; 33 return true; 34 } 35 36 BUG(); 37 } 38 39 static unsigned long 40 msm_gem_shrinker_count(struct shrinker *shrinker, struct shrink_control *sc) 41 { 42 struct msm_drm_private *priv = 43 container_of(shrinker, struct msm_drm_private, shrinker); 44 struct drm_device *dev = priv->dev; 45 struct msm_gem_object *msm_obj; 46 unsigned long count = 0; 47 bool unlock; 48 49 if (!msm_gem_shrinker_lock(dev, &unlock)) 50 return 0; 51 52 list_for_each_entry(msm_obj, &priv->inactive_list, mm_list) { 53 if (is_purgeable(msm_obj)) 54 count += msm_obj->base.size >> PAGE_SHIFT; 55 } 56 57 if (unlock) 58 mutex_unlock(&dev->struct_mutex); 59 60 return count; 61 } 62 63 static unsigned long 64 msm_gem_shrinker_scan(struct shrinker *shrinker, struct shrink_control *sc) 65 { 66 struct msm_drm_private *priv = 67 container_of(shrinker, struct msm_drm_private, shrinker); 68 struct drm_device *dev = priv->dev; 69 struct msm_gem_object *msm_obj; 70 unsigned long freed = 0; 71 bool unlock; 72 73 if (!msm_gem_shrinker_lock(dev, &unlock)) 74 return SHRINK_STOP; 75 76 list_for_each_entry(msm_obj, &priv->inactive_list, mm_list) { 77 if (freed >= sc->nr_to_scan) 78 break; 79 if (is_purgeable(msm_obj)) { 80 msm_gem_purge(&msm_obj->base); 81 freed += msm_obj->base.size >> PAGE_SHIFT; 82 } 83 } 84 85 if (unlock) 86 mutex_unlock(&dev->struct_mutex); 87 88 if (freed > 0) 89 pr_info_ratelimited("Purging %lu bytes\n", freed << PAGE_SHIFT); 90 91 return freed; 92 } 93 94 static int 95 msm_gem_shrinker_vmap(struct notifier_block *nb, unsigned long event, void *ptr) 96 { 97 struct msm_drm_private *priv = 98 container_of(nb, struct msm_drm_private, vmap_notifier); 99 struct drm_device *dev = priv->dev; 100 struct msm_gem_object *msm_obj; 101 unsigned unmapped = 0; 102 bool unlock; 103 104 if (!msm_gem_shrinker_lock(dev, &unlock)) 105 return NOTIFY_DONE; 106 107 list_for_each_entry(msm_obj, &priv->inactive_list, mm_list) { 108 if (is_vunmapable(msm_obj)) { 109 msm_gem_vunmap(&msm_obj->base); 110 /* since we don't know any better, lets bail after a few 111 * and if necessary the shrinker will be invoked again. 112 * Seems better than unmapping *everything* 113 */ 114 if (++unmapped >= 15) 115 break; 116 } 117 } 118 119 if (unlock) 120 mutex_unlock(&dev->struct_mutex); 121 122 *(unsigned long *)ptr += unmapped; 123 124 if (unmapped > 0) 125 pr_info_ratelimited("Purging %u vmaps\n", unmapped); 126 127 return NOTIFY_DONE; 128 } 129 130 /** 131 * msm_gem_shrinker_init - Initialize msm shrinker 132 * @dev_priv: msm device 133 * 134 * This function registers and sets up the msm shrinker. 135 */ 136 void msm_gem_shrinker_init(struct drm_device *dev) 137 { 138 struct msm_drm_private *priv = dev->dev_private; 139 priv->shrinker.count_objects = msm_gem_shrinker_count; 140 priv->shrinker.scan_objects = msm_gem_shrinker_scan; 141 priv->shrinker.seeks = DEFAULT_SEEKS; 142 WARN_ON(register_shrinker(&priv->shrinker)); 143 144 priv->vmap_notifier.notifier_call = msm_gem_shrinker_vmap; 145 WARN_ON(register_vmap_purge_notifier(&priv->vmap_notifier)); 146 } 147 148 /** 149 * msm_gem_shrinker_cleanup - Clean up msm shrinker 150 * @dev_priv: msm device 151 * 152 * This function unregisters the msm shrinker. 153 */ 154 void msm_gem_shrinker_cleanup(struct drm_device *dev) 155 { 156 struct msm_drm_private *priv = dev->dev_private; 157 158 if (priv->shrinker.nr_deferred) { 159 WARN_ON(unregister_vmap_purge_notifier(&priv->vmap_notifier)); 160 unregister_shrinker(&priv->shrinker); 161 } 162 } 163