/* * Copyright (C) 2012-2017 Red Hat, Inc. * * This file is released under the GPL. */ #include "dm.h" #include "dm-bio-prison-v2.h" #include <linux/spinlock.h> #include <linux/mempool.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/rwsem.h> /*----------------------------------------------------------------*/ #define MIN_CELLS 1024 struct dm_bio_prison_v2 { struct workqueue_struct *wq; spinlock_t lock; struct rb_root cells; mempool_t cell_pool; }; static struct kmem_cache *_cell_cache; /*----------------------------------------------------------------*/ /* * @nr_cells should be the number of cells you want in use _concurrently_. * Don't confuse it with the number of distinct keys. */ struct dm_bio_prison_v2 *dm_bio_prison_create_v2(struct workqueue_struct *wq) { struct dm_bio_prison_v2 *prison = kzalloc(sizeof(*prison), GFP_KERNEL); int ret; if (!prison) return NULL; prison->wq = wq; spin_lock_init(&prison->lock); ret = mempool_init_slab_pool(&prison->cell_pool, MIN_CELLS, _cell_cache); if (ret) { kfree(prison); return NULL; } prison->cells = RB_ROOT; return prison; } EXPORT_SYMBOL_GPL(dm_bio_prison_create_v2); void dm_bio_prison_destroy_v2(struct dm_bio_prison_v2 *prison) { mempool_exit(&prison->cell_pool); kfree(prison); } EXPORT_SYMBOL_GPL(dm_bio_prison_destroy_v2); struct dm_bio_prison_cell_v2 *dm_bio_prison_alloc_cell_v2(struct dm_bio_prison_v2 *prison, gfp_t gfp) { return mempool_alloc(&prison->cell_pool, gfp); } EXPORT_SYMBOL_GPL(dm_bio_prison_alloc_cell_v2); void dm_bio_prison_free_cell_v2(struct dm_bio_prison_v2 *prison, struct dm_bio_prison_cell_v2 *cell) { mempool_free(cell, &prison->cell_pool); } EXPORT_SYMBOL_GPL(dm_bio_prison_free_cell_v2); static void __setup_new_cell(struct dm_cell_key_v2 *key, struct dm_bio_prison_cell_v2 *cell) { memset(cell, 0, sizeof(*cell)); memcpy(&cell->key, key, sizeof(cell->key)); bio_list_init(&cell->bios); } static int cmp_keys(struct dm_cell_key_v2 *lhs, struct dm_cell_key_v2 *rhs) { if (lhs->virtual < rhs->virtual) return -1; if (lhs->virtual > rhs->virtual) return 1; if (lhs->dev < rhs->dev) return -1; if (lhs->dev > rhs->dev) return 1; if (lhs->block_end <= rhs->block_begin) return -1; if (lhs->block_begin >= rhs->block_end) return 1; return 0; } /* * Returns true if node found, otherwise it inserts a new one. */ static bool __find_or_insert(struct dm_bio_prison_v2 *prison, struct dm_cell_key_v2 *key, struct dm_bio_prison_cell_v2 *cell_prealloc, struct dm_bio_prison_cell_v2 **result) { int r; struct rb_node **new = &prison->cells.rb_node, *parent = NULL; while (*new) { struct dm_bio_prison_cell_v2 *cell = rb_entry(*new, struct dm_bio_prison_cell_v2, node); r = cmp_keys(key, &cell->key); parent = *new; if (r < 0) new = &((*new)->rb_left); else if (r > 0) new = &((*new)->rb_right); else { *result = cell; return true; } } __setup_new_cell(key, cell_prealloc); *result = cell_prealloc; rb_link_node(&cell_prealloc->node, parent, new); rb_insert_color(&cell_prealloc->node, &prison->cells); return false; } static bool __get(struct dm_bio_prison_v2 *prison, struct dm_cell_key_v2 *key, unsigned lock_level, struct bio *inmate, struct dm_bio_prison_cell_v2 *cell_prealloc, struct dm_bio_prison_cell_v2 **cell) { if (__find_or_insert(prison, key, cell_prealloc, cell)) { if ((*cell)->exclusive_lock) { if (lock_level <= (*cell)->exclusive_level) { bio_list_add(&(*cell)->bios, inmate); return false; } } (*cell)->shared_count++; } else (*cell)->shared_count = 1; return true; } bool dm_cell_get_v2(struct dm_bio_prison_v2 *prison, struct dm_cell_key_v2 *key, unsigned lock_level, struct bio *inmate, struct dm_bio_prison_cell_v2 *cell_prealloc, struct dm_bio_prison_cell_v2 **cell_result) { int r; unsigned long flags; spin_lock_irqsave(&prison->lock, flags); r = __get(prison, key, lock_level, inmate, cell_prealloc, cell_result); spin_unlock_irqrestore(&prison->lock, flags); return r; } EXPORT_SYMBOL_GPL(dm_cell_get_v2); static bool __put(struct dm_bio_prison_v2 *prison, struct dm_bio_prison_cell_v2 *cell) { BUG_ON(!cell->shared_count); cell->shared_count--; // FIXME: shared locks granted above the lock level could starve this if (!cell->shared_count) { if (cell->exclusive_lock){ if (cell->quiesce_continuation) { queue_work(prison->wq, cell->quiesce_continuation); cell->quiesce_continuation = NULL; } } else { rb_erase(&cell->node, &prison->cells); return true; } } return false; } bool dm_cell_put_v2(struct dm_bio_prison_v2 *prison, struct dm_bio_prison_cell_v2 *cell) { bool r; unsigned long flags; spin_lock_irqsave(&prison->lock, flags); r = __put(prison, cell); spin_unlock_irqrestore(&prison->lock, flags); return r; } EXPORT_SYMBOL_GPL(dm_cell_put_v2); static int __lock(struct dm_bio_prison_v2 *prison, struct dm_cell_key_v2 *key, unsigned lock_level, struct dm_bio_prison_cell_v2 *cell_prealloc, struct dm_bio_prison_cell_v2 **cell_result) { struct dm_bio_prison_cell_v2 *cell; if (__find_or_insert(prison, key, cell_prealloc, &cell)) { if (cell->exclusive_lock) return -EBUSY; cell->exclusive_lock = true; cell->exclusive_level = lock_level; *cell_result = cell; // FIXME: we don't yet know what level these shared locks // were taken at, so have to quiesce them all. return cell->shared_count > 0; } else { cell = cell_prealloc; cell->shared_count = 0; cell->exclusive_lock = true; cell->exclusive_level = lock_level; *cell_result = cell; } return 0; } int dm_cell_lock_v2(struct dm_bio_prison_v2 *prison, struct dm_cell_key_v2 *key, unsigned lock_level, struct dm_bio_prison_cell_v2 *cell_prealloc, struct dm_bio_prison_cell_v2 **cell_result) { int r; unsigned long flags; spin_lock_irqsave(&prison->lock, flags); r = __lock(prison, key, lock_level, cell_prealloc, cell_result); spin_unlock_irqrestore(&prison->lock, flags); return r; } EXPORT_SYMBOL_GPL(dm_cell_lock_v2); static void __quiesce(struct dm_bio_prison_v2 *prison, struct dm_bio_prison_cell_v2 *cell, struct work_struct *continuation) { if (!cell->shared_count) queue_work(prison->wq, continuation); else cell->quiesce_continuation = continuation; } void dm_cell_quiesce_v2(struct dm_bio_prison_v2 *prison, struct dm_bio_prison_cell_v2 *cell, struct work_struct *continuation) { unsigned long flags; spin_lock_irqsave(&prison->lock, flags); __quiesce(prison, cell, continuation); spin_unlock_irqrestore(&prison->lock, flags); } EXPORT_SYMBOL_GPL(dm_cell_quiesce_v2); static int __promote(struct dm_bio_prison_v2 *prison, struct dm_bio_prison_cell_v2 *cell, unsigned new_lock_level) { if (!cell->exclusive_lock) return -EINVAL; cell->exclusive_level = new_lock_level; return cell->shared_count > 0; } int dm_cell_lock_promote_v2(struct dm_bio_prison_v2 *prison, struct dm_bio_prison_cell_v2 *cell, unsigned new_lock_level) { int r; unsigned long flags; spin_lock_irqsave(&prison->lock, flags); r = __promote(prison, cell, new_lock_level); spin_unlock_irqrestore(&prison->lock, flags); return r; } EXPORT_SYMBOL_GPL(dm_cell_lock_promote_v2); static bool __unlock(struct dm_bio_prison_v2 *prison, struct dm_bio_prison_cell_v2 *cell, struct bio_list *bios) { BUG_ON(!cell->exclusive_lock); bio_list_merge(bios, &cell->bios); bio_list_init(&cell->bios); if (cell->shared_count) { cell->exclusive_lock = 0; return false; } rb_erase(&cell->node, &prison->cells); return true; } bool dm_cell_unlock_v2(struct dm_bio_prison_v2 *prison, struct dm_bio_prison_cell_v2 *cell, struct bio_list *bios) { bool r; unsigned long flags; spin_lock_irqsave(&prison->lock, flags); r = __unlock(prison, cell, bios); spin_unlock_irqrestore(&prison->lock, flags); return r; } EXPORT_SYMBOL_GPL(dm_cell_unlock_v2); /*----------------------------------------------------------------*/ int __init dm_bio_prison_init_v2(void) { _cell_cache = KMEM_CACHE(dm_bio_prison_cell_v2, 0); if (!_cell_cache) return -ENOMEM; return 0; } void dm_bio_prison_exit_v2(void) { kmem_cache_destroy(_cell_cache); _cell_cache = NULL; }