blob: d942ff54c957c746dea519b7b2567d43dfa49a58 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2015-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* 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, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#include <mali_kbase.h>
#include <linux/mm.h>
#include <linux/migrate.h>
#include <linux/dma-mapping.h>
#include <linux/highmem.h>
#include <linux/spinlock.h>
#include <linux/shrinker.h>
#include <linux/atomic.h>
#include <linux/version.h>
#if KERNEL_VERSION(4, 11, 0) <= LINUX_VERSION_CODE
#include <linux/sched/signal.h>
#else
#include <linux/signal.h>
#endif
#define pool_dbg(pool, format, ...) \
dev_dbg(pool->kbdev->dev, "%s-pool [%zu/%zu]: " format, \
(pool->next_pool) ? "kctx" : "kbdev", \
kbase_mem_pool_size(pool), \
kbase_mem_pool_max_size(pool), \
##__VA_ARGS__)
#define NOT_DIRTY false
#define NOT_RECLAIMED false
static bool mali_kbase_mem_pool_order_pages_enabled = false;
static void kbase_mem_pool_ordered_add_locked(struct kbase_mem_pool *pool,
struct page *p);
static struct page *kbase_mem_pool_ordered_spill(
struct kbase_mem_pool *next_pool, struct page *p);
static void kbase_mem_pool_ordered_free(struct kbase_mem_pool *pool, struct page *p,
bool dirty);
static void kbase_mem_pool_ordered_free_locked(struct kbase_mem_pool *pool, struct page *p,
bool dirty);
static void kbase_mem_pool_ordered_free_pages(struct kbase_mem_pool *pool,
size_t nr_pages, struct tagged_addr *pages, bool dirty,
bool reclaimed);
static void kbase_mem_pool_ordered_free_pages_locked(
struct kbase_mem_pool *pool, size_t nr_pages,
struct tagged_addr *pages, bool dirty, bool reclaimed);
static struct page *kbase_mem_pool_ordered_add_spill(
struct kbase_mem_pool *pool, struct page *p);
static struct page *kbase_mem_pool_ordered_add_spill_locked(
struct kbase_mem_pool *pool, struct page *p);
static void kbase_mem_pool_ordered_add_list_spill(struct kbase_mem_pool *pool,
struct list_head *page_list, struct list_head *spillover_list);
static void kbase_mem_pool_ordered_add_list_spill_locked(
struct kbase_mem_pool *pool, struct list_head *page_list,
struct list_head *spillover_list);
static void kbase_mem_pool_ordered_add_array_spill(struct kbase_mem_pool *pool,
size_t nr_pages, struct tagged_addr *pages,
struct list_head *spillover_list, bool zero, bool sync);
static void kbase_mem_pool_ordered_add_array_spill_locked(
struct kbase_mem_pool *pool, size_t nr_pages,
struct tagged_addr *pages, struct list_head *spillover_list,
bool zero, bool sync);
/**
* can_alloc_page() - Check if the current thread can allocate a physical page
*
* @pool: Pointer to the memory pool.
* @page_owner: Pointer to the task/process that created the Kbase context
* for which a page needs to be allocated. It can be NULL if
* the page won't be associated with Kbase context.
*
* This function checks if the current thread can make a request to kernel to
* allocate a physical page. If the process that created the context is exiting or
* is being killed, then there is no point in doing a page allocation.
*
* The check done by the function is particularly helpful when the system is running
* low on memory. When a page is allocated from the context of a kernel thread, OoM
* killer doesn't consider the kernel thread for killing and kernel keeps retrying
* to allocate the page as long as the OoM killer is able to kill processes.
* The check allows to quickly exit the page allocation loop once OoM
* killer has initiated the killing of @page_owner, thereby unblocking the context
* termination for @page_owner and freeing of GPU memory allocated by it. This helps
* in preventing the kernel panic and also limits the number of innocent processes
* that get killed.
*
* Return: true if the page can be allocated otherwise false.
*/
static inline bool can_alloc_page(struct kbase_mem_pool *pool, struct task_struct *page_owner)
{
if (page_owner && ((page_owner->flags & PF_EXITING) || fatal_signal_pending(page_owner))) {
dev_info(pool->kbdev->dev, "%s : Process %s/%d exiting", __func__, page_owner->comm,
task_pid_nr(page_owner));
return false;
}
return true;
}
static size_t kbase_mem_pool_capacity(struct kbase_mem_pool *pool)
{
ssize_t max_size = kbase_mem_pool_max_size(pool);
ssize_t cur_size = kbase_mem_pool_size(pool);
return max(max_size - cur_size, (ssize_t)0);
}
static bool kbase_mem_pool_is_full(struct kbase_mem_pool *pool)
{
return kbase_mem_pool_size(pool) >= kbase_mem_pool_max_size(pool);
}
static bool kbase_mem_pool_is_empty(struct kbase_mem_pool *pool)
{
return kbase_mem_pool_size(pool) == 0;
}
static bool set_pool_new_page_metadata(struct kbase_mem_pool *pool, struct page *p,
struct list_head *page_list, size_t *list_size)
{
struct kbase_page_metadata *page_md = kbase_page_private(p);
bool not_movable = false;
lockdep_assert_held(&pool->pool_lock);
/* Free the page instead of adding it to the pool if it's not movable.
* Only update page status and add the page to the memory pool if
* it is not isolated.
*/
if (!IS_ENABLED(CONFIG_PAGE_MIGRATION_SUPPORT))
not_movable = true;
else {
spin_lock(&page_md->migrate_lock);
if (PAGE_STATUS_GET(page_md->status) == (u8)NOT_MOVABLE) {
not_movable = true;
} else if (!WARN_ON_ONCE(IS_PAGE_ISOLATED(page_md->status))) {
page_md->status = PAGE_STATUS_SET(page_md->status, (u8)MEM_POOL);
page_md->data.mem_pool.pool = pool;
page_md->data.mem_pool.kbdev = pool->kbdev;
list_add(&p->lru, page_list);
(*list_size)++;
}
spin_unlock(&page_md->migrate_lock);
}
if (not_movable) {
kbase_free_page_later(pool->kbdev, p);
pool_dbg(pool, "skipping a not movable page\n");
}
return not_movable;
}
static void kbase_mem_pool_add_locked(struct kbase_mem_pool *pool,
struct page *p)
{
bool queue_work_to_free = false;
if (mali_kbase_mem_pool_order_pages_enabled) {
kbase_mem_pool_ordered_add_locked(pool, p);
return;
}
lockdep_assert_held(&pool->pool_lock);
if (!pool->order && kbase_is_page_migration_enabled()) {
if (set_pool_new_page_metadata(pool, p, &pool->page_list, &pool->cur_size))
queue_work_to_free = true;
} else {
list_add(&p->lru, &pool->page_list);
pool->cur_size++;
}
if (queue_work_to_free) {
struct kbase_mem_migrate *mem_migrate = &pool->kbdev->mem_migrate;
queue_work(mem_migrate->free_pages_workq, &mem_migrate->free_pages_work);
}
pool_dbg(pool, "added page\n");
}
static void kbase_mem_pool_add(struct kbase_mem_pool *pool, struct page *p)
{
kbase_mem_pool_lock(pool);
kbase_mem_pool_add_locked(pool, p);
kbase_mem_pool_unlock(pool);
}
static void kbase_mem_pool_add_list_locked(struct kbase_mem_pool *pool,
struct list_head *page_list, size_t nr_pages)
{
bool queue_work_to_free = false;
lockdep_assert_held(&pool->pool_lock);
if (!pool->order && kbase_is_page_migration_enabled()) {
struct page *p, *tmp;
list_for_each_entry_safe(p, tmp, page_list, lru) {
list_del_init(&p->lru);
if (set_pool_new_page_metadata(pool, p, &pool->page_list, &pool->cur_size))
queue_work_to_free = true;
}
} else {
list_splice(page_list, &pool->page_list);
pool->cur_size += nr_pages;
}
if (queue_work_to_free) {
struct kbase_mem_migrate *mem_migrate = &pool->kbdev->mem_migrate;
queue_work(mem_migrate->free_pages_workq, &mem_migrate->free_pages_work);
}
pool_dbg(pool, "added %zu pages\n", nr_pages);
}
static void kbase_mem_pool_add_list(struct kbase_mem_pool *pool,
struct list_head *page_list, size_t nr_pages)
{
kbase_mem_pool_lock(pool);
kbase_mem_pool_add_list_locked(pool, page_list, nr_pages);
kbase_mem_pool_unlock(pool);
}
static struct page *kbase_mem_pool_remove_locked(struct kbase_mem_pool *pool,
enum kbase_page_status status)
{
struct page *p;
lockdep_assert_held(&pool->pool_lock);
if (kbase_mem_pool_is_empty(pool))
return NULL;
p = list_first_entry(&pool->page_list, struct page, lru);
if (!pool->order && kbase_is_page_migration_enabled()) {
struct kbase_page_metadata *page_md = kbase_page_private(p);
spin_lock(&page_md->migrate_lock);
WARN_ON(PAGE_STATUS_GET(page_md->status) != (u8)MEM_POOL);
page_md->status = PAGE_STATUS_SET(page_md->status, (u8)status);
spin_unlock(&page_md->migrate_lock);
}
list_del_init(&p->lru);
pool->cur_size--;
pool_dbg(pool, "removed page\n");
return p;
}
static struct page *kbase_mem_pool_remove(struct kbase_mem_pool *pool,
enum kbase_page_status status)
{
struct page *p;
kbase_mem_pool_lock(pool);
p = kbase_mem_pool_remove_locked(pool, status);
kbase_mem_pool_unlock(pool);
return p;
}
static void kbase_mem_pool_sync_page(struct kbase_mem_pool *pool,
struct page *p)
{
struct device *dev = pool->kbdev->dev;
dma_addr_t dma_addr = pool->order ? kbase_dma_addr_as_priv(p) : kbase_dma_addr(p);
dma_sync_single_for_device(dev, dma_addr, (PAGE_SIZE << pool->order), DMA_BIDIRECTIONAL);
}
static void kbase_mem_pool_zero_page(struct kbase_mem_pool *pool,
struct page *p)
{
int i;
for (i = 0; i < (1U << pool->order); i++)
clear_highpage(p+i);
kbase_mem_pool_sync_page(pool, p);
}
static void kbase_mem_pool_spill(struct kbase_mem_pool *next_pool,
struct page *p)
{
if (mali_kbase_mem_pool_order_pages_enabled) {
kbase_mem_pool_ordered_spill(next_pool, p);
return;
}
/* Zero page before spilling */
kbase_mem_pool_zero_page(next_pool, p);
kbase_mem_pool_add(next_pool, p);
}
struct page *kbase_mem_alloc_page(struct kbase_mem_pool *pool)
{
struct page *p;
gfp_t gfp = __GFP_ZERO;
struct kbase_device *const kbdev = pool->kbdev;
struct device *const dev = kbdev->dev;
dma_addr_t dma_addr;
int i;
/* don't warn on higher order failures */
if (pool->order)
gfp |= GFP_HIGHUSER | __GFP_NOWARN;
else
gfp |= kbase_is_page_migration_enabled() ? GFP_HIGHUSER_MOVABLE : GFP_HIGHUSER;
p = kbdev->mgm_dev->ops.mgm_alloc_page(kbdev->mgm_dev,
pool->group_id, gfp, pool->order);
if (!p)
return NULL;
dma_addr = dma_map_page(dev, p, 0, (PAGE_SIZE << pool->order),
DMA_BIDIRECTIONAL);
if (dma_mapping_error(dev, dma_addr)) {
kbdev->mgm_dev->ops.mgm_free_page(kbdev->mgm_dev,
pool->group_id, p, pool->order);
return NULL;
}
/* Setup page metadata for 4KB pages when page migration is enabled */
if (!pool->order && kbase_is_page_migration_enabled()) {
INIT_LIST_HEAD(&p->lru);
if (!kbase_alloc_page_metadata(kbdev, p, dma_addr, pool->group_id)) {
dma_unmap_page(dev, dma_addr, PAGE_SIZE, DMA_BIDIRECTIONAL);
kbdev->mgm_dev->ops.mgm_free_page(kbdev->mgm_dev, pool->group_id, p,
pool->order);
return NULL;
}
} else {
WARN_ON(dma_addr != page_to_phys(p));
for (i = 0; i < (1u << pool->order); i++)
kbase_set_dma_addr_as_priv(p + i, dma_addr + PAGE_SIZE * i);
}
return p;
}
static void enqueue_free_pool_pages_work(struct kbase_mem_pool *pool)
{
struct kbase_mem_migrate *mem_migrate = &pool->kbdev->mem_migrate;
if (!pool->order && kbase_is_page_migration_enabled())
queue_work(mem_migrate->free_pages_workq, &mem_migrate->free_pages_work);
}
void kbase_mem_pool_free_page(struct kbase_mem_pool *pool, struct page *p)
{
struct kbase_device *kbdev;
if (WARN_ON(!pool))
return;
if (WARN_ON(!p))
return;
kbdev = pool->kbdev;
if (!pool->order && kbase_is_page_migration_enabled()) {
kbase_free_page_later(kbdev, p);
pool_dbg(pool, "page to be freed to kernel later\n");
} else {
int i;
dma_addr_t dma_addr = kbase_dma_addr_as_priv(p);
for (i = 0; i < (1u << pool->order); i++)
kbase_clear_dma_addr_as_priv(p + i);
dma_unmap_page(kbdev->dev, dma_addr, (PAGE_SIZE << pool->order), DMA_BIDIRECTIONAL);
kbdev->mgm_dev->ops.mgm_free_page(kbdev->mgm_dev, pool->group_id, p, pool->order);
pool_dbg(pool, "freed page to kernel\n");
}
}
static size_t kbase_mem_pool_shrink_locked(struct kbase_mem_pool *pool,
size_t nr_to_shrink)
{
struct page *p;
size_t i;
lockdep_assert_held(&pool->pool_lock);
for (i = 0; i < nr_to_shrink && !kbase_mem_pool_is_empty(pool); i++) {
p = kbase_mem_pool_remove_locked(pool, FREE_IN_PROGRESS);
kbase_mem_pool_free_page(pool, p);
}
/* Freeing of pages will be deferred when page migration is enabled. */
enqueue_free_pool_pages_work(pool);
return i;
}
static size_t kbase_mem_pool_shrink(struct kbase_mem_pool *pool,
size_t nr_to_shrink)
{
size_t nr_freed;
kbase_mem_pool_lock(pool);
nr_freed = kbase_mem_pool_shrink_locked(pool, nr_to_shrink);
kbase_mem_pool_unlock(pool);
return nr_freed;
}
int kbase_mem_pool_grow(struct kbase_mem_pool *pool, size_t nr_to_grow,
struct task_struct *page_owner)
{
struct page *p;
size_t i;
kbase_mem_pool_lock(pool);
pool->dont_reclaim = true;
for (i = 0; i < nr_to_grow; i++) {
if (pool->dying) {
pool->dont_reclaim = false;
kbase_mem_pool_shrink_locked(pool, nr_to_grow);
kbase_mem_pool_unlock(pool);
return -ENOMEM;
}
kbase_mem_pool_unlock(pool);
if (unlikely(!can_alloc_page(pool, page_owner)))
return -ENOMEM;
p = kbase_mem_alloc_page(pool);
if (!p) {
kbase_mem_pool_lock(pool);
pool->dont_reclaim = false;
kbase_mem_pool_unlock(pool);
return -ENOMEM;
}
kbase_mem_pool_lock(pool);
kbase_mem_pool_add_locked(pool, p);
}
pool->dont_reclaim = false;
kbase_mem_pool_unlock(pool);
return 0;
}
KBASE_EXPORT_TEST_API(kbase_mem_pool_grow);
void kbase_mem_pool_trim(struct kbase_mem_pool *pool, size_t new_size)
{
size_t cur_size;
int err = 0;
cur_size = kbase_mem_pool_size(pool);
if (new_size > pool->max_size)
new_size = pool->max_size;
if (new_size < cur_size)
kbase_mem_pool_shrink(pool, cur_size - new_size);
else if (new_size > cur_size)
err = kbase_mem_pool_grow(pool, new_size - cur_size, NULL);
if (err) {
size_t grown_size = kbase_mem_pool_size(pool);
dev_warn(pool->kbdev->dev,
"Mem pool not grown to the required size of %zu bytes, grown for additional %zu bytes instead!\n",
(new_size - cur_size), (grown_size - cur_size));
}
}
void kbase_mem_pool_set_max_size(struct kbase_mem_pool *pool, size_t max_size)
{
size_t cur_size;
size_t nr_to_shrink;
kbase_mem_pool_lock(pool);
pool->max_size = max_size;
cur_size = kbase_mem_pool_size(pool);
if (max_size < cur_size) {
nr_to_shrink = cur_size - max_size;
kbase_mem_pool_shrink_locked(pool, nr_to_shrink);
}
kbase_mem_pool_unlock(pool);
}
KBASE_EXPORT_TEST_API(kbase_mem_pool_set_max_size);
static unsigned long kbase_mem_pool_reclaim_count_objects(struct shrinker *s,
struct shrink_control *sc)
{
struct kbase_mem_pool *pool;
size_t pool_size;
pool = container_of(s, struct kbase_mem_pool, reclaim);
kbase_mem_pool_lock(pool);
if (pool->dont_reclaim && !pool->dying) {
kbase_mem_pool_unlock(pool);
/* Tell shrinker to skip reclaim
* even though freeable pages are available
*/
return 0;
}
pool_size = kbase_mem_pool_size(pool);
kbase_mem_pool_unlock(pool);
return pool_size;
}
static unsigned long kbase_mem_pool_reclaim_scan_objects(struct shrinker *s,
struct shrink_control *sc)
{
struct kbase_mem_pool *pool;
unsigned long freed;
pool = container_of(s, struct kbase_mem_pool, reclaim);
kbase_mem_pool_lock(pool);
if (pool->dont_reclaim && !pool->dying) {
kbase_mem_pool_unlock(pool);
/* Tell shrinker that reclaim can't be made and
* do not attempt again for this reclaim context.
*/
return SHRINK_STOP;
}
pool_dbg(pool, "reclaim scan %ld:\n", sc->nr_to_scan);
freed = kbase_mem_pool_shrink_locked(pool, sc->nr_to_scan);
kbase_mem_pool_unlock(pool);
pool_dbg(pool, "reclaim freed %ld pages\n", freed);
return freed;
}
int kbase_mem_pool_init(struct kbase_mem_pool *pool, const struct kbase_mem_pool_config *config,
unsigned int order, int group_id, struct kbase_device *kbdev,
struct kbase_mem_pool *next_pool)
{
if (WARN_ON(group_id < 0) ||
WARN_ON(group_id >= MEMORY_GROUP_MANAGER_NR_GROUPS)) {
return -EINVAL;
}
pool->cur_size = 0;
pool->max_size = kbase_mem_pool_config_get_max_size(config);
pool->order = order;
pool->group_id = group_id;
pool->kbdev = kbdev;
pool->next_pool = next_pool;
pool->dying = false;
atomic_set(&pool->isolation_in_progress_cnt, 0);
spin_lock_init(&pool->pool_lock);
INIT_LIST_HEAD(&pool->page_list);
pool->reclaim.count_objects = kbase_mem_pool_reclaim_count_objects;
pool->reclaim.scan_objects = kbase_mem_pool_reclaim_scan_objects;
pool->reclaim.seeks = DEFAULT_SEEKS;
/* Kernel versions prior to 3.1 :
* struct shrinker does not define batch
*/
pool->reclaim.batch = 0;
#if KERNEL_VERSION(6, 0, 0) > LINUX_VERSION_CODE
register_shrinker(&pool->reclaim);
#else
register_shrinker(&pool->reclaim, "mali-mem-pool");
#endif
pool_dbg(pool, "initialized\n");
return 0;
}
KBASE_EXPORT_TEST_API(kbase_mem_pool_init);
void kbase_mem_pool_mark_dying(struct kbase_mem_pool *pool)
{
kbase_mem_pool_lock(pool);
pool->dying = true;
kbase_mem_pool_unlock(pool);
}
void kbase_mem_pool_term(struct kbase_mem_pool *pool)
{
struct kbase_mem_pool *next_pool = pool->next_pool;
struct page *p, *tmp;
size_t nr_to_spill = 0;
LIST_HEAD(spill_list);
LIST_HEAD(free_list);
int i;
pool_dbg(pool, "terminate()\n");
unregister_shrinker(&pool->reclaim);
kbase_mem_pool_lock(pool);
pool->max_size = 0;
if (next_pool && !kbase_mem_pool_is_full(next_pool)) {
/* Spill to next pool (may overspill) */
nr_to_spill = kbase_mem_pool_capacity(next_pool);
nr_to_spill = min(kbase_mem_pool_size(pool), nr_to_spill);
/* Zero pages first without holding the next_pool lock */
for (i = 0; i < nr_to_spill; i++) {
p = kbase_mem_pool_remove_locked(pool, SPILL_IN_PROGRESS);
if (p)
list_add(&p->lru, &spill_list);
}
}
while (!kbase_mem_pool_is_empty(pool)) {
/* Free remaining pages to kernel */
p = kbase_mem_pool_remove_locked(pool, FREE_IN_PROGRESS);
if (p)
list_add(&p->lru, &free_list);
}
kbase_mem_pool_unlock(pool);
if (mali_kbase_mem_pool_order_pages_enabled) {
if (next_pool && (!list_empty(&spill_list) ||
!list_empty(&free_list))) {
list_splice_init(&free_list, &spill_list);
list_for_each_entry(p, &spill_list, lru)
kbase_mem_pool_zero_page(pool, p);
kbase_mem_pool_ordered_add_list_spill(
next_pool, &spill_list, &free_list);
}
} else {
if (next_pool && nr_to_spill) {
list_for_each_entry(p, &spill_list, lru)
kbase_mem_pool_zero_page(pool, p);
/* Add new page list to next_pool */
kbase_mem_pool_add_list(next_pool, &spill_list,
nr_to_spill);
pool_dbg(pool, "terminate() spilled %zu pages\n",
nr_to_spill);
}
}
list_for_each_entry_safe(p, tmp, &free_list, lru) {
list_del_init(&p->lru);
kbase_mem_pool_free_page(pool, p);
}
/* Freeing of pages will be deferred when page migration is enabled. */
enqueue_free_pool_pages_work(pool);
/* Before returning wait to make sure there are no pages undergoing page isolation
* which will require reference to this pool.
*/
if (kbase_is_page_migration_enabled()) {
while (atomic_read(&pool->isolation_in_progress_cnt))
cpu_relax();
}
pool_dbg(pool, "terminated\n");
}
KBASE_EXPORT_TEST_API(kbase_mem_pool_term);
struct page *kbase_mem_pool_alloc(struct kbase_mem_pool *pool)
{
struct page *p;
do {
pool_dbg(pool, "alloc()\n");
p = kbase_mem_pool_remove(pool, ALLOCATE_IN_PROGRESS);
if (p)
return p;
pool = pool->next_pool;
} while (pool);
return NULL;
}
struct page *kbase_mem_pool_alloc_locked(struct kbase_mem_pool *pool)
{
lockdep_assert_held(&pool->pool_lock);
pool_dbg(pool, "alloc_locked()\n");
return kbase_mem_pool_remove_locked(pool, ALLOCATE_IN_PROGRESS);
}
void kbase_mem_pool_free(struct kbase_mem_pool *pool, struct page *p,
bool dirty)
{
struct kbase_mem_pool *next_pool = pool->next_pool;
if (mali_kbase_mem_pool_order_pages_enabled) {
kbase_mem_pool_ordered_free(pool, p, dirty);
return;
}
pool_dbg(pool, "free()\n");
if (!kbase_mem_pool_is_full(pool)) {
/* Add to our own pool */
if (dirty)
kbase_mem_pool_sync_page(pool, p);
kbase_mem_pool_add(pool, p);
} else if (next_pool && !kbase_mem_pool_is_full(next_pool)) {
/* Spill to next pool */
kbase_mem_pool_spill(next_pool, p);
} else {
/* Free page */
kbase_mem_pool_free_page(pool, p);
/* Freeing of pages will be deferred when page migration is enabled. */
enqueue_free_pool_pages_work(pool);
}
}
void kbase_mem_pool_free_locked(struct kbase_mem_pool *pool, struct page *p,
bool dirty)
{
if (mali_kbase_mem_pool_order_pages_enabled) {
kbase_mem_pool_ordered_free_locked(pool, p, dirty);
return;
}
pool_dbg(pool, "free_locked()\n");
lockdep_assert_held(&pool->pool_lock);
if (!kbase_mem_pool_is_full(pool)) {
/* Add to our own pool */
if (dirty)
kbase_mem_pool_sync_page(pool, p);
kbase_mem_pool_add_locked(pool, p);
} else {
/* Free page */
kbase_mem_pool_free_page(pool, p);
/* Freeing of pages will be deferred when page migration is enabled. */
enqueue_free_pool_pages_work(pool);
}
}
int kbase_mem_pool_alloc_pages(struct kbase_mem_pool *pool, size_t nr_4k_pages,
struct tagged_addr *pages, bool partial_allowed,
struct task_struct *page_owner)
{
struct page *p;
size_t nr_from_pool;
size_t i = 0;
int err = -ENOMEM;
size_t nr_pages_internal;
nr_pages_internal = nr_4k_pages / (1u << (pool->order));
if (nr_pages_internal * (1u << pool->order) != nr_4k_pages)
return -EINVAL;
pool_dbg(pool, "alloc_pages(4k=%zu):\n", nr_4k_pages);
pool_dbg(pool, "alloc_pages(internal=%zu):\n", nr_pages_internal);
/* Get pages from this pool */
kbase_mem_pool_lock(pool);
nr_from_pool = min(nr_pages_internal, kbase_mem_pool_size(pool));
while (nr_from_pool--) {
int j;
p = kbase_mem_pool_remove_locked(pool, ALLOCATE_IN_PROGRESS);
if (pool->order) {
pages[i++] = as_tagged_tag(page_to_phys(p),
HUGE_HEAD | HUGE_PAGE);
for (j = 1; j < (1u << pool->order); j++)
pages[i++] = as_tagged_tag(page_to_phys(p) +
PAGE_SIZE * j,
HUGE_PAGE);
} else {
pages[i++] = as_tagged(page_to_phys(p));
}
}
kbase_mem_pool_unlock(pool);
if (i != nr_4k_pages && pool->next_pool) {
/* Allocate via next pool */
err = kbase_mem_pool_alloc_pages(pool->next_pool, nr_4k_pages - i, pages + i,
partial_allowed, page_owner);
if (err < 0)
goto err_rollback;
i += err;
} else {
/* Get any remaining pages from kernel */
while (i != nr_4k_pages) {
if (unlikely(!can_alloc_page(pool, page_owner)))
goto err_rollback;
p = kbase_mem_alloc_page(pool);
if (!p) {
if (partial_allowed)
goto done;
else
goto err_rollback;
}
if (pool->order) {
int j;
pages[i++] = as_tagged_tag(page_to_phys(p),
HUGE_PAGE |
HUGE_HEAD);
for (j = 1; j < (1u << pool->order); j++) {
phys_addr_t phys;
phys = page_to_phys(p) + PAGE_SIZE * j;
pages[i++] = as_tagged_tag(phys,
HUGE_PAGE);
}
} else {
pages[i++] = as_tagged(page_to_phys(p));
}
}
}
done:
pool_dbg(pool, "alloc_pages(%zu) done\n", i);
return i;
err_rollback:
kbase_mem_pool_free_pages(pool, i, pages, NOT_DIRTY, NOT_RECLAIMED);
dev_warn(pool->kbdev->dev,
"Failed allocation request for remaining %zu pages after obtaining %zu pages already.\n",
nr_4k_pages, i);
return err;
}
int kbase_mem_pool_alloc_pages_locked(struct kbase_mem_pool *pool,
size_t nr_4k_pages, struct tagged_addr *pages)
{
struct page *p;
size_t i;
size_t nr_pages_internal;
lockdep_assert_held(&pool->pool_lock);
nr_pages_internal = nr_4k_pages / (1u << (pool->order));
if (nr_pages_internal * (1u << pool->order) != nr_4k_pages)
return -EINVAL;
pool_dbg(pool, "alloc_pages_locked(4k=%zu):\n", nr_4k_pages);
pool_dbg(pool, "alloc_pages_locked(internal=%zu):\n",
nr_pages_internal);
if (kbase_mem_pool_size(pool) < nr_pages_internal) {
pool_dbg(pool, "Failed alloc\n");
return -ENOMEM;
}
for (i = 0; i < nr_pages_internal; i++) {
int j;
p = kbase_mem_pool_remove_locked(pool, ALLOCATE_IN_PROGRESS);
if (pool->order) {
*pages++ = as_tagged_tag(page_to_phys(p),
HUGE_HEAD | HUGE_PAGE);
for (j = 1; j < (1u << pool->order); j++) {
*pages++ = as_tagged_tag(page_to_phys(p) +
PAGE_SIZE * j,
HUGE_PAGE);
}
} else {
*pages++ = as_tagged(page_to_phys(p));
}
}
return nr_4k_pages;
}
static void kbase_mem_pool_add_array(struct kbase_mem_pool *pool,
size_t nr_pages, struct tagged_addr *pages,
bool zero, bool sync)
{
struct page *p;
size_t nr_to_pool = 0;
LIST_HEAD(new_page_list);
size_t i;
if (!nr_pages)
return;
pool_dbg(pool, "add_array(%zu, zero=%d, sync=%d):\n",
nr_pages, zero, sync);
/* Zero/sync pages first without holding the pool lock */
for (i = 0; i < nr_pages; i++) {
if (unlikely(!as_phys_addr_t(pages[i])))
continue;
if (is_huge_head(pages[i]) || !is_huge(pages[i])) {
p = as_page(pages[i]);
if (zero)
kbase_mem_pool_zero_page(pool, p);
else if (sync)
kbase_mem_pool_sync_page(pool, p);
list_add(&p->lru, &new_page_list);
nr_to_pool++;
}
pages[i] = as_tagged(0);
}
/* Add new page list to pool */
kbase_mem_pool_add_list(pool, &new_page_list, nr_to_pool);
pool_dbg(pool, "add_array(%zu) added %zu pages\n",
nr_pages, nr_to_pool);
}
static void kbase_mem_pool_add_array_locked(struct kbase_mem_pool *pool,
size_t nr_pages, struct tagged_addr *pages,
bool zero, bool sync)
{
struct page *p;
size_t nr_to_pool = 0;
LIST_HEAD(new_page_list);
size_t i;
lockdep_assert_held(&pool->pool_lock);
if (!nr_pages)
return;
pool_dbg(pool, "add_array_locked(%zu, zero=%d, sync=%d):\n",
nr_pages, zero, sync);
/* Zero/sync pages first */
for (i = 0; i < nr_pages; i++) {
if (unlikely(!as_phys_addr_t(pages[i])))
continue;
if (is_huge_head(pages[i]) || !is_huge(pages[i])) {
p = as_page(pages[i]);
if (zero)
kbase_mem_pool_zero_page(pool, p);
else if (sync)
kbase_mem_pool_sync_page(pool, p);
list_add(&p->lru, &new_page_list);
nr_to_pool++;
}
pages[i] = as_tagged(0);
}
/* Add new page list to pool */
kbase_mem_pool_add_list_locked(pool, &new_page_list, nr_to_pool);
pool_dbg(pool, "add_array_locked(%zu) added %zu pages\n",
nr_pages, nr_to_pool);
}
void kbase_mem_pool_free_pages(struct kbase_mem_pool *pool, size_t nr_pages,
struct tagged_addr *pages, bool dirty, bool reclaimed)
{
struct kbase_mem_pool *next_pool = pool->next_pool;
struct page *p;
size_t nr_to_pool;
LIST_HEAD(to_pool_list);
size_t i = 0;
bool pages_released = false;
if (mali_kbase_mem_pool_order_pages_enabled) {
kbase_mem_pool_ordered_free_pages(pool, nr_pages, pages, dirty,
reclaimed);
return;
}
pool_dbg(pool, "free_pages(%zu):\n", nr_pages);
if (!reclaimed) {
/* Add to this pool */
nr_to_pool = kbase_mem_pool_capacity(pool);
nr_to_pool = min(nr_pages, nr_to_pool);
kbase_mem_pool_add_array(pool, nr_to_pool, pages, false, dirty);
i += nr_to_pool;
if (i != nr_pages && next_pool) {
/* Spill to next pool (may overspill) */
nr_to_pool = kbase_mem_pool_capacity(next_pool);
nr_to_pool = min(nr_pages - i, nr_to_pool);
kbase_mem_pool_add_array(next_pool, nr_to_pool,
pages + i, true, dirty);
i += nr_to_pool;
}
}
/* Free any remaining pages to kernel */
for (; i < nr_pages; i++) {
if (unlikely(!as_phys_addr_t(pages[i])))
continue;
if (is_huge(pages[i]) && !is_huge_head(pages[i])) {
pages[i] = as_tagged(0);
continue;
}
p = as_page(pages[i]);
kbase_mem_pool_free_page(pool, p);
pages[i] = as_tagged(0);
pages_released = true;
}
/* Freeing of pages will be deferred when page migration is enabled. */
if (pages_released)
enqueue_free_pool_pages_work(pool);
pool_dbg(pool, "free_pages(%zu) done\n", nr_pages);
}
void kbase_mem_pool_free_pages_locked(struct kbase_mem_pool *pool,
size_t nr_pages, struct tagged_addr *pages, bool dirty,
bool reclaimed)
{
struct page *p;
size_t nr_to_pool;
LIST_HEAD(to_pool_list);
size_t i = 0;
bool pages_released = false;
if (mali_kbase_mem_pool_order_pages_enabled) {
kbase_mem_pool_ordered_free_pages_locked(pool, nr_pages, pages,
dirty, reclaimed);
return;
}
lockdep_assert_held(&pool->pool_lock);
pool_dbg(pool, "free_pages_locked(%zu):\n", nr_pages);
if (!reclaimed) {
/* Add to this pool */
nr_to_pool = kbase_mem_pool_capacity(pool);
nr_to_pool = min(nr_pages, nr_to_pool);
kbase_mem_pool_add_array_locked(pool, nr_to_pool, pages, false,
dirty);
i += nr_to_pool;
}
/* Free any remaining pages to kernel */
for (; i < nr_pages; i++) {
if (unlikely(!as_phys_addr_t(pages[i])))
continue;
if (is_huge(pages[i]) && !is_huge_head(pages[i])) {
pages[i] = as_tagged(0);
continue;
}
p = as_page(pages[i]);
kbase_mem_pool_free_page(pool, p);
pages[i] = as_tagged(0);
pages_released = true;
}
/* Freeing of pages will be deferred when page migration is enabled. */
if (pages_released)
enqueue_free_pool_pages_work(pool);
pool_dbg(pool, "free_pages_locked(%zu) done\n", nr_pages);
}
static void kbase_mem_pool_ordered_add_locked(struct kbase_mem_pool *pool,
struct page *p)
{
struct page* page_in_list;
phys_addr_t page_phys_addr;
bool added = false;
lockdep_assert_held(&pool->pool_lock);
page_phys_addr = page_to_phys(p);
list_for_each_entry(page_in_list, &pool->page_list, lru) {
if (page_phys_addr < page_to_phys(page_in_list)) {
list_add_tail(&p->lru, &page_in_list->lru);
added = true;
break;
}
}
if (!added) {
list_add_tail(&p->lru, &pool->page_list);
}
pool->cur_size++;
pool_dbg(pool, "added page\n");
}
static struct page *kbase_mem_pool_ordered_spill(
struct kbase_mem_pool *next_pool, struct page *p)
{
/* Zero page before spilling */
kbase_mem_pool_zero_page(next_pool, p);
return kbase_mem_pool_ordered_add_spill(next_pool, p);
}
static void kbase_mem_pool_ordered_free(struct kbase_mem_pool *pool,
struct page *p, bool dirty)
{
struct kbase_mem_pool *next_pool = pool->next_pool;
struct page *spilled_page;
pool_dbg(pool, "free()\n");
/* Add to our own pool */
if (dirty)
kbase_mem_pool_sync_page(pool, p);
spilled_page = kbase_mem_pool_ordered_add_spill(pool, p);
if (next_pool && spilled_page) {
/* Spill to next pool */
spilled_page = kbase_mem_pool_ordered_spill(next_pool,
spilled_page);
}
if (spilled_page) {
/* Free page */
kbase_mem_pool_free_page(pool, spilled_page);
}
}
static void kbase_mem_pool_ordered_free_locked(struct kbase_mem_pool *pool,
struct page *p, bool dirty)
{
struct page *spilled_page;
pool_dbg(pool, "free_locked()\n");
lockdep_assert_held(&pool->pool_lock);
/* Add to our own pool */
if (dirty)
kbase_mem_pool_sync_page(pool, p);
spilled_page = kbase_mem_pool_ordered_add_spill_locked(pool, p);
if (spilled_page) {
/* Free page */
kbase_mem_pool_free_page(pool, spilled_page);
}
}
static void kbase_mem_pool_ordered_free_pages(struct kbase_mem_pool *pool,
size_t nr_pages, struct tagged_addr *pages, bool dirty,
bool reclaimed)
{
struct kbase_mem_pool *next_pool = pool->next_pool;
struct page *p;
struct page *tmp;
LIST_HEAD(to_pool_list);
LIST_HEAD(spillover_list);
pool_dbg(pool, "free_pages(%zu):\n", nr_pages);
if (!reclaimed) {
/* Add to this pool, spilling over to any next pool */
if (next_pool) {
kbase_mem_pool_ordered_add_array_spill(pool, nr_pages,
pages, &spillover_list, false, dirty);
kbase_mem_pool_ordered_add_list_spill(next_pool,
&spillover_list, &to_pool_list);
} else {
kbase_mem_pool_ordered_add_array_spill(pool, nr_pages,
pages, &to_pool_list, false, dirty);
}
}
/* Free any remaining pages to kernel */
list_for_each_entry_safe(p, tmp, &to_pool_list, lru) {
list_del_init(&p->lru);
kbase_mem_pool_free_page(pool, p);
}
pool_dbg(pool, "free_pages(%zu) done\n", nr_pages);
}
static void kbase_mem_pool_ordered_free_pages_locked(
struct kbase_mem_pool *pool, size_t nr_pages,
struct tagged_addr *pages, bool dirty, bool reclaimed)
{
struct kbase_mem_pool *next_pool = pool->next_pool;
struct page *p;
struct page *tmp;
LIST_HEAD(to_pool_list);
LIST_HEAD(spillover_list);
lockdep_assert_held(&pool->pool_lock);
pool_dbg(pool, "free_pages_locked(%zu):\n", nr_pages);
if (!reclaimed) {
/* Add to this pool, spilling over to any next pool */
if (next_pool) {
kbase_mem_pool_ordered_add_array_spill_locked(pool,
nr_pages, pages, &spillover_list, false,
dirty);
kbase_mem_pool_ordered_add_list_spill_locked(next_pool,
&spillover_list, &to_pool_list);
} else {
kbase_mem_pool_ordered_add_array_spill_locked(pool,
nr_pages, pages, &to_pool_list, false,
dirty);
}
}
/* Free any remaining pages to kernel */
list_for_each_entry_safe(p, tmp, &to_pool_list, lru) {
list_del_init(&p->lru);
kbase_mem_pool_free_page(pool, p);
}
pool_dbg(pool, "free_pages_locked(%zu) done\n", nr_pages);
}
static struct page *kbase_mem_pool_ordered_add_spill(
struct kbase_mem_pool *pool, struct page *p)
{
struct page *spilled_page;
kbase_mem_pool_lock(pool);
spilled_page = kbase_mem_pool_ordered_add_spill_locked(pool, p);
kbase_mem_pool_unlock(pool);
return spilled_page;
}
static struct page *kbase_mem_pool_ordered_add_spill_locked(
struct kbase_mem_pool *pool, struct page *p)
{
struct page* spilled_page = NULL;
lockdep_assert_held(&pool->pool_lock);
kbase_mem_pool_add_locked(pool, p);
if (pool->cur_size > kbase_mem_pool_max_size(pool)) {
spilled_page = list_last_entry(
&pool->page_list, struct page, lru);
list_del_init(&spilled_page->lru);
pool->cur_size--;
}
return spilled_page;
}
static void kbase_mem_pool_ordered_add_list_spill(struct kbase_mem_pool *pool,
struct list_head *page_list, struct list_head *spillover_list)
{
kbase_mem_pool_lock(pool);
kbase_mem_pool_ordered_add_list_spill_locked(
pool, page_list, spillover_list);
kbase_mem_pool_unlock(pool);
}
static void kbase_mem_pool_ordered_add_list_spill_locked(
struct kbase_mem_pool *pool, struct list_head *page_list,
struct list_head *spillover_list)
{
struct page* page;
struct page* spilled_page;
struct page* tmp;
lockdep_assert_held(&pool->pool_lock);
list_for_each_entry_safe(page, tmp, page_list, lru) {
list_del_init(&page->lru);
spilled_page = kbase_mem_pool_ordered_add_spill_locked(
pool, page);
if (spilled_page) {
list_add(&spilled_page->lru, spillover_list);
}
}
}
static void kbase_mem_pool_ordered_add_array_spill(
struct kbase_mem_pool *pool, size_t nr_pages,
struct tagged_addr *pages, struct list_head *spillover_list,
bool zero, bool sync)
{
struct page *p;
size_t nr_to_pool = 0;
LIST_HEAD(new_page_list);
size_t i;
if (!nr_pages)
return;
pool_dbg(pool, "add_array(%zu, zero=%d, sync=%d):\n",
nr_pages, zero, sync);
/* Zero/sync pages first without holding the pool lock */
for (i = 0; i < nr_pages; i++) {
if (unlikely(!as_phys_addr_t(pages[i])))
continue;
if (is_huge_head(pages[i]) || !is_huge(pages[i])) {
p = as_page(pages[i]);
if (zero)
kbase_mem_pool_zero_page(pool, p);
else if (sync)
kbase_mem_pool_sync_page(pool, p);
list_add(&p->lru, &new_page_list);
nr_to_pool++;
}
pages[i] = as_tagged(0);
}
/* Add new page list to pool */
kbase_mem_pool_ordered_add_list_spill(
pool, &new_page_list, spillover_list);
pool_dbg(pool, "add_array(%zu) added %zu pages\n",
nr_pages, nr_to_pool);
}
static void kbase_mem_pool_ordered_add_array_spill_locked(
struct kbase_mem_pool *pool,
size_t nr_pages, struct tagged_addr *pages,
struct list_head *spillover_list,
bool zero, bool sync)
{
struct page *p;
size_t nr_to_pool = 0;
LIST_HEAD(new_page_list);
size_t i;
lockdep_assert_held(&pool->pool_lock);
if (!nr_pages)
return;
pool_dbg(pool, "add_array(%zu, zero=%d, sync=%d):\n",
nr_pages, zero, sync);
/* Zero/sync pages first without holding the pool lock */
for (i = 0; i < nr_pages; i++) {
if (unlikely(!as_phys_addr_t(pages[i])))
continue;
if (is_huge_head(pages[i]) || !is_huge(pages[i])) {
p = as_page(pages[i]);
if (zero)
kbase_mem_pool_zero_page(pool, p);
else if (sync)
kbase_mem_pool_sync_page(pool, p);
list_add(&p->lru, &new_page_list);
nr_to_pool++;
}
pages[i] = as_tagged(0);
}
/* Add new page list to pool */
kbase_mem_pool_ordered_add_list_spill_locked(pool, &new_page_list,
spillover_list);
pool_dbg(pool, "add_array(%zu) added %zu pages\n",
nr_pages, nr_to_pool);
}