android_kernel_samsung_sm86.../kgsl_pool.c
Hareesh Gundu 94cc00f43a kgsl: Implement SHMEM vendor hook callback
Add support to allocate higher order pages for SHMEM driver
through kgsl vendor hook (VH) callback. KGSL register for
VH callback during probe time. In VH callback KGSL allocate
higher order pages and split into 4k page to prepare 4k page
list to serve subsequent VH callback request for specific
allocation request. If specific allocation request size is
PAGE_SIZE shmem will allocate instead of KGSL VH Callback.

Change-Id: Ib73be5e71a5d2138a948d22dc4299603f960c62b
Signed-off-by: Hareesh Gundu <quic_hareeshg@quicinc.com>
2023-05-25 12:25:30 -07:00

767 lines
18 KiB
C

// 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();
}