#include <linux/pfn.h>
#include <linux/rbtree.h>
#include <linux/slab.h>
+#include <linux/spinlock.h>
#include <linux/vmalloc.h>
#include <linux/workqueue.h>
static int pcpu_reserved_chunk_limit;
/*
- * One mutex to rule them all.
- *
- * The following mutex is grabbed in the outermost public alloc/free
- * interface functions and released only when the operation is
- * complete. As such, every function in this file other than the
- * outermost functions are called under pcpu_mutex.
- *
- * It can easily be switched to use spinlock such that only the area
- * allocation and page population commit are protected with it doing
- * actual [de]allocation without holding any lock. However, given
- * what this allocator does, I think it's better to let them run
- * sequentially.
+ * Synchronization rules.
+ *
+ * There are two locks - pcpu_alloc_mutex and pcpu_lock. The former
+ * protects allocation/reclaim paths, chunks and chunk->page arrays.
+ * The latter is a spinlock and protects the index data structures -
+ * chunk slots, rbtree, chunks and area maps in chunks.
+ *
+ * During allocation, pcpu_alloc_mutex is kept locked all the time and
+ * pcpu_lock is grabbed and released as necessary. All actual memory
+ * allocations are done using GFP_KERNEL with pcpu_lock released.
+ *
+ * Free path accesses and alters only the index data structures, so it
+ * can be safely called from atomic context. When memory needs to be
+ * returned to the system, free path schedules reclaim_work which
+ * grabs both pcpu_alloc_mutex and pcpu_lock, unlinks chunks to be
+ * reclaimed, release both locks and frees the chunks. Note that it's
+ * necessary to grab both locks to remove a chunk from circulation as
+ * allocation path might be referencing the chunk with only
+ * pcpu_alloc_mutex locked.
*/
-static DEFINE_MUTEX(pcpu_mutex);
+static DEFINE_MUTEX(pcpu_alloc_mutex); /* protects whole alloc and reclaim */
+static DEFINE_SPINLOCK(pcpu_lock); /* protects index data structures */
static struct list_head *pcpu_slot __read_mostly; /* chunk list slots */
static struct rb_root pcpu_addr_root = RB_ROOT; /* chunks by address */
* kzalloc() is used; otherwise, vmalloc() is used. The returned
* memory is always zeroed.
*
+ * CONTEXT:
+ * Does GFP_KERNEL allocation.
+ *
* RETURNS:
* Pointer to the allocated area on success, NULL on failure.
*/
* New slot according to the changed state is determined and @chunk is
* moved to the slot. Note that the reserved chunk is never put on
* chunk slots.
+ *
+ * CONTEXT:
+ * pcpu_lock.
*/
static void pcpu_chunk_relocate(struct pcpu_chunk *chunk, int oslot)
{
* searchs for the chunk with the highest start address which isn't
* beyond @addr.
*
+ * CONTEXT:
+ * pcpu_lock.
+ *
* RETURNS:
* The address of the found chunk.
*/
* @new: chunk to insert
*
* Insert @new into address rb tree.
+ *
+ * CONTEXT:
+ * pcpu_lock.
*/
static void pcpu_chunk_addr_insert(struct pcpu_chunk *new)
{
* A single allocation can split an area into three areas, so this
* function makes sure that @chunk->map has at least two extra slots.
*
+ * CONTEXT:
+ * pcpu_alloc_mutex, pcpu_lock. pcpu_lock is released and reacquired
+ * if area map is extended.
+ *
* RETURNS:
* 0 if noop, 1 if successfully extended, -errno on failure.
*/
if (chunk->map_alloc >= chunk->map_used + 2)
return 0;
+ spin_unlock_irq(&pcpu_lock);
+
new_alloc = PCPU_DFL_MAP_ALLOC;
while (new_alloc < chunk->map_used + 2)
new_alloc *= 2;
new = pcpu_mem_alloc(new_alloc * sizeof(new[0]));
- if (!new)
+ if (!new) {
+ spin_lock_irq(&pcpu_lock);
return -ENOMEM;
+ }
+
+ /*
+ * Acquire pcpu_lock and switch to new area map. Only free
+ * could have happened inbetween, so map_used couldn't have
+ * grown.
+ */
+ spin_lock_irq(&pcpu_lock);
+ BUG_ON(new_alloc < chunk->map_used + 2);
size = chunk->map_alloc * sizeof(chunk->map[0]);
memcpy(new, chunk->map, size);
* is inserted after the target block.
*
* @chunk->map must have enough free slots to accomodate the split.
+ *
+ * CONTEXT:
+ * pcpu_lock.
*/
static void pcpu_split_block(struct pcpu_chunk *chunk, int i,
int head, int tail)
*
* @chunk->map must have at least two free slots.
*
+ * CONTEXT:
+ * pcpu_lock.
+ *
* RETURNS:
* Allocated offset in @chunk on success, -1 if no matching area is
* found.
* Free area starting from @freeme to @chunk. Note that this function
* only modifies the allocation map. It doesn't depopulate or unmap
* the area.
+ *
+ * CONTEXT:
+ * pcpu_lock.
*/
static void pcpu_free_area(struct pcpu_chunk *chunk, int freeme)
{
* For each cpu, depopulate and unmap pages [@page_start,@page_end)
* from @chunk. If @flush is true, vcache is flushed before unmapping
* and tlb after.
+ *
+ * CONTEXT:
+ * pcpu_alloc_mutex.
*/
static void pcpu_depopulate_chunk(struct pcpu_chunk *chunk, int off, int size,
bool flush)
*
* For each cpu, populate and map pages [@page_start,@page_end) into
* @chunk. The area is cleared on return.
+ *
+ * CONTEXT:
+ * pcpu_alloc_mutex, does GFP_KERNEL allocation.
*/
static int pcpu_populate_chunk(struct pcpu_chunk *chunk, int off, int size)
{
* @align: alignment of area (max PAGE_SIZE)
* @reserved: allocate from the reserved chunk if available
*
- * Allocate percpu area of @size bytes aligned at @align. Might
- * sleep. Might trigger writeouts.
+ * Allocate percpu area of @size bytes aligned at @align.
+ *
+ * CONTEXT:
+ * Does GFP_KERNEL allocation.
*
* RETURNS:
* Percpu pointer to the allocated area on success, NULL on failure.
*/
static void *pcpu_alloc(size_t size, size_t align, bool reserved)
{
- void *ptr = NULL;
struct pcpu_chunk *chunk;
int slot, off;
return NULL;
}
- mutex_lock(&pcpu_mutex);
+ mutex_lock(&pcpu_alloc_mutex);
+ spin_lock_irq(&pcpu_lock);
/* serve reserved allocations from the reserved chunk if available */
if (reserved && pcpu_reserved_chunk) {
chunk = pcpu_reserved_chunk;
if (size > chunk->contig_hint ||
pcpu_extend_area_map(chunk) < 0)
- goto out_unlock;
+ goto fail_unlock;
off = pcpu_alloc_area(chunk, size, align);
if (off >= 0)
goto area_found;
- goto out_unlock;
+ goto fail_unlock;
}
+restart:
/* search through normal chunks */
for (slot = pcpu_size_to_slot(size); slot < pcpu_nr_slots; slot++) {
list_for_each_entry(chunk, &pcpu_slot[slot], list) {
if (size > chunk->contig_hint)
continue;
- if (pcpu_extend_area_map(chunk) < 0)
- goto out_unlock;
+
+ switch (pcpu_extend_area_map(chunk)) {
+ case 0:
+ break;
+ case 1:
+ goto restart; /* pcpu_lock dropped, restart */
+ default:
+ goto fail_unlock;
+ }
+
off = pcpu_alloc_area(chunk, size, align);
if (off >= 0)
goto area_found;
}
/* hmmm... no space left, create a new chunk */
+ spin_unlock_irq(&pcpu_lock);
+
chunk = alloc_pcpu_chunk();
if (!chunk)
- goto out_unlock;
+ goto fail_unlock_mutex;
+
+ spin_lock_irq(&pcpu_lock);
pcpu_chunk_relocate(chunk, -1);
pcpu_chunk_addr_insert(chunk);
-
- off = pcpu_alloc_area(chunk, size, align);
- if (off < 0)
- goto out_unlock;
+ goto restart;
area_found:
+ spin_unlock_irq(&pcpu_lock);
+
/* populate, map and clear the area */
if (pcpu_populate_chunk(chunk, off, size)) {
+ spin_lock_irq(&pcpu_lock);
pcpu_free_area(chunk, off);
- goto out_unlock;
+ goto fail_unlock;
}
- ptr = __addr_to_pcpu_ptr(chunk->vm->addr + off);
-out_unlock:
- mutex_unlock(&pcpu_mutex);
- return ptr;
+ mutex_unlock(&pcpu_alloc_mutex);
+
+ return __addr_to_pcpu_ptr(chunk->vm->addr + off);
+
+fail_unlock:
+ spin_unlock_irq(&pcpu_lock);
+fail_unlock_mutex:
+ mutex_unlock(&pcpu_alloc_mutex);
+ return NULL;
}
/**
* Allocate percpu area of @size bytes aligned at @align. Might
* sleep. Might trigger writeouts.
*
+ * CONTEXT:
+ * Does GFP_KERNEL allocation.
+ *
* RETURNS:
* Percpu pointer to the allocated area on success, NULL on failure.
*/
* percpu area if arch has set it up; otherwise, allocation is served
* from the same dynamic area. Might sleep. Might trigger writeouts.
*
+ * CONTEXT:
+ * Does GFP_KERNEL allocation.
+ *
* RETURNS:
* Percpu pointer to the allocated area on success, NULL on failure.
*/
* @work: unused
*
* Reclaim all fully free chunks except for the first one.
+ *
+ * CONTEXT:
+ * workqueue context.
*/
static void pcpu_reclaim(struct work_struct *work)
{
struct list_head *head = &pcpu_slot[pcpu_nr_slots - 1];
struct pcpu_chunk *chunk, *next;
- mutex_lock(&pcpu_mutex);
+ mutex_lock(&pcpu_alloc_mutex);
+ spin_lock_irq(&pcpu_lock);
list_for_each_entry_safe(chunk, next, head, list) {
WARN_ON(chunk->immutable);
list_move(&chunk->list, &todo);
}
- mutex_unlock(&pcpu_mutex);
+ spin_unlock_irq(&pcpu_lock);
+ mutex_unlock(&pcpu_alloc_mutex);
list_for_each_entry_safe(chunk, next, &todo, list) {
pcpu_depopulate_chunk(chunk, 0, pcpu_unit_size, false);
* free_percpu - free percpu area
* @ptr: pointer to area to free
*
- * Free percpu area @ptr. Might sleep.
+ * Free percpu area @ptr.
+ *
+ * CONTEXT:
+ * Can be called from atomic context.
*/
void free_percpu(void *ptr)
{
void *addr = __pcpu_ptr_to_addr(ptr);
struct pcpu_chunk *chunk;
+ unsigned long flags;
int off;
if (!ptr)
return;
- mutex_lock(&pcpu_mutex);
+ spin_lock_irqsave(&pcpu_lock, flags);
chunk = pcpu_chunk_addr_search(addr);
off = addr - chunk->vm->addr;
}
}
- mutex_unlock(&pcpu_mutex);
+ spin_unlock_irqrestore(&pcpu_lock, flags);
}
EXPORT_SYMBOL_GPL(free_percpu);