| /* |
| * Copyright (C) 2012 Broadcom Corporation |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
| */ |
| |
| #include <linux/bootmem.h> |
| #include <linux/spinlock.h> |
| #include <linux/mm.h> |
| #include <linux/kernel.h> |
| #include <linux/pci.h> |
| #include <linux/ioport.h> |
| #include <linux/list.h> |
| #include <linux/vmalloc.h> |
| #include <linux/compiler.h> |
| #include <linux/module.h> |
| |
| #include <linux/brcmstb/brcmstb.h> |
| |
| #if 0 |
| #define DBG printk |
| #else |
| #define DBG(...) /* */ |
| #endif |
| |
| #define LINUX_MIN_MEM 64 |
| #define MAX_BMEM_REGIONS 4 |
| |
| struct bmem_region { |
| unsigned long addr; |
| unsigned long size; |
| int valid; |
| }; |
| |
| static struct bmem_region bmem_regions[MAX_BMEM_REGIONS]; |
| static unsigned int n_bmem_regions; |
| |
| #if defined(CONFIG_BRCM_IKOS) |
| static unsigned int bmem_disabled = 1; |
| #else |
| static unsigned int bmem_disabled; |
| #endif |
| |
| /*********************************************************************** |
| * MEMC1 handling |
| ***********************************************************************/ |
| |
| static int __init brcm_memc1_bmem(void) |
| { |
| struct bmem_region *r = NULL; |
| |
| if (brcm_dram1_size_mb && |
| brcm_dram1_size_mb > brcm_dram1_linux_mb && |
| n_bmem_regions < MAX_BMEM_REGIONS) { |
| r = &bmem_regions[n_bmem_regions++]; |
| r->addr = brcm_dram1_start + (brcm_dram1_linux_mb << 20); |
| r->size = (brcm_dram1_size_mb - brcm_dram1_linux_mb) << 20; |
| r->valid = 1; |
| printk(KERN_INFO "bmem: adding extra %lu MB RESERVED region at " |
| "%lu MB (0x%08lx@0x%08lx)\n", r->size >> 20, |
| r->addr >> 20, r->size, r->addr); |
| } |
| return 0; |
| } |
| |
| core_initcall(brcm_memc1_bmem); |
| |
| /*********************************************************************** |
| * BMEM (reserved A/V buffer memory) support |
| ***********************************************************************/ |
| static __init struct bmem_region *bmem_add(char *str) |
| { |
| unsigned long addr = 0, size; |
| unsigned long lower_mem_bytes; |
| char *orig_str = str; |
| struct bmem_region *i, *j; |
| |
| lower_mem_bytes = (brcm_dram0_size_mb > BRCM_MAX_LOWER_MB) ? |
| (BRCM_MAX_LOWER_MB << 20) : (brcm_dram0_size_mb << 20); |
| |
| size = (unsigned long)memparse(str, &str); |
| if (*str == '@') |
| addr = (unsigned long)memparse(str + 1, &str); |
| |
| if ((size > 0x40000000) || (addr >= 0x80000000) || |
| (size+addr >= 0x80000000)) { |
| printk(KERN_WARNING "bmem: argument '%s' " |
| "is out of range, ignoring\n", orig_str); |
| return NULL; |
| } |
| |
| if (size == 0) { |
| printk(KERN_INFO "bmem: disabling reserved memory\n"); |
| bmem_disabled = 1; |
| return NULL; |
| } |
| |
| if ((addr & ~PAGE_MASK) || (size & ~PAGE_MASK)) { |
| printk(KERN_WARNING "bmem: ignoring invalid range '%s' " |
| "(is it missing an 'M' suffix?)\n", orig_str); |
| return NULL; |
| } |
| |
| if (addr == 0) { |
| /* |
| * default: bmem=xxM allocates xx megabytes at the end of |
| * lower memory |
| */ |
| if (size >= lower_mem_bytes) { |
| printk(KERN_WARNING "bmem: '%s' is larger than " |
| "lower memory (%lu MB), ignoring\n", |
| orig_str, brcm_dram0_size_mb); |
| return NULL; |
| } |
| addr = lower_mem_bytes - size; |
| } |
| |
| if (n_bmem_regions == MAX_BMEM_REGIONS) { |
| printk(KERN_WARNING "bmem: too many regions, " |
| "ignoring extras\n"); |
| return NULL; |
| } |
| |
| for (i = bmem_regions; i < (bmem_regions + n_bmem_regions); ++i) { |
| if (addr < (i->addr + i->size) && (addr + size) > i->addr) { |
| printk(KERN_WARNING "bmem: %ld MB @ %ld MB " |
| "overlaps with existing region, " |
| "ignoring\n", size/1024/1024, addr/1024/1024); |
| return NULL; |
| } |
| /* The current brcm_free_bootmem assumes the input in ascending |
| * order if two mem regions are in the same range. Otherwise, it |
| * may miss. Adding the following code for sorting though |
| * complexity here is O(N^2). Since we usually only have 3 |
| * memory regions, I guess this can be forgiven. |
| */ |
| if (addr < i->addr) { |
| for (j = bmem_regions + n_bmem_regions; j > i; --j) { |
| *j = *(j-1); |
| } |
| break; |
| } |
| } |
| |
| i->addr = addr; |
| i->size = size; |
| ++n_bmem_regions; |
| |
| return i; |
| } |
| |
| /* |
| * Parses command line for bmem= options |
| */ |
| static int __init bmem_setup(char *str) |
| { |
| bmem_add(str); |
| return 0; |
| } |
| |
| early_param("bmem", bmem_setup); |
| |
| static struct bmem_region *bootlog_region = NULL; |
| |
| #ifdef CONFIG_BOOTLOG_COPY |
| |
| unsigned long bootlog_get_addr(void) |
| { |
| if (bootlog_region) { |
| return bootlog_region->addr; |
| } |
| return 0; |
| } |
| |
| unsigned long bootlog_get_size(void) |
| { |
| if (bootlog_region) { |
| return bootlog_region->size; |
| } |
| return 0; |
| } |
| |
| static int __init bootlog_setup(char *str) |
| { |
| if (bootlog_region != NULL) { |
| printk(KERN_WARNING "bootlog: bootlog already set," |
| " ignoring additional range '%s'\n", str); |
| return 0; |
| } |
| |
| bootlog_region = bmem_add(str); |
| return 0; |
| } |
| |
| early_param("bootlog", bootlog_setup); |
| |
| #endif /* CONFIG_BOOTLOG_COPY */ |
| |
| |
| /* |
| * Returns index if the supplied range falls entirely within a bmem region |
| */ |
| int bmem_find_region(unsigned long addr, unsigned long size) |
| { |
| int i, idx = 0; |
| |
| for (i = 0; i < n_bmem_regions; i++) { |
| if (!bmem_regions[i].valid) |
| continue; |
| if ((addr >= bmem_regions[i].addr) && |
| ((addr + size) <= |
| (bmem_regions[i].addr + bmem_regions[i].size))) { |
| return idx; |
| } |
| idx++; |
| } |
| return -ENOENT; |
| } |
| EXPORT_SYMBOL(bmem_find_region); |
| |
| /* |
| * Finds the IDX'th valid bmem region, and fills in addr/size if it exists. |
| * Returns 0 on success, <0 on failure. |
| */ |
| int bmem_region_info(int idx, unsigned long *addr, unsigned long *size) |
| { |
| int i; |
| |
| for (i = 0; i < n_bmem_regions; i++) { |
| if (!bmem_regions[i].valid) |
| continue; |
| if (!idx) { |
| *addr = bmem_regions[i].addr; |
| *size = bmem_regions[i].size; |
| return 0; |
| } |
| idx--; |
| } |
| return -ENOENT; |
| } |
| EXPORT_SYMBOL(bmem_region_info); |
| |
| /* |
| * Special handling for __get_user_pages() on BMEM reserved memory: |
| * |
| * 1) Override the VM_IO | VM_PFNMAP sanity checks |
| * 2) No cache flushes (this is explicitly under application control) |
| * 3) vm_normal_page() does not work on these regions |
| * 4) Don't need to worry about any kinds of faults; pages are always present |
| * |
| * The vanilla kernel behavior was to prohibit O_DIRECT operations on our |
| * BMEM regions, but direct I/O is absolutely required for PVR and video |
| * playback from SATA/USB. |
| */ |
| int bmem_get_page(struct mm_struct *mm, struct vm_area_struct *vma, |
| unsigned long start, struct page **page) |
| { |
| unsigned long pg = start & PAGE_MASK, pfn; |
| int ret = -EFAULT; |
| pgd_t *pgd; |
| pud_t *pud; |
| pmd_t *pmd; |
| pte_t *pte; |
| |
| pgd = pgd_offset(mm, pg); |
| BUG_ON(pgd_none(*pgd)); |
| pud = pud_offset(pgd, pg); |
| BUG_ON(pud_none(*pud)); |
| pmd = pmd_offset(pud, pg); |
| if (pmd_none(*pmd)) |
| return ret; |
| |
| pte = pte_offset_map(pmd, pg); |
| if (pte_none(*pte)) |
| goto out; |
| |
| pfn = pte_pfn(*pte); |
| if (likely(bmem_find_region(pfn << PAGE_SHIFT, PAGE_SIZE) < 0)) |
| goto out; |
| |
| if (page) { |
| *page = pfn_to_page(pfn); |
| get_page(*page); |
| } |
| ret = 0; |
| |
| out: |
| pte_unmap(pte); |
| return ret; |
| } |
| |
| static int __initdata bmem_defaults_set; |
| |
| static void __init brcm_set_default_bmem(void) |
| { |
| /* |
| * Default: (no valid bmem= options specified) |
| * |
| * Lower memory is the first 256MB of system RAM. |
| * bmem gets all but (LINUX_MIN_MEM) megabytes of lower memory. |
| * Linux gets all upper and high memory (if present). |
| * |
| * Options: |
| * |
| * Define one or more custom bmem regions, e.g.: |
| * bmem=128M (128MB region at the end of lower memory) |
| * bmem=128M@64M (128MB hole at 64MB mark) |
| * bmem=16M@64M bmem=4M@128M bmem=16M@200M (multiple holes) |
| * |
| * Disable bmem; give all system RAM to the Linux VM: |
| * bmem=0 |
| * |
| * Overlapping or invalid regions will usually be silently dropped. |
| * If you are doing something tricky, watch the boot messages to |
| * make sure it turns out the way you intended. |
| */ |
| if (bmem_disabled) { |
| n_bmem_regions = 0; |
| } else { |
| if (n_bmem_regions == 0 && |
| (brcm_dram0_size_mb > LINUX_MIN_MEM)) { |
| n_bmem_regions = 1; |
| bmem_regions[0].addr = LINUX_MIN_MEM << 20; |
| bmem_regions[0].size = |
| (((brcm_dram0_size_mb <= BRCM_MAX_LOWER_MB) ? |
| brcm_dram0_size_mb : BRCM_MAX_LOWER_MB) - |
| LINUX_MIN_MEM) << 20; |
| } |
| } |
| } |
| |
| /* |
| * Invokes free_bootmem(), but truncates ranges where necessary to |
| * avoid releasing the bmem region(s) back to the VM |
| */ |
| void __init brcm_free_bootmem(unsigned long addr, unsigned long size) |
| { |
| if (!bmem_defaults_set) { |
| brcm_set_default_bmem(); |
| bmem_defaults_set = 1; |
| } |
| |
| while (size) { |
| unsigned long chunksize = size; |
| int i; |
| struct bmem_region *r = NULL; |
| |
| /* |
| * Find the first bmem region (if any) that fits entirely |
| * within the current bootmem address range. |
| */ |
| for (i = 0; i < n_bmem_regions; i++) { |
| if ((bmem_regions[i].addr >= addr) && |
| ((bmem_regions[i].addr + bmem_regions[i].size) <= |
| (addr + size))) { |
| if (!r || (r->addr > bmem_regions[i].addr)) |
| r = &bmem_regions[i]; |
| } |
| } |
| |
| /* |
| * Skip over every bmem region; call free_bootmem() for |
| * every Linux region. A Linux region is created for |
| * each chunk of the memory map that is not reserved |
| * for bmem. |
| */ |
| if (r) { |
| if (addr == r->addr) { |
| printk(KERN_INFO "%s: adding %lu MB " |
| "RESERVED region at %lu MB " |
| "(0x%08lx@0x%08lx)\n", |
| (r==bootlog_region) ? "bootlog" : "bmem", |
| r->size >> 20, r->addr >> 20, |
| r->size, r->addr); |
| chunksize = r->size; |
| if (r != bootlog_region) |
| r->valid = 1; |
| goto skip; |
| } else { |
| BUG_ON(addr > r->addr); |
| chunksize = r->addr - addr; |
| } |
| } |
| BUG_ON(chunksize > size); |
| |
| printk(KERN_DEBUG "bmem: adding %lu MB LINUX region at %lu MB " |
| "(0x%08lx@0x%08lx)\n", chunksize >> 20, addr >> 20, |
| chunksize, addr); |
| free_bootmem(addr, chunksize); |
| |
| skip: |
| addr += chunksize; |
| size -= chunksize; |
| } |
| } |
| |
| /* |
| * Create /proc/iomem entries for bmem |
| */ |
| static int __init bmem_region_setup(void) |
| { |
| int i, idx = 0; |
| |
| for (i = 0; i < n_bmem_regions; i++) { |
| struct resource *r; |
| char *name; |
| |
| if (!bmem_regions[i].valid) |
| continue; |
| |
| r = kzalloc(sizeof(*r), GFP_KERNEL); |
| name = kzalloc(16, GFP_KERNEL); |
| if (!r || !name) |
| break; |
| |
| sprintf(name, "bmem.%d", idx); |
| r->start = bmem_regions[i].addr; |
| r->end = bmem_regions[i].addr + bmem_regions[i].size - 1; |
| r->flags = IORESOURCE_MEM; |
| r->name = name; |
| |
| insert_resource(&iomem_resource, r); |
| idx++; |
| } |
| return 0; |
| } |
| |
| arch_initcall(bmem_region_setup); |
| |