123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766 |
- // SPDX-License-Identifier: GPL-2.0-only
- /*
- * Copyright (c) 2016-2021, The Linux Foundation. All rights reserved.
- * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved.
- */
- #include <asm/cacheflush.h>
- #include <linux/debugfs.h>
- #include <linux/highmem.h>
- #include <linux/mempool.h>
- #include <linux/of.h>
- #include <linux/scatterlist.h>
- #include <linux/version.h>
- #include "kgsl_debugfs.h"
- #include "kgsl_device.h"
- #include "kgsl_pool.h"
- #include "kgsl_sharedmem.h"
- #include "kgsl_trace.h"
- #ifdef CONFIG_QCOM_KGSL_SORT_POOL
- struct kgsl_pool_page_entry {
- phys_addr_t physaddr;
- struct page *page;
- struct rb_node node;
- };
- static struct kmem_cache *addr_page_cache;
- /**
- * struct kgsl_page_pool - Structure to hold information for the pool
- * @pool_order: Page order describing the size of the page
- * @page_count: Number of pages currently present in the pool
- * @reserved_pages: Number of pages reserved at init for the pool
- * @list_lock: Spinlock for page list in the pool
- * @pool_rbtree: RB tree with all pages held/reserved in this pool
- * @mempool: Mempool to pre-allocate tracking structs for pages in this pool
- * @debug_root: Pointer to the debugfs root for this pool
- * @max_pages: Limit on number of pages this pool can hold
- */
- struct kgsl_page_pool {
- unsigned int pool_order;
- unsigned int page_count;
- unsigned int reserved_pages;
- spinlock_t list_lock;
- struct rb_root pool_rbtree;
- mempool_t *mempool;
- struct dentry *debug_root;
- unsigned int max_pages;
- };
- static void *_pool_entry_alloc(gfp_t gfp_mask, void *arg)
- {
- return kmem_cache_alloc(addr_page_cache, gfp_mask);
- }
- static void _pool_entry_free(void *element, void *arg)
- {
- return kmem_cache_free(addr_page_cache, element);
- }
- static int
- __kgsl_pool_add_page(struct kgsl_page_pool *pool, struct page *p)
- {
- struct rb_node **node, *parent = NULL;
- struct kgsl_pool_page_entry *new_page, *entry;
- gfp_t gfp_mask = GFP_KERNEL & ~__GFP_DIRECT_RECLAIM;
- new_page = pool->mempool ? mempool_alloc(pool->mempool, gfp_mask) :
- kmem_cache_alloc(addr_page_cache, gfp_mask);
- if (new_page == NULL)
- return -ENOMEM;
- spin_lock(&pool->list_lock);
- node = &pool->pool_rbtree.rb_node;
- new_page->physaddr = page_to_phys(p);
- new_page->page = p;
- while (*node != NULL) {
- parent = *node;
- entry = rb_entry(parent, struct kgsl_pool_page_entry, node);
- if (new_page->physaddr < entry->physaddr)
- node = &parent->rb_left;
- else
- node = &parent->rb_right;
- }
- rb_link_node(&new_page->node, parent, node);
- rb_insert_color(&new_page->node, &pool->pool_rbtree);
- /*
- * page_count may be read without the list_lock held. Use WRITE_ONCE
- * to avoid compiler optimizations that may break consistency.
- */
- ASSERT_EXCLUSIVE_WRITER(pool->page_count);
- WRITE_ONCE(pool->page_count, pool->page_count + 1);
- spin_unlock(&pool->list_lock);
- return 0;
- }
- static struct page *
- __kgsl_pool_get_page(struct kgsl_page_pool *pool)
- {
- struct rb_node *node;
- struct kgsl_pool_page_entry *entry;
- struct page *p;
- node = rb_first(&pool->pool_rbtree);
- if (!node)
- return NULL;
- entry = rb_entry(node, struct kgsl_pool_page_entry, node);
- p = entry->page;
- rb_erase(&entry->node, &pool->pool_rbtree);
- if (pool->mempool)
- mempool_free(entry, pool->mempool);
- else
- kmem_cache_free(addr_page_cache, entry);
- /*
- * page_count may be read without the list_lock held. Use WRITE_ONCE
- * to avoid compiler optimizations that may break consistency.
- */
- ASSERT_EXCLUSIVE_WRITER(pool->page_count);
- WRITE_ONCE(pool->page_count, pool->page_count - 1);
- return p;
- }
- static void kgsl_pool_list_init(struct kgsl_page_pool *pool)
- {
- pool->pool_rbtree = RB_ROOT;
- }
- static void kgsl_pool_cache_init(void)
- {
- addr_page_cache = KMEM_CACHE(kgsl_pool_page_entry, 0);
- }
- static void kgsl_pool_cache_destroy(void)
- {
- kmem_cache_destroy(addr_page_cache);
- }
- static void kgsl_destroy_page_pool(struct kgsl_page_pool *pool)
- {
- mempool_destroy(pool->mempool);
- }
- #else
- /**
- * struct kgsl_page_pool - Structure to hold information for the pool
- * @pool_order: Page order describing the size of the page
- * @page_count: Number of pages currently present in the pool
- * @reserved_pages: Number of pages reserved at init for the pool
- * @list_lock: Spinlock for page list in the pool
- * @page_list: List of pages held/reserved in this pool
- * @debug_root: Pointer to the debugfs root for this pool
- * @max_pages: Limit on number of pages this pool can hold
- */
- struct kgsl_page_pool {
- unsigned int pool_order;
- unsigned int page_count;
- unsigned int reserved_pages;
- spinlock_t list_lock;
- struct list_head page_list;
- struct dentry *debug_root;
- unsigned int max_pages;
- };
- static int
- __kgsl_pool_add_page(struct kgsl_page_pool *pool, struct page *p)
- {
- spin_lock(&pool->list_lock);
- list_add_tail(&p->lru, &pool->page_list);
- /*
- * page_count may be read without the list_lock held. Use WRITE_ONCE
- * to avoid compiler optimizations that may break consistency.
- */
- ASSERT_EXCLUSIVE_WRITER(pool->page_count);
- WRITE_ONCE(pool->page_count, pool->page_count + 1);
- spin_unlock(&pool->list_lock);
- return 0;
- }
- static struct page *
- __kgsl_pool_get_page(struct kgsl_page_pool *pool)
- {
- struct page *p;
- p = list_first_entry_or_null(&pool->page_list, struct page, lru);
- if (p) {
- /*
- * page_count may be read without the list_lock held. Use
- * WRITE_ONCE to avoid compiler optimizations that may break
- * consistency.
- */
- ASSERT_EXCLUSIVE_WRITER(pool->page_count);
- WRITE_ONCE(pool->page_count, pool->page_count - 1);
- list_del(&p->lru);
- }
- return p;
- }
- static void kgsl_pool_list_init(struct kgsl_page_pool *pool)
- {
- INIT_LIST_HEAD(&pool->page_list);
- }
- static void kgsl_pool_cache_init(void)
- {
- }
- static void kgsl_pool_cache_destroy(void)
- {
- }
- static void kgsl_destroy_page_pool(struct kgsl_page_pool *pool)
- {
- }
- #endif
- static struct kgsl_page_pool kgsl_pools[6];
- static int kgsl_num_pools;
- static int kgsl_pool_max_pages;
- /* Return the index of the pool for the specified order */
- static int kgsl_get_pool_index(int order)
- {
- int i;
- for (i = 0; i < kgsl_num_pools; i++) {
- if (kgsl_pools[i].pool_order == order)
- return i;
- }
- return -EINVAL;
- }
- /* Returns KGSL pool corresponding to input page order*/
- static struct kgsl_page_pool *
- _kgsl_get_pool_from_order(int order)
- {
- int index = kgsl_get_pool_index(order);
- return index >= 0 ? &kgsl_pools[index] : NULL;
- }
- /* Add a page to specified pool */
- static void
- _kgsl_pool_add_page(struct kgsl_page_pool *pool, struct page *p)
- {
- if (!p)
- return;
- /*
- * Sanity check to make sure we don't re-pool a page that
- * somebody else has a reference to.
- */
- if (WARN_ON(unlikely(page_count(p) > 1))) {
- __free_pages(p, pool->pool_order);
- return;
- }
- if (__kgsl_pool_add_page(pool, p)) {
- __free_pages(p, pool->pool_order);
- trace_kgsl_pool_free_page(pool->pool_order);
- return;
- }
- /* Use READ_ONCE to read page_count without holding list_lock */
- trace_kgsl_pool_add_page(pool->pool_order, READ_ONCE(pool->page_count));
- mod_node_page_state(page_pgdat(p), NR_KERNEL_MISC_RECLAIMABLE,
- (1 << pool->pool_order));
- }
- /* Returns a page from specified pool */
- static struct page *
- _kgsl_pool_get_page(struct kgsl_page_pool *pool)
- {
- struct page *p = NULL;
- spin_lock(&pool->list_lock);
- p = __kgsl_pool_get_page(pool);
- spin_unlock(&pool->list_lock);
- if (p != NULL) {
- /* Use READ_ONCE to read page_count without holding list_lock */
- trace_kgsl_pool_get_page(pool->pool_order,
- READ_ONCE(pool->page_count));
- mod_node_page_state(page_pgdat(p), NR_KERNEL_MISC_RECLAIMABLE,
- -(1 << pool->pool_order));
- }
- return p;
- }
- int kgsl_pool_size_total(void)
- {
- int i;
- int total = 0;
- for (i = 0; i < kgsl_num_pools; i++) {
- struct kgsl_page_pool *kgsl_pool = &kgsl_pools[i];
- spin_lock(&kgsl_pool->list_lock);
- total += kgsl_pool->page_count * (1 << kgsl_pool->pool_order);
- spin_unlock(&kgsl_pool->list_lock);
- }
- return total;
- }
- /* Returns the total number of pages in all pools excluding reserved pages */
- static unsigned long kgsl_pool_size_nonreserved(void)
- {
- int i;
- unsigned long total = 0;
- for (i = 0; i < kgsl_num_pools; i++) {
- struct kgsl_page_pool *pool = &kgsl_pools[i];
- spin_lock(&pool->list_lock);
- if (pool->page_count > pool->reserved_pages)
- total += (pool->page_count - pool->reserved_pages) *
- (1 << pool->pool_order);
- spin_unlock(&pool->list_lock);
- }
- return total;
- }
- /*
- * Returns a page from specified pool only if pool
- * currently holds more number of pages than reserved
- * pages.
- */
- static struct page *
- _kgsl_pool_get_nonreserved_page(struct kgsl_page_pool *pool)
- {
- struct page *p = NULL;
- spin_lock(&pool->list_lock);
- if (pool->page_count <= pool->reserved_pages) {
- spin_unlock(&pool->list_lock);
- return NULL;
- }
- p = __kgsl_pool_get_page(pool);
- spin_unlock(&pool->list_lock);
- if (p != NULL) {
- /* Use READ_ONCE to read page_count without holding list_lock */
- trace_kgsl_pool_get_page(pool->pool_order,
- READ_ONCE(pool->page_count));
- mod_node_page_state(page_pgdat(p), NR_KERNEL_MISC_RECLAIMABLE,
- -(1 << pool->pool_order));
- }
- return p;
- }
- /*
- * This will shrink the specified pool by num_pages or by
- * (page_count - reserved_pages), whichever is smaller.
- */
- static unsigned int
- _kgsl_pool_shrink(struct kgsl_page_pool *pool,
- unsigned int num_pages, bool exit)
- {
- int j;
- unsigned int pcount = 0;
- struct page *(*get_page)(struct kgsl_page_pool *) =
- _kgsl_pool_get_nonreserved_page;
- if (pool == NULL || num_pages == 0)
- return pcount;
- num_pages = (num_pages + (1 << pool->pool_order) - 1) >>
- pool->pool_order;
- /* This is to ensure that we free reserved pages */
- if (exit)
- get_page = _kgsl_pool_get_page;
- for (j = 0; j < num_pages; j++) {
- struct page *page = get_page(pool);
- if (!page)
- break;
- __free_pages(page, pool->pool_order);
- pcount += (1 << pool->pool_order);
- trace_kgsl_pool_free_page(pool->pool_order);
- }
- return pcount;
- }
- /*
- * This function removes number of pages specified by
- * target_pages from the total pool size.
- *
- * Remove target_pages from the pool, starting from higher order pool.
- */
- static unsigned long
- kgsl_pool_reduce(int target_pages, bool exit)
- {
- int i, ret;
- unsigned long pcount = 0;
- for (i = (kgsl_num_pools - 1); i >= 0; i--) {
- if (target_pages <= 0)
- return pcount;
- /* Remove target_pages pages from this pool */
- ret = _kgsl_pool_shrink(&kgsl_pools[i], target_pages, exit);
- target_pages -= ret;
- pcount += ret;
- }
- return pcount;
- }
- void kgsl_pool_free_pages(struct page **pages, unsigned int pcount)
- {
- int i;
- if (!pages)
- return;
- for (i = 0; i < pcount;) {
- /*
- * Free each page or compound page group individually.
- */
- struct page *p = pages[i];
- i += 1 << compound_order(p);
- kgsl_pool_free_page(p);
- }
- }
- static int kgsl_pool_get_retry_order(unsigned int order)
- {
- int i;
- for (i = kgsl_num_pools-1; i > 0; i--)
- if (order >= kgsl_pools[i].pool_order)
- return kgsl_pools[i].pool_order;
- return 0;
- }
- /*
- * Return true if the pool of specified page size is supported
- * or no pools are supported otherwise return false.
- */
- static bool kgsl_pool_available(unsigned int page_size)
- {
- int order = get_order(page_size);
- if (!kgsl_num_pools)
- return true;
- return (kgsl_get_pool_index(order) >= 0);
- }
- u32 kgsl_get_page_size(size_t size, unsigned int align)
- {
- u32 pool;
- for (pool = rounddown_pow_of_two(size); pool > PAGE_SIZE; pool >>= 1)
- if ((align >= ilog2(pool)) && (size >= pool) &&
- kgsl_pool_available(pool))
- return pool;
- return PAGE_SIZE;
- }
- int kgsl_pool_alloc_page(int *page_size, struct page **pages,
- unsigned int pages_len, unsigned int *align,
- struct device *dev)
- {
- int j;
- int pcount = 0;
- struct kgsl_page_pool *pool;
- struct page *page = NULL;
- struct page *p = NULL;
- int order = get_order(*page_size);
- int pool_idx;
- size_t size = 0;
- if ((pages == NULL) || pages_len < (*page_size >> PAGE_SHIFT))
- return -EINVAL;
- /* If the pool is not configured get pages from the system */
- if (!kgsl_num_pools) {
- gfp_t gfp_mask = kgsl_gfp_mask(order);
- page = alloc_pages(gfp_mask, order);
- if (page == NULL) {
- /* Retry with lower order pages */
- if (order > 0) {
- size = PAGE_SIZE << --order;
- goto eagain;
- } else
- return -ENOMEM;
- }
- trace_kgsl_pool_alloc_page_system(order);
- goto done;
- }
- pool = _kgsl_get_pool_from_order(order);
- if (pool == NULL) {
- /* Retry with lower order pages */
- if (order > 0) {
- size = PAGE_SIZE << kgsl_pool_get_retry_order(order);
- goto eagain;
- } else {
- /*
- * Fall back to direct allocation in case
- * pool with zero order is not present
- */
- gfp_t gfp_mask = kgsl_gfp_mask(order);
- page = alloc_pages(gfp_mask, order);
- if (page == NULL)
- return -ENOMEM;
- trace_kgsl_pool_alloc_page_system(order);
- goto done;
- }
- }
- pool_idx = kgsl_get_pool_index(order);
- page = _kgsl_pool_get_page(pool);
- /* Allocate a new page if not allocated from pool */
- if (page == NULL) {
- gfp_t gfp_mask = kgsl_gfp_mask(order);
- page = alloc_pages(gfp_mask, order);
- if (!page) {
- if (pool_idx > 0) {
- /* Retry with lower order pages */
- size = PAGE_SIZE <<
- kgsl_pools[pool_idx-1].pool_order;
- goto eagain;
- } else
- return -ENOMEM;
- }
- trace_kgsl_pool_alloc_page_system(order);
- }
- done:
- kgsl_zero_page(page, order, dev);
- for (j = 0; j < (*page_size >> PAGE_SHIFT); j++) {
- p = nth_page(page, j);
- pages[pcount] = p;
- pcount++;
- }
- return pcount;
- eagain:
- trace_kgsl_pool_try_page_lower(get_order(*page_size));
- *page_size = kgsl_get_page_size(size, ilog2(size));
- *align = ilog2(*page_size);
- return -EAGAIN;
- }
- void kgsl_pool_free_page(struct page *page)
- {
- struct kgsl_page_pool *pool;
- int page_order;
- if (page == NULL)
- return;
- page_order = compound_order(page);
- if (!kgsl_pool_max_pages ||
- (kgsl_pool_size_total() < kgsl_pool_max_pages)) {
- pool = _kgsl_get_pool_from_order(page_order);
- /* Use READ_ONCE to read page_count without holding list_lock */
- if (pool && (READ_ONCE(pool->page_count) < pool->max_pages)) {
- _kgsl_pool_add_page(pool, page);
- return;
- }
- }
- /* Give back to system as not added to pool */
- __free_pages(page, page_order);
- trace_kgsl_pool_free_page(page_order);
- }
- /* Functions for the shrinker */
- static unsigned long
- kgsl_pool_shrink_scan_objects(struct shrinker *shrinker,
- struct shrink_control *sc)
- {
- /* sc->nr_to_scan represents number of pages to be removed*/
- unsigned long pcount = kgsl_pool_reduce(sc->nr_to_scan, false);
- /* If pools are exhausted return SHRINK_STOP */
- return pcount ? pcount : SHRINK_STOP;
- }
- static unsigned long
- kgsl_pool_shrink_count_objects(struct shrinker *shrinker,
- struct shrink_control *sc)
- {
- /*
- * Return non-reserved pool size as we don't
- * want shrinker to free reserved pages.
- */
- return kgsl_pool_size_nonreserved();
- }
- /* Shrinker callback data*/
- static struct shrinker kgsl_pool_shrinker = {
- .count_objects = kgsl_pool_shrink_count_objects,
- .scan_objects = kgsl_pool_shrink_scan_objects,
- .seeks = DEFAULT_SEEKS,
- .batch = 0,
- };
- int kgsl_pool_reserved_get(void *data, u64 *val)
- {
- struct kgsl_page_pool *pool = data;
- *val = (u64) pool->reserved_pages;
- return 0;
- }
- int kgsl_pool_page_count_get(void *data, u64 *val)
- {
- struct kgsl_page_pool *pool = data;
- /* Use READ_ONCE to read page_count without holding list_lock */
- *val = (u64) READ_ONCE(pool->page_count);
- return 0;
- }
- static void kgsl_pool_reserve_pages(struct kgsl_page_pool *pool,
- struct device_node *node)
- {
- u32 reserved = 0;
- int i;
- of_property_read_u32(node, "qcom,mempool-reserved", &reserved);
- reserved = min_t(u32, reserved, pool->max_pages);
- /* Limit the total number of reserved pages to 4096 */
- pool->reserved_pages = min_t(u32, reserved, 4096);
- #if IS_ENABLED(CONFIG_QCOM_KGSL_SORT_POOL)
- /*
- * Pre-allocate tracking structs for reserved_pages so that
- * the pool can hold them even in low memory conditions
- */
- pool->mempool = mempool_create(pool->reserved_pages,
- _pool_entry_alloc, _pool_entry_free, NULL);
- #endif
- for (i = 0; i < pool->reserved_pages; i++) {
- gfp_t gfp_mask = kgsl_gfp_mask(pool->pool_order);
- struct page *page;
- page = alloc_pages(gfp_mask, pool->pool_order);
- _kgsl_pool_add_page(pool, page);
- }
- }
- static int kgsl_of_parse_mempool(struct kgsl_page_pool *pool,
- struct device_node *node)
- {
- u32 size;
- int order;
- unsigned char name[8];
- if (of_property_read_u32(node, "qcom,mempool-page-size", &size))
- return -EINVAL;
- order = get_order(size);
- if (order > 8) {
- pr_err("kgsl: %pOF: pool order %d is too big\n", node, order);
- return -EINVAL;
- }
- pool->pool_order = order;
- if (of_property_read_u32(node, "qcom,mempool-max-pages", &pool->max_pages))
- pool->max_pages = UINT_MAX;
- spin_lock_init(&pool->list_lock);
- kgsl_pool_list_init(pool);
- kgsl_pool_reserve_pages(pool, node);
- snprintf(name, sizeof(name), "%d_order", (pool->pool_order));
- kgsl_pool_init_debugfs(pool->debug_root, name, (void *) pool);
- return 0;
- }
- void kgsl_probe_page_pools(void)
- {
- struct device_node *node, *child;
- int index = 0;
- node = of_find_compatible_node(NULL, NULL, "qcom,gpu-mempools");
- if (!node)
- return;
- /* Get Max pages limit for mempool */
- of_property_read_u32(node, "qcom,mempool-max-pages",
- &kgsl_pool_max_pages);
- kgsl_pool_cache_init();
- for_each_child_of_node(node, child) {
- if (!kgsl_of_parse_mempool(&kgsl_pools[index], child))
- index++;
- if (index == ARRAY_SIZE(kgsl_pools)) {
- of_node_put(child);
- break;
- }
- }
- kgsl_num_pools = index;
- of_node_put(node);
- /* Initialize shrinker */
- #if (KERNEL_VERSION(6, 0, 0) <= LINUX_VERSION_CODE)
- register_shrinker(&kgsl_pool_shrinker, "kgsl_pool_shrinker");
- #else
- register_shrinker(&kgsl_pool_shrinker);
- #endif
- }
- void kgsl_exit_page_pools(void)
- {
- int i;
- /* Release all pages in pools, if any.*/
- kgsl_pool_reduce(INT_MAX, true);
- /* Unregister shrinker */
- unregister_shrinker(&kgsl_pool_shrinker);
- /* Destroy helper structures */
- for (i = 0; i < kgsl_num_pools; i++)
- kgsl_destroy_page_pool(&kgsl_pools[i]);
- /* Destroy the kmem cache */
- kgsl_pool_cache_destroy();
- }
|