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