| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Support for Medifield PNW Camera Imaging ISP subsystem. |
| * |
| * Copyright (c) 2010 Intel Corporation. All Rights Reserved. |
| * |
| * Copyright (c) 2010 Silicon Hive www.siliconhive.com. |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License version |
| * 2 as published by the Free Software Foundation. |
| * |
| * 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. |
| * |
| * |
| */ |
| /* |
| * This file contains functions for dynamic memory pool management |
| */ |
| #include <linux/kernel.h> |
| #include <linux/types.h> |
| #include <linux/mm.h> |
| |
| #include <asm/set_memory.h> |
| |
| #include "atomisp_internal.h" |
| |
| #include "hmm/hmm_pool.h" |
| |
| /* |
| * dynamic memory pool ops. |
| */ |
| static unsigned int get_pages_from_dynamic_pool(void *pool, |
| struct hmm_page_object *page_obj, |
| unsigned int size, bool cached) |
| { |
| struct hmm_page *hmm_page; |
| unsigned long flags; |
| unsigned int i = 0; |
| struct hmm_dynamic_pool_info *dypool_info = pool; |
| |
| if (!dypool_info) |
| return 0; |
| |
| spin_lock_irqsave(&dypool_info->list_lock, flags); |
| if (dypool_info->initialized) { |
| while (!list_empty(&dypool_info->pages_list)) { |
| hmm_page = list_entry(dypool_info->pages_list.next, |
| struct hmm_page, list); |
| |
| list_del(&hmm_page->list); |
| dypool_info->pgnr--; |
| spin_unlock_irqrestore(&dypool_info->list_lock, flags); |
| |
| page_obj[i].page = hmm_page->page; |
| page_obj[i++].type = HMM_PAGE_TYPE_DYNAMIC; |
| kmem_cache_free(dypool_info->pgptr_cache, hmm_page); |
| |
| if (i == size) |
| return i; |
| |
| spin_lock_irqsave(&dypool_info->list_lock, flags); |
| } |
| } |
| spin_unlock_irqrestore(&dypool_info->list_lock, flags); |
| |
| return i; |
| } |
| |
| static void free_pages_to_dynamic_pool(void *pool, |
| struct hmm_page_object *page_obj) |
| { |
| struct hmm_page *hmm_page; |
| unsigned long flags; |
| int ret; |
| struct hmm_dynamic_pool_info *dypool_info = pool; |
| |
| if (!dypool_info) |
| return; |
| |
| spin_lock_irqsave(&dypool_info->list_lock, flags); |
| if (!dypool_info->initialized) { |
| spin_unlock_irqrestore(&dypool_info->list_lock, flags); |
| return; |
| } |
| spin_unlock_irqrestore(&dypool_info->list_lock, flags); |
| |
| if (page_obj->type == HMM_PAGE_TYPE_RESERVED) |
| return; |
| |
| if (dypool_info->pgnr >= dypool_info->pool_size) { |
| /* free page directly back to system */ |
| ret = set_pages_wb(page_obj->page, 1); |
| if (ret) |
| dev_err(atomisp_dev, |
| "set page to WB err ...ret=%d\n", ret); |
| /* |
| W/A: set_pages_wb seldom return value = -EFAULT |
| indicate that address of page is not in valid |
| range(0xffff880000000000~0xffffc7ffffffffff) |
| then, _free_pages would panic; Do not know why page |
| address be valid, it maybe memory corruption by lowmemory |
| */ |
| if (!ret) { |
| __free_pages(page_obj->page, 0); |
| hmm_mem_stat.sys_size--; |
| } |
| return; |
| } |
| hmm_page = kmem_cache_zalloc(dypool_info->pgptr_cache, |
| GFP_KERNEL); |
| if (!hmm_page) { |
| /* free page directly */ |
| ret = set_pages_wb(page_obj->page, 1); |
| if (ret) |
| dev_err(atomisp_dev, |
| "set page to WB err ...ret=%d\n", ret); |
| if (!ret) { |
| __free_pages(page_obj->page, 0); |
| hmm_mem_stat.sys_size--; |
| } |
| return; |
| } |
| |
| hmm_page->page = page_obj->page; |
| |
| /* |
| * add to pages_list of pages_pool |
| */ |
| spin_lock_irqsave(&dypool_info->list_lock, flags); |
| list_add_tail(&hmm_page->list, &dypool_info->pages_list); |
| dypool_info->pgnr++; |
| spin_unlock_irqrestore(&dypool_info->list_lock, flags); |
| hmm_mem_stat.dyc_size++; |
| } |
| |
| static int hmm_dynamic_pool_init(void **pool, unsigned int pool_size) |
| { |
| struct hmm_dynamic_pool_info *dypool_info; |
| |
| if (pool_size == 0) |
| return 0; |
| |
| dypool_info = kmalloc(sizeof(struct hmm_dynamic_pool_info), |
| GFP_KERNEL); |
| if (unlikely(!dypool_info)) |
| return -ENOMEM; |
| |
| dypool_info->pgptr_cache = kmem_cache_create("pgptr_cache", |
| sizeof(struct hmm_page), 0, |
| SLAB_HWCACHE_ALIGN, NULL); |
| if (!dypool_info->pgptr_cache) { |
| kfree(dypool_info); |
| return -ENOMEM; |
| } |
| |
| INIT_LIST_HEAD(&dypool_info->pages_list); |
| spin_lock_init(&dypool_info->list_lock); |
| dypool_info->initialized = true; |
| dypool_info->pool_size = pool_size; |
| dypool_info->pgnr = 0; |
| |
| *pool = dypool_info; |
| |
| return 0; |
| } |
| |
| static void hmm_dynamic_pool_exit(void **pool) |
| { |
| struct hmm_dynamic_pool_info *dypool_info = *pool; |
| struct hmm_page *hmm_page; |
| unsigned long flags; |
| int ret; |
| |
| if (!dypool_info) |
| return; |
| |
| spin_lock_irqsave(&dypool_info->list_lock, flags); |
| if (!dypool_info->initialized) { |
| spin_unlock_irqrestore(&dypool_info->list_lock, flags); |
| return; |
| } |
| dypool_info->initialized = false; |
| |
| while (!list_empty(&dypool_info->pages_list)) { |
| hmm_page = list_entry(dypool_info->pages_list.next, |
| struct hmm_page, list); |
| |
| list_del(&hmm_page->list); |
| spin_unlock_irqrestore(&dypool_info->list_lock, flags); |
| |
| /* can cause thread sleep, so cannot be put into spin_lock */ |
| ret = set_pages_wb(hmm_page->page, 1); |
| if (ret) |
| dev_err(atomisp_dev, |
| "set page to WB err...ret=%d\n", ret); |
| if (!ret) { |
| __free_pages(hmm_page->page, 0); |
| hmm_mem_stat.dyc_size--; |
| hmm_mem_stat.sys_size--; |
| } |
| kmem_cache_free(dypool_info->pgptr_cache, hmm_page); |
| spin_lock_irqsave(&dypool_info->list_lock, flags); |
| } |
| |
| spin_unlock_irqrestore(&dypool_info->list_lock, flags); |
| |
| kmem_cache_destroy(dypool_info->pgptr_cache); |
| |
| kfree(dypool_info); |
| |
| *pool = NULL; |
| } |
| |
| static int hmm_dynamic_pool_inited(void *pool) |
| { |
| struct hmm_dynamic_pool_info *dypool_info = pool; |
| |
| if (!dypool_info) |
| return 0; |
| |
| return dypool_info->initialized; |
| } |
| |
| struct hmm_pool_ops dynamic_pops = { |
| .pool_init = hmm_dynamic_pool_init, |
| .pool_exit = hmm_dynamic_pool_exit, |
| .pool_alloc_pages = get_pages_from_dynamic_pool, |
| .pool_free_pages = free_pages_to_dynamic_pool, |
| .pool_inited = hmm_dynamic_pool_inited, |
| }; |