2011-10-31 20:19:11 +00:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2011 Red Hat, Inc.
|
|
|
|
*
|
|
|
|
* This file is released under the GPL.
|
|
|
|
*/
|
|
|
|
#include "dm-block-manager.h"
|
|
|
|
#include "dm-persistent-data-internal.h"
|
|
|
|
#include "../dm-bufio.h"
|
|
|
|
|
|
|
|
#include <linux/crc32c.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/rwsem.h>
|
|
|
|
#include <linux/device-mapper.h>
|
|
|
|
#include <linux/stacktrace.h>
|
2017-02-05 14:30:50 +00:00
|
|
|
#include <linux/sched/task.h>
|
2011-10-31 20:19:11 +00:00
|
|
|
|
|
|
|
#define DM_MSG_PREFIX "block manager"
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------*/
|
|
|
|
|
2015-11-19 13:50:32 +00:00
|
|
|
#ifdef CONFIG_DM_DEBUG_BLOCK_MANAGER_LOCKING
|
|
|
|
|
2011-10-31 20:19:11 +00:00
|
|
|
/*
|
|
|
|
* This is a read/write semaphore with a couple of differences.
|
|
|
|
*
|
|
|
|
* i) There is a restriction on the number of concurrent read locks that
|
|
|
|
* may be held at once. This is just an implementation detail.
|
|
|
|
*
|
|
|
|
* ii) Recursive locking attempts are detected and return EINVAL. A stack
|
2012-10-29 15:18:08 +00:00
|
|
|
* trace is also emitted for the previous lock acquisition.
|
2011-10-31 20:19:11 +00:00
|
|
|
*
|
|
|
|
* iii) Priority is given to write locks.
|
|
|
|
*/
|
|
|
|
#define MAX_HOLDERS 4
|
|
|
|
#define MAX_STACK 10
|
|
|
|
|
|
|
|
typedef unsigned long stack_entries[MAX_STACK];
|
|
|
|
|
|
|
|
struct block_lock {
|
|
|
|
spinlock_t lock;
|
|
|
|
__s32 count;
|
|
|
|
struct list_head waiters;
|
|
|
|
struct task_struct *holders[MAX_HOLDERS];
|
|
|
|
|
|
|
|
#ifdef CONFIG_DM_DEBUG_BLOCK_STACK_TRACING
|
|
|
|
struct stack_trace traces[MAX_HOLDERS];
|
|
|
|
stack_entries entries[MAX_HOLDERS];
|
|
|
|
#endif
|
|
|
|
};
|
|
|
|
|
|
|
|
struct waiter {
|
|
|
|
struct list_head list;
|
|
|
|
struct task_struct *task;
|
|
|
|
int wants_write;
|
|
|
|
};
|
|
|
|
|
|
|
|
static unsigned __find_holder(struct block_lock *lock,
|
|
|
|
struct task_struct *task)
|
|
|
|
{
|
|
|
|
unsigned i;
|
|
|
|
|
|
|
|
for (i = 0; i < MAX_HOLDERS; i++)
|
|
|
|
if (lock->holders[i] == task)
|
|
|
|
break;
|
|
|
|
|
|
|
|
BUG_ON(i == MAX_HOLDERS);
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* call this *after* you increment lock->count */
|
|
|
|
static void __add_holder(struct block_lock *lock, struct task_struct *task)
|
|
|
|
{
|
|
|
|
unsigned h = __find_holder(lock, NULL);
|
|
|
|
#ifdef CONFIG_DM_DEBUG_BLOCK_STACK_TRACING
|
|
|
|
struct stack_trace *t;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
get_task_struct(task);
|
|
|
|
lock->holders[h] = task;
|
|
|
|
|
|
|
|
#ifdef CONFIG_DM_DEBUG_BLOCK_STACK_TRACING
|
|
|
|
t = lock->traces + h;
|
|
|
|
t->nr_entries = 0;
|
|
|
|
t->max_entries = MAX_STACK;
|
|
|
|
t->entries = lock->entries[h];
|
|
|
|
t->skip = 2;
|
|
|
|
save_stack_trace(t);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
/* call this *before* you decrement lock->count */
|
|
|
|
static void __del_holder(struct block_lock *lock, struct task_struct *task)
|
|
|
|
{
|
|
|
|
unsigned h = __find_holder(lock, task);
|
|
|
|
lock->holders[h] = NULL;
|
|
|
|
put_task_struct(task);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __check_holder(struct block_lock *lock)
|
|
|
|
{
|
|
|
|
unsigned i;
|
|
|
|
|
|
|
|
for (i = 0; i < MAX_HOLDERS; i++) {
|
|
|
|
if (lock->holders[i] == current) {
|
2013-12-13 13:24:44 +00:00
|
|
|
DMERR("recursive lock detected in metadata");
|
2011-10-31 20:19:11 +00:00
|
|
|
#ifdef CONFIG_DM_DEBUG_BLOCK_STACK_TRACING
|
|
|
|
DMERR("previously held here:");
|
|
|
|
print_stack_trace(lock->traces + i, 4);
|
|
|
|
|
2012-10-29 15:18:08 +00:00
|
|
|
DMERR("subsequent acquisition attempted here:");
|
2015-11-24 00:12:05 +00:00
|
|
|
dump_stack();
|
2011-10-31 20:19:11 +00:00
|
|
|
#endif
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __wait(struct waiter *w)
|
|
|
|
{
|
|
|
|
for (;;) {
|
sched/core: Remove set_task_state()
This is a nasty interface and setting the state of a foreign task must
not be done. As of the following commit:
be628be0956 ("bcache: Make gc wakeup sane, remove set_task_state()")
... everyone in the kernel calls set_task_state() with current, allowing
the helper to be removed.
However, as the comment indicates, it is still around for those archs
where computing current is more expensive than using a pointer, at least
in theory. An important arch that is affected is arm64, however this has
been addressed now [1] and performance is up to par making no difference
with either calls.
Of all the callers, if any, it's the locking bits that would care most
about this -- ie: we end up passing a tsk pointer to a lot of the lock
slowpath, and setting ->state on that. The following numbers are based
on two tests: a custom ad-hoc microbenchmark that just measures
latencies (for ~65 million calls) between get_task_state() vs
get_current_state().
Secondly for a higher overview, an unlink microbenchmark was used,
which pounds on a single file with open, close,unlink combos with
increasing thread counts (up to 4x ncpus). While the workload is quite
unrealistic, it does contend a lot on the inode mutex or now rwsem.
[1] https://lkml.kernel.org/r/1483468021-8237-1-git-send-email-mark.rutland@arm.com
== 1. x86-64 ==
Avg runtime set_task_state(): 601 msecs
Avg runtime set_current_state(): 552 msecs
vanilla dirty
Hmean unlink1-processes-2 36089.26 ( 0.00%) 38977.33 ( 8.00%)
Hmean unlink1-processes-5 28555.01 ( 0.00%) 29832.55 ( 4.28%)
Hmean unlink1-processes-8 37323.75 ( 0.00%) 44974.57 ( 20.50%)
Hmean unlink1-processes-12 43571.88 ( 0.00%) 44283.01 ( 1.63%)
Hmean unlink1-processes-21 34431.52 ( 0.00%) 38284.45 ( 11.19%)
Hmean unlink1-processes-30 34813.26 ( 0.00%) 37975.17 ( 9.08%)
Hmean unlink1-processes-48 37048.90 ( 0.00%) 39862.78 ( 7.59%)
Hmean unlink1-processes-79 35630.01 ( 0.00%) 36855.30 ( 3.44%)
Hmean unlink1-processes-110 36115.85 ( 0.00%) 39843.91 ( 10.32%)
Hmean unlink1-processes-141 32546.96 ( 0.00%) 35418.52 ( 8.82%)
Hmean unlink1-processes-172 34674.79 ( 0.00%) 36899.21 ( 6.42%)
Hmean unlink1-processes-203 37303.11 ( 0.00%) 36393.04 ( -2.44%)
Hmean unlink1-processes-224 35712.13 ( 0.00%) 36685.96 ( 2.73%)
== 2. ppc64le ==
Avg runtime set_task_state(): 938 msecs
Avg runtime set_current_state: 940 msecs
vanilla dirty
Hmean unlink1-processes-2 19269.19 ( 0.00%) 30704.50 ( 59.35%)
Hmean unlink1-processes-5 20106.15 ( 0.00%) 21804.15 ( 8.45%)
Hmean unlink1-processes-8 17496.97 ( 0.00%) 17243.28 ( -1.45%)
Hmean unlink1-processes-12 14224.15 ( 0.00%) 17240.21 ( 21.20%)
Hmean unlink1-processes-21 14155.66 ( 0.00%) 15681.23 ( 10.78%)
Hmean unlink1-processes-30 14450.70 ( 0.00%) 15995.83 ( 10.69%)
Hmean unlink1-processes-48 16945.57 ( 0.00%) 16370.42 ( -3.39%)
Hmean unlink1-processes-79 15788.39 ( 0.00%) 14639.27 ( -7.28%)
Hmean unlink1-processes-110 14268.48 ( 0.00%) 14377.40 ( 0.76%)
Hmean unlink1-processes-141 14023.65 ( 0.00%) 16271.69 ( 16.03%)
Hmean unlink1-processes-172 13417.62 ( 0.00%) 16067.55 ( 19.75%)
Hmean unlink1-processes-203 15293.08 ( 0.00%) 15440.40 ( 0.96%)
Hmean unlink1-processes-234 13719.32 ( 0.00%) 16190.74 ( 18.01%)
Hmean unlink1-processes-265 16400.97 ( 0.00%) 16115.22 ( -1.74%)
Hmean unlink1-processes-296 14388.60 ( 0.00%) 16216.13 ( 12.70%)
Hmean unlink1-processes-320 15771.85 ( 0.00%) 15905.96 ( 0.85%)
x86-64 (known to be fast for get_current()/this_cpu_read_stable() caching)
and ppc64 (with paca) show similar improvements in the unlink microbenches.
The small delta for ppc64 (2ms), does not represent the gains on the unlink
runs. In the case of x86, there was a decent amount of variation in the
latency runs, but always within a 20 to 50ms increase), ppc was more constant.
Signed-off-by: Davidlohr Bueso <dbueso@suse.de>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: dave@stgolabs.net
Cc: mark.rutland@arm.com
Link: http://lkml.kernel.org/r/1483479794-14013-5-git-send-email-dave@stgolabs.net
Signed-off-by: Ingo Molnar <mingo@kernel.org>
2017-01-03 21:43:14 +00:00
|
|
|
set_current_state(TASK_UNINTERRUPTIBLE);
|
2011-10-31 20:19:11 +00:00
|
|
|
|
|
|
|
if (!w->task)
|
|
|
|
break;
|
|
|
|
|
|
|
|
schedule();
|
|
|
|
}
|
|
|
|
|
sched/core: Remove set_task_state()
This is a nasty interface and setting the state of a foreign task must
not be done. As of the following commit:
be628be0956 ("bcache: Make gc wakeup sane, remove set_task_state()")
... everyone in the kernel calls set_task_state() with current, allowing
the helper to be removed.
However, as the comment indicates, it is still around for those archs
where computing current is more expensive than using a pointer, at least
in theory. An important arch that is affected is arm64, however this has
been addressed now [1] and performance is up to par making no difference
with either calls.
Of all the callers, if any, it's the locking bits that would care most
about this -- ie: we end up passing a tsk pointer to a lot of the lock
slowpath, and setting ->state on that. The following numbers are based
on two tests: a custom ad-hoc microbenchmark that just measures
latencies (for ~65 million calls) between get_task_state() vs
get_current_state().
Secondly for a higher overview, an unlink microbenchmark was used,
which pounds on a single file with open, close,unlink combos with
increasing thread counts (up to 4x ncpus). While the workload is quite
unrealistic, it does contend a lot on the inode mutex or now rwsem.
[1] https://lkml.kernel.org/r/1483468021-8237-1-git-send-email-mark.rutland@arm.com
== 1. x86-64 ==
Avg runtime set_task_state(): 601 msecs
Avg runtime set_current_state(): 552 msecs
vanilla dirty
Hmean unlink1-processes-2 36089.26 ( 0.00%) 38977.33 ( 8.00%)
Hmean unlink1-processes-5 28555.01 ( 0.00%) 29832.55 ( 4.28%)
Hmean unlink1-processes-8 37323.75 ( 0.00%) 44974.57 ( 20.50%)
Hmean unlink1-processes-12 43571.88 ( 0.00%) 44283.01 ( 1.63%)
Hmean unlink1-processes-21 34431.52 ( 0.00%) 38284.45 ( 11.19%)
Hmean unlink1-processes-30 34813.26 ( 0.00%) 37975.17 ( 9.08%)
Hmean unlink1-processes-48 37048.90 ( 0.00%) 39862.78 ( 7.59%)
Hmean unlink1-processes-79 35630.01 ( 0.00%) 36855.30 ( 3.44%)
Hmean unlink1-processes-110 36115.85 ( 0.00%) 39843.91 ( 10.32%)
Hmean unlink1-processes-141 32546.96 ( 0.00%) 35418.52 ( 8.82%)
Hmean unlink1-processes-172 34674.79 ( 0.00%) 36899.21 ( 6.42%)
Hmean unlink1-processes-203 37303.11 ( 0.00%) 36393.04 ( -2.44%)
Hmean unlink1-processes-224 35712.13 ( 0.00%) 36685.96 ( 2.73%)
== 2. ppc64le ==
Avg runtime set_task_state(): 938 msecs
Avg runtime set_current_state: 940 msecs
vanilla dirty
Hmean unlink1-processes-2 19269.19 ( 0.00%) 30704.50 ( 59.35%)
Hmean unlink1-processes-5 20106.15 ( 0.00%) 21804.15 ( 8.45%)
Hmean unlink1-processes-8 17496.97 ( 0.00%) 17243.28 ( -1.45%)
Hmean unlink1-processes-12 14224.15 ( 0.00%) 17240.21 ( 21.20%)
Hmean unlink1-processes-21 14155.66 ( 0.00%) 15681.23 ( 10.78%)
Hmean unlink1-processes-30 14450.70 ( 0.00%) 15995.83 ( 10.69%)
Hmean unlink1-processes-48 16945.57 ( 0.00%) 16370.42 ( -3.39%)
Hmean unlink1-processes-79 15788.39 ( 0.00%) 14639.27 ( -7.28%)
Hmean unlink1-processes-110 14268.48 ( 0.00%) 14377.40 ( 0.76%)
Hmean unlink1-processes-141 14023.65 ( 0.00%) 16271.69 ( 16.03%)
Hmean unlink1-processes-172 13417.62 ( 0.00%) 16067.55 ( 19.75%)
Hmean unlink1-processes-203 15293.08 ( 0.00%) 15440.40 ( 0.96%)
Hmean unlink1-processes-234 13719.32 ( 0.00%) 16190.74 ( 18.01%)
Hmean unlink1-processes-265 16400.97 ( 0.00%) 16115.22 ( -1.74%)
Hmean unlink1-processes-296 14388.60 ( 0.00%) 16216.13 ( 12.70%)
Hmean unlink1-processes-320 15771.85 ( 0.00%) 15905.96 ( 0.85%)
x86-64 (known to be fast for get_current()/this_cpu_read_stable() caching)
and ppc64 (with paca) show similar improvements in the unlink microbenches.
The small delta for ppc64 (2ms), does not represent the gains on the unlink
runs. In the case of x86, there was a decent amount of variation in the
latency runs, but always within a 20 to 50ms increase), ppc was more constant.
Signed-off-by: Davidlohr Bueso <dbueso@suse.de>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: dave@stgolabs.net
Cc: mark.rutland@arm.com
Link: http://lkml.kernel.org/r/1483479794-14013-5-git-send-email-dave@stgolabs.net
Signed-off-by: Ingo Molnar <mingo@kernel.org>
2017-01-03 21:43:14 +00:00
|
|
|
set_current_state(TASK_RUNNING);
|
2011-10-31 20:19:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void __wake_waiter(struct waiter *w)
|
|
|
|
{
|
|
|
|
struct task_struct *task;
|
|
|
|
|
|
|
|
list_del(&w->list);
|
|
|
|
task = w->task;
|
|
|
|
smp_mb();
|
|
|
|
w->task = NULL;
|
|
|
|
wake_up_process(task);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We either wake a few readers or a single writer.
|
|
|
|
*/
|
|
|
|
static void __wake_many(struct block_lock *lock)
|
|
|
|
{
|
|
|
|
struct waiter *w, *tmp;
|
|
|
|
|
|
|
|
BUG_ON(lock->count < 0);
|
|
|
|
list_for_each_entry_safe(w, tmp, &lock->waiters, list) {
|
|
|
|
if (lock->count >= MAX_HOLDERS)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (w->wants_write) {
|
|
|
|
if (lock->count > 0)
|
|
|
|
return; /* still read locked */
|
|
|
|
|
|
|
|
lock->count = -1;
|
|
|
|
__add_holder(lock, w->task);
|
|
|
|
__wake_waiter(w);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
lock->count++;
|
|
|
|
__add_holder(lock, w->task);
|
|
|
|
__wake_waiter(w);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void bl_init(struct block_lock *lock)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
spin_lock_init(&lock->lock);
|
|
|
|
lock->count = 0;
|
|
|
|
INIT_LIST_HEAD(&lock->waiters);
|
|
|
|
for (i = 0; i < MAX_HOLDERS; i++)
|
|
|
|
lock->holders[i] = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __available_for_read(struct block_lock *lock)
|
|
|
|
{
|
|
|
|
return lock->count >= 0 &&
|
|
|
|
lock->count < MAX_HOLDERS &&
|
|
|
|
list_empty(&lock->waiters);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int bl_down_read(struct block_lock *lock)
|
|
|
|
{
|
|
|
|
int r;
|
|
|
|
struct waiter w;
|
|
|
|
|
|
|
|
spin_lock(&lock->lock);
|
|
|
|
r = __check_holder(lock);
|
|
|
|
if (r) {
|
|
|
|
spin_unlock(&lock->lock);
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (__available_for_read(lock)) {
|
|
|
|
lock->count++;
|
|
|
|
__add_holder(lock, current);
|
|
|
|
spin_unlock(&lock->lock);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
get_task_struct(current);
|
|
|
|
|
|
|
|
w.task = current;
|
|
|
|
w.wants_write = 0;
|
|
|
|
list_add_tail(&w.list, &lock->waiters);
|
|
|
|
spin_unlock(&lock->lock);
|
|
|
|
|
|
|
|
__wait(&w);
|
|
|
|
put_task_struct(current);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int bl_down_read_nonblock(struct block_lock *lock)
|
|
|
|
{
|
|
|
|
int r;
|
|
|
|
|
|
|
|
spin_lock(&lock->lock);
|
|
|
|
r = __check_holder(lock);
|
|
|
|
if (r)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
if (__available_for_read(lock)) {
|
|
|
|
lock->count++;
|
|
|
|
__add_holder(lock, current);
|
|
|
|
r = 0;
|
|
|
|
} else
|
|
|
|
r = -EWOULDBLOCK;
|
|
|
|
|
|
|
|
out:
|
|
|
|
spin_unlock(&lock->lock);
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void bl_up_read(struct block_lock *lock)
|
|
|
|
{
|
|
|
|
spin_lock(&lock->lock);
|
|
|
|
BUG_ON(lock->count <= 0);
|
|
|
|
__del_holder(lock, current);
|
|
|
|
--lock->count;
|
|
|
|
if (!list_empty(&lock->waiters))
|
|
|
|
__wake_many(lock);
|
|
|
|
spin_unlock(&lock->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int bl_down_write(struct block_lock *lock)
|
|
|
|
{
|
|
|
|
int r;
|
|
|
|
struct waiter w;
|
|
|
|
|
|
|
|
spin_lock(&lock->lock);
|
|
|
|
r = __check_holder(lock);
|
|
|
|
if (r) {
|
|
|
|
spin_unlock(&lock->lock);
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lock->count == 0 && list_empty(&lock->waiters)) {
|
|
|
|
lock->count = -1;
|
|
|
|
__add_holder(lock, current);
|
|
|
|
spin_unlock(&lock->lock);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
get_task_struct(current);
|
|
|
|
w.task = current;
|
|
|
|
w.wants_write = 1;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Writers given priority. We know there's only one mutator in the
|
|
|
|
* system, so ignoring the ordering reversal.
|
|
|
|
*/
|
|
|
|
list_add(&w.list, &lock->waiters);
|
|
|
|
spin_unlock(&lock->lock);
|
|
|
|
|
|
|
|
__wait(&w);
|
|
|
|
put_task_struct(current);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void bl_up_write(struct block_lock *lock)
|
|
|
|
{
|
|
|
|
spin_lock(&lock->lock);
|
|
|
|
__del_holder(lock, current);
|
|
|
|
lock->count = 0;
|
|
|
|
if (!list_empty(&lock->waiters))
|
|
|
|
__wake_many(lock);
|
|
|
|
spin_unlock(&lock->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void report_recursive_bug(dm_block_t b, int r)
|
|
|
|
{
|
|
|
|
if (r == -EINVAL)
|
|
|
|
DMERR("recursive acquisition of block %llu requested.",
|
|
|
|
(unsigned long long) b);
|
|
|
|
}
|
|
|
|
|
2015-11-19 13:50:32 +00:00
|
|
|
#else /* !CONFIG_DM_DEBUG_BLOCK_MANAGER_LOCKING */
|
|
|
|
|
|
|
|
#define bl_init(x) do { } while (0)
|
|
|
|
#define bl_down_read(x) 0
|
|
|
|
#define bl_down_read_nonblock(x) 0
|
|
|
|
#define bl_up_read(x) do { } while (0)
|
|
|
|
#define bl_down_write(x) 0
|
|
|
|
#define bl_up_write(x) do { } while (0)
|
|
|
|
#define report_recursive_bug(x, y) do { } while (0)
|
|
|
|
|
|
|
|
#endif /* CONFIG_DM_DEBUG_BLOCK_MANAGER_LOCKING */
|
|
|
|
|
2011-10-31 20:19:11 +00:00
|
|
|
/*----------------------------------------------------------------*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Block manager is currently implemented using dm-bufio. struct
|
|
|
|
* dm_block_manager and struct dm_block map directly onto a couple of
|
|
|
|
* structs in the bufio interface. I want to retain the freedom to move
|
|
|
|
* away from bufio in the future. So these structs are just cast within
|
|
|
|
* this .c file, rather than making it through to the public interface.
|
|
|
|
*/
|
|
|
|
static struct dm_buffer *to_buffer(struct dm_block *b)
|
|
|
|
{
|
|
|
|
return (struct dm_buffer *) b;
|
|
|
|
}
|
|
|
|
|
|
|
|
dm_block_t dm_block_location(struct dm_block *b)
|
|
|
|
{
|
|
|
|
return dm_bufio_get_block_number(to_buffer(b));
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(dm_block_location);
|
|
|
|
|
|
|
|
void *dm_block_data(struct dm_block *b)
|
|
|
|
{
|
|
|
|
return dm_bufio_get_block_data(to_buffer(b));
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(dm_block_data);
|
|
|
|
|
|
|
|
struct buffer_aux {
|
|
|
|
struct dm_block_validator *validator;
|
|
|
|
int write_locked;
|
2015-11-19 13:50:32 +00:00
|
|
|
|
|
|
|
#ifdef CONFIG_DM_DEBUG_BLOCK_MANAGER_LOCKING
|
|
|
|
struct block_lock lock;
|
|
|
|
#endif
|
2011-10-31 20:19:11 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static void dm_block_manager_alloc_callback(struct dm_buffer *buf)
|
|
|
|
{
|
|
|
|
struct buffer_aux *aux = dm_bufio_get_aux_data(buf);
|
|
|
|
aux->validator = NULL;
|
|
|
|
bl_init(&aux->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dm_block_manager_write_callback(struct dm_buffer *buf)
|
|
|
|
{
|
|
|
|
struct buffer_aux *aux = dm_bufio_get_aux_data(buf);
|
|
|
|
if (aux->validator) {
|
|
|
|
aux->validator->prepare_for_write(aux->validator, (struct dm_block *) buf,
|
|
|
|
dm_bufio_get_block_size(dm_bufio_get_client(buf)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------
|
|
|
|
* Public interface
|
|
|
|
*--------------------------------------------------------------*/
|
2012-07-27 14:08:08 +00:00
|
|
|
struct dm_block_manager {
|
|
|
|
struct dm_bufio_client *bufio;
|
2012-07-27 14:08:15 +00:00
|
|
|
bool read_only:1;
|
2012-07-27 14:08:08 +00:00
|
|
|
};
|
|
|
|
|
2011-10-31 20:19:11 +00:00
|
|
|
struct dm_block_manager *dm_block_manager_create(struct block_device *bdev,
|
|
|
|
unsigned block_size,
|
|
|
|
unsigned max_held_per_thread)
|
|
|
|
{
|
2012-07-27 14:08:08 +00:00
|
|
|
int r;
|
|
|
|
struct dm_block_manager *bm;
|
|
|
|
|
|
|
|
bm = kmalloc(sizeof(*bm), GFP_KERNEL);
|
|
|
|
if (!bm) {
|
|
|
|
r = -ENOMEM;
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
|
|
|
|
bm->bufio = dm_bufio_client_create(bdev, block_size, max_held_per_thread,
|
|
|
|
sizeof(struct buffer_aux),
|
|
|
|
dm_block_manager_alloc_callback,
|
|
|
|
dm_block_manager_write_callback);
|
|
|
|
if (IS_ERR(bm->bufio)) {
|
|
|
|
r = PTR_ERR(bm->bufio);
|
|
|
|
kfree(bm);
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
|
2012-07-27 14:08:15 +00:00
|
|
|
bm->read_only = false;
|
|
|
|
|
2012-07-27 14:08:08 +00:00
|
|
|
return bm;
|
|
|
|
|
|
|
|
bad:
|
|
|
|
return ERR_PTR(r);
|
2011-10-31 20:19:11 +00:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(dm_block_manager_create);
|
|
|
|
|
|
|
|
void dm_block_manager_destroy(struct dm_block_manager *bm)
|
|
|
|
{
|
2012-07-27 14:08:08 +00:00
|
|
|
dm_bufio_client_destroy(bm->bufio);
|
|
|
|
kfree(bm);
|
2011-10-31 20:19:11 +00:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(dm_block_manager_destroy);
|
|
|
|
|
|
|
|
unsigned dm_bm_block_size(struct dm_block_manager *bm)
|
|
|
|
{
|
2012-07-27 14:08:08 +00:00
|
|
|
return dm_bufio_get_block_size(bm->bufio);
|
2011-10-31 20:19:11 +00:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(dm_bm_block_size);
|
|
|
|
|
|
|
|
dm_block_t dm_bm_nr_blocks(struct dm_block_manager *bm)
|
|
|
|
{
|
2012-07-27 14:08:08 +00:00
|
|
|
return dm_bufio_get_device_size(bm->bufio);
|
2011-10-31 20:19:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int dm_bm_validate_buffer(struct dm_block_manager *bm,
|
|
|
|
struct dm_buffer *buf,
|
|
|
|
struct buffer_aux *aux,
|
|
|
|
struct dm_block_validator *v)
|
|
|
|
{
|
|
|
|
if (unlikely(!aux->validator)) {
|
|
|
|
int r;
|
|
|
|
if (!v)
|
|
|
|
return 0;
|
2012-07-27 14:08:08 +00:00
|
|
|
r = v->check(v, (struct dm_block *) buf, dm_bufio_get_block_size(bm->bufio));
|
2012-12-21 20:23:34 +00:00
|
|
|
if (unlikely(r)) {
|
2012-12-21 20:23:34 +00:00
|
|
|
DMERR_LIMIT("%s validator check failed for block %llu", v->name,
|
|
|
|
(unsigned long long) dm_bufio_get_block_number(buf));
|
2011-10-31 20:19:11 +00:00
|
|
|
return r;
|
2012-12-21 20:23:34 +00:00
|
|
|
}
|
2011-10-31 20:19:11 +00:00
|
|
|
aux->validator = v;
|
|
|
|
} else {
|
|
|
|
if (unlikely(aux->validator != v)) {
|
2012-12-21 20:23:34 +00:00
|
|
|
DMERR_LIMIT("validator mismatch (old=%s vs new=%s) for block %llu",
|
|
|
|
aux->validator->name, v ? v->name : "NULL",
|
|
|
|
(unsigned long long) dm_bufio_get_block_number(buf));
|
2011-10-31 20:19:11 +00:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
int dm_bm_read_lock(struct dm_block_manager *bm, dm_block_t b,
|
|
|
|
struct dm_block_validator *v,
|
|
|
|
struct dm_block **result)
|
|
|
|
{
|
|
|
|
struct buffer_aux *aux;
|
|
|
|
void *p;
|
|
|
|
int r;
|
|
|
|
|
2012-07-27 14:08:08 +00:00
|
|
|
p = dm_bufio_read(bm->bufio, b, (struct dm_buffer **) result);
|
2015-11-19 12:55:58 +00:00
|
|
|
if (unlikely(IS_ERR(p)))
|
2011-10-31 20:19:11 +00:00
|
|
|
return PTR_ERR(p);
|
|
|
|
|
|
|
|
aux = dm_bufio_get_aux_data(to_buffer(*result));
|
|
|
|
r = bl_down_read(&aux->lock);
|
|
|
|
if (unlikely(r)) {
|
|
|
|
dm_bufio_release(to_buffer(*result));
|
|
|
|
report_recursive_bug(b, r);
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
aux->write_locked = 0;
|
|
|
|
|
|
|
|
r = dm_bm_validate_buffer(bm, to_buffer(*result), aux, v);
|
|
|
|
if (unlikely(r)) {
|
|
|
|
bl_up_read(&aux->lock);
|
|
|
|
dm_bufio_release(to_buffer(*result));
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(dm_bm_read_lock);
|
|
|
|
|
|
|
|
int dm_bm_write_lock(struct dm_block_manager *bm,
|
|
|
|
dm_block_t b, struct dm_block_validator *v,
|
|
|
|
struct dm_block **result)
|
|
|
|
{
|
|
|
|
struct buffer_aux *aux;
|
|
|
|
void *p;
|
|
|
|
int r;
|
|
|
|
|
2012-07-27 14:08:15 +00:00
|
|
|
if (bm->read_only)
|
|
|
|
return -EPERM;
|
|
|
|
|
2012-07-27 14:08:08 +00:00
|
|
|
p = dm_bufio_read(bm->bufio, b, (struct dm_buffer **) result);
|
2015-11-19 12:55:58 +00:00
|
|
|
if (unlikely(IS_ERR(p)))
|
2011-10-31 20:19:11 +00:00
|
|
|
return PTR_ERR(p);
|
|
|
|
|
|
|
|
aux = dm_bufio_get_aux_data(to_buffer(*result));
|
|
|
|
r = bl_down_write(&aux->lock);
|
|
|
|
if (r) {
|
|
|
|
dm_bufio_release(to_buffer(*result));
|
|
|
|
report_recursive_bug(b, r);
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
aux->write_locked = 1;
|
|
|
|
|
|
|
|
r = dm_bm_validate_buffer(bm, to_buffer(*result), aux, v);
|
|
|
|
if (unlikely(r)) {
|
|
|
|
bl_up_write(&aux->lock);
|
|
|
|
dm_bufio_release(to_buffer(*result));
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(dm_bm_write_lock);
|
|
|
|
|
|
|
|
int dm_bm_read_try_lock(struct dm_block_manager *bm,
|
|
|
|
dm_block_t b, struct dm_block_validator *v,
|
|
|
|
struct dm_block **result)
|
|
|
|
{
|
|
|
|
struct buffer_aux *aux;
|
|
|
|
void *p;
|
|
|
|
int r;
|
|
|
|
|
2012-07-27 14:08:08 +00:00
|
|
|
p = dm_bufio_get(bm->bufio, b, (struct dm_buffer **) result);
|
2015-11-19 12:55:58 +00:00
|
|
|
if (unlikely(IS_ERR(p)))
|
2011-10-31 20:19:11 +00:00
|
|
|
return PTR_ERR(p);
|
|
|
|
if (unlikely(!p))
|
|
|
|
return -EWOULDBLOCK;
|
|
|
|
|
|
|
|
aux = dm_bufio_get_aux_data(to_buffer(*result));
|
|
|
|
r = bl_down_read_nonblock(&aux->lock);
|
|
|
|
if (r < 0) {
|
|
|
|
dm_bufio_release(to_buffer(*result));
|
|
|
|
report_recursive_bug(b, r);
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
aux->write_locked = 0;
|
|
|
|
|
|
|
|
r = dm_bm_validate_buffer(bm, to_buffer(*result), aux, v);
|
|
|
|
if (unlikely(r)) {
|
|
|
|
bl_up_read(&aux->lock);
|
|
|
|
dm_bufio_release(to_buffer(*result));
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int dm_bm_write_lock_zero(struct dm_block_manager *bm,
|
|
|
|
dm_block_t b, struct dm_block_validator *v,
|
|
|
|
struct dm_block **result)
|
|
|
|
{
|
|
|
|
int r;
|
|
|
|
struct buffer_aux *aux;
|
|
|
|
void *p;
|
|
|
|
|
2012-07-27 14:08:15 +00:00
|
|
|
if (bm->read_only)
|
|
|
|
return -EPERM;
|
|
|
|
|
2012-07-27 14:08:08 +00:00
|
|
|
p = dm_bufio_new(bm->bufio, b, (struct dm_buffer **) result);
|
2015-11-19 12:55:58 +00:00
|
|
|
if (unlikely(IS_ERR(p)))
|
2011-10-31 20:19:11 +00:00
|
|
|
return PTR_ERR(p);
|
|
|
|
|
|
|
|
memset(p, 0, dm_bm_block_size(bm));
|
|
|
|
|
|
|
|
aux = dm_bufio_get_aux_data(to_buffer(*result));
|
|
|
|
r = bl_down_write(&aux->lock);
|
|
|
|
if (r) {
|
|
|
|
dm_bufio_release(to_buffer(*result));
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
aux->write_locked = 1;
|
|
|
|
aux->validator = v;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2012-07-27 14:08:09 +00:00
|
|
|
EXPORT_SYMBOL_GPL(dm_bm_write_lock_zero);
|
2011-10-31 20:19:11 +00:00
|
|
|
|
2015-10-22 20:46:59 +00:00
|
|
|
void dm_bm_unlock(struct dm_block *b)
|
2011-10-31 20:19:11 +00:00
|
|
|
{
|
|
|
|
struct buffer_aux *aux;
|
|
|
|
aux = dm_bufio_get_aux_data(to_buffer(b));
|
|
|
|
|
|
|
|
if (aux->write_locked) {
|
|
|
|
dm_bufio_mark_buffer_dirty(to_buffer(b));
|
|
|
|
bl_up_write(&aux->lock);
|
|
|
|
} else
|
|
|
|
bl_up_read(&aux->lock);
|
|
|
|
|
|
|
|
dm_bufio_release(to_buffer(b));
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(dm_bm_unlock);
|
|
|
|
|
2014-03-27 14:13:20 +00:00
|
|
|
int dm_bm_flush(struct dm_block_manager *bm)
|
2011-10-31 20:19:11 +00:00
|
|
|
{
|
2012-07-27 14:08:15 +00:00
|
|
|
if (bm->read_only)
|
|
|
|
return -EPERM;
|
|
|
|
|
2012-07-27 14:08:08 +00:00
|
|
|
return dm_bufio_write_dirty_buffers(bm->bufio);
|
2011-10-31 20:19:11 +00:00
|
|
|
}
|
2014-03-27 14:13:20 +00:00
|
|
|
EXPORT_SYMBOL_GPL(dm_bm_flush);
|
2011-10-31 20:19:11 +00:00
|
|
|
|
2013-08-09 11:59:30 +00:00
|
|
|
void dm_bm_prefetch(struct dm_block_manager *bm, dm_block_t b)
|
|
|
|
{
|
|
|
|
dm_bufio_prefetch(bm->bufio, b, 1);
|
|
|
|
}
|
|
|
|
|
2015-04-23 19:06:27 +00:00
|
|
|
bool dm_bm_is_read_only(struct dm_block_manager *bm)
|
|
|
|
{
|
|
|
|
return bm->read_only;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(dm_bm_is_read_only);
|
|
|
|
|
2012-07-27 14:08:15 +00:00
|
|
|
void dm_bm_set_read_only(struct dm_block_manager *bm)
|
|
|
|
{
|
|
|
|
bm->read_only = true;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(dm_bm_set_read_only);
|
|
|
|
|
2013-12-04 21:58:19 +00:00
|
|
|
void dm_bm_set_read_write(struct dm_block_manager *bm)
|
|
|
|
{
|
|
|
|
bm->read_only = false;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(dm_bm_set_read_write);
|
|
|
|
|
2011-10-31 20:19:11 +00:00
|
|
|
u32 dm_bm_checksum(const void *data, size_t len, u32 init_xor)
|
|
|
|
{
|
|
|
|
return crc32c(~(u32) 0, data, len) ^ init_xor;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(dm_bm_checksum);
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------*/
|
|
|
|
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
MODULE_AUTHOR("Joe Thornber <dm-devel@redhat.com>");
|
|
|
|
MODULE_DESCRIPTION("Immutable metadata library for dm");
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------*/
|