162306a36Sopenharmony_ci/* 262306a36Sopenharmony_ci * A Remote Heap. Remote means that we don't touch the memory that the 362306a36Sopenharmony_ci * heap points to. Normal heap implementations use the memory they manage 462306a36Sopenharmony_ci * to place their list. We cannot do that because the memory we manage may 562306a36Sopenharmony_ci * have special properties, for example it is uncachable or of different 662306a36Sopenharmony_ci * endianess. 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Author: Pantelis Antoniou <panto@intracom.gr> 962306a36Sopenharmony_ci * 1062306a36Sopenharmony_ci * 2004 (c) INTRACOM S.A. Greece. This file is licensed under 1162306a36Sopenharmony_ci * the terms of the GNU General Public License version 2. This program 1262306a36Sopenharmony_ci * is licensed "as is" without any warranty of any kind, whether express 1362306a36Sopenharmony_ci * or implied. 1462306a36Sopenharmony_ci */ 1562306a36Sopenharmony_ci#include <linux/types.h> 1662306a36Sopenharmony_ci#include <linux/errno.h> 1762306a36Sopenharmony_ci#include <linux/kernel.h> 1862306a36Sopenharmony_ci#include <linux/export.h> 1962306a36Sopenharmony_ci#include <linux/mm.h> 2062306a36Sopenharmony_ci#include <linux/err.h> 2162306a36Sopenharmony_ci#include <linux/slab.h> 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#include <asm/rheap.h> 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci/* 2662306a36Sopenharmony_ci * Fixup a list_head, needed when copying lists. If the pointers fall 2762306a36Sopenharmony_ci * between s and e, apply the delta. This assumes that 2862306a36Sopenharmony_ci * sizeof(struct list_head *) == sizeof(unsigned long *). 2962306a36Sopenharmony_ci */ 3062306a36Sopenharmony_cistatic inline void fixup(unsigned long s, unsigned long e, int d, 3162306a36Sopenharmony_ci struct list_head *l) 3262306a36Sopenharmony_ci{ 3362306a36Sopenharmony_ci unsigned long *pp; 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci pp = (unsigned long *)&l->next; 3662306a36Sopenharmony_ci if (*pp >= s && *pp < e) 3762306a36Sopenharmony_ci *pp += d; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci pp = (unsigned long *)&l->prev; 4062306a36Sopenharmony_ci if (*pp >= s && *pp < e) 4162306a36Sopenharmony_ci *pp += d; 4262306a36Sopenharmony_ci} 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci/* Grow the allocated blocks */ 4562306a36Sopenharmony_cistatic int grow(rh_info_t * info, int max_blocks) 4662306a36Sopenharmony_ci{ 4762306a36Sopenharmony_ci rh_block_t *block, *blk; 4862306a36Sopenharmony_ci int i, new_blocks; 4962306a36Sopenharmony_ci int delta; 5062306a36Sopenharmony_ci unsigned long blks, blke; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci if (max_blocks <= info->max_blocks) 5362306a36Sopenharmony_ci return -EINVAL; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci new_blocks = max_blocks - info->max_blocks; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci block = kmalloc_array(max_blocks, sizeof(rh_block_t), GFP_ATOMIC); 5862306a36Sopenharmony_ci if (block == NULL) 5962306a36Sopenharmony_ci return -ENOMEM; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci if (info->max_blocks > 0) { 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci /* copy old block area */ 6462306a36Sopenharmony_ci memcpy(block, info->block, 6562306a36Sopenharmony_ci sizeof(rh_block_t) * info->max_blocks); 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci delta = (char *)block - (char *)info->block; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci /* and fixup list pointers */ 7062306a36Sopenharmony_ci blks = (unsigned long)info->block; 7162306a36Sopenharmony_ci blke = (unsigned long)(info->block + info->max_blocks); 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci for (i = 0, blk = block; i < info->max_blocks; i++, blk++) 7462306a36Sopenharmony_ci fixup(blks, blke, delta, &blk->list); 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci fixup(blks, blke, delta, &info->empty_list); 7762306a36Sopenharmony_ci fixup(blks, blke, delta, &info->free_list); 7862306a36Sopenharmony_ci fixup(blks, blke, delta, &info->taken_list); 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci /* free the old allocated memory */ 8162306a36Sopenharmony_ci if ((info->flags & RHIF_STATIC_BLOCK) == 0) 8262306a36Sopenharmony_ci kfree(info->block); 8362306a36Sopenharmony_ci } 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci info->block = block; 8662306a36Sopenharmony_ci info->empty_slots += new_blocks; 8762306a36Sopenharmony_ci info->max_blocks = max_blocks; 8862306a36Sopenharmony_ci info->flags &= ~RHIF_STATIC_BLOCK; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci /* add all new blocks to the free list */ 9162306a36Sopenharmony_ci blk = block + info->max_blocks - new_blocks; 9262306a36Sopenharmony_ci for (i = 0; i < new_blocks; i++, blk++) 9362306a36Sopenharmony_ci list_add(&blk->list, &info->empty_list); 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci return 0; 9662306a36Sopenharmony_ci} 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci/* 9962306a36Sopenharmony_ci * Assure at least the required amount of empty slots. If this function 10062306a36Sopenharmony_ci * causes a grow in the block area then all pointers kept to the block 10162306a36Sopenharmony_ci * area are invalid! 10262306a36Sopenharmony_ci */ 10362306a36Sopenharmony_cistatic int assure_empty(rh_info_t * info, int slots) 10462306a36Sopenharmony_ci{ 10562306a36Sopenharmony_ci int max_blocks; 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci /* This function is not meant to be used to grow uncontrollably */ 10862306a36Sopenharmony_ci if (slots >= 4) 10962306a36Sopenharmony_ci return -EINVAL; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci /* Enough space */ 11262306a36Sopenharmony_ci if (info->empty_slots >= slots) 11362306a36Sopenharmony_ci return 0; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci /* Next 16 sized block */ 11662306a36Sopenharmony_ci max_blocks = ((info->max_blocks + slots) + 15) & ~15; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci return grow(info, max_blocks); 11962306a36Sopenharmony_ci} 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_cistatic rh_block_t *get_slot(rh_info_t * info) 12262306a36Sopenharmony_ci{ 12362306a36Sopenharmony_ci rh_block_t *blk; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci /* If no more free slots, and failure to extend. */ 12662306a36Sopenharmony_ci /* XXX: You should have called assure_empty before */ 12762306a36Sopenharmony_ci if (info->empty_slots == 0) { 12862306a36Sopenharmony_ci printk(KERN_ERR "rh: out of slots; crash is imminent.\n"); 12962306a36Sopenharmony_ci return NULL; 13062306a36Sopenharmony_ci } 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci /* Get empty slot to use */ 13362306a36Sopenharmony_ci blk = list_entry(info->empty_list.next, rh_block_t, list); 13462306a36Sopenharmony_ci list_del_init(&blk->list); 13562306a36Sopenharmony_ci info->empty_slots--; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci /* Initialize */ 13862306a36Sopenharmony_ci blk->start = 0; 13962306a36Sopenharmony_ci blk->size = 0; 14062306a36Sopenharmony_ci blk->owner = NULL; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci return blk; 14362306a36Sopenharmony_ci} 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_cistatic inline void release_slot(rh_info_t * info, rh_block_t * blk) 14662306a36Sopenharmony_ci{ 14762306a36Sopenharmony_ci list_add(&blk->list, &info->empty_list); 14862306a36Sopenharmony_ci info->empty_slots++; 14962306a36Sopenharmony_ci} 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_cistatic void attach_free_block(rh_info_t * info, rh_block_t * blkn) 15262306a36Sopenharmony_ci{ 15362306a36Sopenharmony_ci rh_block_t *blk; 15462306a36Sopenharmony_ci rh_block_t *before; 15562306a36Sopenharmony_ci rh_block_t *after; 15662306a36Sopenharmony_ci rh_block_t *next; 15762306a36Sopenharmony_ci int size; 15862306a36Sopenharmony_ci unsigned long s, e, bs, be; 15962306a36Sopenharmony_ci struct list_head *l; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci /* We assume that they are aligned properly */ 16262306a36Sopenharmony_ci size = blkn->size; 16362306a36Sopenharmony_ci s = blkn->start; 16462306a36Sopenharmony_ci e = s + size; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci /* Find the blocks immediately before and after the given one 16762306a36Sopenharmony_ci * (if any) */ 16862306a36Sopenharmony_ci before = NULL; 16962306a36Sopenharmony_ci after = NULL; 17062306a36Sopenharmony_ci next = NULL; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci list_for_each(l, &info->free_list) { 17362306a36Sopenharmony_ci blk = list_entry(l, rh_block_t, list); 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci bs = blk->start; 17662306a36Sopenharmony_ci be = bs + blk->size; 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci if (next == NULL && s >= bs) 17962306a36Sopenharmony_ci next = blk; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci if (be == s) 18262306a36Sopenharmony_ci before = blk; 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci if (e == bs) 18562306a36Sopenharmony_ci after = blk; 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci /* If both are not null, break now */ 18862306a36Sopenharmony_ci if (before != NULL && after != NULL) 18962306a36Sopenharmony_ci break; 19062306a36Sopenharmony_ci } 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci /* Now check if they are really adjacent */ 19362306a36Sopenharmony_ci if (before && s != (before->start + before->size)) 19462306a36Sopenharmony_ci before = NULL; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci if (after && e != after->start) 19762306a36Sopenharmony_ci after = NULL; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci /* No coalescing; list insert and return */ 20062306a36Sopenharmony_ci if (before == NULL && after == NULL) { 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci if (next != NULL) 20362306a36Sopenharmony_ci list_add(&blkn->list, &next->list); 20462306a36Sopenharmony_ci else 20562306a36Sopenharmony_ci list_add(&blkn->list, &info->free_list); 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci return; 20862306a36Sopenharmony_ci } 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci /* We don't need it anymore */ 21162306a36Sopenharmony_ci release_slot(info, blkn); 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci /* Grow the before block */ 21462306a36Sopenharmony_ci if (before != NULL && after == NULL) { 21562306a36Sopenharmony_ci before->size += size; 21662306a36Sopenharmony_ci return; 21762306a36Sopenharmony_ci } 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci /* Grow the after block backwards */ 22062306a36Sopenharmony_ci if (before == NULL && after != NULL) { 22162306a36Sopenharmony_ci after->start -= size; 22262306a36Sopenharmony_ci after->size += size; 22362306a36Sopenharmony_ci return; 22462306a36Sopenharmony_ci } 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci /* Grow the before block, and release the after block */ 22762306a36Sopenharmony_ci before->size += size + after->size; 22862306a36Sopenharmony_ci list_del(&after->list); 22962306a36Sopenharmony_ci release_slot(info, after); 23062306a36Sopenharmony_ci} 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_cistatic void attach_taken_block(rh_info_t * info, rh_block_t * blkn) 23362306a36Sopenharmony_ci{ 23462306a36Sopenharmony_ci rh_block_t *blk; 23562306a36Sopenharmony_ci struct list_head *l; 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci /* Find the block immediately before the given one (if any) */ 23862306a36Sopenharmony_ci list_for_each(l, &info->taken_list) { 23962306a36Sopenharmony_ci blk = list_entry(l, rh_block_t, list); 24062306a36Sopenharmony_ci if (blk->start > blkn->start) { 24162306a36Sopenharmony_ci list_add_tail(&blkn->list, &blk->list); 24262306a36Sopenharmony_ci return; 24362306a36Sopenharmony_ci } 24462306a36Sopenharmony_ci } 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci list_add_tail(&blkn->list, &info->taken_list); 24762306a36Sopenharmony_ci} 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci/* 25062306a36Sopenharmony_ci * Create a remote heap dynamically. Note that no memory for the blocks 25162306a36Sopenharmony_ci * are allocated. It will upon the first allocation 25262306a36Sopenharmony_ci */ 25362306a36Sopenharmony_cirh_info_t *rh_create(unsigned int alignment) 25462306a36Sopenharmony_ci{ 25562306a36Sopenharmony_ci rh_info_t *info; 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci /* Alignment must be a power of two */ 25862306a36Sopenharmony_ci if ((alignment & (alignment - 1)) != 0) 25962306a36Sopenharmony_ci return ERR_PTR(-EINVAL); 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci info = kmalloc(sizeof(*info), GFP_ATOMIC); 26262306a36Sopenharmony_ci if (info == NULL) 26362306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci info->alignment = alignment; 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci /* Initially everything as empty */ 26862306a36Sopenharmony_ci info->block = NULL; 26962306a36Sopenharmony_ci info->max_blocks = 0; 27062306a36Sopenharmony_ci info->empty_slots = 0; 27162306a36Sopenharmony_ci info->flags = 0; 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci INIT_LIST_HEAD(&info->empty_list); 27462306a36Sopenharmony_ci INIT_LIST_HEAD(&info->free_list); 27562306a36Sopenharmony_ci INIT_LIST_HEAD(&info->taken_list); 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci return info; 27862306a36Sopenharmony_ci} 27962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(rh_create); 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci/* 28262306a36Sopenharmony_ci * Destroy a dynamically created remote heap. Deallocate only if the areas 28362306a36Sopenharmony_ci * are not static 28462306a36Sopenharmony_ci */ 28562306a36Sopenharmony_civoid rh_destroy(rh_info_t * info) 28662306a36Sopenharmony_ci{ 28762306a36Sopenharmony_ci if ((info->flags & RHIF_STATIC_BLOCK) == 0) 28862306a36Sopenharmony_ci kfree(info->block); 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci if ((info->flags & RHIF_STATIC_INFO) == 0) 29162306a36Sopenharmony_ci kfree(info); 29262306a36Sopenharmony_ci} 29362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(rh_destroy); 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ci/* 29662306a36Sopenharmony_ci * Initialize in place a remote heap info block. This is needed to support 29762306a36Sopenharmony_ci * operation very early in the startup of the kernel, when it is not yet safe 29862306a36Sopenharmony_ci * to call kmalloc. 29962306a36Sopenharmony_ci */ 30062306a36Sopenharmony_civoid rh_init(rh_info_t * info, unsigned int alignment, int max_blocks, 30162306a36Sopenharmony_ci rh_block_t * block) 30262306a36Sopenharmony_ci{ 30362306a36Sopenharmony_ci int i; 30462306a36Sopenharmony_ci rh_block_t *blk; 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci /* Alignment must be a power of two */ 30762306a36Sopenharmony_ci if ((alignment & (alignment - 1)) != 0) 30862306a36Sopenharmony_ci return; 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci info->alignment = alignment; 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci /* Initially everything as empty */ 31362306a36Sopenharmony_ci info->block = block; 31462306a36Sopenharmony_ci info->max_blocks = max_blocks; 31562306a36Sopenharmony_ci info->empty_slots = max_blocks; 31662306a36Sopenharmony_ci info->flags = RHIF_STATIC_INFO | RHIF_STATIC_BLOCK; 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci INIT_LIST_HEAD(&info->empty_list); 31962306a36Sopenharmony_ci INIT_LIST_HEAD(&info->free_list); 32062306a36Sopenharmony_ci INIT_LIST_HEAD(&info->taken_list); 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci /* Add all new blocks to the free list */ 32362306a36Sopenharmony_ci for (i = 0, blk = block; i < max_blocks; i++, blk++) 32462306a36Sopenharmony_ci list_add(&blk->list, &info->empty_list); 32562306a36Sopenharmony_ci} 32662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(rh_init); 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci/* Attach a free memory region, coalesces regions if adjacent */ 32962306a36Sopenharmony_ciint rh_attach_region(rh_info_t * info, unsigned long start, int size) 33062306a36Sopenharmony_ci{ 33162306a36Sopenharmony_ci rh_block_t *blk; 33262306a36Sopenharmony_ci unsigned long s, e, m; 33362306a36Sopenharmony_ci int r; 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_ci /* The region must be aligned */ 33662306a36Sopenharmony_ci s = start; 33762306a36Sopenharmony_ci e = s + size; 33862306a36Sopenharmony_ci m = info->alignment - 1; 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci /* Round start up */ 34162306a36Sopenharmony_ci s = (s + m) & ~m; 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci /* Round end down */ 34462306a36Sopenharmony_ci e = e & ~m; 34562306a36Sopenharmony_ci 34662306a36Sopenharmony_ci if (IS_ERR_VALUE(e) || (e < s)) 34762306a36Sopenharmony_ci return -ERANGE; 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_ci /* Take final values */ 35062306a36Sopenharmony_ci start = s; 35162306a36Sopenharmony_ci size = e - s; 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci /* Grow the blocks, if needed */ 35462306a36Sopenharmony_ci r = assure_empty(info, 1); 35562306a36Sopenharmony_ci if (r < 0) 35662306a36Sopenharmony_ci return r; 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_ci blk = get_slot(info); 35962306a36Sopenharmony_ci blk->start = start; 36062306a36Sopenharmony_ci blk->size = size; 36162306a36Sopenharmony_ci blk->owner = NULL; 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_ci attach_free_block(info, blk); 36462306a36Sopenharmony_ci 36562306a36Sopenharmony_ci return 0; 36662306a36Sopenharmony_ci} 36762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(rh_attach_region); 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_ci/* Detatch given address range, splits free block if needed. */ 37062306a36Sopenharmony_ciunsigned long rh_detach_region(rh_info_t * info, unsigned long start, int size) 37162306a36Sopenharmony_ci{ 37262306a36Sopenharmony_ci struct list_head *l; 37362306a36Sopenharmony_ci rh_block_t *blk, *newblk; 37462306a36Sopenharmony_ci unsigned long s, e, m, bs, be; 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_ci /* Validate size */ 37762306a36Sopenharmony_ci if (size <= 0) 37862306a36Sopenharmony_ci return (unsigned long) -EINVAL; 37962306a36Sopenharmony_ci 38062306a36Sopenharmony_ci /* The region must be aligned */ 38162306a36Sopenharmony_ci s = start; 38262306a36Sopenharmony_ci e = s + size; 38362306a36Sopenharmony_ci m = info->alignment - 1; 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_ci /* Round start up */ 38662306a36Sopenharmony_ci s = (s + m) & ~m; 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_ci /* Round end down */ 38962306a36Sopenharmony_ci e = e & ~m; 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci if (assure_empty(info, 1) < 0) 39262306a36Sopenharmony_ci return (unsigned long) -ENOMEM; 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_ci blk = NULL; 39562306a36Sopenharmony_ci list_for_each(l, &info->free_list) { 39662306a36Sopenharmony_ci blk = list_entry(l, rh_block_t, list); 39762306a36Sopenharmony_ci /* The range must lie entirely inside one free block */ 39862306a36Sopenharmony_ci bs = blk->start; 39962306a36Sopenharmony_ci be = blk->start + blk->size; 40062306a36Sopenharmony_ci if (s >= bs && e <= be) 40162306a36Sopenharmony_ci break; 40262306a36Sopenharmony_ci blk = NULL; 40362306a36Sopenharmony_ci } 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_ci if (blk == NULL) 40662306a36Sopenharmony_ci return (unsigned long) -ENOMEM; 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_ci /* Perfect fit */ 40962306a36Sopenharmony_ci if (bs == s && be == e) { 41062306a36Sopenharmony_ci /* Delete from free list, release slot */ 41162306a36Sopenharmony_ci list_del(&blk->list); 41262306a36Sopenharmony_ci release_slot(info, blk); 41362306a36Sopenharmony_ci return s; 41462306a36Sopenharmony_ci } 41562306a36Sopenharmony_ci 41662306a36Sopenharmony_ci /* blk still in free list, with updated start and/or size */ 41762306a36Sopenharmony_ci if (bs == s || be == e) { 41862306a36Sopenharmony_ci if (bs == s) 41962306a36Sopenharmony_ci blk->start += size; 42062306a36Sopenharmony_ci blk->size -= size; 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ci } else { 42362306a36Sopenharmony_ci /* The front free fragment */ 42462306a36Sopenharmony_ci blk->size = s - bs; 42562306a36Sopenharmony_ci 42662306a36Sopenharmony_ci /* the back free fragment */ 42762306a36Sopenharmony_ci newblk = get_slot(info); 42862306a36Sopenharmony_ci newblk->start = e; 42962306a36Sopenharmony_ci newblk->size = be - e; 43062306a36Sopenharmony_ci 43162306a36Sopenharmony_ci list_add(&newblk->list, &blk->list); 43262306a36Sopenharmony_ci } 43362306a36Sopenharmony_ci 43462306a36Sopenharmony_ci return s; 43562306a36Sopenharmony_ci} 43662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(rh_detach_region); 43762306a36Sopenharmony_ci 43862306a36Sopenharmony_ci/* Allocate a block of memory at the specified alignment. The value returned 43962306a36Sopenharmony_ci * is an offset into the buffer initialized by rh_init(), or a negative number 44062306a36Sopenharmony_ci * if there is an error. 44162306a36Sopenharmony_ci */ 44262306a36Sopenharmony_ciunsigned long rh_alloc_align(rh_info_t * info, int size, int alignment, const char *owner) 44362306a36Sopenharmony_ci{ 44462306a36Sopenharmony_ci struct list_head *l; 44562306a36Sopenharmony_ci rh_block_t *blk; 44662306a36Sopenharmony_ci rh_block_t *newblk; 44762306a36Sopenharmony_ci unsigned long start, sp_size; 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_ci /* Validate size, and alignment must be power of two */ 45062306a36Sopenharmony_ci if (size <= 0 || (alignment & (alignment - 1)) != 0) 45162306a36Sopenharmony_ci return (unsigned long) -EINVAL; 45262306a36Sopenharmony_ci 45362306a36Sopenharmony_ci /* Align to configured alignment */ 45462306a36Sopenharmony_ci size = (size + (info->alignment - 1)) & ~(info->alignment - 1); 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci if (assure_empty(info, 2) < 0) 45762306a36Sopenharmony_ci return (unsigned long) -ENOMEM; 45862306a36Sopenharmony_ci 45962306a36Sopenharmony_ci blk = NULL; 46062306a36Sopenharmony_ci list_for_each(l, &info->free_list) { 46162306a36Sopenharmony_ci blk = list_entry(l, rh_block_t, list); 46262306a36Sopenharmony_ci if (size <= blk->size) { 46362306a36Sopenharmony_ci start = (blk->start + alignment - 1) & ~(alignment - 1); 46462306a36Sopenharmony_ci if (start + size <= blk->start + blk->size) 46562306a36Sopenharmony_ci break; 46662306a36Sopenharmony_ci } 46762306a36Sopenharmony_ci blk = NULL; 46862306a36Sopenharmony_ci } 46962306a36Sopenharmony_ci 47062306a36Sopenharmony_ci if (blk == NULL) 47162306a36Sopenharmony_ci return (unsigned long) -ENOMEM; 47262306a36Sopenharmony_ci 47362306a36Sopenharmony_ci /* Just fits */ 47462306a36Sopenharmony_ci if (blk->size == size) { 47562306a36Sopenharmony_ci /* Move from free list to taken list */ 47662306a36Sopenharmony_ci list_del(&blk->list); 47762306a36Sopenharmony_ci newblk = blk; 47862306a36Sopenharmony_ci } else { 47962306a36Sopenharmony_ci /* Fragment caused, split if needed */ 48062306a36Sopenharmony_ci /* Create block for fragment in the beginning */ 48162306a36Sopenharmony_ci sp_size = start - blk->start; 48262306a36Sopenharmony_ci if (sp_size) { 48362306a36Sopenharmony_ci rh_block_t *spblk; 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_ci spblk = get_slot(info); 48662306a36Sopenharmony_ci spblk->start = blk->start; 48762306a36Sopenharmony_ci spblk->size = sp_size; 48862306a36Sopenharmony_ci /* add before the blk */ 48962306a36Sopenharmony_ci list_add(&spblk->list, blk->list.prev); 49062306a36Sopenharmony_ci } 49162306a36Sopenharmony_ci newblk = get_slot(info); 49262306a36Sopenharmony_ci newblk->start = start; 49362306a36Sopenharmony_ci newblk->size = size; 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ci /* blk still in free list, with updated start and size 49662306a36Sopenharmony_ci * for fragment in the end */ 49762306a36Sopenharmony_ci blk->start = start + size; 49862306a36Sopenharmony_ci blk->size -= sp_size + size; 49962306a36Sopenharmony_ci /* No fragment in the end, remove blk */ 50062306a36Sopenharmony_ci if (blk->size == 0) { 50162306a36Sopenharmony_ci list_del(&blk->list); 50262306a36Sopenharmony_ci release_slot(info, blk); 50362306a36Sopenharmony_ci } 50462306a36Sopenharmony_ci } 50562306a36Sopenharmony_ci 50662306a36Sopenharmony_ci newblk->owner = owner; 50762306a36Sopenharmony_ci attach_taken_block(info, newblk); 50862306a36Sopenharmony_ci 50962306a36Sopenharmony_ci return start; 51062306a36Sopenharmony_ci} 51162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(rh_alloc_align); 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_ci/* Allocate a block of memory at the default alignment. The value returned is 51462306a36Sopenharmony_ci * an offset into the buffer initialized by rh_init(), or a negative number if 51562306a36Sopenharmony_ci * there is an error. 51662306a36Sopenharmony_ci */ 51762306a36Sopenharmony_ciunsigned long rh_alloc(rh_info_t * info, int size, const char *owner) 51862306a36Sopenharmony_ci{ 51962306a36Sopenharmony_ci return rh_alloc_align(info, size, info->alignment, owner); 52062306a36Sopenharmony_ci} 52162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(rh_alloc); 52262306a36Sopenharmony_ci 52362306a36Sopenharmony_ci/* Allocate a block of memory at the given offset, rounded up to the default 52462306a36Sopenharmony_ci * alignment. The value returned is an offset into the buffer initialized by 52562306a36Sopenharmony_ci * rh_init(), or a negative number if there is an error. 52662306a36Sopenharmony_ci */ 52762306a36Sopenharmony_ciunsigned long rh_alloc_fixed(rh_info_t * info, unsigned long start, int size, const char *owner) 52862306a36Sopenharmony_ci{ 52962306a36Sopenharmony_ci struct list_head *l; 53062306a36Sopenharmony_ci rh_block_t *blk, *newblk1, *newblk2; 53162306a36Sopenharmony_ci unsigned long s, e, m, bs = 0, be = 0; 53262306a36Sopenharmony_ci 53362306a36Sopenharmony_ci /* Validate size */ 53462306a36Sopenharmony_ci if (size <= 0) 53562306a36Sopenharmony_ci return (unsigned long) -EINVAL; 53662306a36Sopenharmony_ci 53762306a36Sopenharmony_ci /* The region must be aligned */ 53862306a36Sopenharmony_ci s = start; 53962306a36Sopenharmony_ci e = s + size; 54062306a36Sopenharmony_ci m = info->alignment - 1; 54162306a36Sopenharmony_ci 54262306a36Sopenharmony_ci /* Round start up */ 54362306a36Sopenharmony_ci s = (s + m) & ~m; 54462306a36Sopenharmony_ci 54562306a36Sopenharmony_ci /* Round end down */ 54662306a36Sopenharmony_ci e = e & ~m; 54762306a36Sopenharmony_ci 54862306a36Sopenharmony_ci if (assure_empty(info, 2) < 0) 54962306a36Sopenharmony_ci return (unsigned long) -ENOMEM; 55062306a36Sopenharmony_ci 55162306a36Sopenharmony_ci blk = NULL; 55262306a36Sopenharmony_ci list_for_each(l, &info->free_list) { 55362306a36Sopenharmony_ci blk = list_entry(l, rh_block_t, list); 55462306a36Sopenharmony_ci /* The range must lie entirely inside one free block */ 55562306a36Sopenharmony_ci bs = blk->start; 55662306a36Sopenharmony_ci be = blk->start + blk->size; 55762306a36Sopenharmony_ci if (s >= bs && e <= be) 55862306a36Sopenharmony_ci break; 55962306a36Sopenharmony_ci blk = NULL; 56062306a36Sopenharmony_ci } 56162306a36Sopenharmony_ci 56262306a36Sopenharmony_ci if (blk == NULL) 56362306a36Sopenharmony_ci return (unsigned long) -ENOMEM; 56462306a36Sopenharmony_ci 56562306a36Sopenharmony_ci /* Perfect fit */ 56662306a36Sopenharmony_ci if (bs == s && be == e) { 56762306a36Sopenharmony_ci /* Move from free list to taken list */ 56862306a36Sopenharmony_ci list_del(&blk->list); 56962306a36Sopenharmony_ci blk->owner = owner; 57062306a36Sopenharmony_ci 57162306a36Sopenharmony_ci start = blk->start; 57262306a36Sopenharmony_ci attach_taken_block(info, blk); 57362306a36Sopenharmony_ci 57462306a36Sopenharmony_ci return start; 57562306a36Sopenharmony_ci 57662306a36Sopenharmony_ci } 57762306a36Sopenharmony_ci 57862306a36Sopenharmony_ci /* blk still in free list, with updated start and/or size */ 57962306a36Sopenharmony_ci if (bs == s || be == e) { 58062306a36Sopenharmony_ci if (bs == s) 58162306a36Sopenharmony_ci blk->start += size; 58262306a36Sopenharmony_ci blk->size -= size; 58362306a36Sopenharmony_ci 58462306a36Sopenharmony_ci } else { 58562306a36Sopenharmony_ci /* The front free fragment */ 58662306a36Sopenharmony_ci blk->size = s - bs; 58762306a36Sopenharmony_ci 58862306a36Sopenharmony_ci /* The back free fragment */ 58962306a36Sopenharmony_ci newblk2 = get_slot(info); 59062306a36Sopenharmony_ci newblk2->start = e; 59162306a36Sopenharmony_ci newblk2->size = be - e; 59262306a36Sopenharmony_ci 59362306a36Sopenharmony_ci list_add(&newblk2->list, &blk->list); 59462306a36Sopenharmony_ci } 59562306a36Sopenharmony_ci 59662306a36Sopenharmony_ci newblk1 = get_slot(info); 59762306a36Sopenharmony_ci newblk1->start = s; 59862306a36Sopenharmony_ci newblk1->size = e - s; 59962306a36Sopenharmony_ci newblk1->owner = owner; 60062306a36Sopenharmony_ci 60162306a36Sopenharmony_ci start = newblk1->start; 60262306a36Sopenharmony_ci attach_taken_block(info, newblk1); 60362306a36Sopenharmony_ci 60462306a36Sopenharmony_ci return start; 60562306a36Sopenharmony_ci} 60662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(rh_alloc_fixed); 60762306a36Sopenharmony_ci 60862306a36Sopenharmony_ci/* Deallocate the memory previously allocated by one of the rh_alloc functions. 60962306a36Sopenharmony_ci * The return value is the size of the deallocated block, or a negative number 61062306a36Sopenharmony_ci * if there is an error. 61162306a36Sopenharmony_ci */ 61262306a36Sopenharmony_ciint rh_free(rh_info_t * info, unsigned long start) 61362306a36Sopenharmony_ci{ 61462306a36Sopenharmony_ci rh_block_t *blk, *blk2; 61562306a36Sopenharmony_ci struct list_head *l; 61662306a36Sopenharmony_ci int size; 61762306a36Sopenharmony_ci 61862306a36Sopenharmony_ci /* Linear search for block */ 61962306a36Sopenharmony_ci blk = NULL; 62062306a36Sopenharmony_ci list_for_each(l, &info->taken_list) { 62162306a36Sopenharmony_ci blk2 = list_entry(l, rh_block_t, list); 62262306a36Sopenharmony_ci if (start < blk2->start) 62362306a36Sopenharmony_ci break; 62462306a36Sopenharmony_ci blk = blk2; 62562306a36Sopenharmony_ci } 62662306a36Sopenharmony_ci 62762306a36Sopenharmony_ci if (blk == NULL || start > (blk->start + blk->size)) 62862306a36Sopenharmony_ci return -EINVAL; 62962306a36Sopenharmony_ci 63062306a36Sopenharmony_ci /* Remove from taken list */ 63162306a36Sopenharmony_ci list_del(&blk->list); 63262306a36Sopenharmony_ci 63362306a36Sopenharmony_ci /* Get size of freed block */ 63462306a36Sopenharmony_ci size = blk->size; 63562306a36Sopenharmony_ci attach_free_block(info, blk); 63662306a36Sopenharmony_ci 63762306a36Sopenharmony_ci return size; 63862306a36Sopenharmony_ci} 63962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(rh_free); 64062306a36Sopenharmony_ci 64162306a36Sopenharmony_ciint rh_get_stats(rh_info_t * info, int what, int max_stats, rh_stats_t * stats) 64262306a36Sopenharmony_ci{ 64362306a36Sopenharmony_ci rh_block_t *blk; 64462306a36Sopenharmony_ci struct list_head *l; 64562306a36Sopenharmony_ci struct list_head *h; 64662306a36Sopenharmony_ci int nr; 64762306a36Sopenharmony_ci 64862306a36Sopenharmony_ci switch (what) { 64962306a36Sopenharmony_ci 65062306a36Sopenharmony_ci case RHGS_FREE: 65162306a36Sopenharmony_ci h = &info->free_list; 65262306a36Sopenharmony_ci break; 65362306a36Sopenharmony_ci 65462306a36Sopenharmony_ci case RHGS_TAKEN: 65562306a36Sopenharmony_ci h = &info->taken_list; 65662306a36Sopenharmony_ci break; 65762306a36Sopenharmony_ci 65862306a36Sopenharmony_ci default: 65962306a36Sopenharmony_ci return -EINVAL; 66062306a36Sopenharmony_ci } 66162306a36Sopenharmony_ci 66262306a36Sopenharmony_ci /* Linear search for block */ 66362306a36Sopenharmony_ci nr = 0; 66462306a36Sopenharmony_ci list_for_each(l, h) { 66562306a36Sopenharmony_ci blk = list_entry(l, rh_block_t, list); 66662306a36Sopenharmony_ci if (stats != NULL && nr < max_stats) { 66762306a36Sopenharmony_ci stats->start = blk->start; 66862306a36Sopenharmony_ci stats->size = blk->size; 66962306a36Sopenharmony_ci stats->owner = blk->owner; 67062306a36Sopenharmony_ci stats++; 67162306a36Sopenharmony_ci } 67262306a36Sopenharmony_ci nr++; 67362306a36Sopenharmony_ci } 67462306a36Sopenharmony_ci 67562306a36Sopenharmony_ci return nr; 67662306a36Sopenharmony_ci} 67762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(rh_get_stats); 67862306a36Sopenharmony_ci 67962306a36Sopenharmony_ciint rh_set_owner(rh_info_t * info, unsigned long start, const char *owner) 68062306a36Sopenharmony_ci{ 68162306a36Sopenharmony_ci rh_block_t *blk, *blk2; 68262306a36Sopenharmony_ci struct list_head *l; 68362306a36Sopenharmony_ci int size; 68462306a36Sopenharmony_ci 68562306a36Sopenharmony_ci /* Linear search for block */ 68662306a36Sopenharmony_ci blk = NULL; 68762306a36Sopenharmony_ci list_for_each(l, &info->taken_list) { 68862306a36Sopenharmony_ci blk2 = list_entry(l, rh_block_t, list); 68962306a36Sopenharmony_ci if (start < blk2->start) 69062306a36Sopenharmony_ci break; 69162306a36Sopenharmony_ci blk = blk2; 69262306a36Sopenharmony_ci } 69362306a36Sopenharmony_ci 69462306a36Sopenharmony_ci if (blk == NULL || start > (blk->start + blk->size)) 69562306a36Sopenharmony_ci return -EINVAL; 69662306a36Sopenharmony_ci 69762306a36Sopenharmony_ci blk->owner = owner; 69862306a36Sopenharmony_ci size = blk->size; 69962306a36Sopenharmony_ci 70062306a36Sopenharmony_ci return size; 70162306a36Sopenharmony_ci} 70262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(rh_set_owner); 70362306a36Sopenharmony_ci 70462306a36Sopenharmony_civoid rh_dump(rh_info_t * info) 70562306a36Sopenharmony_ci{ 70662306a36Sopenharmony_ci static rh_stats_t st[32]; /* XXX maximum 32 blocks */ 70762306a36Sopenharmony_ci int maxnr; 70862306a36Sopenharmony_ci int i, nr; 70962306a36Sopenharmony_ci 71062306a36Sopenharmony_ci maxnr = ARRAY_SIZE(st); 71162306a36Sopenharmony_ci 71262306a36Sopenharmony_ci printk(KERN_INFO 71362306a36Sopenharmony_ci "info @0x%p (%d slots empty / %d max)\n", 71462306a36Sopenharmony_ci info, info->empty_slots, info->max_blocks); 71562306a36Sopenharmony_ci 71662306a36Sopenharmony_ci printk(KERN_INFO " Free:\n"); 71762306a36Sopenharmony_ci nr = rh_get_stats(info, RHGS_FREE, maxnr, st); 71862306a36Sopenharmony_ci if (nr > maxnr) 71962306a36Sopenharmony_ci nr = maxnr; 72062306a36Sopenharmony_ci for (i = 0; i < nr; i++) 72162306a36Sopenharmony_ci printk(KERN_INFO 72262306a36Sopenharmony_ci " 0x%lx-0x%lx (%u)\n", 72362306a36Sopenharmony_ci st[i].start, st[i].start + st[i].size, 72462306a36Sopenharmony_ci st[i].size); 72562306a36Sopenharmony_ci printk(KERN_INFO "\n"); 72662306a36Sopenharmony_ci 72762306a36Sopenharmony_ci printk(KERN_INFO " Taken:\n"); 72862306a36Sopenharmony_ci nr = rh_get_stats(info, RHGS_TAKEN, maxnr, st); 72962306a36Sopenharmony_ci if (nr > maxnr) 73062306a36Sopenharmony_ci nr = maxnr; 73162306a36Sopenharmony_ci for (i = 0; i < nr; i++) 73262306a36Sopenharmony_ci printk(KERN_INFO 73362306a36Sopenharmony_ci " 0x%lx-0x%lx (%u) %s\n", 73462306a36Sopenharmony_ci st[i].start, st[i].start + st[i].size, 73562306a36Sopenharmony_ci st[i].size, st[i].owner != NULL ? st[i].owner : ""); 73662306a36Sopenharmony_ci printk(KERN_INFO "\n"); 73762306a36Sopenharmony_ci} 73862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(rh_dump); 73962306a36Sopenharmony_ci 74062306a36Sopenharmony_civoid rh_dump_blk(rh_info_t * info, rh_block_t * blk) 74162306a36Sopenharmony_ci{ 74262306a36Sopenharmony_ci printk(KERN_INFO 74362306a36Sopenharmony_ci "blk @0x%p: 0x%lx-0x%lx (%u)\n", 74462306a36Sopenharmony_ci blk, blk->start, blk->start + blk->size, blk->size); 74562306a36Sopenharmony_ci} 74662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(rh_dump_blk); 74762306a36Sopenharmony_ci 748