| /* arch/arm/mach-msm/memory.c |
| * |
| * Copyright (C) 2007 Google, Inc. |
| * Copyright (c) 2009-2012, The Linux Foundation. All rights reserved. |
| * |
| * This software is licensed under the terms of the GNU General Public |
| * License version 2, as published by the Free Software Foundation, and |
| * may be copied, distributed, and modified under those terms. |
| * |
| * 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. |
| * |
| */ |
| |
| #include <linux/mm.h> |
| #include <linux/mm_types.h> |
| #include <linux/bootmem.h> |
| #include <linux/module.h> |
| #include <linux/memory_alloc.h> |
| #include <linux/memblock.h> |
| #include <asm/pgtable.h> |
| #include <asm/io.h> |
| #include <asm/mach/map.h> |
| #include <asm/cacheflush.h> |
| #include <asm/setup.h> |
| #include <asm/mach-types.h> |
| #include <mach/msm_memtypes.h> |
| #include <mach/memory.h> |
| #include <linux/hardirq.h> |
| #if defined(CONFIG_MSM_NPA_REMOTE) |
| #include "npa_remote.h" |
| #include <linux/completion.h> |
| #include <linux/err.h> |
| #endif |
| #include <linux/android_pmem.h> |
| #include <mach/msm_iomap.h> |
| #include <mach/socinfo.h> |
| #include <linux/sched.h> |
| #include <linux/of_fdt.h> |
| |
| /* fixme */ |
| #include <asm/tlbflush.h> |
| #include <../../mm/mm.h> |
| #include <linux/fmem.h> |
| |
| #if defined(CONFIG_ARCH_MSM7X27) |
| static void *strongly_ordered_page; |
| static char strongly_ordered_mem[PAGE_SIZE*2-4]; |
| |
| void __init map_page_strongly_ordered(void) |
| { |
| long unsigned int phys; |
| struct map_desc map[1]; |
| |
| if (strongly_ordered_page) |
| return; |
| |
| strongly_ordered_page = (void*)PFN_ALIGN((int)&strongly_ordered_mem); |
| phys = __pa(strongly_ordered_page); |
| |
| map[0].pfn = __phys_to_pfn(phys); |
| map[0].virtual = MSM_STRONGLY_ORDERED_PAGE; |
| map[0].length = PAGE_SIZE; |
| map[0].type = MT_MEMORY_SO; |
| iotable_init(map, ARRAY_SIZE(map)); |
| |
| printk(KERN_ALERT "Initialized strongly ordered page successfully\n"); |
| } |
| #else |
| void map_page_strongly_ordered(void) { } |
| #endif |
| |
| #if defined(CONFIG_ARCH_MSM7X27) |
| void write_to_strongly_ordered_memory(void) |
| { |
| *(int *)MSM_STRONGLY_ORDERED_PAGE = 0; |
| } |
| #else |
| void write_to_strongly_ordered_memory(void) { } |
| #endif |
| EXPORT_SYMBOL(write_to_strongly_ordered_memory); |
| |
| /* These cache related routines make the assumption (if outer cache is |
| * available) that the associated physical memory is contiguous. |
| * They will operate on all (L1 and L2 if present) caches. |
| */ |
| void clean_and_invalidate_caches(unsigned long vstart, |
| unsigned long length, unsigned long pstart) |
| { |
| dmac_flush_range((void *)vstart, (void *) (vstart + length)); |
| outer_flush_range(pstart, pstart + length); |
| } |
| |
| void clean_caches(unsigned long vstart, |
| unsigned long length, unsigned long pstart) |
| { |
| dmac_clean_range((void *)vstart, (void *) (vstart + length)); |
| outer_clean_range(pstart, pstart + length); |
| } |
| |
| void invalidate_caches(unsigned long vstart, |
| unsigned long length, unsigned long pstart) |
| { |
| dmac_inv_range((void *)vstart, (void *) (vstart + length)); |
| outer_inv_range(pstart, pstart + length); |
| } |
| |
| void * __init alloc_bootmem_aligned(unsigned long size, unsigned long alignment) |
| { |
| void *unused_addr = NULL; |
| unsigned long addr, tmp_size, unused_size; |
| |
| /* Allocate maximum size needed, see where it ends up. |
| * Then free it -- in this path there are no other allocators |
| * so we can depend on getting the same address back |
| * when we allocate a smaller piece that is aligned |
| * at the end (if necessary) and the piece we really want, |
| * then free the unused first piece. |
| */ |
| |
| tmp_size = size + alignment - PAGE_SIZE; |
| addr = (unsigned long)alloc_bootmem(tmp_size); |
| free_bootmem(__pa(addr), tmp_size); |
| |
| unused_size = alignment - (addr % alignment); |
| if (unused_size) |
| unused_addr = alloc_bootmem(unused_size); |
| |
| addr = (unsigned long)alloc_bootmem(size); |
| if (unused_size) |
| free_bootmem(__pa(unused_addr), unused_size); |
| |
| return (void *)addr; |
| } |
| |
| int (*change_memory_power)(u64, u64, int); |
| |
| int platform_physical_remove_pages(u64 start, u64 size) |
| { |
| if (!change_memory_power) |
| return 0; |
| return change_memory_power(start, size, MEMORY_DEEP_POWERDOWN); |
| } |
| |
| int platform_physical_active_pages(u64 start, u64 size) |
| { |
| if (!change_memory_power) |
| return 0; |
| return change_memory_power(start, size, MEMORY_ACTIVE); |
| } |
| |
| int platform_physical_low_power_pages(u64 start, u64 size) |
| { |
| if (!change_memory_power) |
| return 0; |
| return change_memory_power(start, size, MEMORY_SELF_REFRESH); |
| } |
| |
| char *memtype_name[] = { |
| "SMI_KERNEL", |
| "SMI", |
| "EBI0", |
| "EBI1" |
| }; |
| |
| struct reserve_info *reserve_info; |
| |
| /** |
| * calculate_reserve_limits() - calculate reserve limits for all |
| * memtypes |
| * |
| * for each memtype in the reserve_info->memtype_reserve_table, sets |
| * the `limit' field to the largest size of any memblock of that |
| * memtype. |
| */ |
| static void __init calculate_reserve_limits(void) |
| { |
| struct memblock_region *mr; |
| int memtype; |
| struct memtype_reserve *mt; |
| |
| for_each_memblock(memory, mr) { |
| memtype = reserve_info->paddr_to_memtype(mr->base); |
| if (memtype == MEMTYPE_NONE) { |
| pr_warning("unknown memory type for region at %lx\n", |
| (long unsigned int)mr->base); |
| continue; |
| } |
| mt = &reserve_info->memtype_reserve_table[memtype]; |
| mt->limit = max_t(unsigned long, mt->limit, mr->size); |
| } |
| } |
| |
| static void __init adjust_reserve_sizes(void) |
| { |
| int i; |
| struct memtype_reserve *mt; |
| |
| mt = &reserve_info->memtype_reserve_table[0]; |
| for (i = 0; i < MEMTYPE_MAX; i++, mt++) { |
| if (mt->flags & MEMTYPE_FLAGS_1M_ALIGN) |
| mt->size = (mt->size + SECTION_SIZE - 1) & SECTION_MASK; |
| if (mt->size > mt->limit) { |
| pr_warning("%lx size for %s too large, setting to %lx\n", |
| mt->size, memtype_name[i], mt->limit); |
| mt->size = mt->limit; |
| } |
| } |
| } |
| |
| static void __init reserve_memory_for_mempools(void) |
| { |
| int memtype, memreg_type; |
| struct memtype_reserve *mt; |
| struct memblock_region *mr, *mr_candidate = NULL; |
| int ret; |
| |
| mt = &reserve_info->memtype_reserve_table[0]; |
| for (memtype = 0; memtype < MEMTYPE_MAX; memtype++, mt++) { |
| if (mt->flags & MEMTYPE_FLAGS_FIXED || !mt->size) |
| continue; |
| |
| /* Choose the memory block with the highest physical |
| * address which is large enough, so that we will not |
| * take memory from the lowest memory bank which the kernel |
| * is in (and cause boot problems) and so that we might |
| * be able to steal memory that would otherwise become |
| * highmem. |
| */ |
| for_each_memblock(memory, mr) { |
| memreg_type = |
| reserve_info->paddr_to_memtype(mr->base); |
| if (memtype != memreg_type) |
| continue; |
| if (mr->size >= mt->size |
| && (mr_candidate == NULL |
| || mr->base > mr_candidate->base)) |
| mr_candidate = mr; |
| } |
| BUG_ON(mr_candidate == NULL); |
| /* bump mt up against the top of the region */ |
| mt->start = mr_candidate->base + mr_candidate->size - mt->size; |
| ret = memblock_remove(mt->start, mt->size); |
| BUG_ON(ret); |
| } |
| } |
| |
| static void __init initialize_mempools(void) |
| { |
| struct mem_pool *mpool; |
| int memtype; |
| struct memtype_reserve *mt; |
| |
| mt = &reserve_info->memtype_reserve_table[0]; |
| for (memtype = 0; memtype < MEMTYPE_MAX; memtype++, mt++) { |
| if (!mt->size) |
| continue; |
| mpool = initialize_memory_pool(mt->start, mt->size, memtype); |
| if (!mpool) |
| pr_warning("failed to create %s mempool\n", |
| memtype_name[memtype]); |
| } |
| } |
| |
| #define MAX_FIXED_AREA_SIZE 0x11000000 |
| |
| void __init msm_reserve(void) |
| { |
| unsigned long msm_fixed_area_size; |
| unsigned long msm_fixed_area_start; |
| |
| memory_pool_init(); |
| if (reserve_info->calculate_reserve_sizes) |
| reserve_info->calculate_reserve_sizes(); |
| |
| msm_fixed_area_size = reserve_info->fixed_area_size; |
| msm_fixed_area_start = reserve_info->fixed_area_start; |
| if (msm_fixed_area_size) |
| if (msm_fixed_area_start > reserve_info->low_unstable_address |
| - MAX_FIXED_AREA_SIZE) |
| reserve_info->low_unstable_address = |
| msm_fixed_area_start; |
| |
| calculate_reserve_limits(); |
| adjust_reserve_sizes(); |
| reserve_memory_for_mempools(); |
| initialize_mempools(); |
| } |
| |
| static int get_ebi_memtype(void) |
| { |
| /* on 7x30 and 8x55 "EBI1 kernel PMEM" is really on EBI0 */ |
| if (cpu_is_msm7x30() || cpu_is_msm8x55()) |
| return MEMTYPE_EBI0; |
| return MEMTYPE_EBI1; |
| } |
| |
| void *allocate_contiguous_ebi(unsigned long size, |
| unsigned long align, int cached) |
| { |
| return allocate_contiguous_memory(size, get_ebi_memtype(), |
| align, cached); |
| } |
| EXPORT_SYMBOL(allocate_contiguous_ebi); |
| |
| unsigned long allocate_contiguous_ebi_nomap(unsigned long size, |
| unsigned long align) |
| { |
| return _allocate_contiguous_memory_nomap(size, get_ebi_memtype(), |
| align, __builtin_return_address(0)); |
| } |
| EXPORT_SYMBOL(allocate_contiguous_ebi_nomap); |
| |
| unsigned int msm_ttbr0; |
| |
| void store_ttbr0(void) |
| { |
| /* Store TTBR0 for post-mortem debugging purposes. */ |
| asm("mrc p15, 0, %0, c2, c0, 0\n" |
| : "=r" (msm_ttbr0)); |
| } |
| |
| int request_fmem_c_region(void *unused) |
| { |
| return fmem_set_state(FMEM_C_STATE); |
| } |
| |
| int release_fmem_c_region(void *unused) |
| { |
| return fmem_set_state(FMEM_T_STATE); |
| } |
| |
| static char * const memtype_names[] = { |
| [MEMTYPE_SMI_KERNEL] = "SMI_KERNEL", |
| [MEMTYPE_SMI] = "SMI", |
| [MEMTYPE_EBI0] = "EBI0", |
| [MEMTYPE_EBI1] = "EBI1", |
| }; |
| |
| int msm_get_memory_type_from_name(const char *memtype_name) |
| { |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(memtype_names); i++) { |
| if (memtype_names[i] && |
| strcmp(memtype_name, memtype_names[i]) == 0) |
| return i; |
| } |
| |
| pr_err("Could not find memory type %s\n", memtype_name); |
| return -EINVAL; |
| } |
| |
| static int reserve_memory_type(const char *mem_name, |
| struct memtype_reserve *reserve_table, |
| int size) |
| { |
| int ret = msm_get_memory_type_from_name(mem_name); |
| |
| if (ret >= 0) { |
| reserve_table[ret].size += size; |
| ret = 0; |
| } |
| return ret; |
| } |
| |
| static int __init check_for_compat(unsigned long node) |
| { |
| char **start = __compat_exports_start; |
| |
| for ( ; start < __compat_exports_end; start++) |
| if (of_flat_dt_is_compatible(node, *start)) |
| return 1; |
| |
| return 0; |
| } |
| |
| int __init dt_scan_for_memory_reserve(unsigned long node, const char *uname, |
| int depth, void *data) |
| { |
| char *memory_name_prop; |
| unsigned int *memory_remove_prop; |
| unsigned long memory_name_prop_length; |
| unsigned long memory_remove_prop_length; |
| unsigned long memory_size_prop_length; |
| unsigned int *memory_size_prop; |
| unsigned int memory_size; |
| unsigned int memory_start; |
| int ret; |
| |
| memory_name_prop = of_get_flat_dt_prop(node, |
| "qcom,memory-reservation-type", |
| &memory_name_prop_length); |
| memory_remove_prop = of_get_flat_dt_prop(node, |
| "qcom,memblock-remove", |
| &memory_remove_prop_length); |
| |
| if (memory_name_prop || memory_remove_prop) { |
| if (!check_for_compat(node)) |
| goto out; |
| } else { |
| goto out; |
| } |
| |
| if (memory_name_prop) { |
| if (strnlen(memory_name_prop, memory_name_prop_length) == 0) { |
| WARN(1, "Memory name was malformed\n"); |
| goto mem_remove; |
| } |
| |
| memory_size_prop = of_get_flat_dt_prop(node, |
| "qcom,memory-reservation-size", |
| &memory_size_prop_length); |
| |
| if (memory_size_prop && |
| (memory_size_prop_length == sizeof(unsigned int))) { |
| memory_size = be32_to_cpu(*memory_size_prop); |
| |
| if (reserve_memory_type(memory_name_prop, |
| data, memory_size) == 0) |
| pr_info("%s reserved %s size %x\n", |
| uname, memory_name_prop, memory_size); |
| else |
| WARN(1, "Node %s reserve failed\n", |
| uname); |
| } else { |
| WARN(1, "Node %s specified bad/nonexistent size\n", |
| uname); |
| } |
| } |
| |
| mem_remove: |
| |
| if (memory_remove_prop) { |
| if (memory_remove_prop_length != (2*sizeof(unsigned int))) { |
| WARN(1, "Memory remove malformed\n"); |
| goto out; |
| } |
| |
| memory_start = be32_to_cpu(memory_remove_prop[0]); |
| memory_size = be32_to_cpu(memory_remove_prop[1]); |
| |
| ret = memblock_remove(memory_start, memory_size); |
| if (ret) |
| WARN(1, "Failed to remove memory %x-%x\n", |
| memory_start, memory_start+memory_size); |
| else |
| pr_info("Node %s removed memory %x-%x\n", uname, |
| memory_start, memory_start+memory_size); |
| } |
| |
| out: |
| return 0; |
| } |
| |
| /* This function scans the device tree to populate the memory hole table */ |
| int __init dt_scan_for_memory_hole(unsigned long node, const char *uname, |
| int depth, void *data) |
| { |
| unsigned int *memory_remove_prop; |
| unsigned long memory_remove_prop_length; |
| unsigned long hole_start; |
| unsigned long hole_size; |
| |
| memory_remove_prop = of_get_flat_dt_prop(node, |
| "qcom,memblock-remove", |
| &memory_remove_prop_length); |
| |
| if (memory_remove_prop) { |
| if (!check_for_compat(node)) |
| goto out; |
| } else { |
| goto out; |
| } |
| |
| if (memory_remove_prop) { |
| if (memory_remove_prop_length != (2*sizeof(unsigned int))) { |
| WARN(1, "Memory remove malformed\n"); |
| goto out; |
| } |
| |
| hole_start = be32_to_cpu(memory_remove_prop[0]); |
| hole_size = be32_to_cpu(memory_remove_prop[1]); |
| |
| if (hole_start + hole_size <= MAX_HOLE_ADDRESS) { |
| if (memory_hole_start == 0 && memory_hole_end == 0) { |
| memory_hole_start = hole_start; |
| memory_hole_end = hole_start + hole_size; |
| } else if ((memory_hole_end - memory_hole_start) |
| <= hole_size) { |
| memory_hole_start = hole_start; |
| memory_hole_end = hole_start + hole_size; |
| } |
| } |
| adjust_meminfo(hole_start, hole_size); |
| } |
| |
| out: |
| return 0; |
| } |
| |
| /* |
| * Split the memory bank to reflect the hole, if present, |
| * using the start and end of the memory hole. |
| */ |
| void adjust_meminfo(unsigned long start, unsigned long size) |
| { |
| int i, j; |
| |
| for (i = 0, j = 0; i < meminfo.nr_banks; i++) { |
| struct membank *bank = &meminfo.bank[j]; |
| *bank = meminfo.bank[i]; |
| |
| if (((start + size) <= (bank->start + bank->size)) && |
| (start >= bank->start)) { |
| memmove(bank + 1, bank, |
| (meminfo.nr_banks - i) * sizeof(*bank)); |
| meminfo.nr_banks++; |
| i++; |
| bank[1].size -= (start + size); |
| bank[1].start = (start + size); |
| bank[1].highmem = 0; |
| j++; |
| bank->size = start - bank->start; |
| } |
| j++; |
| } |
| } |
| unsigned long get_ddr_size(void) |
| { |
| unsigned int i; |
| unsigned long ret = 0; |
| |
| for (i = 0; i < meminfo.nr_banks; i++) |
| ret += meminfo.bank[i].size; |
| |
| return ret; |
| } |
| |
| /* Provide a string that anonymous device tree allocations (those not |
| * directly associated with any driver) can use for their "compatible" |
| * field */ |
| EXPORT_COMPAT("qcom,msm-contig-mem"); |