blob: 46570d50a0f3bb7a7a5efd420f1f9b4a99733534 [file] [log] [blame]
/*
* 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);