From: Herbert Xu <[email-masked]>

[DM] dm-crypt: Move post-processing into its own queue

With async crypto we can have a large number of crypto requests
outstanding.  When crypto requests for write operations are completed
they need to be given back to the block layer in process context.

We can't reuse kcryptd because it'll queue these requests after
other requests which can cause starvation as memory isn't released
untill these requests are fully complete.

So this patch establishes a new queue for second stage processing.
For now it only serves the crypto operations for reads.

Signed-off-by: Herbert Xu <[email-masked]>
---

 drivers/md/dm-crypt.c |   48 ++++++++++++++++++++++++++++++++++++++++--------
 1 files changed, 40 insertions(+), 8 deletions(-)

Index: linux-2.6.22-rc1/drivers/md/dm-crypt.c
===================================================================
--- linux-2.6.22-rc1.orig/drivers/md/dm-crypt.c	2007-05-14 18:31:44.000000000 +0100
+++ linux-2.6.22-rc1/drivers/md/dm-crypt.c	2007-05-15 18:00:58.000000000 +0100
@@ -36,7 +36,6 @@ struct dm_crypt_io {
 	struct work_struct work;
 	atomic_t pending;
 	int error;
-	int post_process;
 };
 
 /*
@@ -495,13 +494,25 @@ static void dec_pending(struct dm_crypt_
 }
 
 /*
- * kcryptd:
+ * kcryptd/kcryptd-post:
  *
  * Needed because it would be very unwise to do decryption in an
  * interrupt context.
+ *
+ * kcryptd performs the first stage, which is the actual read for
+ * read processing and encryption for write processing.
+ *
+ * kcryptd-post performs the final stage, which is description for
+ * read processing and the actual write for write processing.
+ *
+ * They must be separated as otherwise the final stages could be
+ * starved by new requests which can block in the first stages due
+ * to memory allocation.
  */
 static struct workqueue_struct *_kcryptd_workqueue;
+static struct workqueue_struct *_kcryptd_post_workqueue;
 static void kcryptd_do_work(struct work_struct *work);
+static void kcryptd_post_work(struct work_struct *work);
 
 static void kcryptd_queue_io(struct dm_crypt_io *io)
 {
@@ -509,6 +520,12 @@ static void kcryptd_queue_io(struct dm_c
 	queue_work(_kcryptd_workqueue, &io->work);
 }
 
+static void kcryptd_queue_postio(struct crypt_io *io)
+{
+	INIT_WORK(&io->work, kcryptd_post_work);
+	queue_work(_kcryptd_post_workqueue, &io->work);
+}
+
 static int crypt_endio(struct bio *clone, unsigned int done, int error)
 {
 	struct dm_crypt_io *io = clone->bi_private;
@@ -535,8 +552,7 @@ static int crypt_endio(struct bio *clone
 	}
 
 	bio_put(clone);
-	io->post_process = 1;
-	kcryptd_queue_io(io);
+	kcryptd_queue_postio(io);
 	return 0;
 
 out:
@@ -659,12 +675,18 @@ static void kcryptd_do_work(struct work_
 {
 	struct dm_crypt_io *io = container_of(work, struct dm_crypt_io, work);
 
-	if (io->post_process)
-		process_read_endio(io);
-	else if (bio_data_dir(io->base_bio) == READ)
+	if (bio_data_dir(io->base_bio) == READ)
 		process_read(io);
 	else
 		process_write(io);
+}
+
+static void kcryptd_post_work(struct work_struct *work)
+{
+	struct crypt_io *io = container_of(work, struct crypt_io, work);
+
+	if (bio_data_dir(io->base_bio) == READ)
+		process_read_endio(io);
 }
 
 /*
@@ -947,7 +969,7 @@ static int crypt_map(struct dm_target *t
 	io = mempool_alloc(cc->io_pool, GFP_NOIO);
 	io->target = ti;
 	io->base_bio = bio;
-	io->error = io->post_process = 0;
+	io->error = 0;
 	atomic_set(&io->pending, 0);
 	kcryptd_queue_io(io);
 
@@ -1073,6 +1095,13 @@ static int __init dm_crypt_init(void)
 		goto bad1;
 	}
 
+	_kcryptd_post_workqueue = create_workqueue("kcryptd-post");
+	if (!_kcryptd_post_workqueue) {
+		r = -ENOMEM;
+		DMERR("couldn't create kcryptd-post");
+		goto bad1_5;
+	}
+
 	r = dm_register_target(&crypt_target);
 	if (r < 0) {
 		DMERR("register failed %d", r);
@@ -1082,6 +1111,8 @@ static int __init dm_crypt_init(void)
 	return 0;
 
 bad2:
+	destroy_workqueue(_kcryptd_post_workqueue);
+bad1_5:
 	destroy_workqueue(_kcryptd_workqueue);
 bad1:
 	kmem_cache_destroy(_crypt_io_pool);
@@ -1095,6 +1126,7 @@ static void __exit dm_crypt_exit(void)
 	if (r < 0)
 		DMERR("unregister failed %d", r);
 
+	destroy_workqueue(_kcryptd_post_workqueue);
 	destroy_workqueue(_kcryptd_workqueue);
 	kmem_cache_destroy(_crypt_io_pool);
 }