/* * Copyright (c) XMEDIA. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include "allocator.h" #ifndef fallthrough #define fallthrough #endif extern struct osal_list_head mmz_list; long long max_malloc_size = 0x40000000UL; static unsigned long _strtoul_ex(const char *s, char **ep, unsigned int base) { char *__end_p = NULL; unsigned long __value; __value = simple_strtoul(s, &__end_p, base); switch (*__end_p) { case 'm': fallthrough; case 'M': __value <<= 10; fallthrough; case 'k': fallthrough; case 'K': __value <<= 10; if (ep != NULL) { (*ep) = __end_p + 1; } fallthrough; default: break; } return __value; } static unsigned long find_fixed_region(unsigned long *region_len, mmz_mmz_t *mmz, unsigned long size, unsigned long align) { unsigned long start; unsigned long fixed_start = 0; unsigned long fixed_len = -1; unsigned long len = 0; unsigned long blank_len = 0; mmz_mmb_t *p = NULL; mmz_trace_func(); align = mmz_grain_align(align); if (align == 0) { align = MMZ_GRAIN; } start = mmz_align2(mmz->phys_start, align); len = mmz_grain_align(size); list_for_each_entry(p, &mmz->mmb_list, list) { mmz_mmb_t *next = NULL; mmz_trace(4, "p->phys_addr=0x%08lX p->length = %luKB \t", p->phys_addr, p->length / SZ_1K); next = list_entry(p->list.next, typeof(*p), list); mmz_trace(4, ",next = 0x%08lX\n\n", next->phys_addr); /* * if p is the first entry or not. */ if (list_first_entry(&mmz->mmb_list, typeof(*p), list) == p) { blank_len = p->phys_addr - start; if ((blank_len < fixed_len) && (blank_len >= len)) { fixed_len = blank_len; fixed_start = start; mmz_trace(4, "%d: fixed_region: start=0x%08lX, len=%luKB\n", __LINE__, fixed_start, fixed_len / SZ_1K); } } start = mmz_align2((p->phys_addr + p->length), align); /* * if aglin is larger than mmz->nbytes, it would trigger the BUG_ON */ // BUG_ON((start < mmz->phys_start) || (start > (mmz->phys_start + mmz->nbytes))); /* * if we have to alloc after the last node. */ if (osal_list_is_last(&p->list, &mmz->mmb_list)) { blank_len = mmz->phys_start + mmz->nbytes - start; if ((blank_len < fixed_len) && (blank_len >= len)) { fixed_len = blank_len; fixed_start = start; mmz_trace(4, "fixed_region: start=0x%08lX, len=%luKB\n", fixed_start, fixed_len / SZ_1K); break; } else { if (fixed_len != -1) { goto out; } fixed_start = 0; mmz_trace(4, "fixed_region: start=0x%08lX, len=%luKB\n", fixed_start, fixed_len / SZ_1K); goto out; } } /* blank is too small */ if ((start + len) > next->phys_addr) { mmz_trace(4, "start=0x%08lX ,len=%lu,next=0x%08lX\n", start, len, next->phys_addr); continue; } blank_len = next->phys_addr - start; if ((blank_len < fixed_len) && (blank_len >= len)) { fixed_len = blank_len; fixed_start = start; mmz_trace(4, "fixed_region: start=0x%08lX, len=%luKB\n", fixed_start, fixed_len / SZ_1K); } } if ((mmz_grain_align(start + len) <= (mmz->phys_start + mmz->nbytes)) && (start >= mmz->phys_start) && (start < (mmz->phys_start + mmz->nbytes))) { fixed_len = len; fixed_start = start; mmz_trace(4, "fixed_region: start=0x%08lX, len=%luKB\n", fixed_start, fixed_len / SZ_1K); } else { fixed_start = 0; mmz_trace(4, "fixed_region: start=0x%08lX, len=%luKB\n", fixed_start, len / SZ_1K); } out: *region_len = len; return fixed_start; } static int do_mmb_alloc(mmz_mmb_t *mmb) { mmz_mmb_t *p = NULL; mmz_trace_func(); /* add mmb sorted */ osal_list_for_each_entry(p, &mmb->zone->mmb_list, list) { if (mmb->phys_addr < p->phys_addr) { break; } if (mmb->phys_addr == p->phys_addr) { printk(KERN_ERR "ERROR: media-mem allocator bad in %s! (%s, %d)", mmb->zone->name, __FUNCTION__, __LINE__); } } osal_list_add(&mmb->list, p->list.prev); mmz_trace(1, MMZ_MMB_FMT_S, mmz_mmb_fmt_arg(mmb)); return 0; } static mmz_mmb_t *__mmb_alloc(const char *name, unsigned long size, unsigned long align, unsigned long gfp, const char *mmz_name, mmz_mmz_t *_user_mmz) { mmz_mmz_t *mmz = NULL; mmz_mmb_t *mmb = NULL; unsigned long start; unsigned long region_len; unsigned long fixed_start = 0; unsigned long fixed_len = ~1; mmz_mmz_t *fixed_mmz = NULL; mmz_trace_func(); if ((size == 0) || (size > max_malloc_size)) { return NULL; } if (align == 0) { align = MMZ_GRAIN; } size = mmz_grain_align(size); mmz_trace(1, "size=%luKB, align=%lu", size / SZ_1K, align); begin_list_for_each_mmz(mmz, gfp, mmz_name) if ((_user_mmz != NULL) && (_user_mmz != mmz)) { continue; } start = find_fixed_region(®ion_len, mmz, size, align); if ((fixed_len > region_len) && (start != 0)) { fixed_len = region_len; fixed_start = start; fixed_mmz = mmz; } end_list_for_each_mmz() if (fixed_mmz == NULL) { return NULL; } mmb = kmalloc(sizeof(mmz_mmb_t), GFP_KERNEL); if (mmb == NULL) { return NULL; } memset(mmb, 0, sizeof(mmz_mmb_t)); mmb->zone = fixed_mmz; mmb->phys_addr = fixed_start; mmb->length = size; if (name != NULL) { strlcpy(mmb->name, name, MMZ_MMB_NAME_LEN); } else { strncpy(mmb->name, "", MMZ_MMB_NAME_LEN); } if (do_mmb_alloc(mmb)) { kfree(mmb); mmb = NULL; } return mmb; } static void *__mmb_map2kern(mmz_mmb_t *mmb, int cached) { /* * already mapped? no need to remap again, * just return mmb's kernel virtual address. */ if (mmb->flags & MMZ_MMB_MAP2KERN) { if ((!!cached * MMZ_MMB_MAP2KERN_CACHED) != (mmb->flags & MMZ_MMB_MAP2KERN_CACHED)) { printk(KERN_ERR "mmb<%s> has been kernel-mapped as %s, \ can not be re-mapped as %s.", mmb->name, (mmb->flags & MMZ_MMB_MAP2KERN_CACHED) ? "cached" : "non-cached", (cached) ? "cached" : "non-cached"); return NULL; } mmb->map_ref++; return mmb->kvirt; } if (cached) { mmb->flags |= MMZ_MMB_MAP2KERN_CACHED; mmb->kvirt = ioremap_cache(mmb->phys_addr, mmb->length); } else { mmb->flags &= ~MMZ_MMB_MAP2KERN_CACHED; /* ioremap_wc has better performance */ mmb->kvirt = ioremap_wc(mmb->phys_addr, mmb->length); } if (mmb->kvirt) { mmb->flags |= MMZ_MMB_MAP2KERN; mmb->map_ref++; } else { mmb->flags &= ~MMZ_MMB_MAP2KERN_CACHED; } return mmb->kvirt; } static void __mmb_free(mmz_mmb_t *mmb) { if (mmb->flags & MMZ_MMB_MAP2KERN_CACHED) { #ifdef CONFIG_64BIT __flush_dcache_area((void *)mmb->kvirt, (size_t)mmb->length); #else __cpuc_flush_dcache_area((void *)mmb->kvirt, (size_t)mmb->length); outer_flush_range(mmb->phys_addr, mmb->phys_addr + mmb->length); #endif } osal_list_del(&mmb->list); kfree(mmb); } static int __mmb_unmap(mmz_mmb_t *mmb) { int ref; if (mmb->flags & MMZ_MMB_MAP2KERN_CACHED) { #ifdef CONFIG_64BIT __flush_dcache_area((void *)mmb->kvirt, (size_t)mmb->length); #else __cpuc_flush_dcache_area((void *)mmb->kvirt, (size_t)mmb->length); outer_flush_range(mmb->phys_addr, mmb->phys_addr + mmb->length); #endif } if (mmb->flags & MMZ_MMB_MAP2KERN) { ref = --mmb->map_ref; if (mmb->map_ref != 0) { return ref; } iounmap(mmb->kvirt); } mmb->kvirt = NULL; mmb->flags &= ~MMZ_MMB_MAP2KERN; mmb->flags &= ~MMZ_MMB_MAP2KERN_CACHED; if ((mmb->flags & MMZ_MMB_RELEASED) && (mmb->phy_ref == 0)) { __mmb_free(mmb); } return 0; } static void *__mmf_map(phys_addr_t phys, int len, int cache) { void *virt = NULL; if (cache) { virt = ioremap_cache(phys, len); } else { virt = ioremap_wc(phys, len); } return virt; } static void __mmf_unmap(void *virt) { if (virt != NULL) { iounmap(virt); } } static int __allocator_init(char *s) { mmz_mmz_t *zone = NULL; char *line = NULL; unsigned long phys_end = 0; while ((line = strsep(&s, ":")) != NULL) { int i; char *argv[6]; for (i = 0; (argv[i] = strsep(&line, ",")) != NULL;) if (++i == ARRAY_SIZE(argv)) { break; } if (i == 4) { zone = mmz_mmz_create("null", 0, 0, 0); if (zone == NULL) { continue; } strlcpy(zone->name, argv[0], MMZ_MMZ_NAME_LEN); zone->gfp = _strtoul_ex(argv[1], NULL, 0); zone->phys_start = _strtoul_ex(argv[2], NULL, 0); zone->nbytes = _strtoul_ex(argv[3], NULL, 0); if (zone->nbytes > max_malloc_size) { max_malloc_size = zone->nbytes; } } else { printk(KERN_ERR "error parameters\n"); return -EINVAL; } // mmz_info_phys_start = zone->phys_start + zone->nbytes - 0x2000; if (mmz_mmz_register(zone)) { printk(KERN_WARNING "Add MMZ failed: " MMZ_MMZ_FMT_S "\n", mmz_mmz_fmt_arg(zone)); mmz_mmz_destroy(zone); return -1; } // if phys_end is 0xFFFFFFFF (32bit) phys_end = (zone->phys_start + zone->nbytes); if ((phys_end == 0) && (zone->nbytes >= PAGE_SIZE)) { // reserve last PAGE_SIZE memory zone->nbytes = zone->nbytes - PAGE_SIZE; } // if phys_end exceed 0xFFFFFFFF (32bit), wraping error if ((zone->phys_start > phys_end) && (phys_end != 0)) { printk(KERN_ERR "MMZ: parameter is not correct! Address exceeds 0xFFFFFFFF\n"); mmz_mmz_unregister(zone); mmz_mmz_destroy(zone); return -1; } zone = NULL; } return 0; } int allocator_setopt(struct mmz_allocator *allocator) { allocator->init = __allocator_init; allocator->mmb_alloc = __mmb_alloc; allocator->mmb_map2kern = __mmb_map2kern; allocator->mmb_unmap = __mmb_unmap; allocator->mmb_free = __mmb_free; allocator->mmf_map = __mmf_map; allocator->mmf_unmap = __mmf_unmap; return 0; }