diff options
author | Srikant Patnaik | 2015-01-13 15:08:24 +0530 |
---|---|---|
committer | Srikant Patnaik | 2015-01-13 15:08:24 +0530 |
commit | 97327692361306d1e6259021bc425e32832fdb50 (patch) | |
tree | fe9088f3248ec61e24f404f21b9793cb644b7f01 /drivers/video/wmt/wmt-mb.c | |
parent | 2d05a8f663478a44e088d122e0d62109bbc801d0 (diff) | |
parent | a3a8b90b61e21be3dde9101c4e86c881e0f06210 (diff) | |
download | FOSSEE-netbook-kernel-source-97327692361306d1e6259021bc425e32832fdb50.tar.gz FOSSEE-netbook-kernel-source-97327692361306d1e6259021bc425e32832fdb50.tar.bz2 FOSSEE-netbook-kernel-source-97327692361306d1e6259021bc425e32832fdb50.zip |
dirty fix to merging
Diffstat (limited to 'drivers/video/wmt/wmt-mb.c')
-rw-r--r-- | drivers/video/wmt/wmt-mb.c | 3357 |
1 files changed, 3357 insertions, 0 deletions
diff --git a/drivers/video/wmt/wmt-mb.c b/drivers/video/wmt/wmt-mb.c new file mode 100644 index 00000000..375d53c8 --- /dev/null +++ b/drivers/video/wmt/wmt-mb.c @@ -0,0 +1,3357 @@ +/*++ + * WonderMedia Memory Block driver + * + * Copyright c 2010 WonderMedia Technologies, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + * 10F, 529, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C +--*/ +/*---------------------------------------------------------------------------- + * MODULE : driver/video/wmt/memblock.c + * DATE : 2008/12/08 + * DESCRIPTION : Physical memory management driver + * HISTORY : + * Version 1.0.0.2 , 2013/11/12 +*----------------------------------------------------------------------------*/ +#ifndef MEMBLOCK_C +#define MEMBLOCK_C +#endif + +#include <linux/version.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/errno.h> +#include <linux/kdev_t.h> +#include <linux/cdev.h> +#include <linux/mman.h> +#include <linux/list.h> +#include <linux/proc_fs.h> +#include <asm/page.h> +#include <asm/uaccess.h> +#include <asm/cacheflush.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/major.h> +#include <linux/sched.h> +#include <linux/swap.h> +#include <mach/wmt_env.h> + +#include "com-mb.h" +#include <linux/wmt-mb.h> + +#define THE_MB_USER "WMT-MB" +#define MB_VERSION_MAJOR 1 +#define MB_VERSION_MINOR 0 +#define MB_VERSION_MICRO 0 +#define MB_VERSION_BUILD 3 + +#define MB_DEBUG +#define MB_INFO(fmt, args...) \ + printk(KERN_INFO "["THE_MB_USER"] " fmt , ## args) +#define MB_WARN(fmt, args...) \ + printk(KERN_WARNING "["THE_MB_USER" *W*] " fmt, ## args) +#define MB_ERROR(fmt, args...) \ + printk(KERN_ERR "["THE_MB_USER" *E*] " fmt , ## args) + +#ifdef MB_DEBUG +#define MB_OPDBG(fmt, args...) { /* FILE OPERATION DBG */ \ + if (MBMSG_LEVEL) { \ + printk("\t" KERN_DEBUG "["THE_MB_USER"] %s: " fmt, \ + __func__ , ## args); \ + } \ +} +#define MB_DBG(fmt, args...) { \ + if (MBMSG_LEVEL > 1) { \ + printk("\t" KERN_DEBUG "["THE_MB_USER"] %s: " fmt, \ + __func__ , ## args); \ + } \ +} +#define MB_WDBG(fmt, args...) { \ + if (MBMSG_LEVEL > 2) { \ + printk("\t" KERN_DEBUG "["THE_MB_USER"] %s: " fmt, \ + __func__ , ## args); \ + } \ +} +#else +#define MB_OPDBG(fmt, args...) do {} while (0) +#define MB_DBG(fmt, args...) do {} while (0) +#define MB_WDBG(fmt, args...) do {} while (0) +#endif + +#ifdef CONFIG_WMT_MB_RESERVE_FROM_IO +#include <linux/fb.h> +#define MBIO_FSCREENINFO FBIOGET_FSCREENINFO +#define MBA_MAX_ORDER 20 /* 2^20 pages = 4 GB */ +#else +#define MBA_MAX_ORDER (MAX_ORDER - 1) /* 2^10 pages = 4 MB */ +#endif +#define MBA_MIN_ORDER (MAX_ORDER - 5) /* 2^6 pages = 256 KB */ + +#define MBAFLAG_STATIC 0x80000000 /* static, without release if empty */ + +#define MBFLAG_CACHED 0x00000001 /* MB mapping as cachable */ + +#define MBFIND_VIRT 0x00000000 /* search virt address */ +#define MBFIND_PHYS 0x00000001 /* search phys address */ + +#define MBUFIND_USER 0x00000001 /* search user address */ +#define MBUFIND_VIRT 0x00000002 /* search kernel virt address */ +#define MBUFIND_PHYS 0x00000004 /* search kernel phys address */ +#define MBUFIND_ALL 0x00000010 /* search all mbu */ +#define MBUFIND_CREATOR 0x00000020 /* search creator */ +#define MBUFIND_MMAP 0x00000040 /* search mbu which mapping from user */ +#define MBUFIND_GETPUT 0x00000080 /* search mbu which came from get/put */ +#define MBUFIND_ADDRMASK (MBUFIND_USER|MBUFIND_VIRT|MBUFIND_PHYS) + +#define MBPROC_BUFSIZE (16*1024) +#define MBA_SHOW_BUFSIZE (8*1024) +#define MB_SHOW_BUFSIZE (4*1024) + +#define MB_COUNT(mb) atomic_read(&(mb)->count) +#define MB_IN_USE(mb) (MB_COUNT(mb)) /* count != 0 */ +#define PAGE_KB(a) ((a)*(PAGE_SIZE/1024)) +#define MB_COMBIND_MBA(h, t) { \ + (h)->pgi.pfn_end = (t)->pgi.pfn_end; \ + (h)->pages += (t)->pages; \ + (h)->tot_free_pages += (t)->tot_free_pages; \ + (h)->max_available_pages += (t)->max_available_pages; \ + list_del(&((t)->mba_list)); \ + wmt_mbah->nr_mba--; \ + kmem_cache_free(wmt_mbah->mba_cachep, t); \ +} + +#define list_loop list_for_each_entry +struct mb_user; + +struct page_info { + unsigned long pfn_start; /* start pfn of this block */ + unsigned long pfn_end; /* end pfn of this block */ +}; + +struct mba_host_struct { + /* MB area link list link all MBAs */ + struct list_head mba_list; + + /* page information of this MBA */ + unsigned int nr_mba; + + /* total pages of all manager MBAs */ + unsigned long tot_pages; + + /* total static pages of all manager MBAs */ + unsigned long tot_static_pages; + + /* total free pages of all manager MBAs */ + unsigned long tot_free_pages; + + /* max free pages of all manager MBAs */ + unsigned long max_available_pages; + + /* allocator of MBA, + use slab to prevent memory from fragment. */ + struct kmem_cache *mba_cachep; + + /* allocator of MB, + use slab to prevent memory from fragment. */ + struct kmem_cache *mb_cachep; + + /* allocator of mb_user, + use slab to prevent memory from fragment. */ + struct kmem_cache *mbu_cachep; + + /* allocator of mb_task_info, + use slab to prevent memory from fragment. */ + struct kmem_cache *mbti_cachep; +}; + +struct mb_area_struct { + /* MB area link list link all MBAs */ + struct list_head mba_list; + + /* link list link to all MBs + belong to this MBA */ + struct list_head mb_list; + + /* pointer point to dedicated MBAH */ + struct mba_host_struct *mbah; + + /* flags of MBA */ + unsigned int flags; + + /* prefech record task id */ + pid_t tgid; + + /* start physical address of this MBA */ + unsigned long phys; + + /* start virtual address of this MBA */ + void *virt; + + /* size of this MB area. Normally, + MAX kernel permitted size */ + unsigned long pages; + + /* page information of this MBA */ + struct page_info pgi; + + /* page information of this MBA */ + unsigned int nr_mb; + + /* cur total free pages of this MBA */ + unsigned long tot_free_pages; + + /* cur max free pages of this MBA */ + unsigned long max_available_pages; +}; + +struct mb_struct { + /* MB link list link all MBs + in dedicated MBA */ + struct list_head mb_list; + + /* pointer point to dedicated MBA */ + struct mb_area_struct *mba; + + /* MB kernel page information */ + struct page_info pgi; + + /* start physical address of this MB */ + unsigned long phys; + + /* start virtual address of this MB */ + void *virt; + + /* allocate size */ + unsigned long size; + + /* current MB use count, + release until zero */ + atomic_t count; + + /* flags of MB */ + unsigned int flags; + + /* point to owner created the mb. + this enlisted in mbu_list */ + struct mb_user *creator; + + /* use for trace the user of mb */ + struct list_head mbu_list; +}; + +struct mb_user { + /* user link list link all users + (include creator) of belonged MB */ + struct list_head mbu_list; + + /* the mb to which this user belong */ + struct mb_struct *mb; + + /* task id to which user belonged, + user space user only */ + pid_t tgid; + + /* mb_mmap and MBIO_GET : user address + mb_get and mb->creator : physical address */ + unsigned long addr; + + /* kernel space user: mb size + * user space user: mmap size + * zero: owner - user space allocate but not mapped yet + * not owner -come from get/put */ + unsigned long size; + + /* user name for recoder */ + char the_user[TASK_COMM_LEN+1]; +}; + +struct mb_task_info { + /* task link list link all tasks which use MBDev */ + struct list_head mbti_list; + pid_t tgid; /* task pid */ + atomic_t count; /* multi open record */ + char task_name[TASK_COMM_LEN+1]; + struct task_struct *task; +}; + +#ifdef CONFIG_WMT_MB_SIZE +static int MB_TOTAL_SIZE = CONFIG_WMT_MB_SIZE * 1024; +#else +static int MB_TOTAL_SIZE = 32 * 1024; +#endif +static struct mba_host_struct *wmt_mbah; +static struct mb_task_info wmt_mbti; +const struct file_operations mb_fops; +static unsigned char MBMSG_LEVEL; +static unsigned char MBMAX_ORDER; +static unsigned char MBMIN_ORDER; +static unsigned char USR2PRDT_METHOD; + +/* read/write spinlock for multientry protection. */ +static spinlock_t mb_do_lock; +static spinlock_t mb_search_lock; +static spinlock_t mb_ioctl_lock; +static spinlock_t mb_task_mm_lock; +static spinlock_t mb_task_lock; +static struct page *pg_user[12800]; /* 12800 pages = 50 MB */ +static char show_mb_buffer[MB_SHOW_BUFSIZE]; +static char show_mba_buffer[MBA_SHOW_BUFSIZE]; + +#define MMU_SEARCH_DONE 1 +typedef int (*mb_page_proc)(void *, dma_addr_t, int); + +static inline int mmu_search_pte( + pte_t *pte, + unsigned long boundary, + unsigned long *addr, + unsigned long *offset, + mb_page_proc proc, + void *priv) +{ + int ret = 0, index = 0; + + /* for each pte */ + while (pte && (*addr < boundary)) { + unsigned long pfn = pte_pfn(*pte); + struct page *pg = pfn_to_page(pfn); + void *virt = page_address(pg); + dma_addr_t phys = virt_to_phys(virt); + + MB_WDBG("\t\t[PTE%3d] PTE[%p]=%x n %lx V %p P %x U %lx[%lx]\n", + index++, pte, pte_val(*pte), pfn, + virt, phys, *addr, *offset); + if (!pfn_valid(pfn)) { + MB_WARN("invalid pfn %ld of addr %lx\n", pfn, *addr); + return -1; + } + if (*offset < PAGE_SIZE) { + ret = proc(priv, phys, *offset); + if (ret) + break; + *offset = 0; + } else + *offset -= PAGE_SIZE; + *addr += PAGE_SIZE; + pte++; +#ifdef MB_WORDY + msleep(30); +#endif + }; + return ret; +} + +static inline int mmu_search_pmd( + pmd_t *pmd, + unsigned long boundary, + unsigned long *addr, + unsigned long *offset, + mb_page_proc proc, + void *priv) +{ + int ret = 0, index = 0; + unsigned long end; + pte_t *pte; + + /* for each pmd */ + while (pmd && (*addr < boundary)) { + /* from start to PMD alignment */ + end = (*addr + PMD_SIZE) & PMD_MASK; + end = min(end, boundary); + pte = pte_offset_map(pmd, *addr); + if (pte == NULL) { + MB_WARN("[%08lx] *pmd=%08llx unknown pte\n", + *addr, (long long)pmd_val(*pmd)); + return -1; + } + MB_WDBG("\t[PMD%3d, %lx, %lx] *(%p)=%x addr %lx\n", index++, + end - PMD_SIZE, end, pmd, pmd_val(*pmd), *addr); + ret = mmu_search_pte(pte, end, addr, offset, proc, priv); + if (ret) + break; + pmd++; + }; + return ret; +} + +static inline int mmu_search_pgd( + pgd_t *pgd, + unsigned long boundary, + unsigned long *addr, + unsigned long *offset, + mb_page_proc proc, + void *priv) +{ + int ret = 0, index = 0; + unsigned long end; + pmd_t *pmd; + pud_t *pud; + + /* for each pgd */ + while (pgd && (*addr < boundary)) { + /* from start to PGDIR alignment */ + end = (*addr + PGDIR_SIZE) & PGDIR_MASK; + end = min(end, boundary); + MB_WDBG("[PGD%3d, %lx, %lx] *(%p)=%x addr %lx\n", index++, + end - PGDIR_SIZE, end, pgd, pgd_val(*pgd), *addr); + pud = pud_offset(pgd, *addr); + if (pud_none(*pud) || pud_bad(*pud)) { + MB_WARN("[%08lx] *pgd=%08llx %s pud\n", + *addr, (long long)pgd_val(*pgd), + pud_none(*pud) ? "(None)" : "(Bad)"); + return -1; + } + pmd = pmd_offset(pud, *addr); + if (pmd == NULL) { + MB_WARN("[%08lx] *pgd=%08llx unknown pmd\n", + *addr, (long long)pgd_val(*pgd)); + return -1; + } + ret = mmu_search_pmd(pmd, end, addr, offset, proc, priv); + if (ret) + break; + pgd++; + }; + + return ret; +} + +struct prdt_search_info { + int index; + int items; + unsigned int size; + struct prdt_struct *prev; + struct prdt_struct *next; +}; + +static int __user_to_prdt_proc(void *priv, dma_addr_t phys, int offset) +{ + struct prdt_search_info *info = (struct prdt_search_info *)priv; + struct prdt_struct *prev = info->prev, *next = info->next; + int len = PAGE_SIZE - offset; + + if (len > info->size) + len = info->size; + + /* Check it could combind with previous one */ + if (prev && + /* prd size boundary check, MAX 60K */ + (prev->size <= ((1 << 16) - (2 * PAGE_SIZE))) && + /* page continuity check */ + ((prev->addr + prev->size) == phys)) + prev->size += len; + else { /* create new one */ + info->prev = prev = next; + info->next = next + 1; + prev->addr = phys + offset; + prev->size = len; + info->index++; + info->items--; + if (!info->items) { + MB_WARN("PRD table full (ptr %p # %d left %d).\n", + next, info->index, info->size); + prev->EDT = 1; + return -1; + } + } + info->size -= len; + prev->reserve = 0; + prev->EDT = (info->size) ? 0 : 1; + MB_WDBG("\t\t\t[PRD %3d] %p start %x(%x + %d) size %x edt %x left %x\n", + info->index, prev, prev->addr, phys, offset, + prev->size, prev->EDT, info->size); + + if (prev->EDT) + return MMU_SEARCH_DONE; + + return 0; +} + +static int __user_to_prdt( + unsigned long user, + unsigned int size, + struct prdt_struct *prdt, + unsigned int items) +{ + struct mm_struct *mm = current->mm; + struct vm_area_struct *vma; + unsigned long addr, end, offset; + struct prdt_search_info info = {0}; + unsigned int idx; + pgd_t *pgd; + int ret; + + if (!prdt || ((size / PAGE_SIZE) + 2) > items) { + MB_WARN("PRD table space not enough (ptr %p at least %lu).\n", + prdt, (size/PAGE_SIZE)+2); + return -EINVAL; + } + + MB_DBG("Memory(%#lx,%d) PRDT(%p,%d)\n", user, size, prdt, items); + info.size = size; + info.next = prdt; + info.items = items; + addr = user; + + down_read(&mm->mmap_sem); + while (info.size > 0) { + vma = find_vma(mm, addr); + if (vma == NULL) { + MB_WARN("user addr %lx not found in task %s\n", + addr, current->comm); + info.next->EDT = 1; + goto fault; + } + + MB_WDBG("VMA found: mm %p start %lx end %lx flags %lx\n", + mm, vma->vm_start, vma->vm_end, vma->vm_flags); + end = PAGE_ALIGN(vma->vm_end); + offset = addr - vma->vm_start; + addr = vma->vm_start; + pgd = pgd_offset(mm, vma->vm_start); + ret = mmu_search_pgd(pgd, end, &addr, &offset, + __user_to_prdt_proc, &info); + if (ret == MMU_SEARCH_DONE) + break; + if (ret) + goto fault; + } + + MB_WDBG("PRDT %p, from %lx size %d\n", prdt, user, size); + for (idx = 0;; idx++) { + MB_WDBG("PRDT[%d] adddr %x size %d EDT %d\n", + idx, prdt[idx].addr, prdt[idx].size, prdt[idx].EDT); + dmac_flush_range(__va(prdt[idx].addr), + __va(prdt[idx].addr + prdt[idx].size)); + outer_flush_range(prdt[idx].addr, + prdt[idx].addr + prdt[idx].size); + if (prdt[idx].EDT) + break; + } + up_read(&mm->mmap_sem); + return 0; +fault: + MB_WARN("USER TO PRDT unfinished, remain size %d\n", info.size); + up_read(&mm->mmap_sem); + return -EFAULT; +} + +static int __user_to_prdt1( + unsigned long user, + unsigned int size, + struct prdt_struct *next, + unsigned int items) +{ + void *ptr_start, *ptr_end, *ptr, *virt = NULL; + int res, pg_size, pg_idx = 0, nr_pages = 0; + struct vm_area_struct *vma; + + ptr_start = ptr = (void *)user; + ptr_end = ptr_start + size; + MB_DBG("Memory(%#lx,%d) PRDT(%p,%d)\n", user, size, next, items); + + down_read(¤t->mm->mmap_sem); + vma = find_vma(current->mm, user); + up_read(¤t->mm->mmap_sem); + /* For kernel direct-mapped memory, take the easy way */ + if (vma && (vma->vm_flags & VM_IO) && (vma->vm_pgoff)) { + unsigned long phys = 0; + /* kernel-allocated, mmaped-to-usermode addresses */ + phys = (vma->vm_pgoff << PAGE_SHIFT) + (user - vma->vm_start); + virt = __va(phys); + MB_INFO("kernel-alloc, mmaped-to-user addr U %lx V %p P %lx", + user, virt, phys); + BUG_ON(1); + return -EFAULT; + } + MB_WDBG("VMA found: mm %p start %lx end %lx flags %lx\n", + current->mm, vma->vm_start, vma->vm_end, vma->vm_flags); + + nr_pages = (size + (PAGE_SIZE - 1)) / PAGE_SIZE; + if (!next || ((size/PAGE_SIZE)+2) > items || nr_pages > 2560) { + MB_WARN("PRD table space full (ptr %p pages %d prdt %d/%lu).\n", + next, nr_pages, items, (size/PAGE_SIZE)+2); + return -EINVAL; + } + + memset(pg_user, 0x0, sizeof(struct page *)*2560); + down_read(¤t->mm->mmap_sem); + res = get_user_pages(current, current->mm, + (unsigned long)ptr, nr_pages, 1, 0, pg_user, NULL); + while (res > 0 && size > 0) { + pg_size = PAGE_SIZE - ((unsigned long)ptr & ~PAGE_MASK); + virt = page_address(pg_user[pg_idx]) + + ((unsigned long)ptr & ~PAGE_MASK); + if (pg_size > size) + pg_size = size; + MB_DBG("Get %d-th user page s %d/%d u %p v %p p %lx\n", + pg_idx, pg_size, size, ptr, virt, __pa(virt)); + if ((next->addr + next->size) != __pa(virt) || + (next->size + pg_size) >= 65536 || !pg_idx) { + if (pg_idx) { + next->EDT = 0; + next++; + } + memset(next, 0x0, sizeof(struct prdt_struct)); + next->addr = __pa(virt); + } + next->size += pg_size; + next->EDT = 1; + size -= pg_size; + ptr += pg_size; + pg_idx++; + } + next->EDT = 1; + up_read(¤t->mm->mmap_sem); + return size; +} + +static int __user_to_prdt2( + unsigned long user, + unsigned int size, + struct prdt_struct *next, + unsigned int items) +{ + void *ptr_start, *ptr_end, *ptr, *virt = NULL; + int res, pg_size, pg_idx = 0, ret = 0; + struct vm_area_struct *vma; + struct page *pages = NULL; + + ptr_start = ptr = (void *)user; + ptr_end = ptr_start + size; + MB_DBG("Memory(%#lx,%d) PRDT(%p,%d)\n", user, size, next, items); + + down_read(¤t->mm->mmap_sem); + vma = find_vma(current->mm, user); + up_read(¤t->mm->mmap_sem); + /* For kernel direct-mapped memory, take the easy way */ + if (vma && (vma->vm_flags & VM_IO) && (vma->vm_pgoff)) { + unsigned long phys = 0; + /* kernel-allocated, mmaped-to-usermode addresses */ + phys = (vma->vm_pgoff << PAGE_SHIFT) + (user - vma->vm_start); + virt = __va(phys); + MB_INFO("kernel-alloc, user-mmaped addr U %lx V %p P %lx", + user, virt, phys); + BUG_ON(1); + return -EFAULT; + } + + if (!next || ((size/PAGE_SIZE)+2) > items) { + MB_WARN("PRD table space full (ptr %p at least %lu)\n", + next, (size/PAGE_SIZE)+2); + return -EINVAL; + } + + MB_WDBG("VMA found: mm %p start %lx end %lx flags %lx\n", + current->mm, vma->vm_start, vma->vm_end, vma->vm_flags); + + while (size) { + down_read(¤t->mm->mmap_sem); + res = get_user_pages(current, current->mm, + (unsigned long)ptr, 1, 1, 0, &pages, NULL); + up_read(¤t->mm->mmap_sem); + pg_size = PAGE_SIZE - ((unsigned long)ptr & ~PAGE_MASK); + if (res != 1) { + MB_ERROR("Get %d-th user pages (a %p s %d/%d) fail\n", + pg_idx, ptr, pg_size, size); + next->EDT = 1; + ret = -EFAULT; + break; + } + virt = page_address(&pages[0]) + + ((unsigned long)ptr & ~PAGE_MASK); + pg_size = (pg_size > size) ? size : pg_size; + MB_DBG("Get %d-th user page s %d/%d u %p v %p p %lx\n", + pg_idx, pg_size, size, ptr, virt, __pa(virt)); + if ((next->addr + next->size) != __pa(virt) || + (next->size + pg_size) >= 65536 || !pg_idx) { + if (pg_idx) { + next->EDT = 0; + next++; + } + memset(next, 0x0, sizeof(struct prdt_struct)); + next->addr = __pa(virt); + } + next->size += pg_size; + next->EDT = 1; + size -= pg_size; + ptr += pg_size; + pg_idx++; + } + return ret; +} + +static void show_prdt(struct prdt_struct *next) +{ + int idx = 1; + while (!next->EDT) { + MB_INFO("PRDT %d-th item: addr %x size %d EDT %d\n", + idx, next->addr, next->size, next->EDT); + idx++; + next++; + } + MB_INFO("PRDT last(%d-th) item: addr %x size %d EDT %d\n", + idx, next->addr, next->size, next->EDT); +} + +/*!************************************************************************* +* wmt_mmu_table_size +* +* Public Function +*/ +/*! +* \brief +* estimate request mmu table size for input size +* +* \parameter +* size [IN] convert size +* +* \retval size of needed mmu table +*/ +unsigned int wmt_mmu_table_size(unsigned int size) +{ + unsigned int nPte, nPde, need; + + if (!size) + return 0; + + nPte = ((PAGE_ALIGN(size)) / PAGE_SIZE) + 1; + nPde = ALIGN(nPte, 1024) / 1024; + + need = (ALIGN(nPde, 1024) / 1024) * PAGE_SIZE; + need += nPte * 4; + + printk(KERN_DEBUG "PDE %d PTE %d RequestSize %d\n", nPde, nPte, need); + return need; +} +EXPORT_SYMBOL(wmt_mmu_table_size); + +/*!************************************************************************* +* wmt_mmu_table_check +* +* Public Function +*/ +/*! +* \brief +* check pre-allocated mmu table is valid for input size +* +* \parameter +* mmu_addr [IN] mmu table address +* mmu_size [IN] mmu table size +* size [IN] convert size +* +* \retval 1 if success +*/ +int wmt_mmu_table_check( + unsigned int *mmu_addr, + unsigned int mmu_size, + unsigned int size) +{ + unsigned int nPte, nPde, request; + + if (!size) { + printk(KERN_WARNING "mmu create failure. NULL size\n"); + return 0; + } + + if (!mmu_size) { + printk(KERN_WARNING "mmu create failure. NULL mmu size\n"); + return 0; + } + + if ((unsigned int)mmu_addr % PAGE_SIZE) { + printk(KERN_WARNING "mmu create fail. PDE Table not align\n"); + return 0; + } + + nPte = ((PAGE_ALIGN(size)) / PAGE_SIZE) + 1; + nPde = ALIGN(nPte, 1024) / 1024; + + request = (ALIGN(nPde, 1024) / 1024) * PAGE_SIZE; + request += nPte * 4; + + if (mmu_size < request) { + printk(KERN_WARNING "mmu create fail. out of mmu size\n"); + printk(KERN_WARNING "(pde %d pte %d request %d but %d)\n", + nPde, nPte, request, mmu_size); + return 0; + } + + return 1; +} +EXPORT_SYMBOL(wmt_mmu_table_check); + +/*!************************************************************************* +* wmt_mmu_table_dump +* +* Public Function +*/ +/*! +* \brief +* dump mmu table +* +* \parameter +* mmu_addr [IN] mmu table address +* size [IN] convert size +* virBufAddr [IN] offset combination of PDE, PTE, and ADDRESS +* +* \retval none +*/ +void wmt_mmu_table_dump(struct mmu_table_info *info) +{ + unsigned int *mmu_addr; + unsigned int size; + unsigned int virBufAddr; + unsigned int *pte = NULL, *pde = NULL; + unsigned int pdeOffset, pteOffset, addrOffset, i = 0; + + if (info == 0) { + printk(KERN_ERR "[WMT_MMU_TABLE] Null input pointer\n"); + return; + } + + mmu_addr = (unsigned int *)mb_phys_to_virt(info->addr); + + size = info->size; + virBufAddr = info->offset; + + addrOffset = virBufAddr % PAGE_SIZE; + pteOffset = (virBufAddr >> 12) % 1024; + pdeOffset = (virBufAddr >> 22) % 1024; + + printk(KERN_INFO "MMU (%x): offset pde %x pte %x addr %x\n", + virBufAddr, pdeOffset, pteOffset, addrOffset); + + pde = mmu_addr; + pde += pdeOffset; + pte = mb_phys_to_virt(*pde); + pte += pteOffset; + + while (size > 0) { + printk(KERN_INFO "[%5d] PDE(%p/%lx) -> PTE(%p/%lx) -> addr %x\n", + i, pde, mb_virt_to_phys(pde), pte, mb_virt_to_phys(pte), + *pte + addrOffset); + if ((size + addrOffset) < PAGE_SIZE) + break; + size -= PAGE_SIZE - addrOffset; + addrOffset = 0; + i++; + pte++; + if (!(i % 1024)) + pde++; + } +} +EXPORT_SYMBOL(wmt_mmu_table_dump); + +/*!************************************************************************* +* wmt_mmu_table_from_phys +* +* Public Function +*/ +/*! +* \brief +* make up mmu table for physical memory +* +* \parameter +* mmu_addr [IN] mmu table address +* mmu_size [IN] mmu table size +* addr [IN] physical address of convert data +* size [IN] convert size +* +* \retval 0xFFFFFFFF if fail +* offset combination of PDE, PTE, and ADDRESS if success +*/ +unsigned int wmt_mmu_table_from_phys( + unsigned int *mmu_addr, + unsigned int mmu_size, + unsigned int addr, + unsigned int size) +{ + unsigned int iPte, nPte, nPde, gPde, virBufAddr; + unsigned int *pte = NULL, *pde = NULL; + + iPte = gPde = nPte = nPde = 0; + if (!wmt_mmu_table_check(mmu_addr, mmu_size, size)) { + printk(KERN_WARNING + "phys %x (size %d) to mmu fail\n", addr, size); + return 0xFFFFFFFF; + } + + virBufAddr = addr % PAGE_SIZE; + nPte = ((PAGE_ALIGN(size)) / PAGE_SIZE); + if (virBufAddr) + nPte++; + nPde = ALIGN(nPte, 1024) / 1024; + gPde = ALIGN(nPde, 1024) / 1024; + + pde = mmu_addr; + pte = pde + (gPde * PAGE_SIZE / sizeof(pte)); + *pde = mb_virt_to_phys(pte); + pde++; + + size += virBufAddr; + while (size > 0) { + *pte = addr & PAGE_MASK; + pte++; iPte++; + if (!(iPte % 1024)) { + *pde = mb_virt_to_phys(pte); + pde++; + } + if (size < PAGE_SIZE) + break; + size -= PAGE_SIZE; + addr += PAGE_SIZE; + } + + return virBufAddr; +} +EXPORT_SYMBOL(wmt_mmu_table_from_phys); + + +struct mmu_search_info { + int iPte; + unsigned *pte; + unsigned *pde; + unsigned int size; +}; + +static int wmt_mmu_table_from_user_proc( + void *priv, dma_addr_t phys, int offset) +{ + struct mmu_search_info *info = (struct mmu_search_info *)priv; + + *info->pte = phys & PAGE_MASK; + + printk(KERN_DEBUG + "\t\t\t[PTE] PDE[%d](%p)=%x PTE[%d](%p)=%x left %d\n", + info->iPte/1024, info->pde, *info->pde, + info->iPte, info->pte, *info->pte, info->size); + info->size -= PAGE_SIZE; + if (info->size < PAGE_SIZE) + return MMU_SEARCH_DONE; + info->iPte++; + info->pte++; + if (!(info->iPte % 1024)) { + *info->pde = mb_virt_to_phys(info->pte); + info->pde++; + } + return 0; +} + +/*!************************************************************************* +* wmt_mmu_table_from_user +* +* Public Function +*/ +/*! +* \brief +* make up mmu table for user memory +* +* \parameter +* mmu_addr [IN] mmu table address +* mmu_size [IN] mmu table size +* addr [IN] physical address of convert data +* size [IN] convert size +* +* \retval 0xFFFFFFFF if fail +* offset combination of PDE, PTE, and ADDRESS if success +*/ +unsigned int wmt_mmu_table_from_user( + unsigned int *mmu_addr, + unsigned int mmu_size, + unsigned int user, + unsigned int size) +{ +#ifndef __KERNEL__ + return wmt_mmu_table_from_phys(mmu_addr, mmu_size, user, size); +#else + struct mm_struct *mm = current->mm; + struct vm_area_struct *vma; + unsigned long addr, end, offset; + unsigned int nPte, nPde, virBufAddr; + struct mmu_search_info info = {0}; + pgd_t *pgd; + int ret; + + nPte = nPde = 0; + if (!wmt_mmu_table_check(mmu_addr, mmu_size, size)) { + printk(KERN_WARNING "phys %x (size %d) to mmu fail\n", + user, size); + return 0xFFFFFFFF; + } + + virBufAddr = user % PAGE_SIZE; + nPte = ((PAGE_ALIGN(size)) / PAGE_SIZE); + if (virBufAddr) + nPte++; + nPde = ALIGN(ALIGN(nPte, 1024) / 1024, 1024) / 1024; + + info.pde = mmu_addr; + info.pte = info.pde + (nPde * PAGE_SIZE / sizeof(info.pte)); + *info.pde = mb_virt_to_phys(info.pte); + info.pde++; + + printk(KERN_DEBUG "Memory(%#x,%d)\n", user, size); + down_read(&mm->mmap_sem); + info.size = size + virBufAddr; + addr = (unsigned long)user; + while (info.size > 0) { + vma = find_vma(mm, addr); + if (vma == NULL) { + printk(KERN_WARNING + "user addr %lx not found in task %s\n", + addr, current->comm); + goto fault; + } + printk(KERN_DEBUG + "VMA found: start %lx end %lx\n", + vma->vm_start, vma->vm_end); + offset = addr - vma->vm_start; + addr = vma->vm_start; + end = PAGE_ALIGN(vma->vm_end); + pgd = pgd_offset(mm, vma->vm_start); + ret = mmu_search_pgd(pgd, end, &addr, &offset, + wmt_mmu_table_from_user_proc, &info); + if (ret == MMU_SEARCH_DONE) + break; + if (ret) + goto fault; + } + up_read(&mm->mmap_sem); + return virBufAddr; +fault: + printk(KERN_WARNING "USER TO PRDT unfinished, remain size %d\n", size); + up_read(&mm->mmap_sem); + return 0xFFFFFFFF; +#endif +} +EXPORT_SYMBOL(wmt_mmu_table_from_user); + +/*!************************************************************************* +* wmt_mmu_table_create +* +* Public Function +*/ +/*! +* \brief +* automatically make up mmu table from giving address +* +* \parameter +* addr [IN] start address (user of phys depends on addr_type) +* size [IN] size of memory +* addr_type [IN] address type (0: phys 1: user) +* info [out] pointer of mmu table info +* +* \retval 0 if success, otherwise error code +*/ +unsigned int wmt_mmu_table_create( + unsigned int addr, + unsigned int size, + unsigned int addr_type, + struct mmu_table_info *info) +{ + unsigned int mmuSize = 0; + unsigned int *mmuAddr = NULL; + + if (!addr || !size || !info || addr_type > 1) { + printk(KERN_ERR "[WMT_MMU_TABLE] invalid args:\n"); + printk(KERN_ERR "\t addr %x # %x type %x info %p\n", + addr, size, addr_type, info); + return -EINVAL; + } + + mmuSize = wmt_mmu_table_size(size); + + info->addr = mb_alloc(mmuSize); + if (!info->addr) { + printk(KERN_ERR "[WMT_MMU_TABLE] create fail. Out of MB (%d)\n", + mmuSize); + return -ENOMEM; + } + info->size = size; + mmuAddr = (unsigned int *)mb_phys_to_virt(info->addr); + + info->offset = (addr_type) ? + wmt_mmu_table_from_user(mmuAddr, mmuSize, addr, size) : + wmt_mmu_table_from_phys(mmuAddr, mmuSize, addr, size); + + if (info->offset == 0xFFFFFFFF) { + printk(KERN_ERR "[WMT_MMU_TABLE] create fail:"); + printk(KERN_ERR "\ttype %x addr %x # %x mmuAddr %p/%x # %d\n", + addr_type, addr, size, mmuAddr, info->addr, mmuSize); + return -EFAULT; + } + + return 0; +} +EXPORT_SYMBOL(wmt_mmu_table_create); + +/*!************************************************************************* +* wmt_mmu_table_destroy +* +* Public Function +*/ +/*! +* \brief +* automatically make up mmu table from giving address +* +* \parameter +* info [IN] start address (user of phys depends on addr_type) +* +* \retval 1 if success +*/ +unsigned int wmt_mmu_table_destroy(struct mmu_table_info *info) +{ + if (!info) { + printk(KERN_ERR "[WMT_MMU_TABLE] destroy fail. NULL mmu table info"); + return -EINVAL; + } + mb_free(info->addr); + return 0; +} +EXPORT_SYMBOL(wmt_mmu_table_destroy); + +/* return address is guaranteeed only under page alignment */ +void *user_to_virt(unsigned long user) +{ + struct vm_area_struct *vma; + unsigned long flags; + void *virt = NULL; + + spin_lock_irqsave(&mb_task_mm_lock, flags); + vma = find_vma(current->mm, user); + /* For kernel direct-mapped memory, take the easy way */ + if (vma && (vma->vm_flags & VM_IO) && (vma->vm_pgoff)) { + unsigned long phys = 0; + /* kernel-allocated, mmaped-to-usermode addresses */ + phys = (vma->vm_pgoff << PAGE_SHIFT) + (user - vma->vm_start); + virt = __va(phys); + MB_INFO("%s kernel-alloc, user-mmaped addr U %lx V %p P %lx", + __func__, user, virt, phys); + } else { + /* otherwise, use get_user_pages() for general userland pages */ + int res, nr_pages = 1; + struct page *pages; + down_read(¤t->mm->mmap_sem); + res = get_user_pages(current, current->mm, + user, nr_pages, 1, 0, &pages, NULL); + up_read(¤t->mm->mmap_sem); + if (res == nr_pages) + virt = page_address(&pages[0]) + (user & ~PAGE_MASK); + MB_INFO("%s userland addr U %lx V %p", __func__, user, virt); + } + spin_unlock_irqrestore(&mb_task_mm_lock, flags); + + return virt; +} +EXPORT_SYMBOL(user_to_virt); + +int user_to_prdt( + unsigned long user, + unsigned int size, + struct prdt_struct *next, + unsigned int items) +{ + int ret; + + switch (USR2PRDT_METHOD) { + case 1: + ret = __user_to_prdt1(user, size, next, items); + break; + case 2: + ret = __user_to_prdt2(user, size, next, items); + break; + default: + ret = __user_to_prdt(user, size, next, items); + break; + } + + if (MBMSG_LEVEL > 1) + show_prdt(next); + + return ret; +} +EXPORT_SYMBOL(user_to_prdt); + +static int mb_show_mb( + struct mb_struct *mb, + char *msg, + char *str, + int size +) +{ + struct mb_user *mbu; + char *p; + + if (!str && MBMSG_LEVEL <= 1) + return 0; + + if (!mb) + return 0; + + memset(show_mb_buffer, 0x0, MB_SHOW_BUFSIZE); + p = show_mb_buffer; + p += sprintf(p, "%s%s[MB,%p] %p %08lx %4ldKB [%4lx,%4lx] %3x\n", + msg ? msg : "", msg ? " " : "", mb, mb->virt, mb->phys, + mb->size/1024, mb->pgi.pfn_start, mb->pgi.pfn_end, + MB_COUNT(mb)); + + if (!list_empty(&mb->mbu_list)) { + list_loop(mbu, &mb->mbu_list, mbu_list) { + char tmp[256], *p1 = tmp; + int expectLen = (int)(p - show_mb_buffer); + p1 += sprintf(tmp, " [%s%s,%p] %08lx ", + (mbu == mb->creator) ? "O" : " ", + (mbu->tgid == MB_DEF_TGID) ? "K" : "U", + mbu, mbu->addr); + p1 += sprintf(p1, "# %4ldKB By %s(%d)\n", + mbu->size/1024, mbu->the_user, mbu->tgid); + expectLen += strlen(tmp) + 1; + if (expectLen > MB_SHOW_BUFSIZE) { + p += sprintf(p, "\n\n .......\n"); + break; + } + p += sprintf(p, "%s", tmp); + } + } + if (str) { + if (strlen(show_mb_buffer) < size) + size = strlen(show_mb_buffer); + strncpy(str, show_mb_buffer, size); + } else { + size = strlen(show_mb_buffer); + MB_DBG("%s", show_mb_buffer); + } + + return size; +} + +static int mb_show_mba( + struct mb_area_struct *mba, + char *msg, + char *str, + int size, + int follow +) +{ + struct mb_struct *mb; + char *p; + int idx = 1; + + if (!str && MBMSG_LEVEL <= 1) + return 0; + + if (!mba) + return 0; + + memset(show_mba_buffer, 0x0, MBA_SHOW_BUFSIZE); + + p = show_mba_buffer; + if (msg) + p += sprintf(p, "%s ", msg); + p += sprintf(p, "[%p] %p %08lx %3ldMB [%5lx,%5lx] ", + mba, mba->virt, mba->phys, PAGE_KB(mba->pages)/1024, + mba->pgi.pfn_start, mba->pgi.pfn_end); + p += sprintf(p, "%3x %6ldKB/%6ldKB %s\n", mba->nr_mb, + PAGE_KB(mba->max_available_pages), + PAGE_KB(mba->tot_free_pages), + (mba->flags | MBAFLAG_STATIC) ? "S" : "D"); + if (follow) { + p += sprintf(p, " index - [MemBlock] VirtAddr PhysAddr"); + p += sprintf(p, " size [ zs, ze] cnt\n"); + list_loop(mb, &mba->mb_list, mb_list) { + if ((MBA_SHOW_BUFSIZE - (p - show_mba_buffer)) < 22) { + p += sprintf(p, "\n\n more ...\n"); + break; + } + p += sprintf(p, " %5d - ", idx++); + /* -2 is for \n and zero */ + p += mb_show_mb(mb, NULL, p, + MBA_SHOW_BUFSIZE - (p - show_mba_buffer) - 2); + } + } + + p += sprintf(p, "\n"); + + if (str) { + if (strlen(show_mba_buffer) < size) + size = strlen(show_mba_buffer); + strncpy(str, show_mba_buffer, size); + } else { + size = strlen(show_mba_buffer); + MB_DBG("%s", show_mba_buffer); + } + + return size; +} + +/* return physical address */ +#ifdef CONFIG_WMT_MB_RESERVE_FROM_IO +/* mb_do_lock locked */ +static void *mb_alloc_pages(unsigned long *phys, unsigned long *nr_pages) +{ + unsigned int order = get_order(num_physpages << PAGE_SHIFT); + unsigned int size = *nr_pages << PAGE_SHIFT; + unsigned long addr = ((1 << order) - *nr_pages) << PAGE_SHIFT; + void *virt = NULL; + + if (!nr_pages || !phys) { + MB_WARN("mb_alloc_pages fail. unknown argument\n"); + return NULL; + } + + *phys = 0; + if (*nr_pages == 0) { + MB_WARN("mb_alloc_pages zero size\n"); + return NULL; + } + + if (!request_mem_region(addr, size, "memblock")) { + MB_WARN("request memory region fail. addr %#lx size %d(KB).\n", + addr, size/1024); + return NULL; + } + + virt = ioremap(addr, size); + if (!virt) { + MB_WARN("cannot ioremap memory. addr %#lx size %d(KB).\n", + addr, size/1024); + release_mem_region(addr, size); + return NULL; + } + + *phys = addr; + MB_DBG("allocate mem region. addr V %p P %#lx size %d(KB)\n", + virt, addr, size/1024); + + return virt; +} + +/* mb_do_lock locked */ +static void mb_free_pages(void *virt, unsigned long phys, unsigned int nr_pages) +{ + iounmap(virt); + MB_DBG("release mem region. addr V %p P %#lx size %d(KB)\n", + virt, phys, 4*nr_pages); + return release_mem_region(phys, nr_pages << PAGE_SHIFT); +} + +#else +/* mb_do_lock locked */ +static void *mb_alloc_pages(unsigned long *phys, unsigned long *nr_pages) +{ + unsigned int i, order; + unsigned long virt = 0; + struct zone *zone; + + if (!nr_pages || !phys) { + MB_WARN("mb_alloc_pages fail. unknown argument\n"); + return NULL; + } + + *phys = 0; + if (*nr_pages == 0) { + MB_WARN("mb_alloc_pages zero size\n"); + return NULL; + } + + order = get_order(*nr_pages * PAGE_SIZE); + order = max(order, (unsigned int)(MBA_MIN_ORDER)); + MB_DBG(" %ld/%d pages, order %d\n", *nr_pages, 1 << order, order); + if (order > MBMAX_ORDER) { + MB_WARN("mb_alloc_mba fail. page out of size, %ld/%d/%d\n", + *nr_pages, (1<<order), (1<<MBMAX_ORDER)); + return NULL; + } + + zone = (first_online_pgdat())->node_zones; + for (i = order; i < MAX_ORDER; i++) { + if (zone->free_area[i].nr_free) { + virt = __get_free_pages(GFP_ATOMIC | GFP_DMA, order); + break; + } + } + + if (virt) { + *nr_pages = 1 << order; + *phys = __pa(virt); + for (i = 0; i < *nr_pages; i++) + SetPageReserved(virt_to_page(virt + i * PAGE_SIZE)); + MB_DBG("allocate mem region. addr V %#lx P %#lx size %ld(KB)\n", + virt, *phys, PAGE_KB(*nr_pages)); + } else { + struct free_area *fa; + char msg[256], *p = msg; + MB_WARN("__get_free_pages fail! (pages: %d free %lu)\n", + 1 << order, (unsigned long)nr_free_pages()); + zone = (first_online_pgdat())->node_zones; + fa = zone->free_area; + p += sprintf(msg, "DMA ZONE:"); + for (i = 0; i < MAX_ORDER; i++) + p += sprintf(p, " %ld*%dKB", fa[i].nr_free, 4 << i); + MB_WARN("%s= %ldkB\n", msg, PAGE_KB(nr_free_pages())); + } + + return (void *)virt; +} + +/* mb_do_lock locked */ +static void mb_free_pages(void *virt, unsigned long phys, unsigned int nr_pages) +{ + unsigned int index; + + if (!phys || !nr_pages) { + MB_WARN("mb_free_pages unknow addr V %p P %#lx size %d pages\n", + virt, phys, nr_pages); + return; + } + + for (index = 0; index < nr_pages; index++) { + unsigned long addr = (unsigned long)virt + index * PAGE_SIZE; + ClearPageReserved(virt_to_page(addr)); + } + + free_pages((unsigned long)virt, get_order(nr_pages * PAGE_SIZE)); +} +#endif + +/* mb_do_lock locked */ +static struct mb_area_struct *mb_alloc_mba(unsigned int pages) +{ + struct mba_host_struct *mbah = wmt_mbah; + struct mb_area_struct *mba; + + if (!mbah || !pages) { + MB_WARN("mb_alloc_mba fail. mbah %p, pages %d\n", mbah, pages); + return NULL; + } + + mba = kmem_cache_alloc(mbah->mba_cachep, GFP_ATOMIC); + if (!mba) { + MB_WARN("mb_alloc_mba fail. out of memory\n"); + return NULL; + } + + memset(mba, 0x0, sizeof(struct mb_area_struct)); + mba->pages = pages; + mba->virt = mb_alloc_pages(&mba->phys, &mba->pages); + if (!mba->virt) { + MB_WARN("mb_alloc_mba fail. no available space\n"); + kmem_cache_free(mbah->mba_cachep, mba); + return NULL; + } + + /* initialization */ + INIT_LIST_HEAD(&mba->mb_list); + INIT_LIST_HEAD(&mba->mba_list); + mba->mbah = mbah; + mba->tot_free_pages = mba->max_available_pages = mba->pages; + mba->pgi.pfn_start = mba->phys >> PAGE_SHIFT; + mba->pgi.pfn_end = mba->pgi.pfn_start + mba->pages; + list_add_tail(&mba->mba_list, &mbah->mba_list); + + /* update MBA host */ + mbah->nr_mba++; + mbah->tot_pages += mba->pages; + mbah->tot_free_pages += mba->tot_free_pages; + if (mbah->max_available_pages < mba->max_available_pages) + mbah->max_available_pages = mba->max_available_pages; + mb_show_mba(mba, "alloc", NULL, 0, 0); + + return mba; +} + +/* mb_do_lock locked */ +static int mb_free_mba(struct mb_area_struct *mba) +{ + struct mba_host_struct *mbah; + struct mb_area_struct *entry; + + if (!mba || !mba->mbah || mba->nr_mb) { + MB_WARN("mb_free_mba fail. unknow arg.(%p,%p,%x)\n", + mba, mba ? mba->mbah : NULL, mba ? mba->nr_mb : 0); + return -EFAULT; + } + + mbah = mba->mbah; + list_loop(entry, &mbah->mba_list, mba_list) { + if (entry == mba) + break; + } + + if (entry != mba) { + MB_WARN("mb_free_mba fail. unknow MBA %p\n", mba); + return -EFAULT; + } + if (mba->flags & MBAFLAG_STATIC) + return 0; + + mb_show_mba(mba, "ReleaseMBA", NULL, 0, 0); + + /* free mba */ + list_del(&mba->mba_list); + mb_free_pages(mba->virt, mba->phys, mba->pages); + mbah->nr_mba--; + mbah->tot_free_pages -= mba->tot_free_pages; + mbah->tot_pages -= mba->pages; + kmem_cache_free(mbah->mba_cachep, mba); + mba = NULL; + + /* update max mb size */ + mbah->max_available_pages = 0; + list_loop(entry, &mbah->mba_list, mba_list) { + if (mbah->max_available_pages < entry->max_available_pages) + mbah->max_available_pages = entry->max_available_pages; + } + + return 0; +} + +/* mb_do_lock locked */ +static struct mb_struct *mb_alloc_mb( + struct mb_area_struct *mba, + unsigned long size) +{ + struct mba_host_struct *mbah; + struct mb_struct *mb, *entry; + struct list_head *next; + unsigned long pages, zs, ze; + + if (!mba || !mba->mbah || !size) { + MB_WARN("mb_alloc_mb fail. unknow arg.(%p,%p,%lx)\n", + mba, mba ? mba->mbah : NULL, size); + return NULL; + } + + mbah = mba->mbah; + size = PAGE_ALIGN(size); + pages = size >> PAGE_SHIFT; + + /* mb free size is not enough */ + if (mba->max_available_pages < pages) { + MB_WARN("mb_alloc_mb fail. no space in MBA (%lx<%lx)\n", + pages, mba->max_available_pages); + return NULL; + } + + /* search available zone + zone start from mba start */ + ze = zs = mba->pgi.pfn_start; + next = &mba->mb_list; + list_loop(entry, &mba->mb_list, mb_list) { + next = &entry->mb_list; + ze = entry->pgi.pfn_start; + if ((ze - zs) >= pages) + break; + zs = entry->pgi.pfn_end; + } + + if (zs >= ze) { + next = &mba->mb_list; + ze = mba->pgi.pfn_end; + } + + /* impossible, something wrong */ + if ((ze - zs) < pages) { + MB_WARN("something wrong in MBA %p when allocate MB.\n", mba); + return NULL; + } + + MB_DBG("Zone finding start %lx end %lx size %lx for size %lx\n", + zs, ze, ze - zs, pages); + + mb = kmem_cache_alloc(mbah->mb_cachep, GFP_ATOMIC); + if (!mb) { + MB_WARN("mb_alloc_mb mb_cachep out of memory\n"); + return NULL; + } + + memset(mb, 0x0, sizeof(struct mb_struct)); + INIT_LIST_HEAD(&mb->mb_list); + INIT_LIST_HEAD(&mb->mbu_list); + mb->mba = mba; + mb->pgi.pfn_start = zs; + mb->pgi.pfn_end = mb->pgi.pfn_start + pages; + mb->size = size; + mb->phys = mba->phys + ((zs - mba->pgi.pfn_start) << PAGE_SHIFT); + mb->virt = mba->virt + (mb->phys - mba->phys); + list_add_tail(&mb->mb_list, next); + + mba->nr_mb++; + mba->tot_free_pages -= pages; + mbah->tot_free_pages -= pages; + + /* update mba */ + zs = mba->pgi.pfn_start; + mba->max_available_pages = 0; + list_loop(entry, &mba->mb_list, mb_list) { + mba->max_available_pages = + max(entry->pgi.pfn_start - zs, mba->max_available_pages); + zs = entry->pgi.pfn_end; + } + mba->max_available_pages = + max(mba->pgi.pfn_end - zs, mba->max_available_pages); + + /* update mbah */ + mbah->max_available_pages = 0; + list_loop(mba, &mbah->mba_list, mba_list) { + if (mbah->max_available_pages < mba->max_available_pages) + mbah->max_available_pages = mba->max_available_pages; + } + + mb_show_mb(mb, "alloc", NULL, 0); + + return mb; +} + +/* mb_do_lock locked */ +static int mb_free_mb(struct mb_struct *mb) +{ + unsigned long zs, nr_pages; + struct mba_host_struct *mbah; + struct mb_area_struct *mba; + struct mb_struct *entry; + + if (!mb) { + MB_WARN("mb_free_mb fail. unknow MB %p.\n", mb); + return -EFAULT; + } + + if (MB_IN_USE(mb)) { + MB_WARN("mb_free_mb fail. invalid arg.(%p,%x,%lx,%ldKB)\n", + mb, MB_COUNT(mb), mb->phys, mb->size/1024); + return -EINVAL; + } + + mba = mb->mba; + if (!mba || !mba->mbah) { + MB_WARN("mb_free_mb fail. unknow para.(%p,%p)\n", + mba, mba ? mba->mbah : NULL); + return -EFAULT; + } + + list_loop(entry, &mba->mb_list, mb_list) { + if (entry == mb) + break; + } + + if (entry != mb) { + MB_WARN("mb_free_mb fail. unknow MB %p\n", mb); + return -EFAULT; + } + + mbah = mba->mbah; + + mb_show_mb(mb, "Retrieve unused MB", NULL, 0); + + /* free mb */ + list_del(&mb->mb_list); + kmem_cache_free(mbah->mb_cachep, mb); + mba->nr_mb--; + mb = NULL; + + /* unused mba, release it */ + if (!mba->nr_mb && !(mba->flags & MBAFLAG_STATIC)) + return mb_free_mba(mba); + + /* update max mb size and free mb size */ + /* reduce old one and then increase new one */ + mbah->tot_free_pages -= mba->tot_free_pages; + zs = mba->pgi.pfn_start; + mba->tot_free_pages = mba->max_available_pages = nr_pages = 0; + list_loop(entry, &mba->mb_list, mb_list) { + nr_pages = max(entry->pgi.pfn_start - zs, nr_pages); + mba->tot_free_pages += entry->pgi.pfn_start - zs; + zs = entry->pgi.pfn_end; + } + mba->tot_free_pages += mba->pgi.pfn_end - zs; + mba->max_available_pages = max(mba->pgi.pfn_end - zs, nr_pages); + mbah->tot_free_pages += mba->tot_free_pages; /* add new one */ + mbah->max_available_pages = + max(mbah->max_available_pages, mba->max_available_pages); + + return 0; +} + +/* type 0 - virtual address + * 1 - physical address */ +static struct mb_struct *mb_find_mb(void *addr, int type) +{ + struct mba_host_struct *mbah = wmt_mbah; + struct mb_area_struct *mba; + struct mb_struct *mb; + unsigned long flags; + + if (!addr || (type != MBFIND_PHYS && type != MBFIND_VIRT)) { + MB_WARN("mb_find_mb fail. unknow type %d addr %p\n", + type, addr); + return NULL; + } + + MB_DBG("IN, type %x addr %p\n", type, addr); + + spin_lock_irqsave(&mb_search_lock, flags); + + list_loop(mba, &mbah->mba_list, mba_list) { + void *eaddr, *saddr = (void *)(mba->phys); + if (type != MBFIND_PHYS) + saddr = mba->virt; + eaddr = saddr + mba->pages*PAGE_SIZE; + if (addr < saddr || addr > eaddr) + continue; /* address out of mba range */ + list_loop(mb, &mba->mb_list, mb_list) { + void *mbaddr = (void *)(mb->phys); + if (type != MBFIND_PHYS) + mbaddr = mb->virt; + if (addr >= mbaddr && addr < (mbaddr + mb->size)) { + spin_unlock_irqrestore(&mb_search_lock, flags); + MB_DBG("OUT, va %p pa %lx s %ld(KB) cnt %d\n", + mb->virt, mb->phys, mb->size/1024, + MB_COUNT(mb)); + return mb; + } + } + } + + spin_unlock_irqrestore(&mb_search_lock, flags); + + MB_DBG("OUT, NULL mb\n"); + + return NULL; +} + +/* + * addr - search address + * tgid - task pid + * st - search type (MBUFIND_XXX) + * MBUFIND_USER: user address + * MBUFIND_VIRT: kernel virt address + * MBUFIND_PHYS: kernel phys address + * MBUFIND_ALL: + * 1. mbu->tgid == tgid + * 2. tgid == MB_DEF_TGID, address and + * one of MBUFIND_VIRT and MBUFIND_PHYS must be set + * tgid != MB_DEF_TGID, address and MBUFIND_USER must be set + * MBUFIND_CREATOR: + * 1. mbu->tgid == tgid + * 2. tgid == MB_DEF_TGID, address and + * one of MBUFIND_VIRT and MBUFIND_PHYS must be set + * tgid != MB_DEF_TGID, address and MBUFIND_USER must be set + * 3. mbu == mb->creator + * 4. mbu->size == mb->size + * MBUFIND_GETPUT: + * 1. mbu->tgid == tgid + * 2. tgid == MB_DEF_TGID, address and + * MBUFIND_PHYS must be set (mbu->addr == mb->phys) + * tgid != MB_DEF_TGID, address and + * MBUFIND_USER must be set (mbu->addr == user address) + * 3. mbu->size == 0 + * MBUFIND_MMAP: + * 1. mbu->tgid == tgid + * 2. address and MBUFIND_USER must be set + * 3. mbu->size != 0 + */ +static struct mb_user *mb_find_mbu(void *addr, pid_t tgid, int st) +{ + struct mba_host_struct *mbah = wmt_mbah; + struct mb_area_struct *mba = NULL; + struct mb_struct *mb = NULL; + struct mb_user *mbu = NULL; + unsigned long flags; + + if (!addr || !(st & MBUFIND_ADDRMASK)) { + MB_WARN("mb_find_mbu fail. unknow addr %p\n", addr); + return NULL; + } + + MB_DBG("IN, addr %p search type %x TGID %x CurTask %s\n", + addr, st, tgid, current->comm); + + spin_lock_irqsave(&mb_search_lock, flags); + list_loop(mba, &mbah->mba_list, mba_list) { + if (st & (MBUFIND_VIRT | MBUFIND_PHYS)) { + void *eaddr, *saddr = (void *)mba->phys; + if (st & MBUFIND_VIRT) + saddr = mba->virt; + eaddr = saddr + mba->pages*PAGE_SIZE; + if (addr < saddr || addr > eaddr) + continue; /* address out of mba range */ + } + list_loop(mb, &mba->mb_list, mb_list) { + if ((st & MBUFIND_PHYS) && (addr != (void *)mb->phys)) + continue; /* physical address not match */ + if ((st & MBUFIND_VIRT) && (addr != mb->virt)) + continue; /* virtual address not match */ + list_loop(mbu, &mb->mbu_list, mbu_list) { + if (mbu->tgid != tgid && tgid != MB_DEF_TGID) + continue; /* tgid not match */ + if ((st & MBUFIND_USER) && + (addr != (void *)mbu->addr)) + continue; /* user address not match */ + if (st & MBUFIND_ALL) + goto leave; /* found */ + if (st & MBUFIND_CREATOR) { + mbu = mb->creator; + goto leave; /* found */ + } + /* found (if mmap, mbu->size has map size) */ + if (st & MBUFIND_MMAP && mbu->size) + goto leave; + /* found (if get/put, mbu->size should be 0) */ + if (st & MBUFIND_GETPUT && + mbu->addr && !mbu->size) + goto leave; + } + } + } + mbu = NULL; + +leave: + if (mbu == NULL) + MB_DBG("OUT, NULL mbu\n"); + + if (mbu) + MB_DBG("OUT, mbu %p (%p TGID %x a %#lx s %ld(KB) by %s)\n", + mbu, mbu->mb, mbu->tgid, mbu->addr, + mbu->size/1024, mbu->the_user); + + spin_unlock_irqrestore(&mb_search_lock, flags); + + return mbu; +} + +static void mb_update_mbah(void) +{ + unsigned long mbah_free = 0, mbah_max = 0; + struct mba_host_struct *mbah = wmt_mbah; + struct mb_area_struct *mba = NULL; + struct mb_struct *mb = NULL; + + if (!mbah) { + MB_WARN("mb_update_mbah fail. unknow mbah\n"); + return; + } + + list_loop(mba, &mbah->mba_list, mba_list) { + unsigned long mba_free = 0, mba_max = 0, nr_pages = 0; + unsigned long zs = mba->pgi.pfn_start; /* zone start */ + list_loop(mb, &mba->mb_list, mb_list) { + nr_pages = max(mb->pgi.pfn_start - zs, nr_pages); + mba_free += mb->pgi.pfn_start - zs; + zs = mb->pgi.pfn_end; + } + mba_free += mba->pgi.pfn_end - zs; + mbah_free += mba_free; + mba_max = max(mba->pgi.pfn_end - zs, nr_pages); + mbah_max = max(mbah_max, mba_max); + } + + mbah->tot_free_pages = mbah_free; + mbah->max_available_pages = mbah_max; + + return; +} + +/* called from kernel only, mb_do_lock isn't needed */ +void *mb_do_phys_to_virt(unsigned long phys, pid_t tgid, char *name) +{ + struct mb_struct *mb = NULL; + void *virt = NULL; + + MB_DBG("IN, Phys %lx TGID %x NAME %s\n", phys, tgid, name); + mb = mb_find_mb((void *)phys, MBFIND_PHYS); + if (mb) + virt = mb->virt + (phys - mb->phys); + else { + virt = __va(phys); + MB_WARN("%s do phys to virt fail. addr %lx not found\n", + name, phys); + } + + MB_DBG("OUT, Virt %p\n", virt); + + return virt; +} +EXPORT_SYMBOL(mb_do_phys_to_virt); + +/* called from kernel only, mb_do_lock isn't needed */ +unsigned long mb_do_virt_to_phys(void *virt, pid_t tgid, char *name) +{ + struct mb_struct *mb = NULL; + unsigned long phys = 0x0; + + MB_DBG("IN, Virt %p TGID %x NAME %s\n", virt, tgid, name); + mb = mb_find_mb(virt, MBFIND_VIRT); + if (mb) + phys = mb->phys + (virt - mb->virt); + else { + phys = __pa(virt); + MB_WARN("%s do virt to phys fail. addr %p not found\n", + name, virt); + } + + MB_DBG("OUT, Phys %lx\n", phys); + + return phys; +} +EXPORT_SYMBOL(mb_do_virt_to_phys); + +unsigned long mb_do_user_to_phys(unsigned long user, pid_t tgid, char *name) +{ + unsigned long flags, phys = 0; + struct mb_user *mbu = NULL; + + MB_DBG("IN, usr %lx TGID %x name %s\n", user, tgid, name); + + spin_lock_irqsave(&mb_do_lock, flags); + mbu = mb_find_mbu((void *)user, tgid, MBUFIND_USER|MBUFIND_ALL); + if (!mbu) { + spin_unlock_irqrestore(&mb_do_lock, flags); + MB_WARN("%s do user to phys. unknow addr %lx\n", name, user); + return 0; + } + + if (!mbu->mb) + MB_WARN("%s do user to phys fail. unknow block (addr %lx)\n", + name, user); + else + phys = mbu->mb->phys; + + spin_unlock_irqrestore(&mb_do_lock, flags); + + MB_DBG("OUT, Phys %#lx\n", phys); + + return phys; +} +EXPORT_SYMBOL(mb_do_user_to_phys); + +unsigned long mb_do_user_to_virt(unsigned long user, pid_t tgid, char *name) +{ + struct mb_user *mbu = NULL; + unsigned long flags; + void *virt = NULL; + + MB_DBG("IN, usr %lx TGID %x name %s\n", user, tgid, name); + + spin_lock_irqsave(&mb_do_lock, flags); + mbu = mb_find_mbu((void *)user, tgid, MBUFIND_USER|MBUFIND_ALL); + if (!mbu) { + spin_unlock_irqrestore(&mb_do_lock, flags); + MB_WARN("%s user to virt fail. unknow addr %lx\n", name, user); + return 0; + } + + if (!mbu->mb) + MB_WARN("%s user to virt fail. unknow block (addr %lx)\n", + name, user); + else + virt = mbu->mb->virt; + + spin_unlock_irqrestore(&mb_do_lock, flags); + + MB_DBG("OUT, Virt %p\n", virt); + + return (unsigned long)virt; +} +EXPORT_SYMBOL(mb_do_user_to_virt); + +/* physical address */ +int mb_do_counter(unsigned long addr, char *name) +{ + struct mb_struct *mb; + unsigned long flags; + int counter = -1; + + MB_DBG("IN, addr %#lx name %s\n", addr, name); + + if (!addr || !wmt_mbah) { + MB_WARN("%s do counter fail. invalid args %p/%lx\n", + name, wmt_mbah, addr); + return -EINVAL; + } + spin_lock_irqsave(&mb_do_lock, flags); + mb = mb_find_mb((void *)addr, MBFIND_PHYS); + counter = (mb) ? MB_COUNT(mb) : -1; + spin_unlock_irqrestore(&mb_do_lock, flags); + + MB_DBG("OUT, addr %#lx count %d\n", addr, counter); + + return counter; +} +EXPORT_SYMBOL(mb_do_counter); + +unsigned long mb_do_alloc(unsigned long size, pid_t tgid, char *name) +{ + struct mb_area_struct *entry, *mba = NULL; + struct mb_struct *mb = NULL; + struct mb_user *mbu = NULL; + unsigned long flags, addr; + unsigned int pages; + size_t ns; /* name size */ + + if (!name) { + MB_WARN("mb_alloc fail. null user tgid %x\n", tgid); + return 0; + } + + size = PAGE_ALIGN(size); + pages = size >> PAGE_SHIFT; + + if (!pages) { + MB_WARN("%s alloc fail. unavailable size %x(KB)\n", + name, 4*pages); + return 0; + } + + mbu = kmem_cache_alloc(wmt_mbah->mbu_cachep, GFP_ATOMIC); + if (!mbu) { + MB_WARN("%s alloc fail. mbu_cachep out of memory.\n", name); + return 0; + } + memset(mbu, 0x0, sizeof(struct mb_user)); + + MB_DBG("IN, TGID %x size %ld(KB) name %s\n", tgid, size/1024, name); + + spin_lock_irqsave(&mb_do_lock, flags); + + if (pages > wmt_mbah->max_available_pages) { +#ifdef CONFIG_WMT_MB_DYNAMIC_ALLOCATE_SUPPORT + mba = mb_alloc_mba(pages); + if (mba == NULL) + MB_WARN("%s alloc %u page fail. [MBA %lu/%lu/%lu]\n", + name, pages, wmt_mbah->max_available_pages, + wmt_mbah->tot_free_pages, wmt_mbah->tot_pages); +#else + MB_DBG("DYNAMIC ALLOCATED(%u) unsuported. [MBA %lu/%lu/%lu]\n", + pages, wmt_mbah->max_available_pages, + wmt_mbah->tot_free_pages, wmt_mbah->tot_pages); + goto error; +#endif + } else { + list_loop(entry, &wmt_mbah->mba_list, mba_list) { + if (entry->max_available_pages >= pages) { + mba = entry; + break; + } + } + } + + if (!mba) { + MB_WARN("%s alloc fail. dedicated MBA not found\n", name); + goto error; + } + + mb = mb_alloc_mb(mba, size); + if (mb == NULL) { + MB_WARN("%s alloc fail. create MB\n", name); + goto error; + } + + INIT_LIST_HEAD(&mbu->mbu_list); + mbu->mb = mb; + mbu->tgid = tgid; + mbu->size = mb->size; + /* mbu->addr = 0; // address of creator is 0 */ + ns = strlen(name); + if (ns > TASK_COMM_LEN) + ns = TASK_COMM_LEN; + strncpy(mbu->the_user, name, ns); + mbu->the_user[ns] = 0; + list_add_tail(&mbu->mbu_list, &mb->mbu_list); + atomic_inc(&mb->count); + mb->creator = mbu; + addr = mb->phys; + spin_unlock_irqrestore(&mb_do_lock, flags); + + MB_DBG("OUT, Addr %lx TGID %x size %ld(KB) name %s\n", + addr, tgid, size/1024, name); + + return addr; + +error: + + kmem_cache_free(wmt_mbah->mbu_cachep, mbu); + spin_unlock_irqrestore(&mb_do_lock, flags); + return 0; +} +EXPORT_SYMBOL(mb_do_alloc); + +/* physical address */ +int mb_do_free(unsigned long addr, pid_t tgid, char *name) +{ + struct mb_struct *mb; + struct mb_user *mbu; + unsigned long flags; + size_t ns; /* name size */ + int ret; + + if (!addr || !name) { + MB_WARN("mb_free fail. invalid args %lx/%s\n", + addr, name); + return -EINVAL; + } + + MB_DBG("IN, TGID %x addr %#lx name %s\n", tgid, addr, name); + + spin_lock_irqsave(&mb_do_lock, flags); + mbu = mb_find_mbu((void *)addr, tgid, MBUFIND_PHYS|MBUFIND_CREATOR); + if (!mbu || !mbu->mb) { + spin_unlock_irqrestore(&mb_do_lock, flags); + MB_WARN("%s free fail. unknow addr %lx\n", name, addr); + return -EINVAL; + } + + ns = strlen(name); + if (ns > TASK_COMM_LEN) + ns = TASK_COMM_LEN; + if (strncmp(mbu->the_user, name, ns)) + MB_DBG("Owner no match. (%s/%s)\n", mbu->the_user, name); + + mb = mbu->mb; + + list_del(&mbu->mbu_list); + kmem_cache_free(wmt_mbah->mbu_cachep, mbu); + mb->creator = NULL; + atomic_dec(&mb->count); + if (MB_IN_USE(mb)) { + MB_DBG("<mb_free, MB %8lx still in use. Cnt %d>\n", + mb->phys, MB_COUNT(mb)); + spin_unlock_irqrestore(&mb_do_lock, flags); + return 0; + } + + ret = mb_free_mb(mb); + spin_unlock_irqrestore(&mb_do_lock, flags); + + MB_DBG("OUT\n"); + + return ret; +} +EXPORT_SYMBOL(mb_do_free); + +int mb_do_get(unsigned long addr, pid_t tgid, char *name) +{ + struct mb_user *mbu = NULL, *newmbu; + struct mb_struct *mb = NULL; + unsigned long flags; + size_t ns; /* name size */ + void *virt = (void *)addr; + + if (!addr || !name) { + MB_WARN("mb_do_get fail. invalid args %lx/%s\n", + addr, name); + return -EINVAL; + } + + newmbu = kmem_cache_alloc(wmt_mbah->mbu_cachep, GFP_ATOMIC); + if (!newmbu) { + MB_DBG("<mbu_cachep out of memory.>\n"); + return -ENOMEM; + } + + MB_DBG("IN, TGID %x addr %#lx name %s\n", tgid, addr, name); + + spin_lock_irqsave(&mb_do_lock, flags); + if (tgid != MB_DEF_TGID) { /* find user address, only exist on MMAP */ + mbu = mb_find_mbu(virt, tgid, MBUFIND_USER|MBUFIND_MMAP); + mb = (mbu) ? mbu->mb : NULL; + } else + mb = mb_find_mb(virt, MBFIND_PHYS); + + if (!mb) { + spin_unlock_irqrestore(&mb_do_lock, flags); + kmem_cache_free(wmt_mbah->mbu_cachep, newmbu); + MB_WARN("%s mb_get fail. unknown addr %8lx\n", name, addr); + return -EFAULT; + } + + memset(newmbu, 0x0, sizeof(struct mb_user)); + INIT_LIST_HEAD(&newmbu->mbu_list); + newmbu->addr = addr; + newmbu->mb = mb; + newmbu->tgid = tgid; + ns = strlen(name); + if (ns > TASK_COMM_LEN) + ns = TASK_COMM_LEN; + strncpy(newmbu->the_user, name, ns); + newmbu->the_user[ns] = 0; + atomic_inc(&mb->count); + list_add_tail(&newmbu->mbu_list, &mb->mbu_list); + + spin_unlock_irqrestore(&mb_do_lock, flags); + + MB_DBG("out, [mbu] addr %lx %s [mb] addr %lx cnt %d\n", + addr, newmbu->the_user, mb->phys, MB_COUNT(mb)); + + return 0; +} +EXPORT_SYMBOL(mb_do_get); + +int mb_do_put(unsigned long addr, pid_t tgid, char *name) +{ + struct mb_struct *mb; + struct mb_user *mbu; + unsigned long flags; + void *virt = (void *)addr; + + if (!addr || !name) { + MB_WARN("mb_do_put fail. invalid args %lx/%s\n", addr, name); + return -EINVAL; + } + + MB_DBG("IN, TGID %x addr %#lx name %s\n", tgid, addr, name); + + spin_lock_irqsave(&mb_do_lock, flags); + if (tgid == MB_DEF_TGID) + mbu = mb_find_mbu(virt, tgid, MBUFIND_PHYS|MBUFIND_GETPUT); + else + mbu = mb_find_mbu(virt, tgid, MBUFIND_USER|MBUFIND_GETPUT); + + if (!mbu) { + spin_unlock_irqrestore(&mb_do_lock, flags); + MB_WARN("%s mb_put fail. unknow addr %8lx\n", name, addr); + return -EINVAL; + } + + mb = mbu->mb; + if (!MB_IN_USE(mb)) { + spin_unlock_irqrestore(&mb_do_lock, flags); + MB_WARN("%s mb_put fail. block %p unbalance.\n", name, mb); + return -EPERM; + } + + list_del_init(&mbu->mbu_list); + atomic_dec(&mb->count); + /* retrieve unused block */ + if (!MB_IN_USE(mb)) + mb_free_mb(mb); + + spin_unlock_irqrestore(&mb_do_lock, flags); + kmem_cache_free(wmt_mbah->mbu_cachep, mbu); + + MB_DBG("out, [mbu] addr %lx %s [mb] addr %lx cnt %d\n", + addr, mbu->the_user, mb->phys, MB_COUNT(mb)); + + return 0; +} +EXPORT_SYMBOL(mb_do_put); + +#define DEVICE_NAME "Memory Block" + +static int mb_dev_major; +static int mb_dev_minor; +static int mb_dev_nr; +static struct cdev *mb_cdev; +static struct class *mb_dev_class; + +static int mb_open(struct inode *inode, struct file *filp) +{ + struct mb_task_info *mbti = NULL; + unsigned long flags; + int task_exist = 0; + size_t ns; + + if (filp->private_data) { + MB_ERROR("none empty private data.\n"); + return -EFAULT; + } + + spin_lock_irqsave(&mb_task_lock, flags); + list_loop(mbti, &wmt_mbti.mbti_list, mbti_list) { + if (mbti->task && mbti->tgid == current->tgid) { + task_exist = 1; + break; + } + } + if (!task_exist) { + mbti = kmem_cache_alloc(wmt_mbah->mbti_cachep, GFP_ATOMIC); + if (!mbti) { + spin_unlock_irqrestore(&mb_task_lock, flags); + MB_ERROR("out of memory (mb_task_info).\n"); + return -EFAULT; + } + memset(mbti, 0x0, sizeof(struct mb_task_info)); + INIT_LIST_HEAD(&mbti->mbti_list); + mbti->task = current; + mbti->tgid = current->tgid; + ns = strlen(current->comm); + if (ns > TASK_COMM_LEN) + ns = TASK_COMM_LEN; + strncpy(mbti->task_name, current->comm, ns); + mbti->task_name[ns] = 0; + atomic_set(&mbti->count, 0); + list_add_tail(&mbti->mbti_list, &wmt_mbti.mbti_list); + } + atomic_inc(&mbti->count); + MB_DBG("mb driver is opened by task(%p) %s TGID %x count %d.\n", + current, current->comm, current->tgid, + atomic_read(&(mbti->count))); + filp->private_data = (void *)mbti; + spin_unlock_irqrestore(&mb_task_lock, flags); + + return 0; +} + +static int mb_release(struct inode *inode, struct file *filp) +{ + struct mb_area_struct *mba; + struct mb_struct *mb; + struct mb_user *mbu; + struct mb_task_info *cmbti = NULL, *mbti = NULL; + unsigned long flags, tflags; + int task_exist = 0; + + cmbti = (struct mb_task_info *)(filp->private_data); + if (!cmbti) { + MB_ERROR("none empty private data.\n"); + return -EFAULT; + } + + spin_lock_irqsave(&mb_task_lock, tflags); + list_loop(mbti, &wmt_mbti.mbti_list, mbti_list) { + if (mbti->task && mbti->tgid == cmbti->tgid) { + atomic_dec(&mbti->count); + task_exist = 1; + break; + } + } + if (!task_exist) { + spin_unlock_irqrestore(&mb_task_lock, tflags); + MB_INFO("mb driver is closed by unknown task %s TGID %x.\n", + current->comm, current->tgid); + return 0; + } + + MB_DBG("mb driver is closed by task %s TGID %x cnt %d.\n", + mbti->task_name, mbti->tgid, atomic_read(&mbti->count)); + if (atomic_read(&mbti->count)) { + spin_unlock_irqrestore(&mb_task_lock, tflags); + return 0; + } + + spin_lock_irqsave(&mb_ioctl_lock, flags); +RESCAN_MBA: + /* munmap virtual memroy and retrieve unused MB */ + list_loop(mba, &wmt_mbah->mba_list, mba_list) { +#ifdef CONFIG_WMT_MB_DYNAMIC_ALLOCATE_SUPPORT + if (mba->tgid == mbti->tgid) { /* free prefetched mba marker */ + wmt_mbah->tot_static_pages -= mba->pages; + mba->flags &= ~(MBAFLAG_STATIC); + mba->tgid = MB_DEF_TGID; + if (!mba->nr_mb) { + mb_free_mba(mba); + goto RESCAN_MBA; + } + } +#endif + list_loop(mb, &mba->mb_list, mb_list) { +RESCAN_MBU: + list_loop(mbu, &mb->mbu_list, mbu_list) { + if (mbu->tgid == mbti->tgid) { + char msg[128], *p = msg; + const char *opStr[3] = { + "\"ALLOC\"", + "\"MAP\"", + "\"GET\""}; + int op = (mbu->size) ? 1 : 2; + if (mb->creator == mbu) { + op = 0; + mb->creator = NULL; + } + p += sprintf(p, "Stop mb operation %s", + opStr[op]); + p += sprintf(p, "(addr %lx # %lu(KB) ", + mb->phys, mbu->size/1024); + p += sprintf(p, "by %s) (Cnt %d)\n", + mbu->the_user, MB_COUNT(mb)-1); + MB_INFO("%s", msg); + atomic_dec(&mb->count); + list_del_init(&mbu->mbu_list); + kmem_cache_free( + wmt_mbah->mbu_cachep, mbu); + if (MB_COUNT(mb)) + goto RESCAN_MBU; + MB_INFO("Recycle mb addr %lx # %lu\n", + mb->phys, mb->size); + mb_free_mb(mb); + goto RESCAN_MBA; + } + } + } + } + + mb_update_mbah(); + list_del(&mbti->mbti_list); + kmem_cache_free(wmt_mbah->mbti_cachep, mbti); + filp->private_data = NULL; + spin_unlock_irqrestore(&mb_ioctl_lock, flags); + spin_unlock_irqrestore(&mb_task_lock, tflags); + + return 0; +} + +static long mb_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct mb_task_info *mbti = NULL; + struct mb_area_struct *mba = NULL; + struct mb_struct *mb = NULL; + struct mb_user *mbu = NULL; + unsigned long value, flags, phys, virt, size; + pid_t tgid = MB_DEF_TGID; + int count, ret = 0, cmdSize; + size_t len; + + /* check type and number, if fail return ENOTTY */ +#ifdef CONFIG_WMT_MB_RESERVE_FROM_IO + if (_IOC_TYPE(cmd) != MB_IOC_MAGIC && cmd != FBIOGET_FSCREENINFO) + return -ENOTTY; +#else + if (_IOC_TYPE(cmd) != MB_IOC_MAGIC) + return -ENOTTY; +#endif + + /* check argument area */ + cmdSize = _IOC_SIZE(cmd); + if (_IOC_DIR(cmd) & _IOC_READ) + ret = !access_ok(VERIFY_WRITE, (void __user *) arg, cmdSize); + else if (_IOC_DIR(cmd) & _IOC_WRITE) + ret = !access_ok(VERIFY_READ, (void __user *) arg, cmdSize); + + if (ret) + return -EFAULT; + + /* check task validation */ + if (!filp || !filp->private_data) { + MB_ERROR("mmap with null file info or task.\n"); + return -EFAULT; + } + + mbti = (struct mb_task_info *)(filp->private_data); + tgid = mbti->tgid; + if (tgid != current->tgid) { + MB_WARN("ioctl within diff task.\n"); + MB_WARN("Open: task %s TGID %x VS Curr: task %s TGID %x\n", + mbti->task_name, tgid, current->comm, current->tgid); + } + + switch (cmd) { + /* _IOR (MB_IOC_MAGIC, 16, unsigned long) O: mb driver version */ + case MBIO_GET_VERSION: + value = MB_VERSION_MAJOR << 24 | + MB_VERSION_MINOR << 16 | + MB_VERSION_MICRO << 8 | + MB_VERSION_BUILD; + put_user(value, (unsigned long *)arg); + break; + + /* _IOWR(MB_IOC_MAGIC, 0, unsigned long) + * I: size + * O: physical address */ + case MBIO_MALLOC: + get_user(value, (unsigned long *)arg); + phys = mb_do_alloc(value, tgid, mbti->task_name); + if (!phys) { + MB_WARN("MALLOC: size %ld fail.\n\n", value); + return -EFAULT; + } + MB_OPDBG("MALLOC: addr %lx size %ld\n\n", phys, value); + put_user(phys, (unsigned long *)arg); + break; + + /* _IOW (MB_IOC_MAGIC, 17, unsigned long) O: property of MB */ + case MBIO_SET_CACHABLE: + get_user(value, (unsigned long *)arg); + spin_lock_irqsave(&mb_do_lock, flags); + mb = mb_find_mb((void *)value, MBFIND_PHYS); + if (!mb) { + spin_unlock_irqrestore(&mb_do_lock, flags); + MB_WARN("SET_CACHABLE: phys %8lx (name %s) fail\n", + value, mbti->task_name); + return -EFAULT; + } + mb->flags |= MBFLAG_CACHED; + spin_unlock_irqrestore(&mb_do_lock, flags); + MB_OPDBG("SET_CACHABLE: phys %8lx\n\n", value); + break; + + /* _IOW (MB_IOC_MAGIC,18, unsigned long) O: property of MB */ + case MBIO_CLR_CACHABLE: + get_user(value, (unsigned long *)arg); + spin_lock_irqsave(&mb_do_lock, flags); + mb = mb_find_mb((void *)value, MBFIND_PHYS); + if (!mb) { + spin_unlock_irqrestore(&mb_do_lock, flags); + MB_WARN("CLR_CACHABLE: phys %8lx (name %s) fail\n", + value, mbti->task_name); + return -EFAULT; + } + mb->flags &= ~MBFLAG_CACHED; + spin_unlock_irqrestore(&mb_do_lock, flags); + MB_OPDBG("CLR_CACHABLE: phys %8lx\n\n", value); + break; + + /* _IOWR(MB_IOC_MAGIC, 1, unsigned long) + * I: user address if map, physical address if not map + * O: ummap size */ + case MBIO_FREE: /* if not unmap, unmap first */ + get_user(value, (unsigned long *)arg); + spin_lock_irqsave(&mb_ioctl_lock, flags); + size = value; + mbu = mb_find_mbu((void *)value, tgid, + MBUFIND_USER|MBUFIND_MMAP); + if (mbu != NULL) { + size = mbu->size; + value = mbu->mb->phys; + list_del(&mbu->mbu_list); + atomic_dec(&mbu->mb->count); + kmem_cache_free(wmt_mbah->mbu_cachep, mbu); + } else + MB_DBG("FREE: unknown MMAP addr %lx\n", value); + ret = mb_do_free(value, tgid, mbti->task_name); + spin_unlock_irqrestore(&mb_ioctl_lock, flags); + if (ret < 0) + MB_WARN("FREE: addr %lx fail\n", value); + /* return mmap size for ummap */ + put_user(size, (unsigned long *)arg); + MB_OPDBG("FREE: addr %lx out %ld\n\n", value, size); + break; + + /* _IOWR(MB_IOC_MAGIC, 2, unsigned long) + * I: user address + * O: ummap size */ + case MBIO_UNMAP: + get_user(value, (unsigned long *)arg); + spin_lock_irqsave(&mb_ioctl_lock, flags); + mbu = mb_find_mbu((void *)value, tgid, + MBUFIND_USER|MBUFIND_MMAP); + if (mbu == NULL) { + spin_unlock_irqrestore(&mb_ioctl_lock, flags); + MB_WARN("UNMAP: addr %lx fail\n\n", value); + return -EFAULT; + } + mb = mbu->mb; + size = mbu->size; + list_del(&mbu->mbu_list); + kmem_cache_free(wmt_mbah->mbu_cachep, mbu); + atomic_dec(&mb->count); + count = MB_COUNT(mb); + if (count <= 0) + MB_WARN("UNMAP: MB %8lx count %d weird\n\n", + mb->phys, count); + spin_unlock_irqrestore(&mb_ioctl_lock, flags); + /* return mmap size for ummap */ + put_user(size, (unsigned long *)arg); + return 0; + + /* _IOWR(MB_IOC_MAGIC, 3, unsigned long) + * I: phys address + * O: mb size */ + case MBIO_MBSIZE: + get_user(value, (unsigned long *)arg); + spin_lock_irqsave(&mb_ioctl_lock, flags); + mb = mb_find_mb((void *)value, MBFIND_PHYS); + if (mb == NULL) { + spin_unlock_irqrestore(&mb_ioctl_lock, flags); + MB_WARN("MBSIZE: user %lx fail.\n\n", value); + return -EFAULT; + } + size = mb->size; + spin_unlock_irqrestore(&mb_ioctl_lock, flags); + MB_OPDBG("MBSIZE: addr %lx size %ld\n\n", value, size); + put_user(size, (unsigned long *)arg); + break; + + /* _IOR (MB_IOC_MAGIC, 4, unsigned long) + * O: max free mba size */ + case MBIO_MAX_AVAILABLE_SIZE: + put_user(wmt_mbah->max_available_pages*PAGE_SIZE, + (unsigned long *)arg); + MB_OPDBG("MAX_MB_SIZE: size %ld KB\n\n", + PAGE_KB(wmt_mbah->max_available_pages)); + break; + + /* _IOW (MB_IOC_MAGIC, 5, unsigned long) + * I: user address */ + case MBIO_GET: + get_user(value, (unsigned long *)arg); + ret = mb_do_get(value, tgid, mbti->task_name); + if (MBMSG_LEVEL) { + spin_lock_irqsave(&mb_ioctl_lock, flags); + mbu = mb_find_mbu((void *)value, tgid, + MBUFIND_USER|MBUFIND_GETPUT); + count = mbu ? MB_COUNT(mbu->mb) : 0xffffffff; + MB_OPDBG("GET: addr %lx cnt %x\n\n", value, count); + spin_unlock_irqrestore(&mb_ioctl_lock, flags); + } + break; + + /* _IOW (MB_IOC_MAGIC, 6, unsigned long) + * I: user address */ + case MBIO_PUT: + get_user(value, (unsigned long *)arg); + if (MBMSG_LEVEL) { + spin_lock_irqsave(&mb_ioctl_lock, flags); + mbu = mb_find_mbu((void *)value, tgid, + MBUFIND_USER|MBUFIND_GETPUT); + count = mbu ? MB_COUNT(mbu->mb) - 1 : 0xffffffff; + MB_OPDBG("PUT: addr %lx cnt %x\n\n", value, count); + spin_unlock_irqrestore(&mb_ioctl_lock, flags); + } + ret = mb_do_put(value, tgid, mbti->task_name); + break; + + /* _IOWR(MB_IOC_MAGIC, 7, unsigned long) + * I: user address + * O: virt address */ + case MBIO_USER_TO_VIRT: + get_user(value, (unsigned long *)arg); + virt = mb_do_user_to_virt(value, tgid, mbti->task_name); + put_user(virt, (unsigned long *)arg); + MB_OPDBG("USER_TO_VERT: user %8lx virt %lx\n\n", value, virt); + break; + + /* _IOWR(MB_IOC_MAGIC, 8, unsigned long) + * I: user address + * O: phys address */ + case MBIO_USER_TO_PHYS: + get_user(value, (unsigned long *)arg); + phys = mb_do_user_to_phys(value, tgid, mbti->task_name); + put_user(phys, (unsigned long *)arg); + MB_OPDBG("USER_TO_PHYS: user %8lx phys %lx\n\n", value, phys); + break; + +#ifdef CONFIG_WMT_MB_RESERVE_FROM_IO + /* This IOCTRL is provide AP to recognize MB region as framebuffer, + then can access mb memory directly. */ + case MBIO_FSCREENINFO: + { + struct fb_fix_screeninfo ffs; + + if (!wmt_mbah || list_empty(&wmt_mbah->mba_list)) { + MB_WARN("MBIO_FSCREENINFO: MB driver does not init.\n"); + return -EFAULT; + } + + mba = (struct mb_area_struct *)wmt_mbah->mba_list.next; + ffs.smem_start = mba->phys; + ffs.smem_len = wmt_mbah->tot_static_pages * PAGE_SIZE; + if (copy_to_user((void __user *)arg, &ffs, sizeof(ffs))) + ret = -EFAULT; + MB_OPDBG("FSCREENINFO: phys %lx len %x\n\n", + ffs.smem_start, ffs.smem_len); + break; + } +#endif + + /* _IOW (MB_IOC_MAGIC, 9, unsigned long) + * I: size */ + case MBIO_PREFETCH: + { + unsigned long fetch_size = 0; + get_user(value, (unsigned long *)arg); +#ifdef CONFIG_WMT_MB_DYNAMIC_ALLOCATE_SUPPORT +{ + unsigned long try_size = 0; + + MB_OPDBG("PREFETCH: size %ld KB\n\n", value/1024); + spin_lock_irqsave(&mb_ioctl_lock, flags); + while (try_size < value) { + int order; + order = get_order(value-fetch_size); + if (order > MBMAX_ORDER) + order = MBMAX_ORDER; + try_size += (1 << order) * PAGE_SIZE; + mba = mb_alloc_mba(1 << order); + if (mba) { + mba->flags |= MBAFLAG_STATIC; + mba->tgid = current->tgid; + wmt_mbah->tot_static_pages += mba->pages; + fetch_size += mba->pages * PAGE_SIZE; + } + } + spin_unlock_irqrestore(&mb_ioctl_lock, flags); + MB_INFO("PREFETCH: SIZE %ld / %ld kB by %s\n", + fetch_size/1024, value/1024, current->comm); +} +#else + MB_WARN("PREFETCH: SIZE %ld / %ld kB by %s fail\n", + fetch_size/1024, value/1024, current->comm); + MB_WARN("PREFETCH: Dynamic Allocated Not Support\n"); + ret = -ENOTTY; +#endif + break; + } + + /* _IOW (MB_IOC_MAGIC, 10, unsigned long) + * O: static mba size */ + case MBIO_STATIC_SIZE: + put_user(wmt_mbah->tot_static_pages * PAGE_SIZE, + (unsigned long *)arg); + MB_OPDBG("STATIC_SIZE: size %ld KB\n\n", + PAGE_KB(wmt_mbah->tot_static_pages)); + break; + + /* _IOWR(MB_IOC_MAGIC,11, unsigned long) + * I: physical address + * O: use counter */ + case MBIO_MB_USER_COUNT: + get_user(value, (unsigned long *)arg); + + spin_lock_irqsave(&mb_ioctl_lock, flags); + mb = mb_find_mb((void *)value, MBFIND_PHYS); + if (mb == NULL) + size = 0; + else + size = MB_COUNT(mb); + spin_unlock_irqrestore(&mb_ioctl_lock, flags); + + put_user(size, (unsigned long *)arg); + MB_OPDBG("USER_COUNT: addr %lx count %ld\n\n", value, size); + break; + + /* _IO (MB_IOC_MAGIC,12) */ + case MBIO_FORCE_RESET: + spin_lock_irqsave(&mb_ioctl_lock, flags); +RESCAN_MBA: + list_loop(mba, &wmt_mbah->mba_list, mba_list) { + list_loop(mb, &mba->mb_list, mb_list) { + list_loop( + mbu, &mb->mbu_list, mbu_list) { + list_del(&mbu->mbu_list); + kmem_cache_free( + wmt_mbah->mbu_cachep, mbu); + } + mb->creator = NULL; + atomic_set(&mb->count, 0); + mb_free_mb(mb); + /* because mba link maybe breaken */ + goto RESCAN_MBA; + } + } + mb_update_mbah(); + spin_unlock_irqrestore(&mb_ioctl_lock, flags); + MB_OPDBG("RESET: OK\n\n"); + break; + + /* _IOW (MB_IOC_MAGIC,13, unsigned long) // I: phys address */ + case MBIO_GET_BY_PHYS: + get_user(value, (unsigned long *)arg); + if (!value || !mbti->task_name || tgid == MB_DEF_TGID) { + MB_WARN("GET(phys): invalid args %lx/%d/%s\n", + value, tgid, mbti->task_name); + return -EINVAL; + } + + mbu = kmem_cache_alloc(wmt_mbah->mbu_cachep, GFP_ATOMIC); + if (!mbu) { + MB_WARN("GET(phys): mbu_cachep out of memory\n"); + return -ENOMEM; + } + + spin_lock_irqsave(&mb_do_lock, flags); + mb = mb_find_mb((void *)value, MBFIND_PHYS); + if (!mb) { + spin_unlock_irqrestore(&mb_do_lock, flags); + kmem_cache_free(wmt_mbah->mbu_cachep, mbu); + MB_WARN("GET(phys): unknown phys %8lx name %s\n", + value, mbti->task_name); + return -EFAULT; + } + + memset(mbu, 0x0, sizeof(struct mb_user)); + INIT_LIST_HEAD(&mbu->mbu_list); + mbu->addr = value; + mbu->mb = mb; + mbu->tgid = tgid; + len = strlen(mbti->task_name); + if (len > TASK_COMM_LEN) + len = TASK_COMM_LEN; + strncpy(mbu->the_user, mbti->task_name, len); + mbu->the_user[len] = 0; + atomic_inc(&mb->count); + list_add_tail(&mbu->mbu_list, &mb->mbu_list); + spin_unlock_irqrestore(&mb_do_lock, flags); + MB_OPDBG("GET(phys): phys %8lx cnt %x\n\n", + value, MB_COUNT(mb)); + break; + + /* _IOW (MB_IOC_MAGIC,14, unsigned long) // I: phys address */ + case MBIO_PUT_BY_PHYS: + get_user(value, (unsigned long *)arg); + if (!value || !mbti->task_name || tgid == MB_DEF_TGID) { + MB_WARN("PUT(phys): invalid args %lx/%d/%s\n", + value, tgid, mbti->task_name); + return -EINVAL; + } + + spin_lock_irqsave(&mb_do_lock, flags); + mbu = mb_find_mbu((void *)value, tgid, + MBUFIND_PHYS|MBUFIND_GETPUT); + if (!mbu) { + spin_unlock_irqrestore(&mb_do_lock, flags); + MB_WARN("PUT(phys): unknow phys %8lx name %s\n", + value, mbti->task_name); + return -EINVAL; + } + + mb = mbu->mb; + if (!MB_IN_USE(mb)) { + spin_unlock_irqrestore(&mb_do_lock, flags); + MB_WARN("PUT(phys): phys %8lx unbalance.\n", value); + return -EPERM; + } + + list_del_init(&mbu->mbu_list); + atomic_dec(&mb->count); + size = MB_COUNT(mb); + /* retrieve unused block */ + if (!size) + mb_free_mb(mb); + + spin_unlock_irqrestore(&mb_do_lock, flags); + kmem_cache_free(wmt_mbah->mbu_cachep, mbu); + + MB_OPDBG("PUT(phys): phys %8lx cnt %lx\n\n", value, size); + + break; + + /* _IOW (MB_IOC_MAGIC,15, unsigned long) // I: user address */ + case MBIO_SYNC_CACHE: + get_user(value, (unsigned long *)arg); + spin_lock_irqsave(&mb_ioctl_lock, flags); + mbu = mb_find_mbu((void *)value, tgid, + MBUFIND_USER|MBUFIND_MMAP); + if (mbu == NULL) { + spin_unlock_irqrestore(&mb_ioctl_lock, flags); + MB_WARN("SYNC_CACHE: invalid addr %lx\n\n", value); + return -EFAULT; + } + mb = mbu->mb; + size = mbu->size; + dmac_flush_range(mb->virt, mb->virt + size); + outer_flush_range(mb->phys, mb->phys + size); + MB_OPDBG("SYNC_CACHE: (%lx # %lx) V %p ~ %p P %lx ~ %lx\n\n", + value, size, mb->virt, mb->virt + size, + mb->phys, mb->phys + size); + spin_unlock_irqrestore(&mb_ioctl_lock, flags); + return 0; + + default: + ret = -ENOTTY; + break; + } + + return ret; +} + +static int mb_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct mb_user *mbu = NULL; + struct mb_task_info *mbti; + struct mb_struct *mb; + unsigned long off, flags; + unsigned int len; + pid_t tgid; + size_t ns; + + if (!filp || !filp->private_data) { + MB_ERROR("mmap with null file info or task.\n"); + return -EFAULT; + } + + mbti = (struct mb_task_info *)(filp->private_data); + tgid = mbti->tgid; + if (tgid != current->tgid) { + MB_WARN("mmap within diff task.\n"); + MB_WARN("Open: task %s TGID %x VS Curr: task %s TGID %x\n", + mbti->task_name, tgid, current->comm, current->tgid); + } + + off = vma->vm_pgoff << PAGE_SHIFT; + MB_DBG("IN, offset %lx TGID %x name %s\n", off, tgid, mbti->task_name); +#ifdef CONFIG_WMT_MB_RESERVE_FROM_IO + /* if page offset under MB total size, it means page offset + * is not physical address from MB_alloc and then map function + * works like framebuffer. + * Suppose MB total size will not cross MBAH phys start address */ + if (vma->vm_pgoff <= wmt_mbah->tot_static_pages) { + struct mb_area_struct *mba = NULL; + unsigned long mbtotal = + wmt_mbah->tot_static_pages << PAGE_SHIFT; + if ((vma->vm_end - vma->vm_start + off) > mbtotal) { + MB_ERROR("mmap io fail. range %lx @ [%lx, %lx] s %lx\n", + off, vma->vm_start, vma->vm_end, mbtotal); + return -EINVAL; + } + mba = (struct mb_area_struct *)wmt_mbah->mba_list.next; + vma->vm_pgoff += mba->phys >> PAGE_SHIFT; + vma->vm_flags |= VM_IO | VM_RESERVED; + if (io_remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, + vma->vm_end - vma->vm_start, vma->vm_page_prot)) { + MB_ERROR("mmap io fail. (io_remap pfn %lx len %lx)\n", + vma->vm_pgoff, vma->vm_end - vma->vm_start); + return -EAGAIN; + } + MB_OPDBG("MAP: IO from %lx to %lx/%lx size %ld\n\n", + vma->vm_pgoff << PAGE_SHIFT, + vma->vm_start, vma->vm_end, + vma->vm_end - vma->vm_start); + return 0; + } +#endif + + mbu = kmem_cache_alloc(wmt_mbah->mbu_cachep, GFP_ATOMIC); + if (!mbu) { + MB_ERROR("mbu_cachep out of memory.\n"); + return -ENOMEM; + } + + spin_lock_irqsave(&mb_ioctl_lock, flags); + mb = mb_find_mb((void *)off, MBFIND_PHYS); + if (!mb) { + spin_unlock_irqrestore(&mb_ioctl_lock, flags); + kmem_cache_free(wmt_mbah->mbu_cachep, mbu); + MB_ERROR("map addr %lx not exist in MB.\n", off); + return -EINVAL; + } + + len = PAGE_ALIGN(mb->phys + mb->size); + if ((vma->vm_end - vma->vm_start + off) > len) { + spin_unlock_irqrestore(&mb_ioctl_lock, flags); + kmem_cache_free(wmt_mbah->mbu_cachep, mbu); + return -EINVAL; + } + + memset(mbu, 0x0, sizeof(struct mb_user)); + INIT_LIST_HEAD(&mbu->mbu_list); + mbu->tgid = tgid; + mbu->mb = mb; + mbu->addr = vma->vm_start; + mbu->size = vma->vm_end - vma->vm_start; + ns = strlen(mbti->task_name); + if (ns > TASK_COMM_LEN) + ns = TASK_COMM_LEN; + strncpy(mbu->the_user, mbti->task_name, ns); + mbu->the_user[ns] = 0; + list_add_tail(&mbu->mbu_list, &mb->mbu_list); + atomic_inc(&mb->count); + + off = mb->phys; + spin_unlock_irqrestore(&mb_ioctl_lock, flags); + + vma->vm_flags |= VM_IO | VM_RESERVED; + if (!(mb->flags & MBFLAG_CACHED)) + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + if (io_remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, + vma->vm_end - vma->vm_start, vma->vm_page_prot)) { + spin_lock_irqsave(&mb_ioctl_lock, flags); + list_del(&mbu->mbu_list); + atomic_dec(&mb->count); + kmem_cache_free(wmt_mbah->mbu_cachep, mbu); + spin_unlock_irqrestore(&mb_ioctl_lock, flags); + MB_ERROR("virtual address memory map fail.\n"); + return -EAGAIN; + } + + MB_OPDBG("MAP: IO from %lx to %lx/%lx size %ld mbcnt %d\n\n", + off, vma->vm_start, vma->vm_end, + (vma->vm_end - vma->vm_start), MB_COUNT(mb)); + + return 0; +} + +/*!************************************************************************* + driver file operations struct define +****************************************************************************/ +const struct file_operations mb_fops = { + .owner = THIS_MODULE, + .open = mb_open, + .release = mb_release, + .unlocked_ioctl = mb_ioctl, + .mmap = mb_mmap, +}; + +static int mb_probe(struct platform_device *dev) +{ + struct mb_area_struct *mba = NULL, *bind = NULL; + unsigned long flags; + int ret = 0; + dev_t dev_no; + + dev_no = MKDEV(mb_dev_major, mb_dev_minor); + + if (wmt_mbah) { + MB_ERROR("%s dirty.\n", DEVICE_NAME); + return -EINVAL; + } + + /* register char device */ + mb_cdev = cdev_alloc(); + if (!mb_cdev) { + MB_ERROR("alloc dev error.\n"); + return -ENOMEM; + } + + cdev_init(mb_cdev, &mb_fops); + ret = cdev_add(mb_cdev, dev_no, 1); + + if (ret) { + MB_ERROR("reg char dev error(%d).\n", ret); + cdev_del(mb_cdev); + return ret; + } + + wmt_mbah = kmalloc(sizeof(struct mba_host_struct), GFP_ATOMIC); + if (!wmt_mbah) { + MB_ERROR("out of memory (mb_area_struct).\n"); + cdev_del(mb_cdev); + return -ENOMEM; + } + memset(wmt_mbah, 0x0, sizeof(struct mba_host_struct)); + + wmt_mbah->mba_cachep = kmem_cache_create("mb_area_struct", + sizeof(struct mb_area_struct), + 0, + SLAB_HWCACHE_ALIGN, + NULL); + if (!wmt_mbah->mba_cachep) { + MB_ERROR("out of memory (mba_cachep).\n"); + kfree(wmt_mbah); + cdev_del(mb_cdev); + return -ENOMEM; + } + + wmt_mbah->mb_cachep = kmem_cache_create("mb_struct", + sizeof(struct mb_struct), + 0, + SLAB_HWCACHE_ALIGN, + NULL); + if (!wmt_mbah->mb_cachep) { + MB_ERROR("out of memory (mb_cachep).\n"); + kmem_cache_destroy(wmt_mbah->mba_cachep); + kfree(wmt_mbah); + cdev_del(mb_cdev); + return -ENOMEM; + } + + wmt_mbah->mbu_cachep = kmem_cache_create("mb_user", + sizeof(struct mb_user), + 0, + SLAB_HWCACHE_ALIGN, + NULL); + if (!wmt_mbah->mbu_cachep) { + MB_ERROR("out of memory (mbu_cachep).\n"); + kmem_cache_destroy(wmt_mbah->mb_cachep); + kmem_cache_destroy(wmt_mbah->mba_cachep); + kfree(wmt_mbah); + cdev_del(mb_cdev); + return -ENOMEM; + } + + wmt_mbah->mbti_cachep = kmem_cache_create("mb_task_info", + sizeof(struct mb_task_info), + 0, + SLAB_HWCACHE_ALIGN, + NULL); + if (!wmt_mbah->mbti_cachep) { + MB_ERROR("out of memory (mbti_cachep).\n"); + kmem_cache_destroy(wmt_mbah->mbu_cachep); + kmem_cache_destroy(wmt_mbah->mb_cachep); + kmem_cache_destroy(wmt_mbah->mba_cachep); + kfree(wmt_mbah); + cdev_del(mb_cdev); + return -ENOMEM; + } + + MBMAX_ORDER = MBA_MAX_ORDER; + MBMIN_ORDER = MBA_MIN_ORDER; + INIT_LIST_HEAD(&wmt_mbah->mba_list); + INIT_LIST_HEAD(&wmt_mbti.mbti_list); + + spin_lock_init(&mb_do_lock); + spin_lock_init(&mb_search_lock); + spin_lock_init(&mb_ioctl_lock); + spin_lock_init(&mb_task_mm_lock); + spin_lock_init(&mb_task_lock); + + MB_INFO("Preparing VIDEO BUFFER (SIZE %d kB) ...\n", MB_TOTAL_SIZE); + spin_lock_irqsave(&mb_do_lock, flags); + while (PAGE_KB(wmt_mbah->tot_static_pages) < MB_TOTAL_SIZE) { + int pages = MB_TOTAL_SIZE*1024 - + wmt_mbah->tot_static_pages * PAGE_SIZE; + pages = min((pages >> PAGE_SHIFT), (int)(1 << MBMAX_ORDER)); + mba = mb_alloc_mba(pages); + if (mba) { + mba->flags |= MBAFLAG_STATIC; + mba->tgid = MB_DEF_TGID; + wmt_mbah->tot_static_pages += mba->pages; + /* conbine to continue mba if possible */ + if (bind && bind->pgi.pfn_end == mba->pgi.pfn_start) { + MB_COMBIND_MBA(bind, mba); + continue; + } + if (bind && bind->pgi.pfn_start == mba->pgi.pfn_end) + MB_COMBIND_MBA(mba, bind); + bind = mba; + } + } + spin_unlock_irqrestore(&mb_do_lock, flags); + + mb_update_mbah(); + MB_INFO("MB version: %d.%d.%d.%d\n", + MB_VERSION_MAJOR, MB_VERSION_MINOR, + MB_VERSION_MICRO, MB_VERSION_BUILD); + MB_INFO("MAX MB Area size: Max %ld Kb Min %ld Kb\n", + PAGE_KB(1 << MBMAX_ORDER), PAGE_KB(1 << MBMIN_ORDER)); + MB_INFO("prob /dev/%s major %d, minor %d\n", + DEVICE_NAME, mb_dev_major, mb_dev_minor); + + return ret; +} + +static int mb_remove(struct platform_device *dev) +{ + if (mb_cdev) + cdev_del(mb_cdev); + + if (wmt_mbah) { + if (wmt_mbah->mba_cachep) + kmem_cache_destroy(wmt_mbah->mba_cachep); + if (wmt_mbah->mb_cachep) + kmem_cache_destroy(wmt_mbah->mb_cachep); + if (wmt_mbah->mbu_cachep) + kmem_cache_destroy(wmt_mbah->mbu_cachep); + if (wmt_mbah->mbti_cachep) + kmem_cache_destroy(wmt_mbah->mbti_cachep); + kfree(wmt_mbah); + } + + MB_INFO("MB dev remove\n"); + return 0; +} + +static int mb_suspend(struct platform_device *dev, pm_message_t state) +{ + MB_DBG("MB get suspend, Event %d.\n", state.event); + return 0; +} + +static int mb_resume(struct platform_device *dev) +{ + MB_DBG("MB get resume.\n"); + return 0; +} + +int mb_area_read_proc( + char *buf, char **start, off_t off, + int count, int *eof, void *data +) +{ + struct mb_area_struct *mba; + struct free_area *fa; + struct zone *zone; + unsigned long flags; + unsigned int idx = 1; + char *p = buf, *base = (char *)data; + int datalen = 0, len; + + if (!data || !count) { + p += sprintf(p, "no resource for mb_area read proc. (%p,%d)\n", + data, count); + return p-buf; + } + + if (!wmt_mbah) { + p += sprintf(p, "no MB area host existed.\n"); + return p-buf; + } + + spin_lock_irqsave(&mb_ioctl_lock, flags); + if (!off) { /* re read mb area information */ + p = base; + memset(p, 0x0, MBPROC_BUFSIZE); + + /* show block information */ + p += sprintf(p, "VERSION: %3d.%3d.%3d.%3d\n", + MB_VERSION_MAJOR, MB_VERSION_MINOR, + MB_VERSION_MICRO, MB_VERSION_BUILD); + p += sprintf(p, "MESSAGE LEVEL: %8d /%8lx\n", + MBMSG_LEVEL, __pa(&MBMSG_LEVEL)); + p += sprintf(p, "STATIC MB SIZE: %8ld MB /%5d MB\n", + PAGE_KB(wmt_mbah->tot_static_pages)/1024, + MB_TOTAL_SIZE/1024); + p += sprintf(p, "MAX MBA ORDER: %8d /%8lx\n", + MBMAX_ORDER, __pa(&MBMAX_ORDER)); + p += sprintf(p, "MIN MBA ORDER: %8d /%8lx\n\n", + MBMIN_ORDER, __pa(&MBMIN_ORDER)); + p += sprintf(p, "USER TO PRDT METHOD: %8d /%8lx\n\n", + USR2PRDT_METHOD, __pa(&USR2PRDT_METHOD)); + p += sprintf(p, "total MB areas: %8d\n", + wmt_mbah->nr_mba); + p += sprintf(p, "total size: %8ld kB\n", + PAGE_KB(wmt_mbah->tot_pages)); + p += sprintf(p, "total free size: %8ld kB\n", + PAGE_KB(wmt_mbah->tot_free_pages)); + p += sprintf(p, "max MB size: %8ld kB\n\n", + PAGE_KB(wmt_mbah->max_available_pages)); + + list_loop(mba, &wmt_mbah->mba_list, mba_list) { + p += sprintf(p, "(ID) [MB Area] VirtAddr PhysAddr "); + p += sprintf(p, "size [ zs, ze] MBs Max/"); + p += sprintf(p, " Free Ste\n"); + len = (int)(p-base); + if ((MBPROC_BUFSIZE - len) < 12) { + p += sprintf(p, " more ...\n"); + break; + } + p += sprintf(p, "(%2d)", idx++); + len = (int)(p-base); + /* show all MBs */ + /* -2 is for \n and zero */ + p += mb_show_mba(mba, NULL, p, + MBPROC_BUFSIZE - len - 2, 1); + p += sprintf(p, "\n"); + } + /* show memory fragment */ + zone = (first_online_pgdat())->node_zones; + fa = zone->free_area; + p += sprintf(p, "DMA ZONE:\n"); + for (idx = 0; idx < MAX_ORDER; idx++) + p += sprintf(p, " %5ld * %5ldkB = %5ld kB\n", + fa[idx].nr_free, PAGE_KB((1<<idx)), + fa[idx].nr_free * PAGE_KB((1<<idx))); + p += sprintf(p, " ------------------------------\n"); + p += sprintf(p, " + = %ld kB\n\n", + PAGE_KB((unsigned long)nr_free_pages())); + } + spin_unlock_irqrestore(&mb_ioctl_lock, flags); + datalen = strlen(base); + if (off >= datalen) { + *eof = 1; + return 0; + } + len = min((int)(datalen - off), count); + memcpy(buf, &base[off], len); + *start = (char *)len; /* for case1: *start < page, mass read data */ + + return len; +} + +static struct platform_driver mb_driver = { + .driver.name = "wmt_mb", + .probe = mb_probe, + .remove = mb_remove, + .suspend = mb_suspend, + .resume = mb_resume +}; + +static void mb_platform_release(struct device *device) +{ + return; +} + +static struct platform_device mb_device = { + .name = "wmt_mb", + .id = 0, + .dev = { .release = mb_platform_release, }, + .num_resources = 0, + .resource = NULL, +}; + +static int __init mb_init(void) +{ + int varlen = 32, ret = -1, mbtotal = 0; + unsigned char buf[32]; + dev_t dev_no; + + mb_dev_major = MB_MAJOR; + mb_dev_minor = 0; + mb_dev_nr = 1; + + dev_no = MKDEV(mb_dev_major, mb_dev_minor); + ret = register_chrdev_region(dev_no, mb_dev_nr, "wmt_mb"); + if (ret < 0) { + MB_ERROR("can't get %s device major %d\n", + DEVICE_NAME, mb_dev_major); + return ret; + } + + if (wmt_getsyspara("mbsize", buf, &varlen) == 0) { + sscanf(buf, "%dM", &mbtotal); + mbtotal *= 1024; + MB_INFO("Set MB total size %d KB\n", mbtotal); + if (mbtotal > 0) + MB_TOTAL_SIZE = mbtotal; + } + + wmt_mbah = NULL; + MBMSG_LEVEL = MBMAX_ORDER = MBMIN_ORDER = USR2PRDT_METHOD = 0; + mb_dev_class = class_create(THIS_MODULE, "mbdev"); + device_create(mb_dev_class, NULL, dev_no, NULL, "mbdev"); + +#ifdef CONFIG_PROC_FS + { + void *proc_buffer = kmalloc(MBPROC_BUFSIZE, GFP_KERNEL); + struct proc_dir_entry *res = NULL; + if (proc_buffer) { + memset(proc_buffer, 0x0, MBPROC_BUFSIZE); + res = create_proc_entry("mbinfo", 0, NULL); + if (res) { + res->read_proc = mb_area_read_proc; + res->data = proc_buffer; + MB_DBG("create MB proc\n"); + } else + kfree(proc_buffer); + } + } +#endif + + ret = platform_driver_register(&mb_driver); + if (!ret) { + ret = platform_device_register(&mb_device); + if (ret) + platform_driver_unregister(&mb_driver); + } + + return ret; +} + +/* because mb will not exit from system, not need to release proc resource */ +static void __exit mb_exit(void) +{ + dev_t dev_no; + + platform_driver_unregister(&mb_driver); + platform_device_unregister(&mb_device); + dev_no = MKDEV(mb_dev_major, mb_dev_minor); + device_destroy(mb_dev_class, dev_no); + class_destroy(mb_dev_class); + unregister_chrdev_region(dev_no, mb_dev_nr); + + return; +} + +fs_initcall(mb_init); +module_exit(mb_exit); + +MODULE_AUTHOR("WonderMedia Technologies, Inc."); +MODULE_DESCRIPTION("memory block driver"); +MODULE_LICENSE("GPL"); |