xref: /kernel/linux/linux-6.6/arch/powerpc/lib/rheap.c (revision 62306a36)
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