Commit | Line | Data |
---|---|---|
14cf11af | 1 | /* |
14cf11af PM |
2 | * A Remote Heap. Remote means that we don't touch the memory that the |
3 | * heap points to. Normal heap implementations use the memory they manage | |
4 | * to place their list. We cannot do that because the memory we manage may | |
5 | * have special properties, for example it is uncachable or of different | |
6 | * endianess. | |
7 | * | |
8 | * Author: Pantelis Antoniou <panto@intracom.gr> | |
9 | * | |
10 | * 2004 (c) INTRACOM S.A. Greece. This file is licensed under | |
11 | * the terms of the GNU General Public License version 2. This program | |
12 | * is licensed "as is" without any warranty of any kind, whether express | |
13 | * or implied. | |
14 | */ | |
15 | #include <linux/types.h> | |
16 | #include <linux/errno.h> | |
3839a594 | 17 | #include <linux/kernel.h> |
d4697af4 | 18 | #include <linux/module.h> |
14cf11af | 19 | #include <linux/mm.h> |
4e950f6f | 20 | #include <linux/err.h> |
14cf11af PM |
21 | #include <linux/slab.h> |
22 | ||
23 | #include <asm/rheap.h> | |
24 | ||
25 | /* | |
26 | * Fixup a list_head, needed when copying lists. If the pointers fall | |
27 | * between s and e, apply the delta. This assumes that | |
28 | * sizeof(struct list_head *) == sizeof(unsigned long *). | |
29 | */ | |
30 | static inline void fixup(unsigned long s, unsigned long e, int d, | |
31 | struct list_head *l) | |
32 | { | |
33 | unsigned long *pp; | |
34 | ||
35 | pp = (unsigned long *)&l->next; | |
36 | if (*pp >= s && *pp < e) | |
37 | *pp += d; | |
38 | ||
39 | pp = (unsigned long *)&l->prev; | |
40 | if (*pp >= s && *pp < e) | |
41 | *pp += d; | |
42 | } | |
43 | ||
44 | /* Grow the allocated blocks */ | |
45 | static int grow(rh_info_t * info, int max_blocks) | |
46 | { | |
47 | rh_block_t *block, *blk; | |
48 | int i, new_blocks; | |
49 | int delta; | |
50 | unsigned long blks, blke; | |
51 | ||
52 | if (max_blocks <= info->max_blocks) | |
53 | return -EINVAL; | |
54 | ||
55 | new_blocks = max_blocks - info->max_blocks; | |
56 | ||
3a2f020c | 57 | block = kmalloc(sizeof(rh_block_t) * max_blocks, GFP_ATOMIC); |
14cf11af PM |
58 | if (block == NULL) |
59 | return -ENOMEM; | |
60 | ||
61 | if (info->max_blocks > 0) { | |
62 | ||
63 | /* copy old block area */ | |
64 | memcpy(block, info->block, | |
65 | sizeof(rh_block_t) * info->max_blocks); | |
66 | ||
67 | delta = (char *)block - (char *)info->block; | |
68 | ||
69 | /* and fixup list pointers */ | |
70 | blks = (unsigned long)info->block; | |
71 | blke = (unsigned long)(info->block + info->max_blocks); | |
72 | ||
73 | for (i = 0, blk = block; i < info->max_blocks; i++, blk++) | |
74 | fixup(blks, blke, delta, &blk->list); | |
75 | ||
76 | fixup(blks, blke, delta, &info->empty_list); | |
77 | fixup(blks, blke, delta, &info->free_list); | |
78 | fixup(blks, blke, delta, &info->taken_list); | |
79 | ||
80 | /* free the old allocated memory */ | |
81 | if ((info->flags & RHIF_STATIC_BLOCK) == 0) | |
82 | kfree(info->block); | |
83 | } | |
84 | ||
85 | info->block = block; | |
86 | info->empty_slots += new_blocks; | |
87 | info->max_blocks = max_blocks; | |
88 | info->flags &= ~RHIF_STATIC_BLOCK; | |
89 | ||
90 | /* add all new blocks to the free list */ | |
4942bd80 TT |
91 | blk = block + info->max_blocks - new_blocks; |
92 | for (i = 0; i < new_blocks; i++, blk++) | |
14cf11af PM |
93 | list_add(&blk->list, &info->empty_list); |
94 | ||
95 | return 0; | |
96 | } | |
97 | ||
98 | /* | |
99 | * Assure at least the required amount of empty slots. If this function | |
100 | * causes a grow in the block area then all pointers kept to the block | |
101 | * area are invalid! | |
102 | */ | |
103 | static int assure_empty(rh_info_t * info, int slots) | |
104 | { | |
105 | int max_blocks; | |
106 | ||
107 | /* This function is not meant to be used to grow uncontrollably */ | |
108 | if (slots >= 4) | |
109 | return -EINVAL; | |
110 | ||
111 | /* Enough space */ | |
112 | if (info->empty_slots >= slots) | |
113 | return 0; | |
114 | ||
115 | /* Next 16 sized block */ | |
116 | max_blocks = ((info->max_blocks + slots) + 15) & ~15; | |
117 | ||
118 | return grow(info, max_blocks); | |
119 | } | |
120 | ||
121 | static rh_block_t *get_slot(rh_info_t * info) | |
122 | { | |
123 | rh_block_t *blk; | |
124 | ||
125 | /* If no more free slots, and failure to extend. */ | |
126 | /* XXX: You should have called assure_empty before */ | |
127 | if (info->empty_slots == 0) { | |
128 | printk(KERN_ERR "rh: out of slots; crash is imminent.\n"); | |
129 | return NULL; | |
130 | } | |
131 | ||
132 | /* Get empty slot to use */ | |
133 | blk = list_entry(info->empty_list.next, rh_block_t, list); | |
134 | list_del_init(&blk->list); | |
135 | info->empty_slots--; | |
136 | ||
137 | /* Initialize */ | |
4c35630c | 138 | blk->start = 0; |
14cf11af PM |
139 | blk->size = 0; |
140 | blk->owner = NULL; | |
141 | ||
142 | return blk; | |
143 | } | |
144 | ||
145 | static inline void release_slot(rh_info_t * info, rh_block_t * blk) | |
146 | { | |
147 | list_add(&blk->list, &info->empty_list); | |
148 | info->empty_slots++; | |
149 | } | |
150 | ||
151 | static void attach_free_block(rh_info_t * info, rh_block_t * blkn) | |
152 | { | |
153 | rh_block_t *blk; | |
154 | rh_block_t *before; | |
155 | rh_block_t *after; | |
156 | rh_block_t *next; | |
157 | int size; | |
158 | unsigned long s, e, bs, be; | |
159 | struct list_head *l; | |
160 | ||
161 | /* We assume that they are aligned properly */ | |
162 | size = blkn->size; | |
4c35630c | 163 | s = blkn->start; |
14cf11af PM |
164 | e = s + size; |
165 | ||
166 | /* Find the blocks immediately before and after the given one | |
167 | * (if any) */ | |
168 | before = NULL; | |
169 | after = NULL; | |
170 | next = NULL; | |
171 | ||
172 | list_for_each(l, &info->free_list) { | |
173 | blk = list_entry(l, rh_block_t, list); | |
174 | ||
4c35630c | 175 | bs = blk->start; |
14cf11af PM |
176 | be = bs + blk->size; |
177 | ||
178 | if (next == NULL && s >= bs) | |
179 | next = blk; | |
180 | ||
181 | if (be == s) | |
182 | before = blk; | |
183 | ||
184 | if (e == bs) | |
185 | after = blk; | |
186 | ||
187 | /* If both are not null, break now */ | |
188 | if (before != NULL && after != NULL) | |
189 | break; | |
190 | } | |
191 | ||
192 | /* Now check if they are really adjacent */ | |
4c35630c | 193 | if (before && s != (before->start + before->size)) |
14cf11af PM |
194 | before = NULL; |
195 | ||
4c35630c | 196 | if (after && e != after->start) |
14cf11af PM |
197 | after = NULL; |
198 | ||
199 | /* No coalescing; list insert and return */ | |
200 | if (before == NULL && after == NULL) { | |
201 | ||
202 | if (next != NULL) | |
203 | list_add(&blkn->list, &next->list); | |
204 | else | |
205 | list_add(&blkn->list, &info->free_list); | |
206 | ||
207 | return; | |
208 | } | |
209 | ||
210 | /* We don't need it anymore */ | |
211 | release_slot(info, blkn); | |
212 | ||
213 | /* Grow the before block */ | |
214 | if (before != NULL && after == NULL) { | |
215 | before->size += size; | |
216 | return; | |
217 | } | |
218 | ||
219 | /* Grow the after block backwards */ | |
220 | if (before == NULL && after != NULL) { | |
4c35630c | 221 | after->start -= size; |
14cf11af PM |
222 | after->size += size; |
223 | return; | |
224 | } | |
225 | ||
226 | /* Grow the before block, and release the after block */ | |
227 | before->size += size + after->size; | |
228 | list_del(&after->list); | |
229 | release_slot(info, after); | |
230 | } | |
231 | ||
232 | static void attach_taken_block(rh_info_t * info, rh_block_t * blkn) | |
233 | { | |
234 | rh_block_t *blk; | |
235 | struct list_head *l; | |
236 | ||
237 | /* Find the block immediately before the given one (if any) */ | |
238 | list_for_each(l, &info->taken_list) { | |
239 | blk = list_entry(l, rh_block_t, list); | |
240 | if (blk->start > blkn->start) { | |
241 | list_add_tail(&blkn->list, &blk->list); | |
242 | return; | |
243 | } | |
244 | } | |
245 | ||
246 | list_add_tail(&blkn->list, &info->taken_list); | |
247 | } | |
248 | ||
249 | /* | |
250 | * Create a remote heap dynamically. Note that no memory for the blocks | |
251 | * are allocated. It will upon the first allocation | |
252 | */ | |
253 | rh_info_t *rh_create(unsigned int alignment) | |
254 | { | |
255 | rh_info_t *info; | |
256 | ||
257 | /* Alignment must be a power of two */ | |
258 | if ((alignment & (alignment - 1)) != 0) | |
259 | return ERR_PTR(-EINVAL); | |
260 | ||
3a2f020c | 261 | info = kmalloc(sizeof(*info), GFP_ATOMIC); |
14cf11af PM |
262 | if (info == NULL) |
263 | return ERR_PTR(-ENOMEM); | |
264 | ||
265 | info->alignment = alignment; | |
266 | ||
267 | /* Initially everything as empty */ | |
268 | info->block = NULL; | |
269 | info->max_blocks = 0; | |
270 | info->empty_slots = 0; | |
271 | info->flags = 0; | |
272 | ||
273 | INIT_LIST_HEAD(&info->empty_list); | |
274 | INIT_LIST_HEAD(&info->free_list); | |
275 | INIT_LIST_HEAD(&info->taken_list); | |
276 | ||
277 | return info; | |
278 | } | |
d4697af4 | 279 | EXPORT_SYMBOL_GPL(rh_create); |
14cf11af PM |
280 | |
281 | /* | |
282 | * Destroy a dynamically created remote heap. Deallocate only if the areas | |
283 | * are not static | |
284 | */ | |
285 | void rh_destroy(rh_info_t * info) | |
286 | { | |
287 | if ((info->flags & RHIF_STATIC_BLOCK) == 0 && info->block != NULL) | |
288 | kfree(info->block); | |
289 | ||
290 | if ((info->flags & RHIF_STATIC_INFO) == 0) | |
291 | kfree(info); | |
292 | } | |
d4697af4 | 293 | EXPORT_SYMBOL_GPL(rh_destroy); |
14cf11af PM |
294 | |
295 | /* | |
296 | * Initialize in place a remote heap info block. This is needed to support | |
297 | * operation very early in the startup of the kernel, when it is not yet safe | |
298 | * to call kmalloc. | |
299 | */ | |
300 | void rh_init(rh_info_t * info, unsigned int alignment, int max_blocks, | |
301 | rh_block_t * block) | |
302 | { | |
303 | int i; | |
304 | rh_block_t *blk; | |
305 | ||
306 | /* Alignment must be a power of two */ | |
307 | if ((alignment & (alignment - 1)) != 0) | |
308 | return; | |
309 | ||
310 | info->alignment = alignment; | |
311 | ||
312 | /* Initially everything as empty */ | |
313 | info->block = block; | |
314 | info->max_blocks = max_blocks; | |
315 | info->empty_slots = max_blocks; | |
316 | info->flags = RHIF_STATIC_INFO | RHIF_STATIC_BLOCK; | |
317 | ||
318 | INIT_LIST_HEAD(&info->empty_list); | |
319 | INIT_LIST_HEAD(&info->free_list); | |
320 | INIT_LIST_HEAD(&info->taken_list); | |
321 | ||
322 | /* Add all new blocks to the free list */ | |
323 | for (i = 0, blk = block; i < max_blocks; i++, blk++) | |
324 | list_add(&blk->list, &info->empty_list); | |
325 | } | |
d4697af4 | 326 | EXPORT_SYMBOL_GPL(rh_init); |
14cf11af PM |
327 | |
328 | /* Attach a free memory region, coalesces regions if adjuscent */ | |
4c35630c | 329 | int rh_attach_region(rh_info_t * info, unsigned long start, int size) |
14cf11af PM |
330 | { |
331 | rh_block_t *blk; | |
332 | unsigned long s, e, m; | |
333 | int r; | |
334 | ||
335 | /* The region must be aligned */ | |
4c35630c | 336 | s = start; |
14cf11af PM |
337 | e = s + size; |
338 | m = info->alignment - 1; | |
339 | ||
340 | /* Round start up */ | |
341 | s = (s + m) & ~m; | |
342 | ||
343 | /* Round end down */ | |
344 | e = e & ~m; | |
345 | ||
4c35630c TT |
346 | if (IS_ERR_VALUE(e) || (e < s)) |
347 | return -ERANGE; | |
348 | ||
14cf11af | 349 | /* Take final values */ |
4c35630c TT |
350 | start = s; |
351 | size = e - s; | |
14cf11af PM |
352 | |
353 | /* Grow the blocks, if needed */ | |
354 | r = assure_empty(info, 1); | |
355 | if (r < 0) | |
356 | return r; | |
357 | ||
358 | blk = get_slot(info); | |
359 | blk->start = start; | |
360 | blk->size = size; | |
361 | blk->owner = NULL; | |
362 | ||
363 | attach_free_block(info, blk); | |
364 | ||
365 | return 0; | |
366 | } | |
d4697af4 | 367 | EXPORT_SYMBOL_GPL(rh_attach_region); |
14cf11af PM |
368 | |
369 | /* Detatch given address range, splits free block if needed. */ | |
4c35630c | 370 | unsigned long rh_detach_region(rh_info_t * info, unsigned long start, int size) |
14cf11af PM |
371 | { |
372 | struct list_head *l; | |
373 | rh_block_t *blk, *newblk; | |
374 | unsigned long s, e, m, bs, be; | |
375 | ||
376 | /* Validate size */ | |
377 | if (size <= 0) | |
4c35630c | 378 | return (unsigned long) -EINVAL; |
14cf11af PM |
379 | |
380 | /* The region must be aligned */ | |
4c35630c | 381 | s = start; |
14cf11af PM |
382 | e = s + size; |
383 | m = info->alignment - 1; | |
384 | ||
385 | /* Round start up */ | |
386 | s = (s + m) & ~m; | |
387 | ||
388 | /* Round end down */ | |
389 | e = e & ~m; | |
390 | ||
391 | if (assure_empty(info, 1) < 0) | |
4c35630c | 392 | return (unsigned long) -ENOMEM; |
14cf11af PM |
393 | |
394 | blk = NULL; | |
395 | list_for_each(l, &info->free_list) { | |
396 | blk = list_entry(l, rh_block_t, list); | |
397 | /* The range must lie entirely inside one free block */ | |
4c35630c TT |
398 | bs = blk->start; |
399 | be = blk->start + blk->size; | |
14cf11af PM |
400 | if (s >= bs && e <= be) |
401 | break; | |
402 | blk = NULL; | |
403 | } | |
404 | ||
405 | if (blk == NULL) | |
4c35630c | 406 | return (unsigned long) -ENOMEM; |
14cf11af PM |
407 | |
408 | /* Perfect fit */ | |
409 | if (bs == s && be == e) { | |
410 | /* Delete from free list, release slot */ | |
411 | list_del(&blk->list); | |
412 | release_slot(info, blk); | |
4c35630c | 413 | return s; |
14cf11af PM |
414 | } |
415 | ||
416 | /* blk still in free list, with updated start and/or size */ | |
417 | if (bs == s || be == e) { | |
418 | if (bs == s) | |
4c35630c | 419 | blk->start += size; |
14cf11af PM |
420 | blk->size -= size; |
421 | ||
422 | } else { | |
423 | /* The front free fragment */ | |
424 | blk->size = s - bs; | |
425 | ||
426 | /* the back free fragment */ | |
427 | newblk = get_slot(info); | |
4c35630c | 428 | newblk->start = e; |
14cf11af PM |
429 | newblk->size = be - e; |
430 | ||
431 | list_add(&newblk->list, &blk->list); | |
432 | } | |
433 | ||
4c35630c | 434 | return s; |
14cf11af | 435 | } |
d4697af4 | 436 | EXPORT_SYMBOL_GPL(rh_detach_region); |
14cf11af | 437 | |
4c35630c TT |
438 | /* Allocate a block of memory at the specified alignment. The value returned |
439 | * is an offset into the buffer initialized by rh_init(), or a negative number | |
440 | * if there is an error. | |
441 | */ | |
442 | unsigned long rh_alloc_align(rh_info_t * info, int size, int alignment, const char *owner) | |
14cf11af PM |
443 | { |
444 | struct list_head *l; | |
445 | rh_block_t *blk; | |
446 | rh_block_t *newblk; | |
7c8545e9 | 447 | unsigned long start, sp_size; |
14cf11af | 448 | |
4c35630c | 449 | /* Validate size, and alignment must be power of two */ |
5e980823 | 450 | if (size <= 0 || (alignment & (alignment - 1)) != 0) |
4c35630c | 451 | return (unsigned long) -EINVAL; |
14cf11af PM |
452 | |
453 | /* Align to configured alignment */ | |
454 | size = (size + (info->alignment - 1)) & ~(info->alignment - 1); | |
455 | ||
7c8545e9 | 456 | if (assure_empty(info, 2) < 0) |
4c35630c | 457 | return (unsigned long) -ENOMEM; |
14cf11af PM |
458 | |
459 | blk = NULL; | |
460 | list_for_each(l, &info->free_list) { | |
461 | blk = list_entry(l, rh_block_t, list); | |
7c8545e9 LY |
462 | if (size <= blk->size) { |
463 | start = (blk->start + alignment - 1) & ~(alignment - 1); | |
464 | if (start + size <= blk->start + blk->size) | |
465 | break; | |
466 | } | |
14cf11af PM |
467 | blk = NULL; |
468 | } | |
469 | ||
470 | if (blk == NULL) | |
4c35630c | 471 | return (unsigned long) -ENOMEM; |
14cf11af PM |
472 | |
473 | /* Just fits */ | |
474 | if (blk->size == size) { | |
475 | /* Move from free list to taken list */ | |
476 | list_del(&blk->list); | |
1c2de47c TT |
477 | newblk = blk; |
478 | } else { | |
7c8545e9 LY |
479 | /* Fragment caused, split if needed */ |
480 | /* Create block for fragment in the beginning */ | |
481 | sp_size = start - blk->start; | |
482 | if (sp_size) { | |
483 | rh_block_t *spblk; | |
484 | ||
485 | spblk = get_slot(info); | |
486 | spblk->start = blk->start; | |
487 | spblk->size = sp_size; | |
488 | /* add before the blk */ | |
489 | list_add(&spblk->list, blk->list.prev); | |
490 | } | |
1c2de47c | 491 | newblk = get_slot(info); |
7c8545e9 | 492 | newblk->start = start; |
1c2de47c | 493 | newblk->size = size; |
14cf11af | 494 | |
7c8545e9 LY |
495 | /* blk still in free list, with updated start and size |
496 | * for fragment in the end */ | |
497 | blk->start = start + size; | |
498 | blk->size -= sp_size + size; | |
499 | /* No fragment in the end, remove blk */ | |
500 | if (blk->size == 0) { | |
501 | list_del(&blk->list); | |
502 | release_slot(info, blk); | |
503 | } | |
14cf11af PM |
504 | } |
505 | ||
14cf11af | 506 | newblk->owner = owner; |
14cf11af PM |
507 | attach_taken_block(info, newblk); |
508 | ||
509 | return start; | |
510 | } | |
d4697af4 | 511 | EXPORT_SYMBOL_GPL(rh_alloc_align); |
14cf11af | 512 | |
4c35630c TT |
513 | /* Allocate a block of memory at the default alignment. The value returned is |
514 | * an offset into the buffer initialized by rh_init(), or a negative number if | |
515 | * there is an error. | |
516 | */ | |
517 | unsigned long rh_alloc(rh_info_t * info, int size, const char *owner) | |
5e980823 LY |
518 | { |
519 | return rh_alloc_align(info, size, info->alignment, owner); | |
520 | } | |
d4697af4 | 521 | EXPORT_SYMBOL_GPL(rh_alloc); |
5e980823 | 522 | |
4c35630c TT |
523 | /* Allocate a block of memory at the given offset, rounded up to the default |
524 | * alignment. The value returned is an offset into the buffer initialized by | |
525 | * rh_init(), or a negative number if there is an error. | |
526 | */ | |
527 | unsigned long rh_alloc_fixed(rh_info_t * info, unsigned long start, int size, const char *owner) | |
14cf11af PM |
528 | { |
529 | struct list_head *l; | |
530 | rh_block_t *blk, *newblk1, *newblk2; | |
5e980823 | 531 | unsigned long s, e, m, bs = 0, be = 0; |
14cf11af PM |
532 | |
533 | /* Validate size */ | |
534 | if (size <= 0) | |
4c35630c | 535 | return (unsigned long) -EINVAL; |
14cf11af PM |
536 | |
537 | /* The region must be aligned */ | |
4c35630c | 538 | s = start; |
14cf11af PM |
539 | e = s + size; |
540 | m = info->alignment - 1; | |
541 | ||
542 | /* Round start up */ | |
543 | s = (s + m) & ~m; | |
544 | ||
545 | /* Round end down */ | |
546 | e = e & ~m; | |
547 | ||
548 | if (assure_empty(info, 2) < 0) | |
4c35630c | 549 | return (unsigned long) -ENOMEM; |
14cf11af PM |
550 | |
551 | blk = NULL; | |
552 | list_for_each(l, &info->free_list) { | |
553 | blk = list_entry(l, rh_block_t, list); | |
554 | /* The range must lie entirely inside one free block */ | |
4c35630c TT |
555 | bs = blk->start; |
556 | be = blk->start + blk->size; | |
14cf11af PM |
557 | if (s >= bs && e <= be) |
558 | break; | |
af4d3643 | 559 | blk = NULL; |
14cf11af PM |
560 | } |
561 | ||
562 | if (blk == NULL) | |
4c35630c | 563 | return (unsigned long) -ENOMEM; |
14cf11af PM |
564 | |
565 | /* Perfect fit */ | |
566 | if (bs == s && be == e) { | |
567 | /* Move from free list to taken list */ | |
568 | list_del(&blk->list); | |
569 | blk->owner = owner; | |
570 | ||
571 | start = blk->start; | |
572 | attach_taken_block(info, blk); | |
573 | ||
574 | return start; | |
575 | ||
576 | } | |
577 | ||
578 | /* blk still in free list, with updated start and/or size */ | |
579 | if (bs == s || be == e) { | |
580 | if (bs == s) | |
4c35630c | 581 | blk->start += size; |
14cf11af PM |
582 | blk->size -= size; |
583 | ||
584 | } else { | |
585 | /* The front free fragment */ | |
586 | blk->size = s - bs; | |
587 | ||
588 | /* The back free fragment */ | |
589 | newblk2 = get_slot(info); | |
4c35630c | 590 | newblk2->start = e; |
14cf11af PM |
591 | newblk2->size = be - e; |
592 | ||
593 | list_add(&newblk2->list, &blk->list); | |
594 | } | |
595 | ||
596 | newblk1 = get_slot(info); | |
4c35630c | 597 | newblk1->start = s; |
14cf11af PM |
598 | newblk1->size = e - s; |
599 | newblk1->owner = owner; | |
600 | ||
601 | start = newblk1->start; | |
602 | attach_taken_block(info, newblk1); | |
603 | ||
604 | return start; | |
605 | } | |
d4697af4 | 606 | EXPORT_SYMBOL_GPL(rh_alloc_fixed); |
14cf11af | 607 | |
4c35630c TT |
608 | /* Deallocate the memory previously allocated by one of the rh_alloc functions. |
609 | * The return value is the size of the deallocated block, or a negative number | |
610 | * if there is an error. | |
611 | */ | |
612 | int rh_free(rh_info_t * info, unsigned long start) | |
14cf11af PM |
613 | { |
614 | rh_block_t *blk, *blk2; | |
615 | struct list_head *l; | |
616 | int size; | |
617 | ||
618 | /* Linear search for block */ | |
619 | blk = NULL; | |
620 | list_for_each(l, &info->taken_list) { | |
621 | blk2 = list_entry(l, rh_block_t, list); | |
622 | if (start < blk2->start) | |
623 | break; | |
624 | blk = blk2; | |
625 | } | |
626 | ||
627 | if (blk == NULL || start > (blk->start + blk->size)) | |
628 | return -EINVAL; | |
629 | ||
630 | /* Remove from taken list */ | |
631 | list_del(&blk->list); | |
632 | ||
633 | /* Get size of freed block */ | |
634 | size = blk->size; | |
635 | attach_free_block(info, blk); | |
636 | ||
637 | return size; | |
638 | } | |
d4697af4 | 639 | EXPORT_SYMBOL_GPL(rh_free); |
14cf11af PM |
640 | |
641 | int rh_get_stats(rh_info_t * info, int what, int max_stats, rh_stats_t * stats) | |
642 | { | |
643 | rh_block_t *blk; | |
644 | struct list_head *l; | |
645 | struct list_head *h; | |
646 | int nr; | |
647 | ||
648 | switch (what) { | |
649 | ||
650 | case RHGS_FREE: | |
651 | h = &info->free_list; | |
652 | break; | |
653 | ||
654 | case RHGS_TAKEN: | |
655 | h = &info->taken_list; | |
656 | break; | |
657 | ||
658 | default: | |
659 | return -EINVAL; | |
660 | } | |
661 | ||
662 | /* Linear search for block */ | |
663 | nr = 0; | |
664 | list_for_each(l, h) { | |
665 | blk = list_entry(l, rh_block_t, list); | |
666 | if (stats != NULL && nr < max_stats) { | |
667 | stats->start = blk->start; | |
668 | stats->size = blk->size; | |
669 | stats->owner = blk->owner; | |
670 | stats++; | |
671 | } | |
672 | nr++; | |
673 | } | |
674 | ||
675 | return nr; | |
676 | } | |
d4697af4 | 677 | EXPORT_SYMBOL_GPL(rh_get_stats); |
14cf11af | 678 | |
4c35630c | 679 | int rh_set_owner(rh_info_t * info, unsigned long start, const char *owner) |
14cf11af PM |
680 | { |
681 | rh_block_t *blk, *blk2; | |
682 | struct list_head *l; | |
683 | int size; | |
684 | ||
685 | /* Linear search for block */ | |
686 | blk = NULL; | |
687 | list_for_each(l, &info->taken_list) { | |
688 | blk2 = list_entry(l, rh_block_t, list); | |
689 | if (start < blk2->start) | |
690 | break; | |
691 | blk = blk2; | |
692 | } | |
693 | ||
694 | if (blk == NULL || start > (blk->start + blk->size)) | |
695 | return -EINVAL; | |
696 | ||
697 | blk->owner = owner; | |
698 | size = blk->size; | |
699 | ||
700 | return size; | |
701 | } | |
d4697af4 | 702 | EXPORT_SYMBOL_GPL(rh_set_owner); |
14cf11af PM |
703 | |
704 | void rh_dump(rh_info_t * info) | |
705 | { | |
706 | static rh_stats_t st[32]; /* XXX maximum 32 blocks */ | |
707 | int maxnr; | |
708 | int i, nr; | |
709 | ||
3839a594 | 710 | maxnr = ARRAY_SIZE(st); |
14cf11af PM |
711 | |
712 | printk(KERN_INFO | |
713 | "info @0x%p (%d slots empty / %d max)\n", | |
714 | info, info->empty_slots, info->max_blocks); | |
715 | ||
716 | printk(KERN_INFO " Free:\n"); | |
717 | nr = rh_get_stats(info, RHGS_FREE, maxnr, st); | |
718 | if (nr > maxnr) | |
719 | nr = maxnr; | |
720 | for (i = 0; i < nr; i++) | |
721 | printk(KERN_INFO | |
4c35630c TT |
722 | " 0x%lx-0x%lx (%u)\n", |
723 | st[i].start, st[i].start + st[i].size, | |
14cf11af PM |
724 | st[i].size); |
725 | printk(KERN_INFO "\n"); | |
726 | ||
727 | printk(KERN_INFO " Taken:\n"); | |
728 | nr = rh_get_stats(info, RHGS_TAKEN, maxnr, st); | |
729 | if (nr > maxnr) | |
730 | nr = maxnr; | |
731 | for (i = 0; i < nr; i++) | |
732 | printk(KERN_INFO | |
4c35630c TT |
733 | " 0x%lx-0x%lx (%u) %s\n", |
734 | st[i].start, st[i].start + st[i].size, | |
14cf11af PM |
735 | st[i].size, st[i].owner != NULL ? st[i].owner : ""); |
736 | printk(KERN_INFO "\n"); | |
737 | } | |
d4697af4 | 738 | EXPORT_SYMBOL_GPL(rh_dump); |
14cf11af PM |
739 | |
740 | void rh_dump_blk(rh_info_t * info, rh_block_t * blk) | |
741 | { | |
742 | printk(KERN_INFO | |
4c35630c TT |
743 | "blk @0x%p: 0x%lx-0x%lx (%u)\n", |
744 | blk, blk->start, blk->start + blk->size, blk->size); | |
14cf11af | 745 | } |
d4697af4 SM |
746 | EXPORT_SYMBOL_GPL(rh_dump_blk); |
747 |