Rebecca Schultz Zavin | 050372e | 2012-06-07 16:36:44 -0700 | [diff] [blame] | 1 | /* |
| 2 | * drivers/gpu/ion/ion_mem_pool.c |
| 3 | * |
| 4 | * Copyright (C) 2011 Google, Inc. |
| 5 | * |
| 6 | * This software is licensed under the terms of the GNU General Public |
| 7 | * License version 2, as published by the Free Software Foundation, and |
| 8 | * may be copied, distributed, and modified under those terms. |
| 9 | * |
| 10 | * This program is distributed in the hope that it will be useful, |
| 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | * GNU General Public License for more details. |
| 14 | * |
| 15 | */ |
| 16 | |
Rebecca Schultz Zavin | 8afce33 | 2012-10-10 14:19:17 -0700 | [diff] [blame] | 17 | #include <linux/debugfs.h> |
Rebecca Schultz Zavin | 050372e | 2012-06-07 16:36:44 -0700 | [diff] [blame] | 18 | #include <linux/dma-mapping.h> |
| 19 | #include <linux/err.h> |
Rebecca Schultz Zavin | 8afce33 | 2012-10-10 14:19:17 -0700 | [diff] [blame] | 20 | #include <linux/fs.h> |
Rebecca Schultz Zavin | 050372e | 2012-06-07 16:36:44 -0700 | [diff] [blame] | 21 | #include <linux/list.h> |
Rebecca Schultz Zavin | 8afce33 | 2012-10-10 14:19:17 -0700 | [diff] [blame] | 22 | #include <linux/module.h> |
Rebecca Schultz Zavin | 050372e | 2012-06-07 16:36:44 -0700 | [diff] [blame] | 23 | #include <linux/slab.h> |
| 24 | #include <linux/shrinker.h> |
| 25 | #include "ion_priv.h" |
| 26 | |
Rebecca Schultz Zavin | 8afce33 | 2012-10-10 14:19:17 -0700 | [diff] [blame] | 27 | /* #define DEBUG_PAGE_POOL_SHRINKER */ |
| 28 | |
| 29 | static struct plist_head pools = PLIST_HEAD_INIT(pools); |
| 30 | static struct shrinker shrinker; |
| 31 | |
Rebecca Schultz Zavin | 050372e | 2012-06-07 16:36:44 -0700 | [diff] [blame] | 32 | struct ion_page_pool_item { |
| 33 | struct page *page; |
| 34 | struct list_head list; |
| 35 | }; |
| 36 | |
| 37 | static void *ion_page_pool_alloc_pages(struct ion_page_pool *pool) |
| 38 | { |
| 39 | struct page *page = alloc_pages(pool->gfp_mask, pool->order); |
| 40 | struct scatterlist sg; |
| 41 | |
| 42 | if (!page) |
| 43 | return NULL; |
| 44 | |
| 45 | sg_init_table(&sg, 1); |
| 46 | sg_set_page(&sg, page, PAGE_SIZE << pool->order, 0); |
| 47 | dma_sync_sg_for_device(NULL, &sg, 1, DMA_BIDIRECTIONAL); |
| 48 | |
| 49 | return page; |
| 50 | } |
| 51 | |
| 52 | static void ion_page_pool_free_pages(struct ion_page_pool *pool, |
| 53 | struct page *page) |
| 54 | { |
| 55 | __free_pages(page, pool->order); |
| 56 | } |
| 57 | |
| 58 | static int ion_page_pool_add(struct ion_page_pool *pool, struct page *page) |
| 59 | { |
| 60 | struct ion_page_pool_item *item; |
| 61 | |
| 62 | item = kmalloc(sizeof(struct ion_page_pool_item), GFP_KERNEL); |
| 63 | if (!item) |
| 64 | return -ENOMEM; |
Rebecca Schultz Zavin | d9994c8 | 2012-10-10 10:34:50 -0700 | [diff] [blame] | 65 | |
| 66 | mutex_lock(&pool->mutex); |
Rebecca Schultz Zavin | 050372e | 2012-06-07 16:36:44 -0700 | [diff] [blame] | 67 | item->page = page; |
Rebecca Schultz Zavin | 9fad2fe | 2012-10-08 23:01:23 -0700 | [diff] [blame] | 68 | if (PageHighMem(page)) { |
| 69 | list_add_tail(&item->list, &pool->high_items); |
| 70 | pool->high_count++; |
| 71 | } else { |
| 72 | list_add_tail(&item->list, &pool->low_items); |
| 73 | pool->low_count++; |
| 74 | } |
Rebecca Schultz Zavin | d9994c8 | 2012-10-10 10:34:50 -0700 | [diff] [blame] | 75 | mutex_unlock(&pool->mutex); |
Rebecca Schultz Zavin | 050372e | 2012-06-07 16:36:44 -0700 | [diff] [blame] | 76 | return 0; |
| 77 | } |
| 78 | |
Rebecca Schultz Zavin | 9fad2fe | 2012-10-08 23:01:23 -0700 | [diff] [blame] | 79 | static struct page *ion_page_pool_remove(struct ion_page_pool *pool, bool high) |
Rebecca Schultz Zavin | 050372e | 2012-06-07 16:36:44 -0700 | [diff] [blame] | 80 | { |
| 81 | struct ion_page_pool_item *item; |
| 82 | struct page *page; |
| 83 | |
Rebecca Schultz Zavin | 9fad2fe | 2012-10-08 23:01:23 -0700 | [diff] [blame] | 84 | if (high) { |
| 85 | BUG_ON(!pool->high_count); |
| 86 | item = list_first_entry(&pool->high_items, |
| 87 | struct ion_page_pool_item, list); |
| 88 | pool->high_count--; |
| 89 | } else { |
| 90 | BUG_ON(!pool->low_count); |
| 91 | item = list_first_entry(&pool->low_items, |
| 92 | struct ion_page_pool_item, list); |
| 93 | pool->low_count--; |
| 94 | } |
Rebecca Schultz Zavin | 050372e | 2012-06-07 16:36:44 -0700 | [diff] [blame] | 95 | |
Rebecca Schultz Zavin | 050372e | 2012-06-07 16:36:44 -0700 | [diff] [blame] | 96 | list_del(&item->list); |
| 97 | page = item->page; |
| 98 | kfree(item); |
Rebecca Schultz Zavin | 050372e | 2012-06-07 16:36:44 -0700 | [diff] [blame] | 99 | return page; |
| 100 | } |
| 101 | |
| 102 | void *ion_page_pool_alloc(struct ion_page_pool *pool) |
| 103 | { |
| 104 | struct page *page = NULL; |
| 105 | |
| 106 | BUG_ON(!pool); |
| 107 | |
| 108 | mutex_lock(&pool->mutex); |
Rebecca Schultz Zavin | 9fad2fe | 2012-10-08 23:01:23 -0700 | [diff] [blame] | 109 | if (pool->high_count) |
| 110 | page = ion_page_pool_remove(pool, true); |
| 111 | else if (pool->low_count) |
| 112 | page = ion_page_pool_remove(pool, false); |
Rebecca Schultz Zavin | 050372e | 2012-06-07 16:36:44 -0700 | [diff] [blame] | 113 | mutex_unlock(&pool->mutex); |
| 114 | |
Rebecca Schultz Zavin | 9fad2fe | 2012-10-08 23:01:23 -0700 | [diff] [blame] | 115 | if (!page) |
| 116 | page = ion_page_pool_alloc_pages(pool); |
| 117 | |
Rebecca Schultz Zavin | 050372e | 2012-06-07 16:36:44 -0700 | [diff] [blame] | 118 | return page; |
| 119 | } |
| 120 | |
| 121 | void ion_page_pool_free(struct ion_page_pool *pool, struct page* page) |
| 122 | { |
| 123 | int ret; |
| 124 | |
Rebecca Schultz Zavin | 050372e | 2012-06-07 16:36:44 -0700 | [diff] [blame] | 125 | ret = ion_page_pool_add(pool, page); |
| 126 | if (ret) |
| 127 | ion_page_pool_free_pages(pool, page); |
Rebecca Schultz Zavin | 050372e | 2012-06-07 16:36:44 -0700 | [diff] [blame] | 128 | } |
| 129 | |
Rebecca Schultz Zavin | 8afce33 | 2012-10-10 14:19:17 -0700 | [diff] [blame] | 130 | #ifdef DEBUG_PAGE_POOL_SHRINKER |
| 131 | static int debug_drop_pools_set(void *data, u64 val) |
| 132 | { |
| 133 | struct shrink_control sc; |
| 134 | int objs; |
| 135 | |
| 136 | sc.gfp_mask = -1; |
| 137 | sc.nr_to_scan = 0; |
| 138 | |
| 139 | if (!val) |
| 140 | return 0; |
| 141 | |
| 142 | objs = shrinker.shrink(&shrinker, &sc); |
| 143 | sc.nr_to_scan = objs; |
| 144 | |
| 145 | shrinker.shrink(&shrinker, &sc); |
| 146 | return 0; |
| 147 | } |
| 148 | |
| 149 | static int debug_drop_pools_get(void *data, u64 *val) |
| 150 | { |
| 151 | struct shrink_control sc; |
| 152 | int objs; |
| 153 | |
| 154 | sc.gfp_mask = -1; |
| 155 | sc.nr_to_scan = 0; |
| 156 | |
| 157 | objs = shrinker.shrink(&shrinker, &sc); |
| 158 | *val = objs; |
| 159 | return 0; |
| 160 | } |
| 161 | |
| 162 | DEFINE_SIMPLE_ATTRIBUTE(debug_drop_pools_fops, debug_drop_pools_get, |
| 163 | debug_drop_pools_set, "%llu\n"); |
| 164 | |
| 165 | static int debug_grow_pools_set(void *data, u64 val) |
| 166 | { |
| 167 | struct ion_page_pool *pool; |
| 168 | struct page *page; |
| 169 | |
| 170 | plist_for_each_entry(pool, &pools, list) { |
| 171 | if (val != pool->list.prio) |
| 172 | continue; |
| 173 | page = ion_page_pool_alloc_pages(pool); |
| 174 | if (page) |
| 175 | ion_page_pool_add(pool, page); |
| 176 | } |
| 177 | |
| 178 | return 0; |
| 179 | } |
| 180 | |
| 181 | DEFINE_SIMPLE_ATTRIBUTE(debug_grow_pools_fops, debug_drop_pools_get, |
| 182 | debug_grow_pools_set, "%llu\n"); |
| 183 | #endif |
| 184 | |
| 185 | static int ion_page_pool_total(bool high) |
| 186 | { |
| 187 | struct ion_page_pool *pool; |
| 188 | int total = 0; |
| 189 | |
| 190 | plist_for_each_entry(pool, &pools, list) { |
| 191 | total += high ? (pool->high_count + pool->low_count) * |
| 192 | (1 << pool->order) : |
| 193 | pool->low_count * (1 << pool->order); |
| 194 | } |
| 195 | return total; |
| 196 | } |
| 197 | |
Rebecca Schultz Zavin | 050372e | 2012-06-07 16:36:44 -0700 | [diff] [blame] | 198 | static int ion_page_pool_shrink(struct shrinker *shrinker, |
| 199 | struct shrink_control *sc) |
| 200 | { |
Rebecca Schultz Zavin | 8afce33 | 2012-10-10 14:19:17 -0700 | [diff] [blame] | 201 | struct ion_page_pool *pool; |
Rebecca Schultz Zavin | 050372e | 2012-06-07 16:36:44 -0700 | [diff] [blame] | 202 | int nr_freed = 0; |
| 203 | int i; |
Rebecca Schultz Zavin | 9fad2fe | 2012-10-08 23:01:23 -0700 | [diff] [blame] | 204 | bool high; |
Rebecca Schultz Zavin | 8afce33 | 2012-10-10 14:19:17 -0700 | [diff] [blame] | 205 | int nr_to_scan = sc->nr_to_scan; |
Rebecca Schultz Zavin | 9fad2fe | 2012-10-08 23:01:23 -0700 | [diff] [blame] | 206 | |
| 207 | if (sc->gfp_mask & __GFP_HIGHMEM) |
| 208 | high = true; |
Rebecca Schultz Zavin | 050372e | 2012-06-07 16:36:44 -0700 | [diff] [blame] | 209 | |
Rebecca Schultz Zavin | 8afce33 | 2012-10-10 14:19:17 -0700 | [diff] [blame] | 210 | if (nr_to_scan == 0) |
| 211 | return ion_page_pool_total(high); |
Rebecca Schultz Zavin | 050372e | 2012-06-07 16:36:44 -0700 | [diff] [blame] | 212 | |
Rebecca Schultz Zavin | 8afce33 | 2012-10-10 14:19:17 -0700 | [diff] [blame] | 213 | plist_for_each_entry(pool, &pools, list) { |
| 214 | for (i = 0; i < nr_to_scan; i++) { |
| 215 | struct page *page; |
Rebecca Schultz Zavin | 050372e | 2012-06-07 16:36:44 -0700 | [diff] [blame] | 216 | |
Rebecca Schultz Zavin | 8afce33 | 2012-10-10 14:19:17 -0700 | [diff] [blame] | 217 | mutex_lock(&pool->mutex); |
| 218 | if (high && pool->high_count) { |
| 219 | page = ion_page_pool_remove(pool, true); |
| 220 | } else if (pool->low_count) { |
| 221 | page = ion_page_pool_remove(pool, false); |
| 222 | } else { |
| 223 | mutex_unlock(&pool->mutex); |
| 224 | break; |
| 225 | } |
Rebecca Schultz Zavin | 9fad2fe | 2012-10-08 23:01:23 -0700 | [diff] [blame] | 226 | mutex_unlock(&pool->mutex); |
Rebecca Schultz Zavin | 8afce33 | 2012-10-10 14:19:17 -0700 | [diff] [blame] | 227 | ion_page_pool_free_pages(pool, page); |
| 228 | nr_freed += (1 << pool->order); |
Rebecca Schultz Zavin | 050372e | 2012-06-07 16:36:44 -0700 | [diff] [blame] | 229 | } |
Rebecca Schultz Zavin | 8afce33 | 2012-10-10 14:19:17 -0700 | [diff] [blame] | 230 | nr_to_scan -= i; |
Rebecca Schultz Zavin | 050372e | 2012-06-07 16:36:44 -0700 | [diff] [blame] | 231 | } |
Rebecca Schultz Zavin | 050372e | 2012-06-07 16:36:44 -0700 | [diff] [blame] | 232 | |
Rebecca Schultz Zavin | 8afce33 | 2012-10-10 14:19:17 -0700 | [diff] [blame] | 233 | return ion_page_pool_total(high); |
Rebecca Schultz Zavin | 050372e | 2012-06-07 16:36:44 -0700 | [diff] [blame] | 234 | } |
| 235 | |
| 236 | struct ion_page_pool *ion_page_pool_create(gfp_t gfp_mask, unsigned int order) |
| 237 | { |
| 238 | struct ion_page_pool *pool = kmalloc(sizeof(struct ion_page_pool), |
| 239 | GFP_KERNEL); |
| 240 | if (!pool) |
| 241 | return NULL; |
Rebecca Schultz Zavin | 9fad2fe | 2012-10-08 23:01:23 -0700 | [diff] [blame] | 242 | pool->high_count = 0; |
| 243 | pool->low_count = 0; |
| 244 | INIT_LIST_HEAD(&pool->low_items); |
| 245 | INIT_LIST_HEAD(&pool->high_items); |
Rebecca Schultz Zavin | 050372e | 2012-06-07 16:36:44 -0700 | [diff] [blame] | 246 | pool->gfp_mask = gfp_mask; |
| 247 | pool->order = order; |
| 248 | mutex_init(&pool->mutex); |
Rebecca Schultz Zavin | 8afce33 | 2012-10-10 14:19:17 -0700 | [diff] [blame] | 249 | plist_node_init(&pool->list, order); |
| 250 | plist_add(&pool->list, &pools); |
Rebecca Schultz Zavin | 050372e | 2012-06-07 16:36:44 -0700 | [diff] [blame] | 251 | |
| 252 | return pool; |
| 253 | } |
| 254 | |
| 255 | void ion_page_pool_destroy(struct ion_page_pool *pool) |
| 256 | { |
Rebecca Schultz Zavin | 8afce33 | 2012-10-10 14:19:17 -0700 | [diff] [blame] | 257 | plist_del(&pool->list, &pools); |
Rebecca Schultz Zavin | 050372e | 2012-06-07 16:36:44 -0700 | [diff] [blame] | 258 | kfree(pool); |
| 259 | } |
| 260 | |
Rebecca Schultz Zavin | 8afce33 | 2012-10-10 14:19:17 -0700 | [diff] [blame] | 261 | static int __init ion_page_pool_init(void) |
| 262 | { |
| 263 | shrinker.shrink = ion_page_pool_shrink; |
| 264 | shrinker.seeks = DEFAULT_SEEKS; |
| 265 | shrinker.batch = 0; |
| 266 | register_shrinker(&shrinker); |
| 267 | #ifdef DEBUG_PAGE_POOL_SHRINKER |
| 268 | debugfs_create_file("ion_pools_shrink", 0644, NULL, NULL, |
| 269 | &debug_drop_pools_fops); |
| 270 | debugfs_create_file("ion_pools_grow", 0644, NULL, NULL, |
| 271 | &debug_grow_pools_fops); |
| 272 | #endif |
| 273 | return 0; |
| 274 | } |
| 275 | |
| 276 | static void __exit ion_page_pool_exit(void) |
| 277 | { |
| 278 | unregister_shrinker(&shrinker); |
| 279 | } |
| 280 | |
| 281 | module_init(ion_page_pool_init); |
| 282 | module_exit(ion_page_pool_exit); |