822 lines
21 KiB
C
822 lines
21 KiB
C
|
/*
|
||
|
* Copyright (c) 2007 STMicroelectronics Limited
|
||
|
* Author: Stuart Menefy <stuart.menefy@st.com>
|
||
|
*
|
||
|
* Derived from mm/bigphysarea.c which was:
|
||
|
* Copyright (c) 1996 by Matt Welsh.
|
||
|
* Extended by Roger Butenuth (butenuth@uni-paderborn.de), October 1997
|
||
|
* Extended for linux-2.1.121 till 2.4.0 (June 2000)
|
||
|
* by Pauline Middelink <middelink@polyware.nl>
|
||
|
*
|
||
|
* 17th Jan 2007 Carl Shaw <carl.shaw@st.com>
|
||
|
* Added kernel command line "bpa2parts=" parameter support.
|
||
|
*
|
||
|
* 9th Sep 2008 Pawel Moll <pawel.moll@st.com>
|
||
|
* Added name aliases to "bpa2parts=" syntax.
|
||
|
* Added allocation tracing features.
|
||
|
*
|
||
|
* This is a set of routines which allow you to reserve a large (?)
|
||
|
* amount of physical memory at boot-time, which can be allocated/deallocated
|
||
|
* by drivers. This memory is intended to be used for devices such as
|
||
|
* video framegrabbers which need a lot of physical RAM (above the amount
|
||
|
* allocated by kmalloc). This is by no means efficient or recommended;
|
||
|
* to be used only in extreme circumstances.
|
||
|
*
|
||
|
* Partitions can be defined by a BSP (bpa2_init() shall be called somewhere
|
||
|
* on setup_arch() level) or by a "bpa2parts=" kernel command line parameter:
|
||
|
*
|
||
|
* bpa2parts=<partdef>[,<partdef>]
|
||
|
*
|
||
|
* <partdef> := <names>:<size>[:<base physical address>][:<flags>]
|
||
|
* <names> := <name>[|<names>] (name and aliases separated by '|')
|
||
|
* <name> := partition name string (20 characters max.)
|
||
|
* <size> := standard linux memory size (e.g. 4M or 0x400000)
|
||
|
* <base physical address> := physical address the partition should
|
||
|
* start from (e.g. 32M or 0x02000000)
|
||
|
* <flags> := currently unused
|
||
|
*
|
||
|
* Examples:
|
||
|
*
|
||
|
* bpa2parts=audio:1M,video:10M
|
||
|
*
|
||
|
* bpa2parts=LMI_VID|video|gfx:0x03000000:0x19000000,\
|
||
|
* LMI_SYS|audio:0x05000000:\
|
||
|
* bigphyarea:5M
|
||
|
*
|
||
|
* 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, write to the Free Software
|
||
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#include <linux/ptrace.h>
|
||
|
#include <linux/types.h>
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/init.h>
|
||
|
#include <linux/pci.h>
|
||
|
#include <linux/debugfs.h>
|
||
|
#include <linux/seq_file.h>
|
||
|
#include <linux/string.h>
|
||
|
#include <linux/mm.h>
|
||
|
#include <linux/bootmem.h>
|
||
|
#include <linux/errno.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/pfn.h>
|
||
|
#include <linux/bpa2.h>
|
||
|
|
||
|
|
||
|
|
||
|
#define BPA2_MAX_NAME_LEN 20
|
||
|
#define BPA2_RES_PREFIX "bpa2:"
|
||
|
#define BPA2_RES_PREFIX_LEN 5
|
||
|
|
||
|
|
||
|
|
||
|
struct bpa2_range {
|
||
|
struct bpa2_range *next;
|
||
|
unsigned long base; /* base of allocated block */
|
||
|
unsigned long size; /* size in bytes */
|
||
|
#if defined(CONFIG_BPA2_ALLOC_TRACE)
|
||
|
const char *trace_file;
|
||
|
int trace_line;
|
||
|
#endif
|
||
|
};
|
||
|
|
||
|
struct bpa2_part {
|
||
|
struct resource res;
|
||
|
struct bpa2_range initial_free_list;
|
||
|
struct bpa2_range *free_list;
|
||
|
struct bpa2_range *used_list;
|
||
|
int flags;
|
||
|
int low_mem;
|
||
|
struct list_head list;
|
||
|
int names_cnt;
|
||
|
/* Do not separate two following fields! */
|
||
|
char res_name_prefix[BPA2_RES_PREFIX_LEN]; /* resource name prefix */
|
||
|
char names[1]; /* will be expanded during allocation */
|
||
|
};
|
||
|
|
||
|
|
||
|
|
||
|
static LIST_HEAD(bpa2_parts);
|
||
|
static struct bpa2_part *bpa2_bigphysarea_part;
|
||
|
static DEFINE_SPINLOCK(bpa2_lock);
|
||
|
|
||
|
|
||
|
|
||
|
/* Names form one looong string of fixed-size slots */
|
||
|
static int bpa2_names_size(int names_cnt)
|
||
|
{
|
||
|
return names_cnt * (BPA2_MAX_NAME_LEN + 1);
|
||
|
}
|
||
|
|
||
|
static const char *bpa2_get_name(struct bpa2_part *part, int n)
|
||
|
{
|
||
|
BUG_ON(n >= part->names_cnt);
|
||
|
|
||
|
return part->names + n * (BPA2_MAX_NAME_LEN + 1);
|
||
|
}
|
||
|
|
||
|
static void bpa2_set_name(struct bpa2_part *part, int n, const char *name)
|
||
|
{
|
||
|
BUG_ON(n >= part->names_cnt);
|
||
|
|
||
|
strlcpy(part->names + n * (BPA2_MAX_NAME_LEN + 1), name,
|
||
|
BPA2_MAX_NAME_LEN + 1);
|
||
|
}
|
||
|
|
||
|
static int bpa2_check_name(struct bpa2_part *part, const char *name)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
for (i = 0; i < part->names_cnt; i++)
|
||
|
if (strcmp(name, bpa2_get_name(part, i)) == 0)
|
||
|
return 0;
|
||
|
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
static int __init bpa2_alloc_low(struct bpa2_part *part, unsigned long size,
|
||
|
unsigned long *start)
|
||
|
{
|
||
|
void *addr = alloc_bootmem_low_pages(size);
|
||
|
|
||
|
if (addr == NULL) {
|
||
|
printk(KERN_ERR "bpa2: could not allocate low memory\n");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
part->res.start = virt_to_phys(addr);
|
||
|
part->res.end = virt_to_phys(addr) + size - 1;
|
||
|
part->low_mem = 1;
|
||
|
|
||
|
if (start)
|
||
|
*start = part->res.start;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int __init bpa2_reserve_low(struct bpa2_part *part, unsigned long start,
|
||
|
unsigned long size)
|
||
|
{
|
||
|
if (reserve_bootmem(start, size, BOOTMEM_EXCLUSIVE)) {
|
||
|
printk(KERN_ERR "bpa2: could not allocate boot memory\n");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
part->res.start = start;
|
||
|
part->res.end = start + size - 1;
|
||
|
part->low_mem = 1;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int __init bpa2_init_high(struct bpa2_part *part, unsigned long start,
|
||
|
unsigned long size)
|
||
|
{
|
||
|
part->res.start = start;
|
||
|
part->res.end = start + size - 1;
|
||
|
part->low_mem = 0;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int __init bpa2_add_part(const char **names, int names_cnt,
|
||
|
unsigned long start, unsigned long size, unsigned long flags)
|
||
|
{
|
||
|
int result;
|
||
|
struct bpa2_part *part;
|
||
|
unsigned long start_pfn, end_pfn;
|
||
|
int i;
|
||
|
|
||
|
part = alloc_bootmem(sizeof(*part) + bpa2_names_size(names_cnt) - 1);
|
||
|
if (!part) {
|
||
|
printk(KERN_ERR "bpa2: can't allocate '%s' partition "
|
||
|
"structure\n", *names);
|
||
|
result = -ENOMEM;
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
if (start != PAGE_ALIGN(start)) {
|
||
|
printk(KERN_WARNING "bpa2: '%s' partition start address not "
|
||
|
"page aligned - fixed\n", *names);
|
||
|
start = PAGE_ALIGN(start);
|
||
|
}
|
||
|
|
||
|
if (size != PAGE_ALIGN(size)) {
|
||
|
printk(KERN_WARNING "bpa2: '%s' partition size not page "
|
||
|
"aligned - fixed\n", *names);
|
||
|
size = PAGE_ALIGN(size);
|
||
|
}
|
||
|
|
||
|
part->flags = flags;
|
||
|
part->names_cnt = names_cnt;
|
||
|
|
||
|
for (i = 0; i < names_cnt; i++)
|
||
|
bpa2_set_name(part, i, names[i]);
|
||
|
|
||
|
memcpy(part->res_name_prefix, BPA2_RES_PREFIX, BPA2_RES_PREFIX_LEN);
|
||
|
part->res.name = part->res_name_prefix; /* merged with the first name */
|
||
|
part->res.flags = IORESOURCE_BUSY | IORESOURCE_MEM;
|
||
|
|
||
|
/* Allocate/reserve/initialize requested memory area */
|
||
|
start_pfn = PFN_DOWN(start);
|
||
|
end_pfn = PFN_DOWN(start + size);
|
||
|
if (start == 0) {
|
||
|
result = bpa2_alloc_low(part, size, &start);
|
||
|
} else if ((start_pfn >= min_low_pfn) && (end_pfn <= max_low_pfn)) {
|
||
|
result = bpa2_reserve_low(part, start, size);
|
||
|
} else if ((start_pfn > max_low_pfn) || (end_pfn < min_low_pfn)) {
|
||
|
result = bpa2_init_high(part, start, size);
|
||
|
} else {
|
||
|
printk(KERN_ERR "bpa2: partition spans low memory boundary\n");
|
||
|
result = -EFAULT;
|
||
|
}
|
||
|
if (result != 0) {
|
||
|
printk(KERN_ERR "bpa2: failed to create '%s' partition\n",
|
||
|
*names);
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
/* Declare the resource */
|
||
|
result = insert_resource(&iomem_resource, &part->res);
|
||
|
if (result != 0) {
|
||
|
printk(KERN_ERR "bpa2: could not reserve '%s' partition "
|
||
|
"resource\n", *names);
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
/* Initialize ranges */
|
||
|
part->initial_free_list.next = NULL;
|
||
|
part->initial_free_list.base = start;
|
||
|
part->initial_free_list.size = size;
|
||
|
part->free_list = &part->initial_free_list;
|
||
|
part->used_list = NULL;
|
||
|
|
||
|
/* And finally... */
|
||
|
list_add_tail(&part->list, &bpa2_parts);
|
||
|
printk(KERN_INFO "bpa2: partition '%s' created at 0x%08lx, size %ld kB"
|
||
|
" (0x%08lx B)\n", *names, start, size / 1024, size);
|
||
|
|
||
|
/* Assign the legacy partition pointer, if that's the one */
|
||
|
if (bpa2_bigphysarea_part == NULL &&
|
||
|
bpa2_check_name(part, "bigphysarea") == 0) {
|
||
|
if (part->low_mem)
|
||
|
bpa2_bigphysarea_part = part;
|
||
|
else
|
||
|
printk(KERN_ERR "bpa2: bigphysarea ('%s') not in "
|
||
|
"logical memory\n", *names);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
fail:
|
||
|
if (part)
|
||
|
free_bootmem(virt_to_phys(part), sizeof(*part) +
|
||
|
bpa2_names_size(names_cnt) - 1);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* bpa2_init - initialize bpa2 partitions
|
||
|
* @partdescs: description of the partitions
|
||
|
* @nparts: number of partitions
|
||
|
*
|
||
|
* This function initialises the bpa2 internal data structures
|
||
|
* based on the partition descriptions which are passed in.
|
||
|
*
|
||
|
* This must be called from early in the platform initialisation
|
||
|
* sequence, while bootmem is still active.
|
||
|
*/
|
||
|
void __init bpa2_init(struct bpa2_partition_desc *partdescs, int nparts)
|
||
|
{
|
||
|
for (; nparts; nparts--, partdescs++) {
|
||
|
int names_cnt = 1;
|
||
|
const char **names;
|
||
|
int i;
|
||
|
|
||
|
if (!partdescs->name || !*partdescs->name) {
|
||
|
printk(KERN_ERR "bpa2: no partition name given!\n");
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/* Count aliases given in description */
|
||
|
names = partdescs->aka;
|
||
|
while (names && *names) {
|
||
|
names_cnt++;
|
||
|
names++;
|
||
|
}
|
||
|
|
||
|
/* Create names pointer array */
|
||
|
names = alloc_bootmem(sizeof(*names) * names_cnt);
|
||
|
names[0] = partdescs->name;
|
||
|
for (i = 1; i < names_cnt; i++)
|
||
|
names[i] = partdescs->aka[i - 1];
|
||
|
|
||
|
/* Finally create the partition */
|
||
|
if (bpa2_add_part(names, names_cnt, partdescs->start,
|
||
|
partdescs->size, partdescs->flags) != 0)
|
||
|
printk(KERN_ERR "bpa2: '%s' partition skipped\n",
|
||
|
*names);
|
||
|
|
||
|
free_bootmem(virt_to_phys(names), sizeof(*names) * names_cnt);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Create legacy "bigphysarea" partition (if kindly asked ;-)
|
||
|
*/
|
||
|
static int __init bpa2_bigphys_setup(char *str)
|
||
|
{
|
||
|
const char *name = "bigphysarea";
|
||
|
int pages;
|
||
|
|
||
|
if (get_option(&str, &pages) != 1) {
|
||
|
printk(KERN_ERR "bpa2: wrong 'bigphysarea' parameter\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
bpa2_add_part(&name, 1, 0, pages << PAGE_SHIFT, BPA2_NORMAL);
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
__setup("bigphysarea=", bpa2_bigphys_setup);
|
||
|
|
||
|
/*
|
||
|
* Create "bpa2parts"-defined partitions
|
||
|
*/
|
||
|
static int __init bpa2_parts_setup(char *str)
|
||
|
{
|
||
|
char *desc;
|
||
|
|
||
|
if (!str || !*str)
|
||
|
return -EINVAL;
|
||
|
|
||
|
while ((desc = strsep(&str, ",")) != NULL) {
|
||
|
unsigned long start = 0;
|
||
|
unsigned long size = 0;
|
||
|
int names_cnt = 1;
|
||
|
const char **names;
|
||
|
char *token;
|
||
|
int i;
|
||
|
|
||
|
/* Get '|'-separated partition names token */
|
||
|
token = strsep(&desc, ":");
|
||
|
if (!token || !*token) {
|
||
|
printk(KERN_ERR "bpa2: partition name(s) not given!\n");
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/* Check how many names we have... */
|
||
|
for (i = 0; token[i]; i++)
|
||
|
if (token[i] == '|')
|
||
|
names_cnt++;
|
||
|
|
||
|
/* Separate names & create pointers table */
|
||
|
names = alloc_bootmem(sizeof(*names) * names_cnt);
|
||
|
for (i = 0; i < names_cnt; i++)
|
||
|
names[i] = strsep(&token, "|");
|
||
|
|
||
|
/* Get partition size */
|
||
|
token = strsep(&desc, ":");
|
||
|
if (token) {
|
||
|
size = memparse(token, &token);
|
||
|
if (*token)
|
||
|
size = 0;
|
||
|
}
|
||
|
if (size == 0) {
|
||
|
printk(KERN_ERR "bpa2: partition size not given\n");
|
||
|
free_bootmem(virt_to_phys(names),
|
||
|
sizeof(*names) * names_cnt);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/* Get partition start address (optional) */
|
||
|
token = strsep(&desc, ":");
|
||
|
if (token && *token) {
|
||
|
start = memparse(token, &token);
|
||
|
if (*token)
|
||
|
start = 0;
|
||
|
if (start == 0) {
|
||
|
printk(KERN_ERR "bpa2: Invalid base "
|
||
|
"address!\n");
|
||
|
free_bootmem(virt_to_phys(names),
|
||
|
sizeof(*names) * names_cnt);
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Get partition flags (not implemented yet) */
|
||
|
|
||
|
/* Finally add it to the list... */
|
||
|
if (bpa2_add_part(names, names_cnt, start, size,
|
||
|
BPA2_NORMAL) != 0)
|
||
|
printk(KERN_ERR "bpa2: '%s' partition skipped\n",
|
||
|
*names);
|
||
|
|
||
|
free_bootmem(virt_to_phys(names), sizeof(*names) * names_cnt);
|
||
|
}
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
__setup("bpa2parts=", bpa2_parts_setup);
|
||
|
|
||
|
|
||
|
|
||
|
/**
|
||
|
* bpa2_find_part - find a bpa2 partition based on its name
|
||
|
* @name: name of the partition to find
|
||
|
*
|
||
|
* Return the bpa2 partition corresponding to the requested name.
|
||
|
*/
|
||
|
struct bpa2_part *bpa2_find_part(const char *name)
|
||
|
{
|
||
|
struct bpa2_part *part;
|
||
|
|
||
|
list_for_each_entry(part, &bpa2_parts, list)
|
||
|
if (bpa2_check_name(part, name) == 0)
|
||
|
return part;
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
EXPORT_SYMBOL(bpa2_find_part);
|
||
|
|
||
|
/**
|
||
|
* bpa2_low_part - return whether a partition resides in low memory
|
||
|
* @part: partition to query
|
||
|
*
|
||
|
* Return whether the specified partition resides in low (that is,
|
||
|
* kernel logical) memory. If it does, then functions such as
|
||
|
* phys_to_virt() can be used to convert the allocated memory into
|
||
|
* a virtual address which can be directly dereferenced.
|
||
|
*
|
||
|
* If this is not true, then the region will not be mapped into
|
||
|
* the kernel's address space, and so if access is required it will
|
||
|
* need to be mapped using ioremap() and accessed using readl() etc.
|
||
|
*/
|
||
|
int bpa2_low_part(struct bpa2_part *part)
|
||
|
{
|
||
|
return part->low_mem;
|
||
|
}
|
||
|
EXPORT_SYMBOL(bpa2_low_part);
|
||
|
|
||
|
|
||
|
|
||
|
/**
|
||
|
* __bpa2_alloc_pages - allocate pages from a bpa2 partition
|
||
|
* @part: partition to allocate from
|
||
|
* @count: number of pages to allocate
|
||
|
* @align: required alignment
|
||
|
* @priority: GFP_* flags to use
|
||
|
*
|
||
|
* Allocate `count' pages from the partition. Pages are aligned to
|
||
|
* a multiple of `align'. `priority' has the same meaning in kmalloc, and
|
||
|
* is used for partition management information, it does not influence the
|
||
|
* memory returned.
|
||
|
*
|
||
|
* This function may not be called from an interrupt.
|
||
|
*/
|
||
|
unsigned long __bpa2_alloc_pages(struct bpa2_part *part, int count, int align,
|
||
|
int priority, const char *trace_file, int trace_line)
|
||
|
{
|
||
|
struct bpa2_range *range, **range_ptr;
|
||
|
struct bpa2_range *new_range, *align_range, *used_range;
|
||
|
unsigned long aligned_base = 0;
|
||
|
unsigned long result = 0;
|
||
|
|
||
|
if (count == 0)
|
||
|
return 0;
|
||
|
|
||
|
/* Allocate the data structures we might need here so that we
|
||
|
* don't have problems inside the spinlock.
|
||
|
* Free at the end if not used. */
|
||
|
new_range = kmalloc(sizeof(*new_range), priority);
|
||
|
align_range = kmalloc(sizeof(*align_range), priority);
|
||
|
if ((new_range == NULL) || (align_range == NULL))
|
||
|
goto fail;
|
||
|
|
||
|
if (align == 0)
|
||
|
align = PAGE_SIZE;
|
||
|
else
|
||
|
align = align * PAGE_SIZE;
|
||
|
|
||
|
spin_lock(&bpa2_lock);
|
||
|
|
||
|
/* Search a free block which is large enough, even with alignment. */
|
||
|
range_ptr = &part->free_list;
|
||
|
while (*range_ptr != NULL) {
|
||
|
range = *range_ptr;
|
||
|
aligned_base = ((range->base + align - 1) / align) * align;
|
||
|
if (aligned_base + count * PAGE_SIZE <=
|
||
|
range->base + range->size)
|
||
|
break;
|
||
|
range_ptr = &range->next;
|
||
|
}
|
||
|
if (*range_ptr == NULL)
|
||
|
goto fail_unlock;
|
||
|
range = *range_ptr;
|
||
|
|
||
|
/* When we have to align, the pages needed for alignment can
|
||
|
* be put back to the free pool. */
|
||
|
if (aligned_base != range->base) {
|
||
|
align_range->base = range->base;
|
||
|
align_range->size = aligned_base - range->base;
|
||
|
range->base = aligned_base;
|
||
|
range->size -= align_range->size;
|
||
|
align_range->next = range;
|
||
|
*range_ptr = align_range;
|
||
|
range_ptr = &align_range->next;
|
||
|
align_range = NULL;
|
||
|
}
|
||
|
|
||
|
if (count * PAGE_SIZE < range->size) {
|
||
|
/* Range is larger than needed, create a new list element for
|
||
|
* the used list and shrink the element in the free list. */
|
||
|
new_range->base = range->base;
|
||
|
new_range->size = count * PAGE_SIZE;
|
||
|
range->base = new_range->base + new_range->size;
|
||
|
range->size = range->size - new_range->size;
|
||
|
used_range = new_range;
|
||
|
new_range = NULL;
|
||
|
} else {
|
||
|
/* Range fits perfectly, remove it from free list. */
|
||
|
*range_ptr = range->next;
|
||
|
used_range = range;
|
||
|
}
|
||
|
#if defined(CONFIG_BPA2_ALLOC_TRACE)
|
||
|
/* Save the caller data */
|
||
|
used_range->trace_file = trace_file;
|
||
|
used_range->trace_line = trace_line;
|
||
|
#endif
|
||
|
/* Insert block into used list */
|
||
|
used_range->next = part->used_list;
|
||
|
part->used_list = used_range;
|
||
|
result = used_range->base;
|
||
|
|
||
|
fail_unlock:
|
||
|
spin_unlock(&bpa2_lock);
|
||
|
fail:
|
||
|
if (new_range)
|
||
|
kfree(new_range);
|
||
|
if (align_range)
|
||
|
kfree(align_range);
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
EXPORT_SYMBOL(__bpa2_alloc_pages);
|
||
|
|
||
|
/**
|
||
|
* bpa2_free_pages - free pages allocated from a bpa2 partition
|
||
|
* @part: partition to free pages back to
|
||
|
* @base:
|
||
|
* @align: required alinment
|
||
|
* @priority: GFP_* flags to use
|
||
|
*
|
||
|
* Free pages allocated with `bigphysarea_alloc_pages'. `base' must be an
|
||
|
* address returned by `bigphysarea_alloc_pages'.
|
||
|
* This function my not be called from an interrupt!
|
||
|
*/
|
||
|
void bpa2_free_pages(struct bpa2_part *part, unsigned long base)
|
||
|
{
|
||
|
struct bpa2_range *prev, *next, *range, **range_ptr;
|
||
|
|
||
|
spin_lock(&bpa2_lock);
|
||
|
|
||
|
/* Search the block in the used list. */
|
||
|
for (range_ptr = &part->used_list;
|
||
|
*range_ptr != NULL;
|
||
|
range_ptr = &(*range_ptr)->next)
|
||
|
if ((*range_ptr)->base == base)
|
||
|
break;
|
||
|
if (*range_ptr == NULL) {
|
||
|
printk(KERN_ERR "%s: 0x%08lx not allocated!\n",
|
||
|
__func__, base);
|
||
|
spin_unlock(&bpa2_lock);
|
||
|
return;
|
||
|
}
|
||
|
range = *range_ptr;
|
||
|
|
||
|
/* Remove range from the used list: */
|
||
|
*range_ptr = (*range_ptr)->next;
|
||
|
|
||
|
/* The free-list is sorted by address, search insertion point
|
||
|
* and insert block in free list. */
|
||
|
for (range_ptr = &part->free_list, prev = NULL;
|
||
|
*range_ptr != NULL;
|
||
|
prev = *range_ptr, range_ptr = &(*range_ptr)->next)
|
||
|
if ((*range_ptr)->base >= base)
|
||
|
break;
|
||
|
range->next = *range_ptr;
|
||
|
*range_ptr = range;
|
||
|
|
||
|
/* Concatenate free range with neighbors, if possible.
|
||
|
* Try for upper neighbor (next in list) first, then
|
||
|
* for lower neighbor (predecessor in list). */
|
||
|
next = NULL;
|
||
|
if (range->next != NULL &&
|
||
|
range->base + range->size == range->next->base) {
|
||
|
next = range->next;
|
||
|
range->size += next->size;
|
||
|
range->next = next->next;
|
||
|
}
|
||
|
if (prev != NULL &&
|
||
|
prev->base + prev->size == range->base) {
|
||
|
prev->size += range->size;
|
||
|
prev->next = range->next;
|
||
|
} else {
|
||
|
range = NULL;
|
||
|
}
|
||
|
|
||
|
spin_unlock(&bpa2_lock);
|
||
|
|
||
|
if (next && (next != &part->initial_free_list))
|
||
|
kfree(next);
|
||
|
if (range && (range != &part->initial_free_list))
|
||
|
kfree(range);
|
||
|
}
|
||
|
EXPORT_SYMBOL(bpa2_free_pages);
|
||
|
|
||
|
|
||
|
|
||
|
caddr_t __bigphysarea_alloc_pages(int count, int align, int priority,
|
||
|
const char *trace_file, int trace_line)
|
||
|
{
|
||
|
unsigned long addr;
|
||
|
|
||
|
if (!bpa2_bigphysarea_part)
|
||
|
return NULL;
|
||
|
|
||
|
addr = __bpa2_alloc_pages(bpa2_bigphysarea_part, count,
|
||
|
align, priority, trace_file, trace_line);
|
||
|
|
||
|
if (addr == 0)
|
||
|
return NULL;
|
||
|
|
||
|
return phys_to_virt(addr);
|
||
|
}
|
||
|
EXPORT_SYMBOL(__bigphysarea_alloc_pages);
|
||
|
|
||
|
void bigphysarea_free_pages(caddr_t mapped_addr)
|
||
|
{
|
||
|
unsigned long addr = virt_to_phys(mapped_addr);
|
||
|
|
||
|
bpa2_free_pages(bpa2_bigphysarea_part, addr);
|
||
|
}
|
||
|
EXPORT_SYMBOL(bigphysarea_free_pages);
|
||
|
|
||
|
|
||
|
|
||
|
#ifdef CONFIG_DEBUG_FS
|
||
|
|
||
|
static void *bpa2_seq_start(struct seq_file *s, loff_t *pos)
|
||
|
{
|
||
|
spin_lock(&bpa2_lock);
|
||
|
|
||
|
return seq_list_start(&bpa2_parts, *pos);
|
||
|
}
|
||
|
|
||
|
static void *bpa2_seq_next(struct seq_file *s, void *v, loff_t *pos)
|
||
|
{
|
||
|
return seq_list_next(v, &bpa2_parts, pos);
|
||
|
}
|
||
|
|
||
|
static void bpa2_seq_stop(struct seq_file *s, void *v)
|
||
|
{
|
||
|
spin_unlock(&bpa2_lock);
|
||
|
}
|
||
|
|
||
|
static int bpa2_seq_show(struct seq_file *s, void *v)
|
||
|
{
|
||
|
struct bpa2_part *part = list_entry(v, struct bpa2_part, list);
|
||
|
struct bpa2_range *range;
|
||
|
int free_count, free_total, free_max;
|
||
|
int used_count, used_total, used_max;
|
||
|
int i;
|
||
|
|
||
|
free_count = 0;
|
||
|
free_total = 0;
|
||
|
free_max = 0;
|
||
|
for (range = part->free_list; range != NULL; range = range->next) {
|
||
|
free_count++;
|
||
|
free_total += range->size;
|
||
|
if (range->size > free_max)
|
||
|
free_max = range->size;
|
||
|
}
|
||
|
|
||
|
used_count = 0;
|
||
|
used_total = 0;
|
||
|
used_max = 0;
|
||
|
for (range = part->used_list; range != NULL; range = range->next) {
|
||
|
used_count++;
|
||
|
used_total += range->size;
|
||
|
if (range->size > used_max)
|
||
|
used_max = range->size;
|
||
|
}
|
||
|
|
||
|
seq_printf(s, "Partition: ");
|
||
|
for (i = 0; i < part->names_cnt; i++)
|
||
|
seq_printf(s, "%s'%s'", i > 0 ? " aka " : "",
|
||
|
bpa2_get_name(part, i));
|
||
|
seq_printf(s, "\n");
|
||
|
seq_printf(s, "Size: %d kB, base address: 0x%08x\n",
|
||
|
(part->res.end - part->res.start + 1) / 1024,
|
||
|
part->res.start);
|
||
|
seq_printf(s, "Statistics: free "
|
||
|
" used\n");
|
||
|
seq_printf(s, "- number of blocks: %8d %8d\n",
|
||
|
free_count, used_count);
|
||
|
seq_printf(s, "- size of largest block: %8d kB %8d kB\n",
|
||
|
free_max / 1024, used_max / 1024);
|
||
|
seq_printf(s, "- total: %8d kB %8d kB\n",
|
||
|
free_total / 1024, used_total / 1024);
|
||
|
|
||
|
if (used_count) {
|
||
|
seq_printf(s, "Allocations:\n");
|
||
|
for (range = part->used_list; range != NULL;
|
||
|
range = range->next) {
|
||
|
seq_printf(s, "- %lu B at 0x%.8lx",
|
||
|
range->size, range->base);
|
||
|
#if defined(CONFIG_BPA2_ALLOC_TRACE)
|
||
|
if (range->trace_file)
|
||
|
seq_printf(s, " (%s:%d)", range->trace_file,
|
||
|
range->trace_line);
|
||
|
#endif
|
||
|
seq_printf(s, "\n");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
seq_printf(s, "\n");
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct seq_operations bpa2_seq_ops = {
|
||
|
.start = bpa2_seq_start,
|
||
|
.next = bpa2_seq_next,
|
||
|
.stop = bpa2_seq_stop,
|
||
|
.show = bpa2_seq_show,
|
||
|
};
|
||
|
|
||
|
static int bpa2_debugfs_open(struct inode *inode, struct file *file)
|
||
|
{
|
||
|
return seq_open(file, &bpa2_seq_ops);
|
||
|
}
|
||
|
|
||
|
static const struct file_operations bpa2_debugfs_ops = {
|
||
|
.owner = THIS_MODULE,
|
||
|
.open = bpa2_debugfs_open,
|
||
|
.read = seq_read,
|
||
|
.llseek = seq_lseek,
|
||
|
.release = seq_release,
|
||
|
};
|
||
|
|
||
|
static int __init bpa2_debugfs_init(void)
|
||
|
{
|
||
|
debugfs_create_file("bpa2", S_IFREG | S_IRUGO,
|
||
|
NULL, NULL, &bpa2_debugfs_ops);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
subsys_initcall(bpa2_debugfs_init);
|
||
|
|
||
|
#endif /* CONFIG_DEBUG_FS */
|
||
|
|
||
|
|
||
|
|
||
|
void bpa2_memory(struct bpa2_part *part, unsigned long *base,
|
||
|
unsigned long *size)
|
||
|
{
|
||
|
if (base)
|
||
|
*base = part?
|
||
|
part->res.start
|
||
|
: 0;
|
||
|
if (size)
|
||
|
*size = part?
|
||
|
part->res.end - part->res.start + 1
|
||
|
: 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL(bpa2_memory);
|
||
|
|
||
|
void bigphysarea_memory(unsigned long *base, unsigned long *size)
|
||
|
{
|
||
|
unsigned long phys_base;
|
||
|
|
||
|
bpa2_memory(bpa2_bigphysarea_part, &phys_base, size);
|
||
|
|
||
|
if (base)
|
||
|
*base = bpa2_bigphysarea_part ?
|
||
|
(unsigned long)phys_to_virt(phys_base)
|
||
|
: 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL(bigphysarea_memory);
|