|
@@ -0,0 +1,335 @@
|
|
|
+/*
|
|
|
+ * 2007+ Copyright (c) Evgeniy Polyakov <zbr@ioremap.net>
|
|
|
+ * All rights reserved.
|
|
|
+ *
|
|
|
+ * 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 option) any later version.
|
|
|
+ *
|
|
|
+ * This program is distributed in the hope that it will be useful,
|
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
+ * GNU General Public License for more details.
|
|
|
+ */
|
|
|
+
|
|
|
+#include <linux/bio.h>
|
|
|
+#include <linux/dst.h>
|
|
|
+#include <linux/slab.h>
|
|
|
+#include <linux/mempool.h>
|
|
|
+
|
|
|
+/*
|
|
|
+ * Transaction memory pool size.
|
|
|
+ */
|
|
|
+static int dst_mempool_num = 32;
|
|
|
+module_param(dst_mempool_num, int, 0644);
|
|
|
+
|
|
|
+/*
|
|
|
+ * Transaction tree management.
|
|
|
+ */
|
|
|
+static inline int dst_trans_cmp(dst_gen_t gen, dst_gen_t new)
|
|
|
+{
|
|
|
+ if (gen < new)
|
|
|
+ return 1;
|
|
|
+ if (gen > new)
|
|
|
+ return -1;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+struct dst_trans *dst_trans_search(struct dst_node *node, dst_gen_t gen)
|
|
|
+{
|
|
|
+ struct rb_root *root = &node->trans_root;
|
|
|
+ struct rb_node *n = root->rb_node;
|
|
|
+ struct dst_trans *t, *ret = NULL;
|
|
|
+ int cmp;
|
|
|
+
|
|
|
+ while (n) {
|
|
|
+ t = rb_entry(n, struct dst_trans, trans_entry);
|
|
|
+
|
|
|
+ cmp = dst_trans_cmp(t->gen, gen);
|
|
|
+ if (cmp < 0)
|
|
|
+ n = n->rb_left;
|
|
|
+ else if (cmp > 0)
|
|
|
+ n = n->rb_right;
|
|
|
+ else {
|
|
|
+ ret = t;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ dprintk("%s: %s transaction: id: %llu.\n", __func__,
|
|
|
+ (ret)?"found":"not found", gen);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static int dst_trans_insert(struct dst_trans *new)
|
|
|
+{
|
|
|
+ struct rb_root *root = &new->n->trans_root;
|
|
|
+ struct rb_node **n = &root->rb_node, *parent = NULL;
|
|
|
+ struct dst_trans *ret = NULL, *t;
|
|
|
+ int cmp;
|
|
|
+
|
|
|
+ while (*n) {
|
|
|
+ parent = *n;
|
|
|
+
|
|
|
+ t = rb_entry(parent, struct dst_trans, trans_entry);
|
|
|
+
|
|
|
+ cmp = dst_trans_cmp(t->gen, new->gen);
|
|
|
+ if (cmp < 0)
|
|
|
+ n = &parent->rb_left;
|
|
|
+ else if (cmp > 0)
|
|
|
+ n = &parent->rb_right;
|
|
|
+ else {
|
|
|
+ ret = t;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ new->send_time = jiffies;
|
|
|
+ if (ret) {
|
|
|
+ printk("%s: exist: old: gen: %llu, bio: %llu/%u, send_time: %lu, "
|
|
|
+ "new: gen: %llu, bio: %llu/%u, send_time: %lu.\n",
|
|
|
+ __func__,
|
|
|
+ ret->gen, (u64)ret->bio->bi_sector,
|
|
|
+ ret->bio->bi_size, ret->send_time,
|
|
|
+ new->gen, (u64)new->bio->bi_sector,
|
|
|
+ new->bio->bi_size, new->send_time);
|
|
|
+ return -EEXIST;
|
|
|
+ }
|
|
|
+
|
|
|
+ rb_link_node(&new->trans_entry, parent, n);
|
|
|
+ rb_insert_color(&new->trans_entry, root);
|
|
|
+
|
|
|
+ dprintk("%s: inserted: gen: %llu, bio: %llu/%u, send_time: %lu.\n",
|
|
|
+ __func__, new->gen, (u64)new->bio->bi_sector,
|
|
|
+ new->bio->bi_size, new->send_time);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+int dst_trans_remove_nolock(struct dst_trans *t)
|
|
|
+{
|
|
|
+ struct dst_node *n = t->n;
|
|
|
+
|
|
|
+ if (t->trans_entry.rb_parent_color) {
|
|
|
+ rb_erase(&t->trans_entry, &n->trans_root);
|
|
|
+ t->trans_entry.rb_parent_color = 0;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+int dst_trans_remove(struct dst_trans *t)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+ struct dst_node *n = t->n;
|
|
|
+
|
|
|
+ mutex_lock(&n->trans_lock);
|
|
|
+ ret = dst_trans_remove_nolock(t);
|
|
|
+ mutex_unlock(&n->trans_lock);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * When transaction is completed and there are no more users,
|
|
|
+ * we complete appriate block IO request with given error status.
|
|
|
+ */
|
|
|
+void dst_trans_put(struct dst_trans *t)
|
|
|
+{
|
|
|
+ if (atomic_dec_and_test(&t->refcnt)) {
|
|
|
+ struct bio *bio = t->bio;
|
|
|
+
|
|
|
+ dprintk("%s: completed t: %p, gen: %llu, bio: %p.\n",
|
|
|
+ __func__, t, t->gen, bio);
|
|
|
+
|
|
|
+ bio_endio(bio, t->error);
|
|
|
+ bio_put(bio);
|
|
|
+
|
|
|
+ dst_node_put(t->n);
|
|
|
+ mempool_free(t, t->n->trans_pool);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Process given block IO request: allocate transaction, insert it into the tree
|
|
|
+ * and send/schedule crypto processing.
|
|
|
+ */
|
|
|
+int dst_process_bio(struct dst_node *n, struct bio *bio)
|
|
|
+{
|
|
|
+ struct dst_trans *t;
|
|
|
+ int err = -ENOMEM;
|
|
|
+
|
|
|
+ t = mempool_alloc(n->trans_pool, GFP_NOFS);
|
|
|
+ if (!t)
|
|
|
+ goto err_out_exit;
|
|
|
+
|
|
|
+ t->n = dst_node_get(n);
|
|
|
+ t->bio = bio;
|
|
|
+ t->error = 0;
|
|
|
+ t->retries = 0;
|
|
|
+ atomic_set(&t->refcnt, 1);
|
|
|
+ t->gen = atomic_long_inc_return(&n->gen);
|
|
|
+
|
|
|
+ t->enc = bio_data_dir(bio);
|
|
|
+ dst_bio_to_cmd(bio, &t->cmd, DST_IO, t->gen);
|
|
|
+
|
|
|
+ mutex_lock(&n->trans_lock);
|
|
|
+ err = dst_trans_insert(t);
|
|
|
+ mutex_unlock(&n->trans_lock);
|
|
|
+ if (err)
|
|
|
+ goto err_out_free;
|
|
|
+
|
|
|
+ dprintk("%s: gen: %llu, bio: %llu/%u, dir/enc: %d, need_crypto: %d.\n",
|
|
|
+ __func__, t->gen, (u64)bio->bi_sector,
|
|
|
+ bio->bi_size, t->enc, dst_need_crypto(n));
|
|
|
+
|
|
|
+ if (dst_need_crypto(n) && t->enc)
|
|
|
+ dst_trans_crypto(t);
|
|
|
+ else
|
|
|
+ dst_trans_send(t);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+
|
|
|
+err_out_free:
|
|
|
+ dst_node_put(n);
|
|
|
+ mempool_free(t, n->trans_pool);
|
|
|
+err_out_exit:
|
|
|
+ bio_endio(bio, err);
|
|
|
+ bio_put(bio);
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Scan for timeout/stale transactions.
|
|
|
+ * Each transaction is being resent multiple times before error completion.
|
|
|
+ */
|
|
|
+static void dst_trans_scan(struct work_struct *work)
|
|
|
+{
|
|
|
+ struct dst_node *n = container_of(work, struct dst_node, trans_work.work);
|
|
|
+ struct rb_node *rb_node;
|
|
|
+ struct dst_trans *t;
|
|
|
+ unsigned long timeout = n->trans_scan_timeout;
|
|
|
+ int num = 10 * n->trans_max_retries;
|
|
|
+
|
|
|
+ mutex_lock(&n->trans_lock);
|
|
|
+
|
|
|
+ for (rb_node = rb_first(&n->trans_root); rb_node; ) {
|
|
|
+ t = rb_entry(rb_node, struct dst_trans, trans_entry);
|
|
|
+
|
|
|
+ if (timeout && time_after(t->send_time + timeout, jiffies)
|
|
|
+ && t->retries == 0)
|
|
|
+ break;
|
|
|
+#if 0
|
|
|
+ dprintk("%s: t: %p, gen: %llu, n: %s, retries: %u, max: %u.\n",
|
|
|
+ __func__, t, t->gen, n->name,
|
|
|
+ t->retries, n->trans_max_retries);
|
|
|
+#endif
|
|
|
+ if (--num == 0)
|
|
|
+ break;
|
|
|
+
|
|
|
+ dst_trans_get(t);
|
|
|
+
|
|
|
+ rb_node = rb_next(rb_node);
|
|
|
+
|
|
|
+ if (timeout && (++t->retries < n->trans_max_retries)) {
|
|
|
+ dst_trans_send(t);
|
|
|
+ } else {
|
|
|
+ t->error = -ETIMEDOUT;
|
|
|
+ dst_trans_remove_nolock(t);
|
|
|
+ dst_trans_put(t);
|
|
|
+ }
|
|
|
+
|
|
|
+ dst_trans_put(t);
|
|
|
+ }
|
|
|
+
|
|
|
+ mutex_unlock(&n->trans_lock);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If no timeout specified then system is in the middle of exiting process,
|
|
|
+ * so no need to reschedule scanning process again.
|
|
|
+ */
|
|
|
+ if (timeout) {
|
|
|
+ if (!num)
|
|
|
+ timeout = HZ;
|
|
|
+ schedule_delayed_work(&n->trans_work, timeout);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Flush all transactions and mark them as timed out.
|
|
|
+ * Destroy transaction pools.
|
|
|
+ */
|
|
|
+void dst_node_trans_exit(struct dst_node *n)
|
|
|
+{
|
|
|
+ struct dst_trans *t;
|
|
|
+ struct rb_node *rb_node;
|
|
|
+
|
|
|
+ if (!n->trans_cache)
|
|
|
+ return;
|
|
|
+
|
|
|
+ dprintk("%s: n: %p, cancelling the work.\n", __func__, n);
|
|
|
+ cancel_delayed_work_sync(&n->trans_work);
|
|
|
+ flush_scheduled_work();
|
|
|
+ dprintk("%s: n: %p, work has been cancelled.\n", __func__, n);
|
|
|
+
|
|
|
+ for (rb_node = rb_first(&n->trans_root); rb_node; ) {
|
|
|
+ t = rb_entry(rb_node, struct dst_trans, trans_entry);
|
|
|
+
|
|
|
+ dprintk("%s: t: %p, gen: %llu, n: %s.\n",
|
|
|
+ __func__, t, t->gen, n->name);
|
|
|
+
|
|
|
+ rb_node = rb_next(rb_node);
|
|
|
+
|
|
|
+ t->error = -ETIMEDOUT;
|
|
|
+ dst_trans_remove_nolock(t);
|
|
|
+ dst_trans_put(t);
|
|
|
+ }
|
|
|
+
|
|
|
+ mempool_destroy(n->trans_pool);
|
|
|
+ kmem_cache_destroy(n->trans_cache);
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Initialize transaction storage for given node.
|
|
|
+ * Transaction stores not only control information,
|
|
|
+ * but also network command and crypto data (if needed)
|
|
|
+ * to reduce number of allocations. Thus transaction size
|
|
|
+ * differs from node to node.
|
|
|
+ */
|
|
|
+int dst_node_trans_init(struct dst_node *n, unsigned int size)
|
|
|
+{
|
|
|
+ /*
|
|
|
+ * We need this, since node with given name can be dropped from the
|
|
|
+ * hash table, but be still alive, so subsequent creation of the node
|
|
|
+ * with the same name may collide with existing cache name.
|
|
|
+ */
|
|
|
+
|
|
|
+ snprintf(n->cache_name, sizeof(n->cache_name), "%s-%p", n->name, n);
|
|
|
+
|
|
|
+ n->trans_cache = kmem_cache_create(n->cache_name,
|
|
|
+ size + n->crypto.crypto_attached_size,
|
|
|
+ 0, 0, NULL);
|
|
|
+ if (!n->trans_cache)
|
|
|
+ goto err_out_exit;
|
|
|
+
|
|
|
+ n->trans_pool = mempool_create_slab_pool(dst_mempool_num, n->trans_cache);
|
|
|
+ if (!n->trans_pool)
|
|
|
+ goto err_out_cache_destroy;
|
|
|
+
|
|
|
+ mutex_init(&n->trans_lock);
|
|
|
+ n->trans_root = RB_ROOT;
|
|
|
+
|
|
|
+ INIT_DELAYED_WORK(&n->trans_work, dst_trans_scan);
|
|
|
+ schedule_delayed_work(&n->trans_work, n->trans_scan_timeout);
|
|
|
+
|
|
|
+ dprintk("%s: n: %p, size: %u, crypto: %u.\n",
|
|
|
+ __func__, n, size, n->crypto.crypto_attached_size);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+
|
|
|
+err_out_cache_destroy:
|
|
|
+ kmem_cache_destroy(n->trans_cache);
|
|
|
+err_out_exit:
|
|
|
+ return -ENOMEM;
|
|
|
+}
|