| /* |
| * Contiguous Memory Allocator framework: Best Fit allocator |
| * Copyright (c) 2010 by Samsung Electronics. |
| * Written by Michal Nazarewicz (m.nazarewicz@samsung.com) |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License as |
| * published by the Free Software Foundation; either version 2 of the |
| * License or (at your optional) any later version of the license. |
| */ |
| |
| #define pr_fmt(fmt) "cma: bf: " fmt |
| |
| #ifdef CONFIG_CMA_DEBUG |
| # define DEBUG |
| #endif |
| |
| #include <linux/errno.h> /* Error numbers */ |
| #include <linux/slab.h> /* kmalloc() */ |
| |
| #include <linux/cma.h> /* CMA structures */ |
| |
| |
| /************************* Data Types *************************/ |
| |
| struct cma_bf_item { |
| struct cma_chunk ch; |
| struct rb_node by_size; |
| }; |
| |
| struct cma_bf_private { |
| struct rb_root by_start_root; |
| struct rb_root by_size_root; |
| }; |
| |
| |
| /************************* Prototypes *************************/ |
| |
| /* |
| * Those are only for holes. They must be called whenever hole's |
| * properties change but also whenever chunk becomes a hole or hole |
| * becames a chunk. |
| */ |
| static void __cma_bf_hole_insert_by_size(struct cma_bf_item *item); |
| static void __cma_bf_hole_erase_by_size(struct cma_bf_item *item); |
| static int __must_check |
| __cma_bf_hole_insert_by_start(struct cma_bf_item *item); |
| static void __cma_bf_hole_erase_by_start(struct cma_bf_item *item); |
| |
| /** |
| * __cma_bf_hole_take - takes a chunk of memory out of a hole. |
| * @hole: hole to take chunk from |
| * @size: chunk's size |
| * @alignment: chunk's starting address alignment (must be power of two) |
| * |
| * Takes a @size bytes large chunk from hole @hole which must be able |
| * to hold the chunk. The "must be able" includes also alignment |
| * constraint. |
| * |
| * Returns allocated item or NULL on error (if kmalloc() failed). |
| */ |
| static struct cma_bf_item *__must_check |
| __cma_bf_hole_take(struct cma_bf_item *hole, size_t size, dma_addr_t alignment); |
| |
| /** |
| * __cma_bf_hole_merge_maybe - tries to merge hole with neighbours. |
| * @item: hole to try and merge |
| * |
| * Which items are preserved is undefined so you may not rely on it. |
| */ |
| static void __cma_bf_hole_merge_maybe(struct cma_bf_item *item); |
| |
| |
| /************************* Device API *************************/ |
| |
| int cma_bf_init(struct cma_region *reg) |
| { |
| struct cma_bf_private *prv; |
| struct cma_bf_item *item; |
| |
| prv = kzalloc(sizeof *prv, GFP_KERNEL); |
| if (unlikely(!prv)) |
| return -ENOMEM; |
| |
| item = kzalloc(sizeof *item, GFP_KERNEL); |
| if (unlikely(!item)) { |
| kfree(prv); |
| return -ENOMEM; |
| } |
| |
| item->ch.start = reg->start; |
| item->ch.size = reg->size; |
| item->ch.reg = reg; |
| |
| rb_root_init(&prv->by_start_root, &item->ch.by_start); |
| rb_root_init(&prv->by_size_root, &item->by_size); |
| |
| reg->private_data = prv; |
| return 0; |
| } |
| |
| void cma_bf_cleanup(struct cma_region *reg) |
| { |
| struct cma_bf_private *prv = reg->private_data; |
| struct cma_bf_item *item = |
| rb_entry(prv->by_size_root.rb_node, |
| struct cma_bf_item, by_size); |
| |
| /* We can assume there is only a single hole in the tree. */ |
| WARN_ON(item->by_size.rb_left || item->by_size.rb_right || |
| item->ch.by_start.rb_left || item->ch.by_start.rb_right); |
| |
| kfree(item); |
| kfree(prv); |
| } |
| |
| struct cma_chunk *cma_bf_alloc(struct cma_region *reg, |
| size_t size, dma_addr_t alignment) |
| { |
| struct cma_bf_private *prv = reg->private_data; |
| struct rb_node *node = prv->by_size_root.rb_node; |
| struct cma_bf_item *item = NULL; |
| |
| /* First find hole that is large enough */ |
| while (node) { |
| struct cma_bf_item *i = |
| rb_entry(node, struct cma_bf_item, by_size); |
| |
| if (i->ch.size < size) { |
| node = node->rb_right; |
| } else if (i->ch.size >= size) { |
| node = node->rb_left; |
| item = i; |
| } |
| } |
| if (!item) |
| return NULL; |
| |
| /* Now look for items which can satisfy alignment requirements */ |
| node = &item->by_size; |
| for (;;) { |
| dma_addr_t start = ALIGN(item->ch.start, alignment); |
| dma_addr_t end = item->ch.start + item->ch.size; |
| if (start < end && end - start >= size) { |
| item = __cma_bf_hole_take(item, size, alignment); |
| return likely(item) ? &item->ch : NULL; |
| } |
| |
| node = rb_next(node); |
| if (!node) |
| return NULL; |
| |
| item = rb_entry(node, struct cma_bf_item, by_size); |
| } |
| } |
| |
| void cma_bf_free(struct cma_chunk *chunk) |
| { |
| struct cma_bf_item *item = container_of(chunk, struct cma_bf_item, ch); |
| |
| /* Add new hole */ |
| if (unlikely(__cma_bf_hole_insert_by_start(item))) { |
| /* |
| * We're screwed... Just free the item and forget |
| * about it. Things are broken beyond repair so no |
| * sense in trying to recover. |
| */ |
| kfree(item); |
| } else { |
| __cma_bf_hole_insert_by_size(item); |
| |
| /* Merge with prev and next sibling */ |
| __cma_bf_hole_merge_maybe(item); |
| } |
| } |
| |
| |
| /************************* Basic Tree Manipulation *************************/ |
| |
| static void __cma_bf_hole_insert_by_size(struct cma_bf_item *item) |
| { |
| struct cma_bf_private *prv = item->ch.reg->private_data; |
| struct rb_node **link = &prv->by_size_root.rb_node, *parent = NULL; |
| const typeof(item->ch.size) value = item->ch.size; |
| |
| while (*link) { |
| struct cma_bf_item *i; |
| parent = *link; |
| i = rb_entry(parent, struct cma_bf_item, by_size); |
| link = value <= i->ch.size |
| ? &parent->rb_left |
| : &parent->rb_right; |
| } |
| |
| rb_link_node(&item->by_size, parent, link); |
| rb_insert_color(&item->by_size, &prv->by_size_root); |
| } |
| |
| static void __cma_bf_hole_erase_by_size(struct cma_bf_item *item) |
| { |
| struct cma_bf_private *prv = item->ch.reg->private_data; |
| rb_erase(&item->by_size, &prv->by_size_root); |
| } |
| |
| static int __must_check |
| __cma_bf_hole_insert_by_start(struct cma_bf_item *item) |
| { |
| struct cma_bf_private *prv = item->ch.reg->private_data; |
| struct rb_node **link = &prv->by_start_root.rb_node, *parent = NULL; |
| const typeof(item->ch.start) value = item->ch.start; |
| |
| while (*link) { |
| struct cma_bf_item *i; |
| parent = *link; |
| i = rb_entry(parent, struct cma_bf_item, ch.by_start); |
| |
| if (WARN_ON(value == i->ch.start)) |
| /* |
| * This should *never* happen. And I mean |
| * *never*. We could even BUG on it but |
| * hopefully things are only a bit broken, |
| * ie. system can still run. We produce |
| * a warning and return an error. |
| */ |
| return -EBUSY; |
| |
| link = value <= i->ch.start |
| ? &parent->rb_left |
| : &parent->rb_right; |
| } |
| |
| rb_link_node(&item->ch.by_start, parent, link); |
| rb_insert_color(&item->ch.by_start, &prv->by_start_root); |
| return 0; |
| } |
| |
| static void __cma_bf_hole_erase_by_start(struct cma_bf_item *item) |
| { |
| struct cma_bf_private *prv = item->ch.reg->private_data; |
| rb_erase(&item->ch.by_start, &prv->by_start_root); |
| } |
| |
| |
| /************************* More Tree Manipulation *************************/ |
| |
| static struct cma_bf_item *__must_check |
| __cma_bf_hole_take(struct cma_bf_item *hole, size_t size, size_t alignment) |
| { |
| struct cma_bf_item *item; |
| |
| /* |
| * There are three cases: |
| * 1. the chunk takes the whole hole, |
| * 2. the chunk is at the beginning or at the end of the hole, or |
| * 3. the chunk is in the middle of the hole. |
| */ |
| |
| |
| /* Case 1, the whole hole */ |
| if (size == hole->ch.size) { |
| __cma_bf_hole_erase_by_size(hole); |
| __cma_bf_hole_erase_by_start(hole); |
| return hole; |
| } |
| |
| |
| /* Allocate */ |
| item = kmalloc(sizeof *item, GFP_KERNEL); |
| if (unlikely(!item)) |
| return NULL; |
| |
| item->ch.start = ALIGN(hole->ch.start, alignment); |
| item->ch.size = size; |
| |
| /* Case 3, in the middle */ |
| if (item->ch.start != hole->ch.start |
| && item->ch.start + item->ch.size != |
| hole->ch.start + hole->ch.size) { |
| struct cma_bf_item *tail; |
| |
| /* |
| * Space between the end of the chunk and the end of |
| * the region, ie. space left after the end of the |
| * chunk. If this is dividable by alignment we can |
| * move the chunk to the end of the hole. |
| */ |
| size_t left = |
| hole->ch.start + hole->ch.size - |
| (item->ch.start + item->ch.size); |
| if (left % alignment == 0) { |
| item->ch.start += left; |
| goto case_2; |
| } |
| |
| /* |
| * We are going to add a hole at the end. This way, |
| * we will reduce the problem to case 2 -- the chunk |
| * will be at the end of the hole. |
| */ |
| tail = kmalloc(sizeof *tail, GFP_KERNEL); |
| if (unlikely(!tail)) { |
| kfree(item); |
| return NULL; |
| } |
| |
| tail->ch.start = item->ch.start + item->ch.size; |
| tail->ch.size = |
| hole->ch.start + hole->ch.size - tail->ch.start; |
| tail->ch.reg = hole->ch.reg; |
| |
| if (unlikely(__cma_bf_hole_insert_by_start(tail))) { |
| /* |
| * Things are broken beyond repair... Abort |
| * inserting the hole but still continue with |
| * allocation (seems like the best we can do). |
| */ |
| |
| hole->ch.size = tail->ch.start - hole->ch.start; |
| kfree(tail); |
| } else { |
| __cma_bf_hole_insert_by_size(tail); |
| /* |
| * It's important that we first insert the new |
| * hole in the tree sorted by size and later |
| * reduce the size of the old hole. We will |
| * update the position of the old hole in the |
| * rb tree in code that handles case 2. |
| */ |
| hole->ch.size = tail->ch.start - hole->ch.start; |
| } |
| |
| /* Go to case 2 */ |
| } |
| |
| |
| /* Case 2, at the beginning or at the end */ |
| case_2: |
| /* No need to update the tree; order preserved. */ |
| if (item->ch.start == hole->ch.start) |
| hole->ch.start += item->ch.size; |
| |
| /* Alter hole's size */ |
| hole->ch.size -= size; |
| __cma_bf_hole_erase_by_size(hole); |
| __cma_bf_hole_insert_by_size(hole); |
| |
| return item; |
| } |
| |
| |
| static void __cma_bf_hole_merge_maybe(struct cma_bf_item *item) |
| { |
| struct cma_bf_item *prev; |
| struct rb_node *node; |
| int twice = 2; |
| |
| node = rb_prev(&item->ch.by_start); |
| if (unlikely(!node)) |
| goto next; |
| prev = rb_entry(node, struct cma_bf_item, ch.by_start); |
| |
| for (;;) { |
| if (prev->ch.start + prev->ch.size == item->ch.start) { |
| /* Remove previous hole from trees */ |
| __cma_bf_hole_erase_by_size(prev); |
| __cma_bf_hole_erase_by_start(prev); |
| |
| /* Alter this hole */ |
| item->ch.size += prev->ch.size; |
| item->ch.start = prev->ch.start; |
| __cma_bf_hole_erase_by_size(item); |
| __cma_bf_hole_insert_by_size(item); |
| /* |
| * No need to update by start trees as we do |
| * not break sequence order |
| */ |
| |
| /* Free prev hole */ |
| kfree(prev); |
| } |
| |
| next: |
| if (!--twice) |
| break; |
| |
| node = rb_next(&item->ch.by_start); |
| if (unlikely(!node)) |
| break; |
| prev = item; |
| item = rb_entry(node, struct cma_bf_item, ch.by_start); |
| } |
| } |
| |
| |
| |
| /************************* Register *************************/ |
| static int cma_bf_module_init(void) |
| { |
| static struct cma_allocator alloc = { |
| .name = "bf", |
| .init = cma_bf_init, |
| .cleanup = cma_bf_cleanup, |
| .alloc = cma_bf_alloc, |
| .free = cma_bf_free, |
| }; |
| return cma_allocator_register(&alloc); |
| } |
| module_init(cma_bf_module_init); |