457 lines
11 KiB
C
457 lines
11 KiB
C
/*
|
|
* rmm.c
|
|
*
|
|
* DSP-BIOS Bridge driver support functions for TI OMAP processors.
|
|
*
|
|
* Copyright (C) 2005-2006 Texas Instruments, Inc.
|
|
*
|
|
* This package 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 PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
|
*/
|
|
|
|
/*
|
|
* This memory manager provides general heap management and arbitrary
|
|
* alignment for any number of memory segments.
|
|
*
|
|
* Notes:
|
|
*
|
|
* Memory blocks are allocated from the end of the first free memory
|
|
* block large enough to satisfy the request. Alignment requirements
|
|
* are satisfied by "sliding" the block forward until its base satisfies
|
|
* the alignment specification; if this is not possible then the next
|
|
* free block large enough to hold the request is tried.
|
|
*
|
|
* Since alignment can cause the creation of a new free block - the
|
|
* unused memory formed between the start of the original free block
|
|
* and the start of the allocated block - the memory manager must free
|
|
* this memory to prevent a memory leak.
|
|
*
|
|
* Overlay memory is managed by reserving through rmm_alloc, and freeing
|
|
* it through rmm_free. The memory manager prevents DSP code/data that is
|
|
* overlayed from being overwritten as long as the memory it runs at has
|
|
* been allocated, and not yet freed.
|
|
*/
|
|
|
|
#include <linux/types.h>
|
|
#include <linux/list.h>
|
|
|
|
/* ----------------------------------- Host OS */
|
|
#include <dspbridge/host_os.h>
|
|
|
|
/* ----------------------------------- DSP/BIOS Bridge */
|
|
#include <dspbridge/dbdefs.h>
|
|
|
|
/* ----------------------------------- This */
|
|
#include <dspbridge/rmm.h>
|
|
|
|
/*
|
|
* ======== rmm_header ========
|
|
* This header is used to maintain a list of free memory blocks.
|
|
*/
|
|
struct rmm_header {
|
|
struct rmm_header *next; /* form a free memory link list */
|
|
u32 size; /* size of the free memory */
|
|
u32 addr; /* DSP address of memory block */
|
|
};
|
|
|
|
/*
|
|
* ======== rmm_ovly_sect ========
|
|
* Keeps track of memory occupied by overlay section.
|
|
*/
|
|
struct rmm_ovly_sect {
|
|
struct list_head list_elem;
|
|
u32 addr; /* Start of memory section */
|
|
u32 size; /* Length (target MAUs) of section */
|
|
s32 page; /* Memory page */
|
|
};
|
|
|
|
/*
|
|
* ======== rmm_target_obj ========
|
|
*/
|
|
struct rmm_target_obj {
|
|
struct rmm_segment *seg_tab;
|
|
struct rmm_header **free_list;
|
|
u32 num_segs;
|
|
struct list_head ovly_list; /* List of overlay memory in use */
|
|
};
|
|
|
|
static bool alloc_block(struct rmm_target_obj *target, u32 segid, u32 size,
|
|
u32 align, u32 *dsp_address);
|
|
static bool free_block(struct rmm_target_obj *target, u32 segid, u32 addr,
|
|
u32 size);
|
|
|
|
/*
|
|
* ======== rmm_alloc ========
|
|
*/
|
|
int rmm_alloc(struct rmm_target_obj *target, u32 segid, u32 size,
|
|
u32 align, u32 *dsp_address, bool reserve)
|
|
{
|
|
struct rmm_ovly_sect *sect, *prev_sect = NULL;
|
|
struct rmm_ovly_sect *new_sect;
|
|
u32 addr;
|
|
int status = 0;
|
|
|
|
if (!reserve) {
|
|
if (!alloc_block(target, segid, size, align, dsp_address)) {
|
|
status = -ENOMEM;
|
|
} else {
|
|
/* Increment the number of allocated blocks in this
|
|
* segment */
|
|
target->seg_tab[segid].number++;
|
|
}
|
|
goto func_end;
|
|
}
|
|
/* An overlay section - See if block is already in use. If not,
|
|
* insert into the list in ascending address size. */
|
|
addr = *dsp_address;
|
|
/* Find place to insert new list element. List is sorted from
|
|
* smallest to largest address. */
|
|
list_for_each_entry(sect, &target->ovly_list, list_elem) {
|
|
if (addr <= sect->addr) {
|
|
/* Check for overlap with sect */
|
|
if ((addr + size > sect->addr) || (prev_sect &&
|
|
(prev_sect->addr +
|
|
prev_sect->size >
|
|
addr))) {
|
|
status = -ENXIO;
|
|
}
|
|
break;
|
|
}
|
|
prev_sect = sect;
|
|
}
|
|
if (!status) {
|
|
/* No overlap - allocate list element for new section. */
|
|
new_sect = kzalloc(sizeof(struct rmm_ovly_sect), GFP_KERNEL);
|
|
if (new_sect == NULL) {
|
|
status = -ENOMEM;
|
|
} else {
|
|
new_sect->addr = addr;
|
|
new_sect->size = size;
|
|
new_sect->page = segid;
|
|
if (list_is_last(§->list_elem, &target->ovly_list))
|
|
/* Put new section at the end of the list */
|
|
list_add_tail(&new_sect->list_elem,
|
|
&target->ovly_list);
|
|
else
|
|
/* Put new section just before sect */
|
|
list_add_tail(&new_sect->list_elem,
|
|
§->list_elem);
|
|
}
|
|
}
|
|
func_end:
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* ======== rmm_create ========
|
|
*/
|
|
int rmm_create(struct rmm_target_obj **target_obj,
|
|
struct rmm_segment seg_tab[], u32 num_segs)
|
|
{
|
|
struct rmm_header *hptr;
|
|
struct rmm_segment *sptr, *tmp;
|
|
struct rmm_target_obj *target;
|
|
s32 i;
|
|
int status = 0;
|
|
|
|
/* Allocate DBL target object */
|
|
target = kzalloc(sizeof(struct rmm_target_obj), GFP_KERNEL);
|
|
|
|
if (target == NULL)
|
|
status = -ENOMEM;
|
|
|
|
if (status)
|
|
goto func_cont;
|
|
|
|
target->num_segs = num_segs;
|
|
if (!(num_segs > 0))
|
|
goto func_cont;
|
|
|
|
/* Allocate the memory for freelist from host's memory */
|
|
target->free_list = kzalloc(num_segs * sizeof(struct rmm_header *),
|
|
GFP_KERNEL);
|
|
if (target->free_list == NULL) {
|
|
status = -ENOMEM;
|
|
} else {
|
|
/* Allocate headers for each element on the free list */
|
|
for (i = 0; i < (s32) num_segs; i++) {
|
|
target->free_list[i] =
|
|
kzalloc(sizeof(struct rmm_header), GFP_KERNEL);
|
|
if (target->free_list[i] == NULL) {
|
|
status = -ENOMEM;
|
|
break;
|
|
}
|
|
}
|
|
/* Allocate memory for initial segment table */
|
|
target->seg_tab = kzalloc(num_segs * sizeof(struct rmm_segment),
|
|
GFP_KERNEL);
|
|
if (target->seg_tab == NULL) {
|
|
status = -ENOMEM;
|
|
} else {
|
|
/* Initialize segment table and free list */
|
|
sptr = target->seg_tab;
|
|
for (i = 0, tmp = seg_tab; num_segs > 0;
|
|
num_segs--, i++) {
|
|
*sptr = *tmp;
|
|
hptr = target->free_list[i];
|
|
hptr->addr = tmp->base;
|
|
hptr->size = tmp->length;
|
|
hptr->next = NULL;
|
|
tmp++;
|
|
sptr++;
|
|
}
|
|
}
|
|
}
|
|
func_cont:
|
|
/* Initialize overlay memory list */
|
|
if (!status)
|
|
INIT_LIST_HEAD(&target->ovly_list);
|
|
|
|
if (!status) {
|
|
*target_obj = target;
|
|
} else {
|
|
*target_obj = NULL;
|
|
if (target)
|
|
rmm_delete(target);
|
|
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* ======== rmm_delete ========
|
|
*/
|
|
void rmm_delete(struct rmm_target_obj *target)
|
|
{
|
|
struct rmm_ovly_sect *sect, *tmp;
|
|
struct rmm_header *hptr;
|
|
struct rmm_header *next;
|
|
u32 i;
|
|
|
|
kfree(target->seg_tab);
|
|
|
|
list_for_each_entry_safe(sect, tmp, &target->ovly_list, list_elem) {
|
|
list_del(§->list_elem);
|
|
kfree(sect);
|
|
}
|
|
|
|
if (target->free_list != NULL) {
|
|
/* Free elements on freelist */
|
|
for (i = 0; i < target->num_segs; i++) {
|
|
hptr = next = target->free_list[i];
|
|
while (next) {
|
|
hptr = next;
|
|
next = hptr->next;
|
|
kfree(hptr);
|
|
}
|
|
}
|
|
kfree(target->free_list);
|
|
}
|
|
|
|
kfree(target);
|
|
}
|
|
|
|
/*
|
|
* ======== rmm_free ========
|
|
*/
|
|
bool rmm_free(struct rmm_target_obj *target, u32 segid, u32 dsp_addr, u32 size,
|
|
bool reserved)
|
|
{
|
|
struct rmm_ovly_sect *sect, *tmp;
|
|
bool ret = false;
|
|
|
|
/*
|
|
* Free or unreserve memory.
|
|
*/
|
|
if (!reserved) {
|
|
ret = free_block(target, segid, dsp_addr, size);
|
|
if (ret)
|
|
target->seg_tab[segid].number--;
|
|
|
|
} else {
|
|
/* Unreserve memory */
|
|
list_for_each_entry_safe(sect, tmp, &target->ovly_list,
|
|
list_elem) {
|
|
if (dsp_addr == sect->addr) {
|
|
/* Remove from list */
|
|
list_del(§->list_elem);
|
|
kfree(sect);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* ======== rmm_stat ========
|
|
*/
|
|
bool rmm_stat(struct rmm_target_obj *target, enum dsp_memtype segid,
|
|
struct dsp_memstat *mem_stat_buf)
|
|
{
|
|
struct rmm_header *head;
|
|
bool ret = false;
|
|
u32 max_free_size = 0;
|
|
u32 total_free_size = 0;
|
|
u32 free_blocks = 0;
|
|
|
|
if ((u32) segid < target->num_segs) {
|
|
head = target->free_list[segid];
|
|
|
|
/* Collect data from free_list */
|
|
while (head != NULL) {
|
|
max_free_size = max(max_free_size, head->size);
|
|
total_free_size += head->size;
|
|
free_blocks++;
|
|
head = head->next;
|
|
}
|
|
|
|
/* ul_size */
|
|
mem_stat_buf->size = target->seg_tab[segid].length;
|
|
|
|
/* num_free_blocks */
|
|
mem_stat_buf->num_free_blocks = free_blocks;
|
|
|
|
/* total_free_size */
|
|
mem_stat_buf->total_free_size = total_free_size;
|
|
|
|
/* len_max_free_block */
|
|
mem_stat_buf->len_max_free_block = max_free_size;
|
|
|
|
/* num_alloc_blocks */
|
|
mem_stat_buf->num_alloc_blocks =
|
|
target->seg_tab[segid].number;
|
|
|
|
ret = true;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* ======== balloc ========
|
|
* This allocation function allocates memory from the lowest addresses
|
|
* first.
|
|
*/
|
|
static bool alloc_block(struct rmm_target_obj *target, u32 segid, u32 size,
|
|
u32 align, u32 *dsp_address)
|
|
{
|
|
struct rmm_header *head;
|
|
struct rmm_header *prevhead = NULL;
|
|
struct rmm_header *next;
|
|
u32 tmpalign;
|
|
u32 alignbytes;
|
|
u32 hsize;
|
|
u32 allocsize;
|
|
u32 addr;
|
|
|
|
alignbytes = (align == 0) ? 1 : align;
|
|
prevhead = NULL;
|
|
head = target->free_list[segid];
|
|
|
|
do {
|
|
hsize = head->size;
|
|
next = head->next;
|
|
|
|
addr = head->addr; /* alloc from the bottom */
|
|
|
|
/* align allocation */
|
|
(tmpalign = (u32) addr % alignbytes);
|
|
if (tmpalign != 0)
|
|
tmpalign = alignbytes - tmpalign;
|
|
|
|
allocsize = size + tmpalign;
|
|
|
|
if (hsize >= allocsize) { /* big enough */
|
|
if (hsize == allocsize && prevhead != NULL) {
|
|
prevhead->next = next;
|
|
kfree(head);
|
|
} else {
|
|
head->size = hsize - allocsize;
|
|
head->addr += allocsize;
|
|
}
|
|
|
|
/* free up any hole created by alignment */
|
|
if (tmpalign)
|
|
free_block(target, segid, addr, tmpalign);
|
|
|
|
*dsp_address = addr + tmpalign;
|
|
return true;
|
|
}
|
|
|
|
prevhead = head;
|
|
head = next;
|
|
|
|
} while (head != NULL);
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* ======== free_block ========
|
|
* TO DO: free_block() allocates memory, which could result in failure.
|
|
* Could allocate an rmm_header in rmm_alloc(), to be kept in a pool.
|
|
* free_block() could use an rmm_header from the pool, freeing as blocks
|
|
* are coalesced.
|
|
*/
|
|
static bool free_block(struct rmm_target_obj *target, u32 segid, u32 addr,
|
|
u32 size)
|
|
{
|
|
struct rmm_header *head;
|
|
struct rmm_header *thead;
|
|
struct rmm_header *rhead;
|
|
bool ret = true;
|
|
|
|
/* Create a memory header to hold the newly free'd block. */
|
|
rhead = kzalloc(sizeof(struct rmm_header), GFP_KERNEL);
|
|
if (rhead == NULL) {
|
|
ret = false;
|
|
} else {
|
|
/* search down the free list to find the right place for addr */
|
|
head = target->free_list[segid];
|
|
|
|
if (addr >= head->addr) {
|
|
while (head->next != NULL && addr > head->next->addr)
|
|
head = head->next;
|
|
|
|
thead = head->next;
|
|
|
|
head->next = rhead;
|
|
rhead->next = thead;
|
|
rhead->addr = addr;
|
|
rhead->size = size;
|
|
} else {
|
|
*rhead = *head;
|
|
head->next = rhead;
|
|
head->addr = addr;
|
|
head->size = size;
|
|
thead = rhead->next;
|
|
}
|
|
|
|
/* join with upper block, if possible */
|
|
if (thead != NULL && (rhead->addr + rhead->size) ==
|
|
thead->addr) {
|
|
head->next = rhead->next;
|
|
thead->size = size + thead->size;
|
|
thead->addr = addr;
|
|
kfree(rhead);
|
|
rhead = thead;
|
|
}
|
|
|
|
/* join with the lower block, if possible */
|
|
if ((head->addr + head->size) == rhead->addr) {
|
|
head->next = rhead->next;
|
|
head->size = head->size + rhead->size;
|
|
kfree(rhead);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|