blob: cf037aa3076144500a560cf15530170e93cfadb1 [file] [log] [blame]
/*
* Copyright (C) 2013-2016 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 licence.
*
* A copy of the licence is included with the program, and can also be obtained from Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <linux/list.h>
#include <linux/mm.h>
#include <linux/mm_types.h>
#include <linux/fs.h>
#include <linux/dma-mapping.h>
#include <linux/version.h>
#include <linux/platform_device.h>
#include <linux/workqueue.h>
#include "mali_osk.h"
#include "mali_memory.h"
#include "mali_memory_os_alloc.h"
#include "mali_kernel_linux.h"
/* Minimum size of allocator page pool */
#define MALI_OS_MEMORY_KERNEL_BUFFER_SIZE_IN_PAGES (MALI_OS_MEMORY_KERNEL_BUFFER_SIZE_IN_MB * 256)
#define MALI_OS_MEMORY_POOL_TRIM_JIFFIES (10 * CONFIG_HZ) /* Default to 10s */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0)
static unsigned long dma_attrs_wc= 0;
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0)
/* Write combine dma_attrs */
static DEFINE_DMA_ATTRS(dma_attrs_wc);
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 0, 0)
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 35)
static int mali_mem_os_shrink(int nr_to_scan, gfp_t gfp_mask);
#else
static int mali_mem_os_shrink(struct shrinker *shrinker, int nr_to_scan, gfp_t gfp_mask);
#endif
#else
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 12, 0)
static int mali_mem_os_shrink(struct shrinker *shrinker, struct shrink_control *sc);
#else
static unsigned long mali_mem_os_shrink(struct shrinker *shrinker, struct shrink_control *sc);
static unsigned long mali_mem_os_shrink_count(struct shrinker *shrinker, struct shrink_control *sc);
#endif
#endif
static void mali_mem_os_trim_pool(struct work_struct *work);
extern void show_mem(unsigned int flags);
struct mali_mem_os_allocator mali_mem_os_allocator = {
.pool_lock = __SPIN_LOCK_UNLOCKED(pool_lock),
.pool_pages = LIST_HEAD_INIT(mali_mem_os_allocator.pool_pages),
.pool_count = 0,
.allocated_pages = ATOMIC_INIT(0),
.allocation_limit = 0,
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 12, 0)
.shrinker.shrink = mali_mem_os_shrink,
#else
.shrinker.count_objects = mali_mem_os_shrink_count,
.shrinker.scan_objects = mali_mem_os_shrink,
#endif
.shrinker.seeks = DEFAULT_SEEKS,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0)
.timed_shrinker = __DELAYED_WORK_INITIALIZER(mali_mem_os_allocator.timed_shrinker, mali_mem_os_trim_pool, TIMER_DEFERRABLE),
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 38)
.timed_shrinker = __DEFERRED_WORK_INITIALIZER(mali_mem_os_allocator.timed_shrinker, mali_mem_os_trim_pool),
#else
.timed_shrinker = __DELAYED_WORK_INITIALIZER(mali_mem_os_allocator.timed_shrinker, mali_mem_os_trim_pool),
#endif
};
u32 mali_mem_os_free(struct list_head *os_pages, u32 pages_count, mali_bool cow_flag)
{
LIST_HEAD(pages);
struct mali_page_node *m_page, *m_tmp;
u32 free_pages_nr = 0;
if (MALI_TRUE == cow_flag) {
list_for_each_entry_safe(m_page, m_tmp, os_pages, list) {
/*only handle OS node here */
if (m_page->type == MALI_PAGE_NODE_OS) {
if (1 == _mali_page_node_get_ref_count(m_page)) {
list_move(&m_page->list, &pages);
atomic_sub(1, &mali_mem_os_allocator.allocated_pages);
free_pages_nr ++;
} else {
_mali_page_node_unref(m_page);
m_page->page = NULL;
list_del(&m_page->list);
kfree(m_page);
}
}
}
} else {
list_cut_position(&pages, os_pages, os_pages->prev);
atomic_sub(pages_count, &mali_mem_os_allocator.allocated_pages);
free_pages_nr = pages_count;
}
/* Put pages on pool. */
spin_lock(&mali_mem_os_allocator.pool_lock);
list_splice(&pages, &mali_mem_os_allocator.pool_pages);
mali_mem_os_allocator.pool_count += free_pages_nr;
spin_unlock(&mali_mem_os_allocator.pool_lock);
if (MALI_OS_MEMORY_KERNEL_BUFFER_SIZE_IN_PAGES < mali_mem_os_allocator.pool_count) {
MALI_DEBUG_PRINT(5, ("OS Mem: Starting pool trim timer %u\n", mali_mem_os_allocator.pool_count));
queue_delayed_work(mali_mem_os_allocator.wq, &mali_mem_os_allocator.timed_shrinker, MALI_OS_MEMORY_POOL_TRIM_JIFFIES);
}
return free_pages_nr;
}
/**
* put page without put it into page pool
*/
_mali_osk_errcode_t mali_mem_os_put_page(struct page *page)
{
MALI_DEBUG_ASSERT_POINTER(page);
if (1 == page_count(page)) {
atomic_sub(1, &mali_mem_os_allocator.allocated_pages);
dma_unmap_page(&mali_platform_device->dev, page_private(page),
_MALI_OSK_MALI_PAGE_SIZE, DMA_BIDIRECTIONAL);
ClearPagePrivate(page);
}
put_page(page);
return _MALI_OSK_ERR_OK;
}
_mali_osk_errcode_t mali_mem_os_resize_pages(mali_mem_os_mem *mem_from, mali_mem_os_mem *mem_to, u32 start_page, u32 page_count)
{
struct mali_page_node *m_page, *m_tmp;
u32 i = 0;
MALI_DEBUG_ASSERT_POINTER(mem_from);
MALI_DEBUG_ASSERT_POINTER(mem_to);
if (mem_from->count < start_page + page_count) {
return _MALI_OSK_ERR_INVALID_ARGS;
}
list_for_each_entry_safe(m_page, m_tmp, &mem_from->pages, list) {
if (i >= start_page && i < start_page + page_count) {
list_move_tail(&m_page->list, &mem_to->pages);
mem_from->count--;
mem_to->count++;
}
i++;
}
return _MALI_OSK_ERR_OK;
}
int mali_mem_os_alloc_pages(mali_mem_os_mem *os_mem, u32 size)
{
struct page *new_page;
LIST_HEAD(pages_list);
size_t page_count = PAGE_ALIGN(size) / _MALI_OSK_MALI_PAGE_SIZE;
size_t remaining = page_count;
struct mali_page_node *m_page, *m_tmp;
u32 i;
MALI_DEBUG_ASSERT_POINTER(os_mem);
if (atomic_read(&mali_mem_os_allocator.allocated_pages) * _MALI_OSK_MALI_PAGE_SIZE + size > mali_mem_os_allocator.allocation_limit) {
MALI_DEBUG_PRINT(2, ("Mali Mem: Unable to allocate %u bytes. Currently allocated: %lu, max limit %lu\n",
size,
atomic_read(&mali_mem_os_allocator.allocated_pages) * _MALI_OSK_MALI_PAGE_SIZE,
mali_mem_os_allocator.allocation_limit));
return -ENOMEM;
}
INIT_LIST_HEAD(&os_mem->pages);
os_mem->count = page_count;
/* Grab pages from pool. */
{
size_t pool_pages;
spin_lock(&mali_mem_os_allocator.pool_lock);
pool_pages = min(remaining, mali_mem_os_allocator.pool_count);
for (i = pool_pages; i > 0; i--) {
BUG_ON(list_empty(&mali_mem_os_allocator.pool_pages));
list_move(mali_mem_os_allocator.pool_pages.next, &pages_list);
}
mali_mem_os_allocator.pool_count -= pool_pages;
remaining -= pool_pages;
spin_unlock(&mali_mem_os_allocator.pool_lock);
}
/* Process pages from pool. */
i = 0;
list_for_each_entry_safe(m_page, m_tmp, &pages_list, list) {
BUG_ON(NULL == m_page);
list_move_tail(&m_page->list, &os_mem->pages);
}
/* Allocate new pages, if needed. */
for (i = 0; i < remaining; i++) {
dma_addr_t dma_addr;
gfp_t flags = __GFP_ZERO | __GFP_REPEAT | __GFP_NOWARN | __GFP_COLD;
int err;
#if defined(CONFIG_ARM) && !defined(CONFIG_ARM_LPAE)
flags |= GFP_HIGHUSER;
#else
#ifdef CONFIG_ZONE_DMA32
flags |= GFP_DMA32;
#else
#ifdef CONFIG_ZONE_DMA
flags |= GFP_DMA;
#else
/* arm64 utgard only work on < 4G, but the kernel
* didn't provide method to allocte memory < 4G
*/
MALI_DEBUG_ASSERT(0);
#endif
#endif
#endif
new_page = alloc_page(flags);
if (unlikely(NULL == new_page)) {
/* Calculate the number of pages actually allocated, and free them. */
#ifdef AML_MALI_DEBUG
MALI_PRINT_ERROR(("alloc_page() return NULL\n"));
show_mem(SHOW_MEM_FILTER_NODES);
#endif
os_mem->count = (page_count - remaining) + i;
atomic_add(os_mem->count, &mali_mem_os_allocator.allocated_pages);
mali_mem_os_free(&os_mem->pages, os_mem->count, MALI_FALSE);
return -ENOMEM;
}
/* Ensure page is flushed from CPU caches. */
dma_addr = dma_map_page(&mali_platform_device->dev, new_page,
0, _MALI_OSK_MALI_PAGE_SIZE, DMA_BIDIRECTIONAL);
dma_unmap_page(&mali_platform_device->dev, dma_addr,
_MALI_OSK_MALI_PAGE_SIZE, DMA_BIDIRECTIONAL);
dma_addr = dma_map_page(&mali_platform_device->dev, new_page,
0, _MALI_OSK_MALI_PAGE_SIZE, DMA_BIDIRECTIONAL);
err = dma_mapping_error(&mali_platform_device->dev, dma_addr);
if (unlikely(err)) {
MALI_DEBUG_PRINT_ERROR(("OS Mem: Failed to DMA map page %p: %u",
new_page, err));
__free_page(new_page);
os_mem->count = (page_count - remaining) + i;
atomic_add(os_mem->count, &mali_mem_os_allocator.allocated_pages);
mali_mem_os_free(&os_mem->pages, os_mem->count, MALI_FALSE);
return -EFAULT;
}
/* Store page phys addr */
SetPagePrivate(new_page);
set_page_private(new_page, dma_addr);
m_page = _mali_page_node_allocate(MALI_PAGE_NODE_OS);
if (unlikely(NULL == m_page)) {
#ifdef AML_MALI_DEBUG
show_mem(SHOW_MEM_FILTER_NODES);
#endif
MALI_PRINT_ERROR(("OS Mem: Can't allocate mali_page node! \n"));
dma_unmap_page(&mali_platform_device->dev, page_private(new_page),
_MALI_OSK_MALI_PAGE_SIZE, DMA_BIDIRECTIONAL);
ClearPagePrivate(new_page);
__free_page(new_page);
os_mem->count = (page_count - remaining) + i;
atomic_add(os_mem->count, &mali_mem_os_allocator.allocated_pages);
mali_mem_os_free(&os_mem->pages, os_mem->count, MALI_FALSE);
return -EFAULT;
}
m_page->page = new_page;
list_add_tail(&m_page->list, &os_mem->pages);
}
atomic_add(page_count, &mali_mem_os_allocator.allocated_pages);
if (MALI_OS_MEMORY_KERNEL_BUFFER_SIZE_IN_PAGES > mali_mem_os_allocator.pool_count) {
MALI_DEBUG_PRINT(4, ("OS Mem: Stopping pool trim timer, only %u pages on pool\n", mali_mem_os_allocator.pool_count));
cancel_delayed_work(&mali_mem_os_allocator.timed_shrinker);
}
return 0;
}
_mali_osk_errcode_t mali_mem_os_mali_map(mali_mem_os_mem *os_mem, struct mali_session_data *session, u32 vaddr, u32 start_page, u32 mapping_pgae_num, u32 props)
{
struct mali_page_directory *pagedir = session->page_directory;
struct mali_page_node *m_page;
u32 virt;
u32 prop = props;
MALI_DEBUG_ASSERT_POINTER(session);
MALI_DEBUG_ASSERT_POINTER(os_mem);
MALI_DEBUG_ASSERT(start_page <= os_mem->count);
MALI_DEBUG_ASSERT((start_page + mapping_pgae_num) <= os_mem->count);
if ((start_page + mapping_pgae_num) == os_mem->count) {
virt = vaddr + MALI_MMU_PAGE_SIZE * (start_page + mapping_pgae_num);
list_for_each_entry_reverse(m_page, &os_mem->pages, list) {
virt -= MALI_MMU_PAGE_SIZE;
if (mapping_pgae_num > 0) {
dma_addr_t phys = page_private(m_page->page);
#if defined(CONFIG_ARCH_DMA_ADDR_T_64BIT)
/* Verify that the "physical" address is 32-bit and
* usable for Mali, when on a system with bus addresses
* wider than 32-bit. */
MALI_DEBUG_ASSERT(0 == (phys >> 32));
#endif
mali_mmu_pagedir_update(pagedir, virt, (mali_dma_addr)phys, MALI_MMU_PAGE_SIZE, prop);
} else {
break;
}
mapping_pgae_num--;
}
} else {
u32 i = 0;
virt = vaddr;
list_for_each_entry(m_page, &os_mem->pages, list) {
if (i >= start_page) {
dma_addr_t phys = page_private(m_page->page);
#if defined(CONFIG_ARCH_DMA_ADDR_T_64BIT)
/* Verify that the "physical" address is 32-bit and
* usable for Mali, when on a system with bus addresses
* wider than 32-bit. */
MALI_DEBUG_ASSERT(0 == (phys >> 32));
#endif
mali_mmu_pagedir_update(pagedir, virt, (mali_dma_addr)phys, MALI_MMU_PAGE_SIZE, prop);
}
i++;
virt += MALI_MMU_PAGE_SIZE;
}
}
return _MALI_OSK_ERR_OK;
}
void mali_mem_os_mali_unmap(mali_mem_allocation *alloc)
{
struct mali_session_data *session;
MALI_DEBUG_ASSERT_POINTER(alloc);
session = alloc->session;
MALI_DEBUG_ASSERT_POINTER(session);
mali_session_memory_lock(session);
mali_mem_mali_map_free(session, alloc->psize, alloc->mali_vma_node.vm_node.start,
alloc->flags);
mali_session_memory_unlock(session);
}
int mali_mem_os_cpu_map(mali_mem_backend *mem_bkend, struct vm_area_struct *vma)
{
mali_mem_os_mem *os_mem = &mem_bkend->os_mem;
struct mali_page_node *m_page;
struct page *page;
int ret;
unsigned long addr = vma->vm_start;
MALI_DEBUG_ASSERT(MALI_MEM_OS == mem_bkend->type);
list_for_each_entry(m_page, &os_mem->pages, list) {
/* We should use vm_insert_page, but it does a dcache
* flush which makes it way slower than remap_pfn_range or vm_insert_pfn.
ret = vm_insert_page(vma, addr, page);
*/
page = m_page->page;
ret = vm_insert_pfn(vma, addr, page_to_pfn(page));
if (unlikely(0 != ret)) {
return -EFAULT;
}
addr += _MALI_OSK_MALI_PAGE_SIZE;
}
return 0;
}
_mali_osk_errcode_t mali_mem_os_resize_cpu_map_locked(mali_mem_backend *mem_bkend, struct vm_area_struct *vma, unsigned long start_vaddr, u32 mappig_size)
{
mali_mem_os_mem *os_mem = &mem_bkend->os_mem;
struct mali_page_node *m_page;
int ret;
int offset;
int mapping_page_num;
int count ;
unsigned long vstart = vma->vm_start;
count = 0;
MALI_DEBUG_ASSERT(mem_bkend->type == MALI_MEM_OS);
MALI_DEBUG_ASSERT(0 == start_vaddr % _MALI_OSK_MALI_PAGE_SIZE);
MALI_DEBUG_ASSERT(0 == vstart % _MALI_OSK_MALI_PAGE_SIZE);
offset = (start_vaddr - vstart) / _MALI_OSK_MALI_PAGE_SIZE;
MALI_DEBUG_ASSERT(offset <= os_mem->count);
mapping_page_num = mappig_size / _MALI_OSK_MALI_PAGE_SIZE;
MALI_DEBUG_ASSERT((offset + mapping_page_num) <= os_mem->count);
if ((offset + mapping_page_num) == os_mem->count) {
unsigned long vm_end = start_vaddr + mappig_size;
list_for_each_entry_reverse(m_page, &os_mem->pages, list) {
vm_end -= _MALI_OSK_MALI_PAGE_SIZE;
if (mapping_page_num > 0) {
ret = vm_insert_pfn(vma, vm_end, page_to_pfn(m_page->page));
if (unlikely(0 != ret)) {
/*will return -EBUSY If the page has already been mapped into table, but it's OK*/
if (-EBUSY == ret) {
break;
} else {
MALI_DEBUG_PRINT(1, ("OS Mem: mali_mem_os_resize_cpu_map_locked failed, ret = %d, offset is %d,page_count is %d\n",
ret, offset + mapping_page_num, os_mem->count));
}
return _MALI_OSK_ERR_FAULT;
}
} else {
break;
}
mapping_page_num--;
}
} else {
list_for_each_entry(m_page, &os_mem->pages, list) {
if (count >= offset) {
ret = vm_insert_pfn(vma, vstart, page_to_pfn(m_page->page));
if (unlikely(0 != ret)) {
/*will return -EBUSY If the page has already been mapped into table, but it's OK*/
if (-EBUSY == ret) {
break;
} else {
MALI_DEBUG_PRINT(1, ("OS Mem: mali_mem_os_resize_cpu_map_locked failed, ret = %d, count is %d, offset is %d,page_count is %d\n",
ret, count, offset, os_mem->count));
}
return _MALI_OSK_ERR_FAULT;
}
}
count++;
vstart += _MALI_OSK_MALI_PAGE_SIZE;
}
}
return _MALI_OSK_ERR_OK;
}
u32 mali_mem_os_release(mali_mem_backend *mem_bkend)
{
mali_mem_allocation *alloc;
struct mali_session_data *session;
u32 free_pages_nr = 0;
MALI_DEBUG_ASSERT_POINTER(mem_bkend);
MALI_DEBUG_ASSERT(MALI_MEM_OS == mem_bkend->type);
alloc = mem_bkend->mali_allocation;
MALI_DEBUG_ASSERT_POINTER(alloc);
session = alloc->session;
MALI_DEBUG_ASSERT_POINTER(session);
/* Unmap the memory from the mali virtual address space. */
mali_mem_os_mali_unmap(alloc);
mutex_lock(&mem_bkend->mutex);
/* Free pages */
if (MALI_MEM_BACKEND_FLAG_COWED & mem_bkend->flags) {
/* Lock to avoid the free race condition for the cow shared memory page node. */
_mali_osk_mutex_wait(session->cow_lock);
free_pages_nr = mali_mem_os_free(&mem_bkend->os_mem.pages, mem_bkend->os_mem.count, MALI_TRUE);
_mali_osk_mutex_signal(session->cow_lock);
} else {
free_pages_nr = mali_mem_os_free(&mem_bkend->os_mem.pages, mem_bkend->os_mem.count, MALI_FALSE);
}
mutex_unlock(&mem_bkend->mutex);
MALI_DEBUG_PRINT(4, ("OS Mem free : allocated size = 0x%x, free size = 0x%x\n", mem_bkend->os_mem.count * _MALI_OSK_MALI_PAGE_SIZE,
free_pages_nr * _MALI_OSK_MALI_PAGE_SIZE));
mem_bkend->os_mem.count = 0;
return free_pages_nr;
}
#define MALI_MEM_OS_PAGE_TABLE_PAGE_POOL_SIZE 128
static struct {
struct {
mali_dma_addr phys;
mali_io_address mapping;
} page[MALI_MEM_OS_PAGE_TABLE_PAGE_POOL_SIZE];
size_t count;
spinlock_t lock;
} mali_mem_page_table_page_pool = {
.count = 0,
.lock = __SPIN_LOCK_UNLOCKED(pool_lock),
};
_mali_osk_errcode_t mali_mem_os_get_table_page(mali_dma_addr *phys, mali_io_address *mapping)
{
_mali_osk_errcode_t ret = _MALI_OSK_ERR_NOMEM;
dma_addr_t tmp_phys;
spin_lock(&mali_mem_page_table_page_pool.lock);
if (0 < mali_mem_page_table_page_pool.count) {
u32 i = --mali_mem_page_table_page_pool.count;
*phys = mali_mem_page_table_page_pool.page[i].phys;
*mapping = mali_mem_page_table_page_pool.page[i].mapping;
ret = _MALI_OSK_ERR_OK;
}
spin_unlock(&mali_mem_page_table_page_pool.lock);
if (_MALI_OSK_ERR_OK != ret) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0)
*mapping = dma_alloc_attrs(&mali_platform_device->dev,
_MALI_OSK_MALI_PAGE_SIZE, &tmp_phys,
GFP_KERNEL, dma_attrs_wc);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0)
*mapping = dma_alloc_attrs(&mali_platform_device->dev,
_MALI_OSK_MALI_PAGE_SIZE, &tmp_phys,
GFP_KERNEL, &dma_attrs_wc);
#else
*mapping = dma_alloc_writecombine(&mali_platform_device->dev,
_MALI_OSK_MALI_PAGE_SIZE, &tmp_phys, GFP_KERNEL);
#endif
if (NULL != *mapping) {
ret = _MALI_OSK_ERR_OK;
#if defined(CONFIG_ARCH_DMA_ADDR_T_64BIT)
/* Verify that the "physical" address is 32-bit and
* usable for Mali, when on a system with bus addresses
* wider than 32-bit. */
MALI_DEBUG_ASSERT(0 == (tmp_phys >> 32));
#endif
*phys = (mali_dma_addr)tmp_phys;
}
}
#ifdef AML_MALI_DEBUG
if (ret != _MALI_OSK_ERR_OK) {
MALI_PRINT_ERROR(("dma_alloc_attrs() return NULL\n"));
show_mem(SHOW_MEM_FILTER_NODES);
}
#endif
return ret;
}
void mali_mem_os_release_table_page(mali_dma_addr phys, void *virt)
{
spin_lock(&mali_mem_page_table_page_pool.lock);
if (MALI_MEM_OS_PAGE_TABLE_PAGE_POOL_SIZE > mali_mem_page_table_page_pool.count) {
u32 i = mali_mem_page_table_page_pool.count;
mali_mem_page_table_page_pool.page[i].phys = phys;
mali_mem_page_table_page_pool.page[i].mapping = virt;
++mali_mem_page_table_page_pool.count;
spin_unlock(&mali_mem_page_table_page_pool.lock);
} else {
spin_unlock(&mali_mem_page_table_page_pool.lock);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0)
dma_free_attrs(&mali_platform_device->dev,
_MALI_OSK_MALI_PAGE_SIZE, virt, phys,
dma_attrs_wc);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0)
dma_free_attrs(&mali_platform_device->dev,
_MALI_OSK_MALI_PAGE_SIZE, virt, phys,
&dma_attrs_wc);
#else
dma_free_writecombine(&mali_platform_device->dev,
_MALI_OSK_MALI_PAGE_SIZE, virt, phys);
#endif
}
}
void mali_mem_os_free_page_node(struct mali_page_node *m_page)
{
struct page *page = m_page->page;
MALI_DEBUG_ASSERT(m_page->type == MALI_PAGE_NODE_OS);
if (1 == page_count(page)) {
dma_unmap_page(&mali_platform_device->dev, page_private(page),
_MALI_OSK_MALI_PAGE_SIZE, DMA_BIDIRECTIONAL);
ClearPagePrivate(page);
}
__free_page(page);
m_page->page = NULL;
list_del(&m_page->list);
kfree(m_page);
}
/* The maximum number of page table pool pages to free in one go. */
#define MALI_MEM_OS_CHUNK_TO_FREE 64UL
/* Free a certain number of pages from the page table page pool.
* The pool lock must be held when calling the function, and the lock will be
* released before returning.
*/
static void mali_mem_os_page_table_pool_free(size_t nr_to_free)
{
mali_dma_addr phys_arr[MALI_MEM_OS_CHUNK_TO_FREE];
void *virt_arr[MALI_MEM_OS_CHUNK_TO_FREE];
u32 i;
MALI_DEBUG_ASSERT(nr_to_free <= MALI_MEM_OS_CHUNK_TO_FREE);
/* Remove nr_to_free pages from the pool and store them locally on stack. */
for (i = 0; i < nr_to_free; i++) {
u32 pool_index = mali_mem_page_table_page_pool.count - i - 1;
phys_arr[i] = mali_mem_page_table_page_pool.page[pool_index].phys;
virt_arr[i] = mali_mem_page_table_page_pool.page[pool_index].mapping;
}
mali_mem_page_table_page_pool.count -= nr_to_free;
spin_unlock(&mali_mem_page_table_page_pool.lock);
/* After releasing the spinlock: free the pages we removed from the pool. */
for (i = 0; i < nr_to_free; i++) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0)
dma_free_attrs(&mali_platform_device->dev, _MALI_OSK_MALI_PAGE_SIZE,
virt_arr[i], (dma_addr_t)phys_arr[i], dma_attrs_wc);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0)
dma_free_attrs(&mali_platform_device->dev, _MALI_OSK_MALI_PAGE_SIZE,
virt_arr[i], (dma_addr_t)phys_arr[i], &dma_attrs_wc);
#else
dma_free_writecombine(&mali_platform_device->dev,
_MALI_OSK_MALI_PAGE_SIZE,
virt_arr[i], (dma_addr_t)phys_arr[i]);
#endif
}
}
static void mali_mem_os_trim_page_table_page_pool(void)
{
size_t nr_to_free = 0;
size_t nr_to_keep;
/* Keep 2 page table pages for each 1024 pages in the page cache. */
nr_to_keep = mali_mem_os_allocator.pool_count / 512;
/* And a minimum of eight pages, to accomodate new sessions. */
nr_to_keep += 8;
if (0 == spin_trylock(&mali_mem_page_table_page_pool.lock)) return;
if (nr_to_keep < mali_mem_page_table_page_pool.count) {
nr_to_free = mali_mem_page_table_page_pool.count - nr_to_keep;
nr_to_free = min((size_t)MALI_MEM_OS_CHUNK_TO_FREE, nr_to_free);
}
/* Pool lock will be released by the callee. */
mali_mem_os_page_table_pool_free(nr_to_free);
}
static unsigned long mali_mem_os_shrink_count(struct shrinker *shrinker, struct shrink_control *sc)
{
return mali_mem_os_allocator.pool_count;
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 0, 0)
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 35)
static int mali_mem_os_shrink(int nr_to_scan, gfp_t gfp_mask)
#else
static int mali_mem_os_shrink(struct shrinker *shrinker, int nr_to_scan, gfp_t gfp_mask)
#endif /* Linux < 2.6.35 */
#else
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 12, 0)
static int mali_mem_os_shrink(struct shrinker *shrinker, struct shrink_control *sc)
#else
static unsigned long mali_mem_os_shrink(struct shrinker *shrinker, struct shrink_control *sc)
#endif /* Linux < 3.12.0 */
#endif /* Linux < 3.0.0 */
{
struct mali_page_node *m_page, *m_tmp;
unsigned long flags;
struct list_head *le, pages;
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 0, 0)
int nr = nr_to_scan;
#else
int nr = sc->nr_to_scan;
#endif
if (0 == nr) {
return mali_mem_os_shrink_count(shrinker, sc);
}
if (0 == spin_trylock_irqsave(&mali_mem_os_allocator.pool_lock, flags)) {
/* Not able to lock. */
return -1;
}
if (0 == mali_mem_os_allocator.pool_count) {
/* No pages availble */
spin_unlock_irqrestore(&mali_mem_os_allocator.pool_lock, flags);
return 0;
}
/* Release from general page pool */
nr = min((size_t)nr, mali_mem_os_allocator.pool_count);
mali_mem_os_allocator.pool_count -= nr;
list_for_each(le, &mali_mem_os_allocator.pool_pages) {
--nr;
if (0 == nr) break;
}
list_cut_position(&pages, &mali_mem_os_allocator.pool_pages, le);
spin_unlock_irqrestore(&mali_mem_os_allocator.pool_lock, flags);
list_for_each_entry_safe(m_page, m_tmp, &pages, list) {
mali_mem_os_free_page_node(m_page);
}
if (MALI_OS_MEMORY_KERNEL_BUFFER_SIZE_IN_PAGES > mali_mem_os_allocator.pool_count) {
/* Pools are empty, stop timer */
MALI_DEBUG_PRINT(5, ("Stopping timer, only %u pages on pool\n", mali_mem_os_allocator.pool_count));
cancel_delayed_work(&mali_mem_os_allocator.timed_shrinker);
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 12, 0)
return mali_mem_os_shrink_count(shrinker, sc);
#else
return nr;
#endif
}
static void mali_mem_os_trim_pool(struct work_struct *data)
{
struct mali_page_node *m_page, *m_tmp;
struct list_head *le;
LIST_HEAD(pages);
size_t nr_to_free;
MALI_IGNORE(data);
MALI_DEBUG_PRINT(3, ("OS Mem: Trimming pool %u\n", mali_mem_os_allocator.pool_count));
/* Release from general page pool */
spin_lock(&mali_mem_os_allocator.pool_lock);
if (MALI_OS_MEMORY_KERNEL_BUFFER_SIZE_IN_PAGES < mali_mem_os_allocator.pool_count) {
size_t count = mali_mem_os_allocator.pool_count - MALI_OS_MEMORY_KERNEL_BUFFER_SIZE_IN_PAGES;
const size_t min_to_free = min(64, MALI_OS_MEMORY_KERNEL_BUFFER_SIZE_IN_PAGES);
/* Free half the pages on the pool above the static limit. Or 64 pages, 256KB. */
nr_to_free = max(count / 2, min_to_free);
mali_mem_os_allocator.pool_count -= nr_to_free;
list_for_each(le, &mali_mem_os_allocator.pool_pages) {
--nr_to_free;
if (0 == nr_to_free) break;
}
list_cut_position(&pages, &mali_mem_os_allocator.pool_pages, le);
}
spin_unlock(&mali_mem_os_allocator.pool_lock);
list_for_each_entry_safe(m_page, m_tmp, &pages, list) {
mali_mem_os_free_page_node(m_page);
}
/* Release some pages from page table page pool */
mali_mem_os_trim_page_table_page_pool();
if (MALI_OS_MEMORY_KERNEL_BUFFER_SIZE_IN_PAGES < mali_mem_os_allocator.pool_count) {
MALI_DEBUG_PRINT(4, ("OS Mem: Starting pool trim timer %u\n", mali_mem_os_allocator.pool_count));
queue_delayed_work(mali_mem_os_allocator.wq, &mali_mem_os_allocator.timed_shrinker, MALI_OS_MEMORY_POOL_TRIM_JIFFIES);
}
}
_mali_osk_errcode_t mali_mem_os_init(void)
{
mali_mem_os_allocator.wq = alloc_workqueue("mali-mem", WQ_UNBOUND, 1);
if (NULL == mali_mem_os_allocator.wq) {
return _MALI_OSK_ERR_NOMEM;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0)
dma_attrs_wc = DMA_ATTR_WRITE_COMBINE;
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0)
dma_set_attr(DMA_ATTR_WRITE_COMBINE, &dma_attrs_wc);
#endif
register_shrinker(&mali_mem_os_allocator.shrinker);
return _MALI_OSK_ERR_OK;
}
void mali_mem_os_term(void)
{
struct mali_page_node *m_page, *m_tmp;
unregister_shrinker(&mali_mem_os_allocator.shrinker);
cancel_delayed_work_sync(&mali_mem_os_allocator.timed_shrinker);
if (NULL != mali_mem_os_allocator.wq) {
destroy_workqueue(mali_mem_os_allocator.wq);
mali_mem_os_allocator.wq = NULL;
}
spin_lock(&mali_mem_os_allocator.pool_lock);
list_for_each_entry_safe(m_page, m_tmp, &mali_mem_os_allocator.pool_pages, list) {
mali_mem_os_free_page_node(m_page);
--mali_mem_os_allocator.pool_count;
}
BUG_ON(mali_mem_os_allocator.pool_count);
spin_unlock(&mali_mem_os_allocator.pool_lock);
/* Release from page table page pool */
do {
u32 nr_to_free;
spin_lock(&mali_mem_page_table_page_pool.lock);
nr_to_free = min((size_t)MALI_MEM_OS_CHUNK_TO_FREE, mali_mem_page_table_page_pool.count);
/* Pool lock will be released by the callee. */
mali_mem_os_page_table_pool_free(nr_to_free);
} while (0 != mali_mem_page_table_page_pool.count);
}
_mali_osk_errcode_t mali_memory_core_resource_os_memory(u32 size)
{
mali_mem_os_allocator.allocation_limit = size;
MALI_SUCCESS;
}
u32 mali_mem_os_stat(void)
{
return atomic_read(&mali_mem_os_allocator.allocated_pages) * _MALI_OSK_MALI_PAGE_SIZE;
}