| // 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 reserved 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" |
| |
| /* |
| * reserved memory pool ops. |
| */ |
| static unsigned int get_pages_from_reserved_pool(void *pool, |
| struct hmm_page_object *page_obj, |
| unsigned int size, bool cached) |
| { |
| unsigned long flags; |
| unsigned int i = 0; |
| unsigned int repool_pgnr; |
| int j; |
| struct hmm_reserved_pool_info *repool_info = pool; |
| |
| if (!repool_info) |
| return 0; |
| |
| spin_lock_irqsave(&repool_info->list_lock, flags); |
| if (repool_info->initialized) { |
| repool_pgnr = repool_info->index; |
| |
| for (j = repool_pgnr - 1; j >= 0; j--) { |
| page_obj[i].page = repool_info->pages[j]; |
| page_obj[i].type = HMM_PAGE_TYPE_RESERVED; |
| i++; |
| repool_info->index--; |
| if (i == size) |
| break; |
| } |
| } |
| spin_unlock_irqrestore(&repool_info->list_lock, flags); |
| return i; |
| } |
| |
| static void free_pages_to_reserved_pool(void *pool, |
| struct hmm_page_object *page_obj) |
| { |
| unsigned long flags; |
| struct hmm_reserved_pool_info *repool_info = pool; |
| |
| if (!repool_info) |
| return; |
| |
| spin_lock_irqsave(&repool_info->list_lock, flags); |
| |
| if (repool_info->initialized && |
| repool_info->index < repool_info->pgnr && |
| page_obj->type == HMM_PAGE_TYPE_RESERVED) { |
| repool_info->pages[repool_info->index++] = page_obj->page; |
| } |
| |
| spin_unlock_irqrestore(&repool_info->list_lock, flags); |
| } |
| |
| static int hmm_reserved_pool_setup(struct hmm_reserved_pool_info **repool_info, |
| unsigned int pool_size) |
| { |
| struct hmm_reserved_pool_info *pool_info; |
| |
| pool_info = kmalloc(sizeof(struct hmm_reserved_pool_info), |
| GFP_KERNEL); |
| if (unlikely(!pool_info)) |
| return -ENOMEM; |
| |
| pool_info->pages = kmalloc(sizeof(struct page *) * pool_size, |
| GFP_KERNEL); |
| if (unlikely(!pool_info->pages)) { |
| kfree(pool_info); |
| return -ENOMEM; |
| } |
| |
| pool_info->index = 0; |
| pool_info->pgnr = 0; |
| spin_lock_init(&pool_info->list_lock); |
| pool_info->initialized = true; |
| |
| *repool_info = pool_info; |
| |
| return 0; |
| } |
| |
| static int hmm_reserved_pool_init(void **pool, unsigned int pool_size) |
| { |
| int ret; |
| unsigned int blk_pgnr; |
| unsigned int pgnr = pool_size; |
| unsigned int order = 0; |
| unsigned int i = 0; |
| int fail_number = 0; |
| struct page *pages; |
| int j; |
| struct hmm_reserved_pool_info *repool_info; |
| |
| if (pool_size == 0) |
| return 0; |
| |
| ret = hmm_reserved_pool_setup(&repool_info, pool_size); |
| if (ret) { |
| dev_err(atomisp_dev, "hmm_reserved_pool_setup failed.\n"); |
| return ret; |
| } |
| |
| pgnr = pool_size; |
| |
| i = 0; |
| order = MAX_ORDER; |
| |
| while (pgnr) { |
| blk_pgnr = 1U << order; |
| while (blk_pgnr > pgnr) { |
| order--; |
| blk_pgnr >>= 1U; |
| } |
| BUG_ON(order > MAX_ORDER); |
| |
| pages = alloc_pages(GFP_KERNEL | __GFP_NOWARN, order); |
| if (unlikely(!pages)) { |
| if (order == 0) { |
| fail_number++; |
| dev_err(atomisp_dev, "%s: alloc_pages failed: %d\n", |
| __func__, fail_number); |
| /* if fail five times, will goto end */ |
| |
| /* FIXME: whether is the mechanism is ok? */ |
| if (fail_number == ALLOC_PAGE_FAIL_NUM) |
| goto end; |
| } else { |
| order--; |
| } |
| } else { |
| blk_pgnr = 1U << order; |
| |
| ret = set_pages_uc(pages, blk_pgnr); |
| if (ret) { |
| dev_err(atomisp_dev, |
| "set pages uncached failed\n"); |
| __free_pages(pages, order); |
| goto end; |
| } |
| |
| for (j = 0; j < blk_pgnr; j++) |
| repool_info->pages[i++] = pages + j; |
| |
| repool_info->index += blk_pgnr; |
| repool_info->pgnr += blk_pgnr; |
| |
| pgnr -= blk_pgnr; |
| |
| fail_number = 0; |
| } |
| } |
| |
| end: |
| repool_info->initialized = true; |
| |
| *pool = repool_info; |
| |
| dev_info(atomisp_dev, |
| "hmm_reserved_pool init successfully,hmm_reserved_pool is with %d pages.\n", |
| repool_info->pgnr); |
| return 0; |
| } |
| |
| static void hmm_reserved_pool_exit(void **pool) |
| { |
| unsigned long flags; |
| int i, ret; |
| unsigned int pgnr; |
| struct hmm_reserved_pool_info *repool_info = *pool; |
| |
| if (!repool_info) |
| return; |
| |
| spin_lock_irqsave(&repool_info->list_lock, flags); |
| if (!repool_info->initialized) { |
| spin_unlock_irqrestore(&repool_info->list_lock, flags); |
| return; |
| } |
| pgnr = repool_info->pgnr; |
| repool_info->index = 0; |
| repool_info->pgnr = 0; |
| repool_info->initialized = false; |
| spin_unlock_irqrestore(&repool_info->list_lock, flags); |
| |
| for (i = 0; i < pgnr; i++) { |
| ret = set_pages_wb(repool_info->pages[i], 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(repool_info->pages[i], 0); |
| } |
| |
| kfree(repool_info->pages); |
| kfree(repool_info); |
| |
| *pool = NULL; |
| } |
| |
| static int hmm_reserved_pool_inited(void *pool) |
| { |
| struct hmm_reserved_pool_info *repool_info = pool; |
| |
| if (!repool_info) |
| return 0; |
| |
| return repool_info->initialized; |
| } |
| |
| struct hmm_pool_ops reserved_pops = { |
| .pool_init = hmm_reserved_pool_init, |
| .pool_exit = hmm_reserved_pool_exit, |
| .pool_alloc_pages = get_pages_from_reserved_pool, |
| .pool_free_pages = free_pages_to_reserved_pool, |
| .pool_inited = hmm_reserved_pool_inited, |
| }; |