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